In this article, I will demonstrate how to create a sophisticated map with routing between multiple points. I will also show you how to create map controls and walk you through the complete code.



Working Example


Click on the button below to see a full working example of this code.

 


Load the Azure Maps Map Control and CSS Files 


Load the required CSS in the page's head section first. The CSS should be loaded before the JavaScript controller, and the script can be loaded anywhere before the closing body tag. These two steps are identical when using a static map; however, an Azure map route also requires the use of the map helper script, which is loaded below.


<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js">
<!-- Add a reference to the Azure Maps Rest Helper JavaScript file. -->
<script src="https://samples.azuremaps.com/lib/azure-maps/azure-maps-helper.min.js">

CSS


CSS is used to style the map container. If you omit the CSS stylesheet, the Azure Map may be empty and not displayed. Here we simply set the body to consume 100% of the page.


html,
body {
	width: 100%;
	height: 100%;
	padding: 0;
	margin: 0;
}

#myMap {
	width: 100%;
	height: 100%;
}

JavaScript


First, we will declare the map and data source variables at the top of the script and load the Azure Maps Routing API. Leave the strings inside the curly braces alone. These are variables that are set using the Azure Maps Helper.

The only argument that I may change in the routeUrl string is the travelMode URL argument. This is used to determine the mode of travel. Possible values are 'car', 'truck', 'taxi', 'bus', 'van', 'motorcycle', 'bicycle', 'pedestrian'.


var map, datasource;

// URL for the Azure Maps Route API.
var routeUrl = 'https://{azMapsDomain}/route/directions/json?api-version=1.0&query={query}&routeRepresentation=polyline&travelMode=car&view=Auto';

GetMap Function


Just like static Azure Map example, the getMap() JavaScript function determines how the map is rendered on the page. This can be named anything you want and needs to be invoked on the page. Use a unique name if you want multiple maps on a page.


getMap()

Map Initialization

The map initialization is identical to my previous static map article. The myMap string identifies the div container used for the map. The GeoCoordinates sets the initial map location. Azure Maps uses longitude, latitude, the GeoJSON standard, to represent geographical coordinates, whereas Bing Maps reverses the coordinates and uses latitude and longitude.

Zoom is not necessary as Azure Maps will calculate the map bounds from the routing coordinates that we will place later in the page. The style of the map can be customized and use 'road', 'grayscale_dark', 'night', 'road_shaded_relief', and 'satellite'. In this version of Azure Maps, 'satellite_road_labels' does not work for maps with routing.

In this example, the authentication options interface (authOptions) requires your primary or secondary Azure Maps subscription key.


// Initialize a map instance.
map = new atlas.Map('myMap', {
	// Azure Maps reverses the order of the geocoordinates used with Bing Maps and uses lon,lat instead of lat,long
	center: [-112.169057,37.623964],
	zoom: 12,
	view: 'Auto',
	style: 'road_shaded_relief',// Note: satellite_with_roads does not work on it's own when using directions

	authOptions: {
		 authType: 'subscriptionKey',
		 subscriptionKey: 'ReplaceWithYourAzureMapsAPIKey'
	 }
});

Use the Azure Maps Ready Event to Load Map Assets

Azure map datasources and assets should always be loaded after using the Azure Maps ready event. This event fires as soon as the map is ready to be interacted with programmatically. Other map events can be loaded onto the map using the map.events.add() method. This step is required for both static and map routes.


map.events.add('ready', function () {
	// Map Datasources and assets can now be safely loaded...
})//map.events

Add the Routing Line Layer


There are several options for adding a line between two or more points using the lineLayers interface. Here, I am using the primary accent color of my chosen Galaxie Blog theme as the stroke color, with a 5-pixel width, and a round line cap. 


// Add a layer for rendering the route line and have it render under the map labels.
map.layers.add(new atlas.layer.LineLayer(datasource, null, {
	strokeColor: '#f35800',
	strokeWidth: 5,
	lineJoin: 'round',
	lineCap: 'round'
}), 'labels');

Add Routing Icons and Text Labels


Below, we are setting the global icon and text options. Since there are multiple points on a map when using routing, we use the Azure 'get' method inside an array to extract the map icons and title label properties from the waypoint structures below. The iconOptions interface offers numerous options; here, we are simply retrieving the title and enabling the icons to be placed at exact coordinates, even when there is overlap. 

The textOptions interface allows us to create custom labels on the map. Here, like the icons above, I get the icon title from the waypoint structure below, offset the label below the map point, and set the text to white with a blurred emerald green shadow dropdown effect. 


// Add a layer for rendering point data.
map.layers.add(new atlas.layer.SymbolLayer(datasource, null, {
	iconOptions: {
		image: ['get', 'iconImage'],
		allowOverlap: true,
		ignorePlacement: true
	},
	textOptions: {
		textField: ['get', 'title'],
		size: 18,
		offset: [0, 2],// This controls the offset of the title (i.e. 'Zion National Park')
		color: '#FFFFFF', // Set the title text color to dark grey 141414
		haloColor: '#f35800', // Set the title background text color to light grey
		haloWidth: 2,// The halo must have a width in order to be displayed
		haloBlur: 3//This is similiar to a drop shadow with blur
		// See https://learn.microsoft.com/en-us/javascript/api/azure-maps-control/atlas.textoptions?view=azure-maps-typescript-latest
	},
	filter: ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']] //Only render Point or MultiPoints in this layer.
}));

Creating the WayPoints


We need to create multiple GeoCoordinates, locations, and Waypoint structures to hold routing data. Here, I am setting up a route that starts in Bryce Canyon National Park, travels along Highway 12 to Escalante, and finally to Torrey, Utah.

It's best to use longitude and latitude for geographical coordinates; however, as we will see, we can reverse the order later on using the replace function if you originally stored these coordinates for Bing Maps in the reverse order, using latitude and longitude.

The location string serves as the title for the waypoint and can be changed to anything you want, for example, 'stop 1', ' stop 2, ' stop 3', etc. 

The waypoint structure utilizes the Point class to display our routing locations on the map.  One of the major benefits of Azure Maps over Bing Maps is that the Atlas class can render various types of information, including HTML markers, weather overlays, and other shapes- such as polygons. Refer to the Azure Maps Control documentation for additional examples.

After the waypoints have been created, we add all of these waypoints to the datasource as an array.


// Create our waypoints
// Note the GeoJSON objects have been switched from Bing Maps to Azure Maps. Now we are using longitude first then latitude instead of the other way around.

// Set the vars
var geoCoordinates1 = [-112.169057,37.623964];
var location1 = 'Bryce Canyon National Park, Bryce, UT 84764';
// Create our waypoints
var waypoint1 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates1), {
	title: location1,
	iconImage: 'pin-blue'
});

// Set the vars
var geoCoordinates2 = [-111.604177,37.770375];
var location2 = 'Escalante, UT';
// Create our waypoints
var waypoint2 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates2), {
	title: location2,
	iconImage: 'pin-blue'
});

// Set the vars
var geoCoordinates3 = [-111.421079,38.29902];
var location3 = 'Torrey, UT';
// Create our waypoints
var waypoint3 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates3), {
	title: location3,
	iconImage: 'pin-red'
});

// Add the waypoints to the data source.
datasource.add([waypoint1,waypoint2,waypoint3]);

Create the Map Bounding Box


Here, I pass all the geoCoordinates inside an array to the Azure Maps boundingBox class to generate the map boundaries and create a 100-pixel padding to ensure that the entire map is displayed. 


// Fit the map window to the bounding box defined by the start and end positions.
map.setCamera({
	bounds: atlas.data.BoundingBox.fromPositions([geoCoordinates1,geoCoordinates2,geoCoordinates3]),
	// Padding will essentially zoom out a bit. The default is 50, I am using 100 as I want the destinations on the map to be clearly shown
	padding: 100
});

Use the processRequest Method in the Azure Maps Helper to Calculate Directions


This particular piece of code presented me with some issues, as the processRequest method, located in the Azure Maps Helper, reverses the geographical coordinates. Azure Maps is not always consistent and sometimes reverses the way that it handles them. This is a major frustration of mine and you need to pay attention to how you're using the geocoordinates, especially when migrating from Bing Maps to the Azure Map platform.


// Create the route request with the query using the following format 'startLongitude,startLatitude:endLongitude,endLatitude'. Note: we must reverse the coordinates. Azure Maps is not always consistent when using geocoordinates.
var routeRequestURL = routeUrl
	.replace('{query}', '${geoCoordinates1[1]},${geoCoordinates1[0]}:${geoCoordinates2[1]},${geoCoordinates2[0]}:${geoCoordinates3[1]},${geoCoordinates3[0]}');  

// Process the request and render the route result on the map. This method is in the Azure Maps resources that was loaded to the page.
processRequest(routeRequestURL).then(directions => {
	// Extract the first route from the directions.
	const route = directions.routes[0];
	// Combine all leg coordinates into a single array.
	const routeCoordinates = route.legs.flatMap(leg => leg.points.map(point => [point.longitude, point.latitude]));
	// Create a LineString from the route path points.
	const routeLine = new atlas.data.LineString(routeCoordinates);
	// Add it to the data source.
	datasource.add(routeLine);
});//processRequest

Create the Zoom Control


A multitude of optional map controls are available. In my maps, I use a simple zoom control to allow the users to zoom into the map and float the control at the top right part of the page. You can also use top-left, bottom-right, and bottom-left to place any of the page controls. You can choose a light or dark picker style. 


// Create a zoom control.
map.controls.add(new atlas.control.ZoomControl({
	zoomDelta: parseFloat(1),
	style: "light"
}), {
	position: 'top-right'
}); 

Create a Map Style Control 


The Azure Map style control is an attractive, compact widget that allows users to change the map style. You can edit the available map styles. The control can use either a list, which displays the map styles like a traditional dropdown, or icons, as shown here. Like the zoom control, you can position this on the map.


// Create the style control
map.controls.add(new atlas.control.StyleControl({
	mapStyles: ['road', 'grayscale_dark', 'night', 'road_shaded_relief', 'satellite', 'satellite_road_labels'],
	layout: 'icons'
}), {
	position: 'top-right'
});  

HTML Body

In the HTML body, call the getMap function to load the Azure Map to the page like so:


<body onload="getMap()">
    <div id="myMap"></div>
</body>

Full Code


<!DOCTYPE html>
<html lang="en">

<head>
    <title>Azure Map Routing Example</title>

    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="description" content="This tutorial shows how to calculate a route and display it on the map." />
    <meta name="keywords" content="Microsoft maps, map, gis, API, SDK, services, module, tutorials, routing, directions, route, truck, commercial vehicle" />
    <meta name="author" content="Gregory Alexander" />

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js">

    <!-- Add a reference to the Azure Maps Rest Helper JavaScript file. -->
    <script src="https://samples.azuremaps.com/lib/azure-maps/azure-maps-helper.min.js">

    <script>
        var map, datasource;

		// URL for the Azure Maps Route API.
		var routeUrl = 'https://{azMapsDomain}/route/directions/json?api-version=1.0&query={query}&routeRepresentation=polyline&travelMode=car&view=Auto';

		function getMap() {
			// Initialize a map instance.
			map = new atlas.Map('myMap', {
				// Azure Maps reverses the order of the geocoordinates used with Bing Maps and uses lon,lat instead of lat,long
				center: [-112.169057,37.623964],
				zoom: 12,
				view: 'Auto',
				style: 'road_shaded_relief',// Note: satellite_with_roads does not work on it's own when using directions

				authOptions: {
					 authType: 'subscriptionKey',
					 subscriptionKey: 'ReplaceWithYourAzureMapsApiKey'
				 }
			});

			// Wait until the map resources are ready.
			map.events.add('ready', function () {
				// Create a data source and add it to the map.
				datasource = new atlas.source.DataSource();
				map.sources.add(datasource);

				// Add a layer for rendering the route line and have it render under the map labels.
				map.layers.add(new atlas.layer.LineLayer(datasource, null, {
					strokeColor: '#f35800',
					strokeWidth: 5,
					lineJoin: 'round',
					lineCap: 'round'
				}), 'labels');

				// Add a layer for rendering point data.
				map.layers.add(new atlas.layer.SymbolLayer(datasource, null, {
					iconOptions: {
						image: ['get', 'iconImage'],
						allowOverlap: true,
						ignorePlacement: true
					},
					textOptions: {
						textField: ['get', 'title'],
						size: 18,
						offset: [0, 2],// This controls the offset of the title (i.e. 'Zion National Park')
						color: '#FFFFFF', // Set the title text color to dark grey 141414
						haloColor: '#f35800', // Set the title background text color to light grey
						haloWidth: 2,// The halo must have a width in order to be displayed
						haloBlur: 3//This is similiar to a drop shadow with blur
						// See https://learn.microsoft.com/en-us/javascript/api/azure-maps-control/atlas.textoptions?view=azure-maps-typescript-latest
					},
					filter: ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']] //Only render Point or MultiPoints in this layer.
				}));

				// Create our waypoints
				// Note the GeoJSON objects have been switched from Bing Maps to Azure Maps. Now we are using longitude first then latitude instead of the other way around.

				// Set the vars
				var geoCoordinates1 = [-112.169057,37.623964];
				var location1 = 'Bryce Canyon National Park, Bryce, UT 84764';
				// Create our waypoints
				var waypoint1 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates1), {
					title: location1,
					iconImage: 'pin-blue'
				});

				// Set the vars
				var geoCoordinates2 = [-111.604177,37.770375];
				var location2 = 'Escalante, UT';
				// Create our waypoints
				var waypoint2 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates2), {
					title: location2,
					iconImage: 'pin-blue'
				});

				// Set the vars
				var geoCoordinates3 = [-111.421079,38.29902];
				var location3 = 'Torrey, UT';
				// Create our waypoints
				var waypoint3 = new atlas.data.Feature(new atlas.data.Point(geoCoordinates3), {
					title: location3,
					iconImage: 'pin-red'
				});


				// Add the waypoints to the data source.
				datasource.add([waypoint1,waypoint2,waypoint3]);

				// Fit the map window to the bounding box defined by the start and end positions.
				map.setCamera({
					bounds: atlas.data.BoundingBox.fromPositions([geoCoordinates1,geoCoordinates2,geoCoordinates3]),
					// Padding will essentially zoom out a bit. The default is 50, I am using 100 as I want the destinations on the map to be clearly shown
					padding: 100
				});

				// Create the route request with the query using the following format 'startLongitude,startLatitude:endLongitude,endLatitude'. Note: we must reverse the coordinates. Azure Maps is not always consistent when using geocoordinates.
				var routeRequestURL = routeUrl
					.replace('{query}', '${geoCoordinates1[1]},${geoCoordinates1[0]}:${geoCoordinates2[1]},${geoCoordinates2[0]}:${geoCoordinates3[1]},${geoCoordinates3[0]}');  

				// Process the request and render the route result on the map. This method is in the Azure Maps resources that was loaded to the page.
				processRequest(routeRequestURL).then(directions => {
					// Extract the first route from the directions.
					const route = directions.routes[0];
					// Combine all leg coordinates into a single array.
					const routeCoordinates = route.legs.flatMap(leg => leg.points.map(point => [point.longitude, point.latitude]));
					// Create a LineString from the route path points.
					const routeLine = new atlas.data.LineString(routeCoordinates);
					// Add it to the data source.
					datasource.add(routeLine);
				});//processRequest

				// Add the controls
				// Create a zoom control.
				map.controls.add(new atlas.control.ZoomControl({
					zoomDelta: parseFloat(1),
					style: "light"
			   }), {
				  position: 'top-right'
				}); 

				// Create the style control
				map.controls.add(new atlas.control.StyleControl({
				  mapStyles: ['road', 'road_shaded_relief', 'satellite', 'satellite_road_labels'],
				  layout: 'icons'
				}), {
				  position: 'top-right'
				});  

			});//map.events
		}

    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            padding: 0;
            margin: 0;
        }

        #myMap {
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body onload="getMap()">
    <div id="myMap"></div>
</body>

</html>

Further Reading: