XYZ

Building an App for Routing to Nearby Water Fountains with HERE XYZ

By Boris Guenebaut | 05 December 2018

As a software engineer in the Highly Automated Driving division, I spend most days working on an Android app that helps our HERE True Car drivers monitor the health of our data collection hardware. Helping drivers navigate on specific routes to collect fresh data is a big part of what that application does.

Recently, a new product called HERE XYZ went into Beta as a simple solution to store geospatial data. I sat down with Jayson DeLancey to review and discuss a project I built to try out the new tools.

What is Thirst Free?

As a regular runner too annoyed to carry a flask with me on the move, I occasionally get thirsty and have to use my fuzzy brain to remember the closest drinking water fountain. I figured it would be nice to have a simple web application to show nearby drinking water to reduce time wandering about. I think it could be useful for dog owners and hikers as well.

Here’s an example of some of the water fountains in a nearby state park that can get pretty hot in the summer:

https://here.xyz

How did you find water fountain data in the first place?

There are many data sources such as the Seattle Parks & Recreation and California Open Data Portal that can be useful for projects. In this case, I used a tool called overpass turbo (MIT License) to filter Open Street Map data where “amenity=drinking_water” for North America and exported it as GeoJSON.

Since HERE XYZ organizes data into Spaces, it keeps licensing around data cleanly separated depending on need and terms of use. I used the Swagger API Tools to explore the API for reading and editing a space. Without XYZ Hub, I would have needed to store the GeoJSON data in a file which for large data sets is memory intensive to read and impacts the end user experience of the app.

For example, this HTTP request will return some details on my space such as the ID I will need later:

curl -X GET "https://xyz.api.here.com/hub/spaces" -H "accept: */*" -H "Authorization: Bearer AXoqaCXh6cAqoz0nDxEzUCo"
[
  {
    "id": "jaz191md",
    "title": "water_fountains_usa.geojson",
    "description": "Extracted from http://overpass-turbo.eu",
    "shared": true,
    "owner": "xjmyiygJapVIgnffuM6w"
  },
  ...
]

Should we think of HERE XYZ as just GeoJSON file storage?

That’s just how data is uploaded. Additionally, the XYZ JavaScript library and particularly the here.xyz.maps.layers.TileLayer was great to encapsulate the filtering at the server level so that the client would only need to display the retrieved items. This makes a large dataset such as every water fountain in North America perform better since it is really just a bbox as a spatial filter of the contents of a Space.

For example, the following code shows an object being returned which fetches data from an XYZ space such as the water fountain dataset. You just need to identify the space provider with space id and access token. The data can then be styled with any custom icons or other CSS properties.

return new here.xyz.maps.layers.TileLayer({
   name: 'WaterFountains',
   min: 8,
   max: 20,
   provider: new here.xyz.maps.providers.SpaceProvider({
     name: 'WaterFountainsProvider',
     level: 6,
     space: 'Tyd31ZuT',
     credentials: {
       access_token: WATER_FOUNTAINS_TOKEN
     }
   }),
   style: {
     styleGroups: {
       pointStyle: [{
         zIndex: 3,
         type: "Circle",
         radius: 8,
         fill: "cyan"
       },{
         zIndex: 4,
         type: "Image",
         src: "img/drop.png",
         width: 16,
         height: 16
       }]
     },
     assign: function(feature, zoomlevel) {
       return "pointStyle";
     }
   }
 })

This is just a layer that can be overlayed on top of map tiles. For example, we can use the same TileLayer with an image provider such as the terrain style from the HERE Map Tile API like this:

var layers = [
  new here.xyz.maps.layers.TileLayer({
    name: 'Image Layer',
    min: 1,
    max: 20,
    provider: new here.xyz.maps.providers.ImageProvider({
      name: 'Live Map',
      url: 'https://1.aerial.maps.api.here.com/maptile/2.1/maptile/newest/terrain.day/{z}/{x}/{y}/512/png8?ppi=500&app_id=' + APP_ID + '&app_code=' + APP_CODE
    })
  })
];

How did you integrate in routing?

I built the app in iterative phases, ensuring the current iteration worked at each step before adding more:

  1. I took care of preparing and uploading the dataset.
  2. I forked one of the XYZ JavaScript examples to have something functional right away. I made a few changes like putting my credentials, updating the map display.
  3. I configured the JavaScript code to access the dataset from my space to be able to load the drinking water fountains when zooming/panning.
  4. After making sure all the above was working, I added positioning. The use of HTML5 Geolocation APIs are pretty well documented so that should be easy for anyone to get started. Something developers don’t do enough though, including myself, is the handling of error cases, e.g. if the user denies sharing the location or if the location fetching fails for any reason.

Finally, I added the routing part from the current position to a clicked drinking water fountain. The documentation gives a description of how to Interact with your data by using addEventListener. That is just combined with examples online with the HERE Routing API.

The method to updateRoute() looks like the following:

function updateRoute() {
  if (currentPositionFeature && currentFountainFeature) {
    var start = currentPositionFeature.geometry.coordinates[1] + ',' + currentPositionFeature.geometry.coordinates[0];
    var destination = currentFountainFeature.geometry.coordinates[1] + ',' + currentFountainFeature.geometry.coordinates[0];
    var routeRequestParams = {
      mode: 'shortest;pedestrian',
      representation: 'display',
      waypoint0: start,
      waypoint1: destination,
      routeattributes: 'waypoints,summary,shape,legs',
      maneuverattributes: 'direction,action'
    };

    router.calculateRoute(
      routeRequestParams,
      onRouteCalculationSuccess,
      onRouteCalculationError
    );
  }
}

thirstfree-wide-view

 

I recommend doing the very first request for routing with the least number of parameters, e.g. mode, start, destination and get a successful result before then looking at more granular parameters to tweak the request. Personally, I focused on pedestrian and going for the shortest distance.

What’s next for you and this project?

I will go back to working on my day job. I am obviously pretty excited by the autonomous driving field and the work we do at HERE Technologies as I believe it will tremendously improve people’s lives. I’m confident the more autonomous cars on the roads, ultimately the safer and less trafficked those roads will be.

Definitely a fun project hacking something together with XYZ. I had in mind to display turn-by-turn maneuvers, hence using the parameter maneuverattributes but didn’t get a chance to implement this yet. Some other future directions on a project like this include integrating into a fitness app like Garmin, Nike+, Strava, Fitbit, etc. It might also be a good idea to create a mechanism to update water fountain data and push it back to OpenStreetMap to create a virtuous cycle.

You can find the complete source code for this here-xyz-thirst-free project on GitHub.