HERE Traffic incidents integration in Leaflet.js

Viewed 588

Background:

I'm currently integrating HERE maps into our web-based application. I'm trying both - HERE provided Javascript API and Leaflet at the same time to find the best approach for our use-case.

While JavaScript API provided by HERE maps is OK, rendering wise Leaflet performs much better when using raster tiles.

Issue:

It would be fine by me to use raster tiles + leaflet, but our application also needs to display traffic incidents data.

Traffic incident data is provided by HERE in JSON and XML formats (Documentation link, Example JSON). They provide [Z]/[X]/[Y], quadkey, prox, bbox, or corridor filters which can be used to retrieve filtered data set.

I've tried using [Z]/[X]/[Y] addressing with custom L.TileLayer implementation which loads appropriate JSON, converts it to GeoJSON and displays GeoJSON on map. However that approach is very inefficient and significant performance drop is visible.

Question:

Maybe anyone has already solved this issue and could share any insights on how the HERE traffic incidents could be shown on Leaflet map without encountering performance issues?

2 Answers

I created the following script, which works without any performance issues:

var fg = L.featureGroup().addTo(map);
function loadTraffic(data) {
  fg.clearLayers();
  var d = data.TRAFFICITEMS.TRAFFICITEM.map((r) => {
    var latlngs = [];
    
    if (r.LOCATION.GEOLOC) {
      if (r.LOCATION.GEOLOC.ORIGIN) {
        latlngs.push(L.latLng(r.LOCATION.GEOLOC.ORIGIN.LATITUDE, r.LOCATION.GEOLOC.ORIGIN.LONGITUDE));
      }

      if (r.LOCATION.GEOLOC.TO) {
        if (L.Util.isArray(r.LOCATION.GEOLOC.TO)) {
          r.LOCATION.GEOLOC.TO.forEach((latlng) => {
            latlngs.push(L.latLng(latlng.LATITUDE, latlng.LONGITUDE));
          })
        } else {
          latlngs.push(L.latLng(r.LOCATION.GEOLOC.TO.LATITUDE, r.LOCATION.GEOLOC.TO.LONGITUDE));
        }
      }
    }
    var desc = r.TRAFFICITEMDESCRIPTION.find(x => x.TYPE === "short_desc").content;

    return {
      latlngs,
      desc
    }
  })
  console.log(d);

  d.forEach((road)=>{
    L.polyline(road.latlngs,{color: 'red'}).addTo(fg).bindPopup(road.desc);
  });
  map.fitBounds(fg.getBounds())
}

If this script is not working for you, please share your json file.

Ok, so I've found a solution for this task. Apparently I was on a good path, I only needed to optimize my implementation.

What I had to do to achieve appropriate performance is:

  • Create custom CircleMarker extension which would draw custom icon on canvas
  • Create JS worker which would fetch the data from a given URL, transform it to GeoJSON and return GeoJSON to it's listener
  • Create custom GridLayer implementation, which, in fetchTile function, creates worker instance, passes it a link with appropriate [Z]/[X]/[Y] coordinates already set, adds listener, which listens for worker's done event and returns empty tile
  • On worker's done event, custom GridLayer implementation creates GeoJSON layer, adds it to the dictionary with coordinates as a key and, if zoom level is still the same - adds that layer to the map
  • Add zoomend observer on a map, which removes any layers that does not match current zoom level from the map

Now the map is definitely usable and works way faster than original HERE JS API.

P.S. Sorry, but I can't share the implementation itself due to our company policies.

Related