Allowing users to interact with the default routing by adding waypoints and drag markers can improve the efficiency and accuracy of navigation.
For example, draggable directions on a map can be a valuable tool for logistics managers in the warehouse industry. By customizing the routes based on specific delivery truck needs, you can ensure that deliveries are made efficiently and timely.
In addition, by dragging markers on the map, users can customize their route to include specific streets or areas they want to pass through. This level of personalization enables users to create a route that is tailored to their preferences and needs.
This tutorial demonstrates how use the Maps API for JavaScript to plot markers for the origin and destination on the map and then calculate and display the fastest route between those markers. The tutorial then demonstrates how to dynamically recalculate the default route if a user adds or removes a waypoint marker or changes the location of any of the existing markers.
Add a CSS style to the HTML page. For example, you can use the following basic style that sets the map viewport to 100% height and width of the enclosing container:
In the HTML container that you created, add JavaScript code that uses the Maps API for JavaScript to display the map and enable map interactions.
Within the <script> element of the HTML file that you created, paste or reference the following code snippet:
// Initiate and authenticate your connection to the HERE platform:const platform =newH.service.Platform({'apikey':'Your API key'});// Obtain the default map types from the platform object:const defaultLayers = platform.createDefaultLayers();// Instantiate (and display) a map:var map =newH.Map(
document.getElementById("map"),// Center the map on Great Britain with the zoom level of 6:
defaultLayers.vector.normal.map,{zoom:6,center:{lat:53.480759,lng:-2.242631}});// MapEvents enables the event system.// The behavior variable implements default interactions for pan/zoom (also on mobile touch environments).const behavior =newH.mapevents.Behavior(newH.mapevents.MapEvents(map));// Enable dynamic resizing of the map, based on the current size of the enclosing cntainer
window.addEventListener('resize',()=> map.getViewPort().resize());// Create the default UI:var ui =H.ui.UI.createDefault(map, defaultLayers,);
In the code snippet, replace the value of the apikey variable with your own API key.
Result: The resulting HTML file renders a base map centered on Great Britain, with the zoom level of 6, as shown in the following example:
Figure 1. Base map
Plot the origin and destination points on the map
On your map, add markers that represent the origin and destination locations.
Create the getMarkerIcon() function that applies custom SVG style to each marker:
/**
* Returns an instance of H.map.Icon to style the markers
* @param {number|string} id An identifier that will be displayed as marker label
*
* @return {H.map.Icon}
*/functiongetMarkerIcon(id){const svgCircle =`<svg width="30" height="30" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="marker">
<circle cx="15" cy="15" r="10" fill="#0099D8" stroke="#0099D8" stroke-width="4" />
<text x="50%" y="50%" text-anchor="middle" fill="#FFFFFF" font-family="Arial, sans-serif" font-size="12px" dy=".3em">${id}</text>
</g></svg>`;returnnewH.map.Icon(svgCircle,{anchor:{x:10,y:10}});}
The style produces a marker that is a blue circle with text in the center that corresponds to the marker ID.
Create the addMarker() function that adds an object marker to the map and applies the custom SVG style:
/**
* Create an instance of H.map.Marker and add it to the map
*
* @param {object} position An object with 'lat' and 'lng' properties defining the position of the marker
* @param {string|number} id An identifier that will be displayed as marker label
* @return {H.map.Marker} The instance of the marker that was created
*/functionaddMarker(position, id){const marker =newH.map.Marker(position,{data:{
id
},icon:getMarkerIcon(id),});
map.addObject(marker);return marker;}
Define the coordinates for the origin and destination markers, as shown in the following example:
const origin ={lat:55.953251,lng:-3.188267};// Edinburghconst destination ={lat:51.507797,lng:-0.128604};// London
Call the addMarker() function to plot the origin and destination markers on the map, at the locations that you specified, with different IDs:
origin is a string representing the latitude and longitude of the starting point of the route.
destination is a string representing the latitude and longitude of the destination of the route.
transpotMode is a string specifying the mode of transportation to be used for the route. In this example, it is set to car, which means the route will be optimized for car travel.
return is a string indicating the format of the response from the API. In this example, it is set to polyline, which means the response is a string representing the route as a polyline.
Initialize an empty variable for holding the polyline that represents the calculated route on the map:
let routePolyline;
Define the routeResponseHandler() function to use it as the callback function for the calculateRoute method of the H.service.RoutingService8 object:
/**
* Handler for the H.service.RoutingService8#calculateRoute call
*
* @param {object} response The response object returned by calculateRoute method
*/functionrouteResponseHandler(response){const sections = response.routes[0].sections;const lineStrings =[];
sections.forEach((section)=>{// convert Flexible Polyline encoded string to geometry
lineStrings.push(H.geo.LineString.fromFlexiblePolyline(section.polyline));});const multiLineString =newH.geo.MultiLineString(lineStrings);const bounds = multiLineString.getBoundingBox();// Create the polyline for the routeif(routePolyline){// If the routePolyline we just set the new geometry
routePolyline.setGeometry(multiLineString);}else{// routePolyline is not yet defined, instantiate a new H.map.Polyline
routePolyline =newH.map.Polyline(multiLineString,{style:{lineWidth:5}});}// Add the polyline to the map
map.addObject(routePolyline);}
Define the updateRoute function to call the routing service with the defined parameters:
The calculateRoute method determines the fastest route between two or more points based on the parameters specified in routingParams. When the route is calculated, the routeResponseHandler() function is called with the response object as a parameter.
Call the updateRoute function to display the route between the origin and destination points:
updateRoute();
Result: The map displays the fastest route to get from Edinburgh to London by car, as shown in the following figure:
Figure 3. The fastest route between the origin and destination points as a polyline
Allow users to edit the initial origin and destination points by dragging the corresponding markers to new locations. You can configure your map to dynamically recalculate the route as marker positions change.
Inside the addMarker function, enable dragging for marker objects:
In the marker variable, set the volatile configuration option of the H.map.Marker object to true to enable smooth dragging.
Set the marker.draggable property to true.
See the updated function definition for reference:
/**
* Listen to the dragstart and store the relevant position information of the marker
*/
map.addEventListener('dragstart',function(ev){const target = ev.target;const pointer = ev.currentPointer;if(target instanceofH.map.Marker){// Disable the default draggability of the underlying map
behavior.disable(H.mapevents.Behavior.Feature.PANNING);var targetPosition = map.geoToScreen(target.getGeometry());// Calculate the offset between mouse and target's position// when starting to drag a marker object
target['offset']=newH.math.Point(
pointer.viewportX - targetPosition.x, pointer.viewportY - targetPosition.y);}},false);
This event triggers when a user starts dragging a marker on the map. The function in this event listener checks if the target of the event is a marker object. If true, the function disables the default draggability of the underlying map and calculates the offset between the mouse and the target position when starting to drag the marker object. The offset is used to calculate the new position of the marker as it is dragged across the map.
Add a map event listener for the drag event:
/**
* Listen to the drag event and move the position of the marker as necessary
*/
map.addEventListener('drag',function(ev){const target = ev.target;const pointer = ev.currentPointer;if(target instanceofH.map.Marker){
target.setGeometry(
map.screenToGeo(pointer.viewportX - target['offset'].x, pointer.viewportY - target['offset'].y));}},false);
The function in this event listener updates the position of the marker on the map as the marker is being dragged based on the current position of the pointer.
Add a map event listener for the dragend event:
/**
* Listen to the dragend and update the route
*/
map.addEventListener('dragend',function(ev){const target = ev.target;if(target instanceofH.map.Marker){// re-enable the default draggability of the underlying map// when dragging has completed
behavior.enable(H.mapevents.Behavior.Feature.PANNING);const coords = target.getGeometry();const markerId = target.getData().id;// Update the routing params `origin` and `destination` properties// in case we dragging either the origin or the destination markerif(markerId ==='A'){
routingParams.origin =`${coords.lat},${coords.lng}`;}elseif(markerId ==='B'){
routingParams.destination =`${coords.lat},${coords.lng}`;}updateRoute();}},false);
The dragend event is defined to trigger the updateRoute function to dynamically recalculate and display the new route, based on the updated marker coordinates.
Result: You can now move origin and destination markers to new locations and the map dynamically recalculates the fastest route, as shown in the following figure:
Figure 4. Draggable markers and dynamic route recalculation
Add or remove waypoints
You can further increase map interactivity by enabling users to add or remove draggable waypoint markers. This functionality provides greater flexibility and efficiency in route planning.
Initialize an empty array for markers that correspond route waypoints:
// This array holds instances of H.map.Marker representing the route waypointsconst waypoints =[]
In the routing logic, update the routingParams variable to include the new via parameter, as shown in the following example:
The MultiValueQueryParameter creates a string parameter holding coordinates for multiple waypoints between the origin and destination points. Then, this string is appended to the HERE Routing API query.
Adjust the updateRoute() function to include the waypoints, as shown in the following example:
functionupdateRoute(){// Add waypoints the route must pass through
routingParams.via =newH.service.Url.MultiValueQueryParameter(
waypoints.map(wp=>`${wp.getGeometry().lat},${wp.getGeometry().lng}`));
router.calculateRoute(routingParams, routeResponseHandler, console.error);}
The via parameter adds additional waypoints the route must pass through between the origin and destination locations.
Add a map event listener for the tap event:
/**
* Listen to the tap event to add a new waypoint
*/
map.addEventListener('tap',function(ev){const target = ev.target;const pointer = ev.currentPointer;const coords = map.screenToGeo(pointer.viewportX, pointer.viewportY);if(!(target instanceofH.map.Marker)){const marker =addMarker(coords, waypoints.length +1);
waypoints.push(marker);updateRoute();}});
This event handler allows the user to add new waypoints as markers by clicking on the map at the desired location, which then updates the route accordingly.
Add a map event listener for the dbltap event:
/**
* Listen to the dbltap event to remove a waypoint
*/
map.addEventListener('dbltap',function(ev){const target = ev.target;if(target instanceofH.map.Marker){// Prevent the origin or destination markers from being removedif(['origin','destination'].indexOf(target.getData().id)!==-1){return;}const markerIdx = waypoints.indexOf(target);if(markerIdx !==-1){// Remove the marker from the array of way points
waypoints.splice(markerIdx,1)// Iterate over the remaining waypoints and update their data
waypoints.forEach((marker, idx)=>{const id = idx +1;// Update marker's id
marker.setData({
id
});// Update marker's icon to show its new id
marker.setIcon(getMarkerIcon(id))});}// Remove the marker from the map
map.removeObject(target);updateRoute();}});
The function in this event listener handles the removal of a waypoint when a user double-taps a marker.
When a user double-taps on a marker, the function first checks if the marker is an origin or destination marker. If it is, the function does not allow the marker removal. Otherwise, the function finds the index of the marker in the waypoints array and removes the marker from the array.
After removing the marker from the waypoints array, the function updates the data and icon of the remaining markers in the waypoints array to reflect their new order. Finally, the function removes the marker from the map and calls the updateRoute() function to update the route based on the new set of waypoints.
Result: You can now add, remove, or drag waypoint markers to new locations, while the map dynamically recalculates the fastest rout after each event, as shown in the following example:
Figure 5. Interactive waypoints
Source code
The following section provides the full source code that was used in this tutorial.
Note
Before trying out the following code, replace the value of the apikey parameter with your own API key.
//BOILERPLATE CODE TO INITIALIZE THE MAPconst platform =newH.service.Platform({'apikey':'Your API Key'});// Obtain the default map types from the platform object:const defaultLayers = platform.createDefaultLayers();// Instantiate (and display) a map:var map =newH.Map(
document.getElementById("map"),
defaultLayers.vector.normal.map,{zoom:6,center:{lat:53.480759,lng:-2.242631}});// MapEvents enables the event system// Behavior implements default interactions for pan/zoom (also on mobile touch environments)const behavior =newH.mapevents.Behavior(newH.mapevents.MapEvents(map));// Disable zoom on double-tap to allow removing waypoints on double-tap
behavior.disable(H.mapevents.Behavior.Feature.DBL_TAP_ZOOM);
window.addEventListener('resize',()=> map.getViewPort().resize());// Create the default UI:var ui =H.ui.UI.createDefault(map, defaultLayers,);// ROUTING LOGIC STARTS HERE// This variable holds the instance of the route polylinelet routePolyline;/**
* Handler for the H.service.RoutingService8#calculateRoute call
*
* @param {object} response The response object returned by calculateRoute method
*/functionrouteResponseHandler(response){const sections = response.routes[0].sections;const lineStrings =[];
sections.forEach((section)=>{// convert Flexible Polyline encoded string to geometry
lineStrings.push(H.geo.LineString.fromFlexiblePolyline(section.polyline));});const multiLineString =newH.geo.MultiLineString(lineStrings);const bounds = multiLineString.getBoundingBox();// Create the polyline for the routeif(routePolyline){// If the routePolyline we just set has the new geometry
routePolyline.setGeometry(multiLineString);}else{// If routePolyline is not yet defined, instantiate a new H.map.Polyline
routePolyline =newH.map.Polyline(multiLineString,{style:{lineWidth:5}});}// Add the polyline to the map
map.addObject(routePolyline);}/**
* Returns an instance of H.map.Icon to style the markers
* @param {number|string} id An identifier that will be displayed as marker label
*
* @return {H.map.Icon}
*/functiongetMarkerIcon(id){const svgCircle =`<svg width="30" height="30" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="marker">
<circle cx="15" cy="15" r="10" fill="#0099D8" stroke="#0099D8" stroke-width="4" />
<text x="50%" y="50%" text-anchor="middle" fill="#FFFFFF" font-family="Arial, sans-serif" font-size="12px" dy=".3em">${id}</text>
</g></svg>`;returnnewH.map.Icon(svgCircle,{anchor:{x:10,y:10}});}/**
* Create an instance of H.map.Marker and add it to the map
*
* @param {object} position An object with 'lat' and 'lng' properties defining the position of the marker
* @param {string|number} id An identifier that will be displayed as marker label
* @return {H.map.Marker} The instance of the marker that was created
*/functionaddMarker(position, id){const marker =newH.map.Marker(position,{data:{
id
},icon:getMarkerIcon(id),// Enable smooth draggingvolatility:true});// Enable draggable markers
marker.draggable =true;
map.addObject(marker);return marker;}/**
* This method calls the routing service to retrieve the route line geometry
*/functionupdateRoute(){
routingParams.via =newH.service.Url.MultiValueQueryParameter(
waypoints.map(wp=>`${wp.getGeometry().lat},${wp.getGeometry().lng}`));// Call the routing service with the defined parameters
router.calculateRoute(routingParams, routeResponseHandler, console.error);}// ADD MARKERS FOR ORIGIN/DESTINATIONconst origin ={lat:55.953251,lng:-3.188267};// Edinburghconst destination ={lat:51.507797,lng:-0.128604};// Londonconst originMarker =addMarker(origin,'A');const destinationMarker =addMarker(destination,'B');// CALCULATE THE ROUTE BETWEEN THE TWO WAYPOINTS// This array holds instances of H.map.Marker representing the route waypointsconst waypoints =[]// Define the routing service parametersconst routingParams ={'origin':`${origin.lat},${origin.lng}`,'destination':`${destination.lat},${destination.lng}`,// defines multiple waypoints'via':newH.service.Url.MultiValueQueryParameter(waypoints),'transportMode':'car','return':'polyline'};// Get an instance of the H.service.RoutingService8 serviceconst router = platform.getRoutingService(null,8);// Call the routing service with the defined parameters and display the routeupdateRoute();/**
* Listen to the dragstart and store relevant position information of the marker
*/
map.addEventListener('dragstart',function(ev){const target = ev.target;const pointer = ev.currentPointer;if(target instanceofH.map.Marker){// Disable the default draggability of the underlying map
behavior.disable(H.mapevents.Behavior.Feature.PANNING);var targetPosition = map.geoToScreen(target.getGeometry());// Calculate the offset between mouse and target's position// when starting to drag a marker object
target['offset']=newH.math.Point(
pointer.viewportX - targetPosition.x, pointer.viewportY - targetPosition.y);}},false);/**
* Listen to the dragend and update the route
*/
map.addEventListener('dragend',function(ev){const target = ev.target;if(target instanceofH.map.Marker){// re-enable the default draggability of the underlying map// when dragging has completed
behavior.enable(H.mapevents.Behavior.Feature.PANNING);const coords = target.getGeometry();const markerId = target.getData().id;// Update the routing params `origin` and `destination` properties// in case we dragging either the origin or the destination markerif(markerId ==='A'){
routingParams.origin =`${coords.lat},${coords.lng}`;}elseif(markerId ==='B'){
routingParams.destination =`${coords.lat},${coords.lng}`;}updateRoute();}},false);/**
* Listen to the drag event and move the position of the marker as necessary
*/
map.addEventListener('drag',function(ev){const target = ev.target;const pointer = ev.currentPointer;if(target instanceofH.map.Marker){
target.setGeometry(
map.screenToGeo(pointer.viewportX - target['offset'].x, pointer.viewportY - target['offset'].y));}},false);/**
* Listen to the tap event to add a new waypoint
*/
map.addEventListener('tap',function(ev){const target = ev.target;const pointer = ev.currentPointer;const coords = map.screenToGeo(pointer.viewportX, pointer.viewportY);if(!(target instanceofH.map.Marker)){const marker =addMarker(coords, waypoints.length +1);
waypoints.push(marker);updateRoute();}});/**
* Listen to the dbltap event to remove a waypoint
*/
map.addEventListener('dbltap',function(ev){const target = ev.target;if(target instanceofH.map.Marker){// Prevent origin or destination markers from being removedif(['origin','destination'].indexOf(target.getData().id)!==-1){return;}const markerIdx = waypoints.indexOf(target);if(markerIdx !==-1){// Remove the marker from the array of way points
waypoints.splice(markerIdx,1)// Iterate over the remaining waypoints and update their data
waypoints.forEach((marker, idx)=>{const id = idx +1;// Update marker's id
marker.setData({
id
});// Update marker's icon to show its new id
marker.setIcon(getMarkerIcon(id))});}// Remove the marker from the map
map.removeObject(target);updateRoute();}});
Next steps
To explore other Maps API for JavaScript use cases, see Examples.
To explore the design and features of the Maps API for JavaScript, see the API Reference.