Realtime maps are a popular feature in most modern applications. They are used in apps like Uber or Lyft, and in courier and delivery services to track the location of parcels or cabs and to monitor their progress and movement as they make their way to the customer.
In this article, we will look at how to build a realtime map using .NET and Pusher. Our resulting application will look like this:
To follow along with this article, you’ll need:
To achieve our realtime map, we’ll be making use of two services: Pusher and Google Maps. Pusher is a service that offers simple implementation of realtime functionality in web and mobile applications. We will use it primarily to transmit the realtime updates on our map.
Create a Pusher account, if you have not already, and then set up your application as seen in the screenshot below.
When you have completed the set up, take note of your Pusher application keys as we will need them later on.
Next, we will set up a Google Maps API project. The Google Maps API provides a service for embedding maps in our applications and provides access to location information of businesses, cities and much more for numerous countries all over the world. We will use this service to generate a map and mark the realtime locations on the map.
Using the Google Maps API guide, create a project and copy out the API key.
In this article, using C#, we will build a small application that renders a map, on which the location will be displayed and marked in realtime.
Using the Visual Studio IDE, follow the New Project Wizard. We will need to:
For the purpose of this application, we will define two routes: the route to render the map and the route to send new locations to our map. Create a RouteConfig.cs
file, and paste the following code:
1// RouteConfig.cs 2 routes.MapRoute( 3 name: "Home", 4 url: "", 5 defaults: new { controller = "Home", action = "Index" } 6 ); 7 8 routes.MapRoute( 9 name: "Map", 10 url: "map", 11 defaults: new { controller = "Map", action = "Index" } 12 );
These route definitions specify the route pattern and the Controller and Action to handle it. Based on this, we need to create two controller files in the Controllers directory, HomeController.cs
and MapController.cs
.
đź’ˇ Creating our project with Visual Studio automatically creates the
HomeContoller.cs
file with anIndex
action. We will use this for our home route.
In the HomeController.cs
file, we add:
1// HomeController.cs 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 using System.Web.Mvc.Ajax; 8 9 namespace Gaia.Controllers 10 { 11 public class HomeController : Controller 12 { 13 public ActionResult Index() 14 { 15 return View(); 16 } 17 } 18 }
The above snippet renders our home view using the View function.
đź’ˇ The
View
function creates a view response which we return. When it is invoked, C# looks for the default view of the calling controller class. This default view is theindex.cshtml
file found in the Views directory, in a directory with the same name as the Controller. i.e. The default view of the HomeController class will be theViews/Home/index.cshtml
file.
In the MapController.cs
file, we will receive a location’s longitude and latitude via a POST request and transmit this location data to our map via the Pusher service. Add the following code:
1// MapController.cs 2 3 ... 4 5 public class MapController : Controller 6 { 7 private Pusher pusher; 8 9 public MapController() 10 { 11 var options = new PusherOptions(); 12 options.Cluster = "app_cluster"; 13 14 pusher = new Pusher( 15 "app_id", 16 "app_key", 17 "app_secret", 18 options 19 ); 20 } 21 22 [HttpPost] 23 public JsonResult Index() 24 { 25 var latitude = Request.Form["lat"]; 26 var longitude = Request.Form["lng"]; 27 28 var location = new 29 { 30 latitude = latitude, 31 longitude = longitude 32 }; 33 34 pusher.TriggerAsync("location_channel", "new_location", location); 35 36 return Json( new { status = "success", data = location } ); 37 } 38 }
In the code block above, we create a class variable private Pusher pusher
. Then, we instantiate it to a Pusher client in the class constructor using the app credentials copied earlier from the Pusher dashboard.
We use the Pusher instance to transmit the location data on the location_channel
channel, in the new_location
event. Remember to replace app_id
and the other values with your Pusher app credentials.
⚠️ To use the Pusher client in our controller, you must install the PusherServer library via NuGet, and add
using PusherServer
to the top import statements of yourMapController
class.
Since our map will be rendered on our home route, we will use the Views/Home/index.cshtml
file (which is the default view of the HomeController class).
đź’ˇ Our
Views/Home/index.cshtml
file extends theShared/_Layout.cshtml
file. We have added thetitle
tag and stylesheet imports toShared/_Layout.cshtml
for this reason.
In the Shared/_Layout.cshtml
file, we add:
1<!-- Shared/_Layout.cshtml --> 2 <!DOCTYPE html> 3 <html> 4 <head> 5 <title>Gaia</title> 6 <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.3/css/bootstrap.min.css" /> 7 <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/custom.css")"/> 8 </head> 9 <body> 10 @RenderBody() 11 </body> 12 </html>
In the Views/Home/index.cshtml
file, add the following:
1<!-- Views/Home/index.cshtml --> 2 <div class="container"> 3 <div class="row"> 4 <div class="col-md-6 col-xs-12 col-lg-6"> 5 <h3>A realtime Map</h3> 6 <div id="map"> 7 8 </div> 9 </div> 10 </div> 11 </div> 12 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> 13 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.3/js/bootstrap.min.js"></script>
The block above defines the basic markup of our view. It consists mainly of the div
for holding our map. We have also imported the Bootstrap CSS framework and its jQuery library dependency, to take advantage of some pre-made styles.
Next we will import the Google Maps API JavaScript library and initialize our map in Views/Home/index.cshtml
:
1<!-- Views/Home/index.cshtml --> 2 <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAniUCyk0Gfp_UT1qNTHg2AF4I4ZmQ6EGo&callback=initMap"></script> 3 <script> 4 let lineCoordinates = [] 5 6 let latitude = 6.4541; 7 let longitude = 3.3947; 8 9 let map = false; 10 let marker = false; 11 12 function initMap() { 13 let lagos = {lat: latitude, lng: longitude}; 14 15 map = new google.maps.Map(document.getElementById('map'), { 16 zoom: 10, 17 center: lagos 18 }); 19 20 marker = new google.maps.Marker({ 21 position: lagos, 22 map: map 23 }); 24 25 lineCoordinates.push(marker.getPosition()) 26 } 27 </script>
In the snippet above, we have initialized our map by passing the coordinates of Lagos, Nigeria to the Google Maps library.
Next, we will listen for changes in location (via our Pusher event) and implement the updates to our map. For this we’ll define our map update function. Copy the following code:
1<!-- Views/Home/index.cshtml --> 2 3 [...] 4 5 const updateMap = function(data) { 6 latitude = (data.latitude * 1); 7 longitude = (data.longitude * 1); 8 9 map.setCenter({ 10 lat: latitude, 11 lng: longitude, 12 alt: 0 13 }); 14 15 marker.setPosition({ 16 lat: latitude, 17 lng: longitude, 18 alt: 0 19 }); 20 21 lineCoordinates.push(marker.getPosition()) 22 23 let lineCoordinatesPath = new google.maps.Polyline({ 24 path: lineCoordinates, 25 geodesic: true, 26 map: map, 27 strokeColor: '#FF0000', 28 strokeOpacity: 1.0, 29 strokeWeight: 2 30 }); 31 } 32 </script>
Finally, we’ll listen for our Pusher events and trigger the updateMap
function in our view:
1<!-- Views/Home/index.cshtml 2 3 [...] 4 5 <script src="https://js.pusher.com/4.1/pusher.min.js"></script> 6 <script> 7 const pusher = new Pusher('app_key', { 8 cluster: 'app_cluster' 9 }); 10 11 const channel = pusher.subscribe('location_channel'); 12 13 channel.bind('new_location', function(data) { 14 updateMap(data); 15 }); 16 </script>
In the snippet above, we import and initialize the Pusher JavaScript client. Then we subscribe to the location_channel
and listen to the new_location
event, passing the new location data received to our updateMap
function for realtime updates.
Here is the application when we run it again:
In a few simple steps, we have built a realtime map that updates and marks its current position based on data it receives. This application can be used to get GPS coordinates from a requested cab, or a tracked parcel to view its location and travel path on a map.