How to build a realtime map using .NET

build-realtime-map-net-header.png

In a few steps, build a realtime map that updates and marks its current position based on data it receives using the Pusher .NET library.

Introduction

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:

Prerequisites

To follow along with this article, you’ll need:

  • Visual Studio IDE, find installation instructions here.
  • Basic knowledge of C#.
  • Basic knowledge of JavaScript (ES6 syntax) and jQuery.

Setting up Pusher and Google Maps

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.

Building the Backend

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.

Creating Our Project

Using the Visual Studio IDE, follow the New Project Wizard. We will need to:
– Create our map project.
– Set C# as our language to use.
– Select .NET MVC Project as the template.
– Fill in the Project name e.g. Gaia.
– Fill in the Solution name i.e. application name.

Setting up Our Routes and Controllers

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 an Index 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 the index.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 the Views/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 your MapController class.

Creating Our Map View

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 the Shared/_Layout.cshtml file. We have added the title tag and stylesheet imports to Shared/_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:

Conclusion

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. The entire code is available on GitHub.

If you have any contributions to this application or ideas on other implementations of realtime functionality in .NET applications, kindly leave a comment below!