Search code examples
mapsopenlayersosrm

Add multiple routes with info window in a single ol map


I am trying to get the multiple routes/directions with markers and line from start to end points. I have tried 2 different methods both using osrm api.

  1. In the first one though I am getting the route, direction, and info window but it only shows for the first points, the rest don't appear.

  2. In the 2nd one though I get multiple/all points but I get issues while getting directions for the points. The issues are

    -. It renders a blue image at the end which covers all of the map

    -. it shows direction lines but the markers get hidden.

First tried code

var points = [],
        // msg_el = document.getElementById('msg'),
        url_osrm_nearest = '//router.project-osrm.org/nearest/v1/driving/',
        url_osrm_route = '//router.project-osrm.org/route/v1/driving/',
        vectorSource = new ol.source.Vector(),
        vectorLayer = new ol.layer.Vector({
            source: vectorSource,
        }),
        
        styles = {
            route: new ol.style.Style({
                stroke: new ol.style.Stroke({
                width: 3, color: [40, 4, 40, 0.8]
                })
            }),
            icon: new ol.style.Style({
                image: new ol.style.Icon({
                anchor: [0.5, 1],
                // src: icon_url
                })
            })
        };

        // Pins
        const availablepin = "http://crm.gtmmtransportation.net/Assets/Images/available.png";

        console.clear();

        

        function createMarker(info, icon, content) {
            var marker, content, distance, duration;

            content = ' <div id="content" > ' +
                '<div id="siteNotice">' +
                '<h1 style="font-size: 15px;" id="firstHeading" class="firstHeading sa">' + content.unitNo + '</h1>' +
                "<div style='display: flex; flex-direction: column;align-items: self-end;margin-right: 10px;'>" +
                '<a href="' + content.holdlink + '" title="' + content.holdtt + '"class="hold ' + content.holdclass + '">' + content.holdcurrstat + '</a>' +
                "<p style='font-size: 12px;'>Last Updated By: " + content.status_updating_user + " </p>" +
                "</div>" +
                "</div>" +

                '<div class="bodyContent">' +
                "<span><b>Driver: </b></span><p class='sa'>" + content.driver + '<small style="color: var(--light-font);" > [' + content.loadcount  +  "/" + content.ratingaverage + "]</small></p> <br> " +
            "<span><b>Ph: </b></span><p  class='sa'>" + content.phonenumber + "</p> <br>" +
                "<span><b>Address: </b></span><a  class='sa' href='https://www.google.com/maps/place/" + encodeURIComponent(content.address) + "' target='_blank' rel='noopener noreferrer' style='color: #0e4ddb;'>" + content.address + "</a> <br>" +
                "<span><b>Arrival Date: </b></span><p  class='sa'>" + content.arrivalDate + "</p><br>" +
                "<span><b>Dims: </b></span><p  class='sa'>" + content.dimensions + "</p><br>" +
                "<span><b>Weight: </b></span><p  class='sa'>" + content.weight + "</p><br>" +
                "<span><b>Notes: </b></span><p  class='sa'>" + content.notes + "</p><br>" +
                "</div>" +
                "</div>";


            for(i=0; i< info.length; i++){
                // var last_point = ""
                i == 0 ? last_point = info[i] : last_point = info[i - 1]
                // var last_point = pointss[pointss.length - 1];
                // utils.createFeature(pointss[i]);
                var feature = new ol.Feature({
                    type: 'place',
                    geometry: new ol.geom.Point(ol.proj.fromLonLat(info[i])),
                    content: content
                });

                var src = i == 0 ? icon : 'https://maps.google.com/mapfiles/kml/paddle/2.png';
               
                feature.setStyle(
                    new ol.style.Style({
                        image: new ol.style.Icon({
                            anchor: [0.5, 1],
                            src: src,
                            // size: [40, 40],
                            scale: 0.6
                            // width: '10'
                        })
                    }),
                )
                marker = vectorSource.addFeature(feature);

                //get the route
                var point1 = last_point.join();
                var point2 = info[i].join();
                
                fetch(url_osrm_route + point1 + ';' + point2).then(function(r) { 
                    return r.json();
                }).then(function(json) {
                    distance += (json.routes[0].distance)
                    duration += (json.routes[0].duration)
                    if(json.code !== 'Ok') {
                        return;
                    }
                    utils.createRoute(json.routes[0].geometry);
                });

                // this.map.removeLayers(this.markerGroup);
                // this.map.addLayers(this.markerGroup);

                // marker = L.marker(info, {
                //     icon: icon
                // }).addTo(markerLayer);
                // marker.bindPopup(content);

                // var id = marker._leaflet_id;
                // var id = null
                // if (document.getElementById(id) != null) return;
                // var sidebarElement, infoPart, removePart;
                // sidebarElement = L.DomUtil.create('div', 'sidebarElement', document.getElementById('sidebarElements'));
                // sidebarElement.id = id;

                // infoPart = L.DomUtil.create('div', 'infoSidebarElement', sidebarElement);
                // infoPart.innerHTML = content;
                // L.DomEvent.on(infoPart, 'mouseover', function(evt) {
                //     var marker = markerLayer.getLayer(this.id);
                //     marker.closePopup();
                //     marker.openPopup();
                // }, sidebarElement);
            }

            

        }

        // Available
        var availableunit = [{
                elat: 43.6671042,elng: -116.691093,lat: 42.3986045,lng: -114.5613507,content: 
                {lat: 42.3986045,lng: -114.5613507,holdlink: './Assets/backendfiles/driverstatus.php?action_type=hold&id=36&redirect=maps.php&last_Status=available&username=Ian',holdtt: 'Click to Put on Hold',holdclass: 'hold-danger',holdcurrstat: 'Hold',unitNo: '1051',driver: 'Pedro Bustos',phonenumber: '(208) 358-4460',arrivalDate: '2023-03-20',dimensions: '26ft Box Truck',weight: '',address: 'Twin Falls, ID 83301, USA',notes: '',status_updating_user: '',loadcount: '0',ratingaverage: '0',}},
                {
                elat: 43.6671042,elng: -116.691093,lat: 43.6189362,lng: -116.5346883,
                content: {lat: 43.6189362,lng: -116.5346883,holdlink: './Assets/backendfiles/driverstatus.php?action_type=hold&id=698&redirect=maps.php&last_Status=available&username=Ian',holdtt: 'Click to Put on Hold',holdclass: 'hold-danger',holdcurrstat: 'Hold',unitNo: '1625',driver: ' Frederic Karangwa',phonenumber: '208-230-3901',arrivalDate: '2023-03-16',dimensions: '128x51x70',weight: '3000',address: 'Nampa, ID 83687, USA',notes: '',status_updating_user: '',loadcount: '0',ratingaverage: '0',}}
                ]


        

        function mapimp(arr, icon) {

            if(arr !== ''){
                for (i = 0; i < arr.length; i++) {
                    
                    createMarker([[arr[i].lng, arr[i].lat], [arr[i].elng, arr[i].elat]], icon, arr[i].content);
                }
            }
            
        }

        var center = ol.proj.fromLonLat([-93.59277789139063, 41.590686510484915]);
        markerGroup = new ol.layer.Group({
            layers: [],
            name: 'markerGroup'
        });
        var map = new ol.Map({
            target: 'map',
            layers: [ this.markerGroup ],
            // [
            //     new ol.layer.Tile({
            //         source: new ol.source.OSM(),
            //     }),
            //     vectorLayer
            // ],
            view: new ol.View({
                center: center,
                zoom: 5,
                maxZoom: 18,
            }),
        });
        map.setTarget($("#map")[0]);

        mapimp(availableunit, availablepin)
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.10.1/ol.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.10.1/ol.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GTMM TRANSPORTATION | MAP</title>

    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
    <link rel="stylesheet" href="./Assets/css/style.css">

    <style>
        html,
        body {
            margin: 0;
            margin-left: 10%;
            padding: 0;
        }

        #map {
            width: 100%;
            height: 100vh;
        }

        

        .leaflet-popup-content-wrapper,
        .leaflet-popup-tip {
            padding: 10px;
        }

        #sidebar {
            position: absolute;
            width: 20%;
            height: 100%;
            left: 0;
            top: 0;
            background-color: rgba(255, 255, 255, 0.8);
            overflow: auto;
        }

        .sidebarElement {
            padding: 10px 15px;
            display: table;
            width: 88%;
            border-top: 1px solid #dadada;
            margin: 0 10px;
        }

        .sidebarElement:hover {
            background-color: #eeeeee;
        }

        .sidebarElement .bodyContent span,
        .sidebarElement .bodyContent br {
            display: none;
        }

        .sidebarElement .bodyContent p,
        .sidebarElement .bodyContent a {
            font-size: 12px;
            margin: 0;
            padding: 0;
            line-height: 0;
            line-break: loose;
        }

        .sidebarElement #siteNotice {
            margin-top: 0px;
            margin-bottom: 0px;
            margin-right: 0px;
        }

        .sidebarElement #siteNotice .hold {
            height: 33px;
        }

        .sidebarElement .bodyContent p::after,
        .sidebarElement .bodyContent a::after {
            content: " : ";
            color: red;
        }

        font {
            font-size: 16px;
            font-weight: 600;
            color: rgb(44, 44, 44);
        }

        .infoSidebarElement {
            display: table-cell;
            cursor: pointer;
            width: 90%;
        }

        .removeSidebarElement {
            display: table-cell;
            text-align: right;
            cursor: pointer;
        }

        #counts {
            position: absolute;
            top: 10px;
            right: 10px;
            background-color: #ffff;
            z-index: 1000;
            border-radius: 7px;
        }

        #search {
            margin: 10px;
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
            width: 75%;
        }

        .searchicon {
            text-align: center;
            background-color: #dadada;
            padding: 3.5px 5px;
            margin-left: -10px;
            border-top-right-radius: 7px;
            border-bottom-right-radius: 7px;
        }

        .searchdiv {
            display: flex;
            align-items: center;
            width: 2fr;
            flex: 3;
            width: 80%;
            margin-right: 10px;
        }

        #content p {
            margin: 0;
            padding: 0;
            display: contents;
            color: #8b8b8b;
        }

        #content span {
            color: #8b8b8b;
        }

        .bodyContent {
            margin-top: 10px;
        }

        .searchcontainer {
            position: fixed;
            overflow: hidden;
            background: #fafafa;
            top: 0;
            width: 18%;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        #siteNotice {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: -7px;
            margin-bottom: 20px;
            margin-right: -10px;
        }

        .hold {
            height: 26px;
        }

        .closest_u_container {
            position: absolute;
            z-index: 400;
            top: 33px;
            /* right: calc(63% - 350px); */
            background: #fff;
            border-radius: 8px;
            padding: 5px 10px;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        #loading-bar-spinner.spinner {
            left: 50%;
            /* margin-left: -20px; */
            top: 50%;
            /* margin-top: -20px; */
            /* position: absolute; */
            z-index: 19 !important;
            animation: loading-bar-spinner 900ms linear infinite;
        }

        #loading-bar-spinner.spinner .spinner-icon {
            width: 10px;
            height: 10px;
            border: solid 4px transparent;
            border-top-color: #00C8B1 !important;
            border-left-color: #00C8B1 !important;
            border-radius: 50%;
        }

        @keyframes loading-bar-spinner {
            0%   { transform: rotate(0deg);   transform: rotate(0deg); }
            100% { transform: rotate(360deg); transform: rotate(360deg); }
        }
    </style>

</head>

<body>

    <style>
        canvas{
            width: 100% !important;
            height: 100% !important;
            margin-left: auto;
        }

        #map {
        position: relative;
        }
        #info {
            position: absolute;
            /* height: 1px; */
            /* width: 1px; */
            z-index: 10000;
            background-color: #fff;
            padding: 10px;
            border-radius: 5px;
            max-width: 315px;
        }
        .tooltip.in {
        opacity: 1;
        }
        .tooltip.top .tooltip-arrow {
        border-top-color: white;
        }
        .tooltip-inner {
        border: 2px solid white;
        }
    </style>


    <div id="sidebar">
        <div class="searchcontainer">
            <div class="searchdiv">
                <input type="search" name="search" id="search" onkeyup="search()" placeholder="Search...">
                <div class="searchicon"><i class="fa-solid fa-magnifying-glass"></i></div>
            </div>
            <select name="filter" style="flex: 1;width: 25%;">
                <option>Unit No</option>
                <option>Driver Name</option>
                <option>Phone Number</option>
                <option>Location</option>
                <option>Date</option>
                <option>Dimensions</option>
                <option>Weight</option>
            </select>
        </div>

        <div id="sidebarElements" style="margin-top: 50px;"></div>
    </div>

    <div id="map">
        <div id="info"></div>
    </div>

    <div class="closest_u_container">
        <input type="text" id="start" placeholder="Search to get the closest units"  style="width: 350px">
        <input type="hidden" name="closest_lat" id="closest_lat" value="">
        <input type="hidden" name="closest_lng" id="closest_lng" value="">
        <button type="reset" id="reset" style="position: relative;">RESET</button>
    </div>
    <div id="counts" style="padding: 10px;"><span>Total Units: </span><span id="count" style="font-weight: 500; color: red;"></span></div>

    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.10.1/ol.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.10.1/ol.min.css">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
    
 </body>
</html>

2nd Tried Code

// Create a map instance
var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    })
  ],
  view: new ol.View({
    center: ol.proj.fromLonLat([0, 0]),
    zoom: 2
  })
});

// Define the OSRM API URL and route coordinates
var osrmUrl = 'https://router.project-osrm.org/route/v1/driving/';
var routes = [
  { origin: [-116.5346883, 43.6189362], destination: [-116.691093, 43.6671042] },
  { origin: [-114.5613507, 42.3986045], destination: [-116.691093, 43.6671042] }
];



// Create an array to store the route features
var routeFeatures = [];

// Create a separate layer for marker features
var markerLayer = new ol.layer.Vector({
  source: new ol.source.Vector(),
  style: function (feature) {
    return new ol.style.Style({
      image: new ol.style.Circle({
        radius: 6,
        fill: new ol.style.Fill({
          color: 'blue'
        }),
        stroke: new ol.style.Stroke({
          color: 'white',
          width: 2
        })
      }),
      zIndex: Infinity // Ensure markers are displayed above other layers
    });
  }
});

// Fetch routes and create features for each route
routes.forEach(function (route) {
  fetch(osrmUrl + route.origin[0] + ',' + route.origin[1] + ';' + route.destination[0] + ',' + route.destination[1] + '?overview=full&geometries=geojson')
    .then(function (response) {
      return response.json();
    })
    .then(function (data) {
      var routeCoordinates = data.routes[0].geometry.coordinates;
     
      var routeFeature = new ol.Feature({
        geometry: new ol.geom.LineString(routeCoordinates)
        // type: 'route',
        // geometry: droute
      });

      routeFeatures.push(routeFeature);

      // Create marker features for the starting and ending points
      var startMarker = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat(route.origin))
      });
      var endMarker = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat(route.destination))
      });
    //   console.log(markerLayer)

      markerLayer.getSource().addFeature(startMarker);
      markerLayer.getSource().addFeature(endMarker);

      // Check if all routes have been fetched
      if (routeFeatures.length === routes.length) {
        // Create a route layer
        // console.log("routes adding")
        // console.log(routeFeatures)
        var routeLayer = new ol.layer.Vector({
          source: new ol.source.Vector({
            features: routeFeatures
          }),
          style: new ol.style.Style({
            stroke: new ol.style.Stroke({
              color: 'red',
              width: 2
            })
          })
        //   console.log()
        });
        // console.log(routeLayer)

        // Add the route layer and marker layer to the map
        map.addLayer(routeLayer);
        map.addLayer(markerLayer);

        // Fit the map view to the extent of all routes
        // var routesExtent = ol.extent.createEmpty();
        // var routesExtent = ol.extent;
        // console.log(routesExtent)
        // routeFeatures.forEach(function (feature) {
        //     console.log(feature.getGeometry().getExtent())
        //   ol.extent.extend(routesExtent, feature.getGeometry().getExtent());
        // });
        // map.getView().fit(routesExtent, { padding: [50, 50, 50, 50] });

        // Fit the map view to the extent of all routes
        // console.log(ol.extent.boundingExtent())
        var routesExtent = ol.extent.boundingExtent(
          routeFeatures.map(function (feature) {
            // console.log("route extent feture")

            // console.log(feature)
            return feature.getGeometry().getExtent();
          })
        );
        // console.log("route")
        // console.log(routeFeatures)
        // console.log("marker")
        // console.log(markerLayer)
        // var markerExtent = ol.extent.boundingExtent(
        //   function (markerLayer) {
        //     // console.log("route extent feture")

        //     // console.log(feature)
        //     return markerLayer.getGeometry().getExtent();
        //   }
        // );
        console.log(routesExtent)
        map.getView().fit( { padding: [50, 50, 50, 50] });

        // Fit the map view to the extent of all routes
        // var routesExtent = ol.extent.create();
        // routeFeatures.forEach(function (feature) {
        //   ol.extent.extend(routesExtent, feature.getGeometry().getExtent());
        // });
        // map.getView().fit(routesExtent, { padding: [50, 50, 50, 50] });
    }
    })
    .catch(function (error) {
      console.log('Error:', error);
    });
});
<link href="https://openlayers.org/en/v6.6.1/css/ol.css" rel="stylesheet"/>
<script src="https://openlayers.org/en/v6.6.1/build/ol.js"></script>
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://openlayers.org/en/v6.6.1/css/ol.css" type="text/css">
  <style>
    .map {
      height: 400px;
      width: 100%;
    }
  </style>
</head>
<body>
  <div id="map" class="map"></div>

  <script src="https://openlayers.org/en/v6.6.1/build/ol.js"></script>
  
  </body>
</html>

Any help will be highly appreciated. Thank you.


Solution

  • In your second attempt you need to transform the route coordinates to view projection (similar to calling fromLonlat on the start and end points) otherwise your route is very small and close to the equator in the Atlantic near Africa.

        geometry: new ol.geom.LineString(routeCoordinates).transform('EPSG:4326', 'EPSG:3857')
    

    ol.extent.boundingExtent gets the bounding extent of coordinates, to combine extents use ol.extent.extend

        var routesExtent = ol.extent.createEmpty();
        routeFeatures.forEach(function (feature) {
           ol.extent.extend(routesExtent, feature.getGeometry().getExtent());
        });
    

    // Create a map instance
    var map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ],
      view: new ol.View({
        center: ol.proj.fromLonLat([0, 0]),
        zoom: 2
      })
    });
    
    // Define the OSRM API URL and route coordinates
    var osrmUrl = 'https://router.project-osrm.org/route/v1/driving/';
    var routes = [
      { origin: [-116.5346883, 43.6189362], destination: [-116.691093, 43.6671042] },
      { origin: [-114.5613507, 42.3986045], destination: [-116.691093, 43.6671042] }
    ];
    
    
    
    // Create an array to store the route features
    var routeFeatures = [];
    
    // Create a separate layer for marker features
    var markerLayer = new ol.layer.Vector({
      source: new ol.source.Vector(),
      style: function (feature) {
        return new ol.style.Style({
          image: new ol.style.Circle({
            radius: 6,
            fill: new ol.style.Fill({
              color: 'blue'
            }),
            stroke: new ol.style.Stroke({
              color: 'white',
              width: 2
            })
          }),
          zIndex: Infinity // Ensure markers are displayed above other layers
        });
      }
    });
    
    // Fetch routes and create features for each route
    routes.forEach(function (route) {
      fetch(osrmUrl + route.origin[0] + ',' + route.origin[1] + ';' + route.destination[0] + ',' + route.destination[1] + '?overview=full&geometries=geojson')
        .then(function (response) {
          return response.json();
        })
        .then(function (data) {
          var routeCoordinates = data.routes[0].geometry.coordinates;
         
          var routeFeature = new ol.Feature({
            geometry: new ol.geom.LineString(routeCoordinates).transform('EPSG:4326', 'EPSG:3857')
            // type: 'route',
            // geometry: droute
          });
    
          routeFeatures.push(routeFeature);
    
          // Create marker features for the starting and ending points
          var startMarker = new ol.Feature({
            geometry: new ol.geom.Point(ol.proj.fromLonLat(route.origin))
          });
          var endMarker = new ol.Feature({
            geometry: new ol.geom.Point(ol.proj.fromLonLat(route.destination))
          });
        //   console.log(markerLayer)
    
          markerLayer.getSource().addFeature(startMarker);
          markerLayer.getSource().addFeature(endMarker);
    
          // Check if all routes have been fetched
          if (routeFeatures.length === routes.length) {
            // Create a route layer
            // console.log("routes adding")
            // console.log(routeFeatures)
            var routeLayer = new ol.layer.Vector({
              source: new ol.source.Vector({
                features: routeFeatures
              }),
              style: new ol.style.Style({
                stroke: new ol.style.Stroke({
                  color: 'red',
                  width: 2
                })
              })
            //   console.log()
            });
            // console.log(routeLayer)
    
            // Add the route layer and marker layer to the map
            map.addLayer(routeLayer);
            map.addLayer(markerLayer);
    
            var routesExtent = ol.extent.createEmpty();
            routeFeatures.forEach(function (feature) {
               ol.extent.extend(routesExtent, feature.getGeometry().getExtent());
            });
       
            map.getView().fit(routesExtent, { padding: [50, 50, 50, 50] });
    
        }
        })
        .catch(function (error) {
          console.log('Error:', error);
        });
    });
     
    <link href="https://openlayers.org/en/v6.6.1/css/ol.css" rel="stylesheet"/>
    <script src="https://openlayers.org/en/v6.6.1/build/ol.js"></script>
    <!DOCTYPE html>
    <html>
    <head>
      <link rel="stylesheet" href="https://openlayers.org/en/v6.6.1/css/ol.css" type="text/css">
      <style>
        .map {
          height: 400px;
          width: 100%;
        }
      </style>
    </head>
    <body>
      <div id="map" class="map"></div>
    
      <script src="https://openlayers.org/en/v6.6.1/build/ol.js"></script>
      
      </body>
    </html>