Jul 06, 2013

Realtime Maps Based On XML File Changes With SignalR and ASP.NET MVC

This article explores how to implement realtime Google and Bing Maps based on XML file changes by external system. It can be used in many ways like to track and display real-time position of bus, train or flight in the maps.

1. Assuming the external system inserts current location in XML file.

2. The .NET FileSystemWatcher object is used to monitor XML File changes. When a location is inserted, the FileSystemWatcher fires an event to notify that a change has occurred.

3. On change event, the current location is passed to client using SignalR

4. The map is updated by client side code.

real time maps

XML Structure:


<?xml version="1.0" encoding="utf-8" ?>
<locations>
  <location name="A" lat="9.074614" lng="77.849121"></location>
  <location name="B" lat="15.24" lng="77.16"></location>
</locations>

The external system inserts location tag only.

Getting Started:

1. Create ASP.NET MVC 4 Basic project in VS2012.

2. Install SignalR by running following command in Package Manager Console:

install-package Microsoft.AspNet.SignalR

3. In Solution Explorer, right-click the project > select Add > New Folder > give name Hubs.

4. Right-click the Hubs folder > click Add > New Item > select “SignalR Hub Class” > give name MapsHub.cs.

5. Open the Global.asax file, and add a call to the method RouteTable.Routes.MapHubs(); as the first line of code in the Application_Start method.


  protected void Application_Start()
        {
            RouteTable.Routes.MapHubs();
            AreaRegistration.RegisterAllAreas();
            //...Other code...
        }

FileSystemWatcher:

First create Location class as below:


 public class Location
    {
        public String Name { get; set; }
        public double Lat { get; set; }
        public double Lng { get; set; }
    }

Add a new Class FileWatcher.cs to implement FileSystemWatcher for XML file as below:


 public static class FileWatcher
    {
        private static FileSystemWatcher fWatcher;
        private static String xmlFilePath = HttpContext.Current.Server.MapPath("~/App_Data/location.xml");
        private static List<Location> objLoc = new List<Location>();


        public static IEnumerable<Location> Data
        {
            get
            {
                return objLoc.AsReadOnly(); 
            }
        }

        /// <summary>
        /// One Time Initialization
        /// </summary>
        public static void init()
        {
            Initialize();
            ReadNewLines();
        }


        /// <summary>
        /// To setup FileSystemWatcher
        /// </summary>
        private static void Initialize()
        {
            fWatcher = new FileSystemWatcher();
            fWatcher.Path = Path.GetDirectoryName(xmlFilePath);
            fWatcher.Filter = Path.GetFileName(xmlFilePath);
            fWatcher.Changed += watcher_Changed;
            fWatcher.EnableRaisingEvents = true;
            fWatcher.Error += OnError;
        }

        private static void OnError(object sender, ErrorEventArgs e)
        {
            fWatcher.Dispose();
            Initialize();
        }


        private static void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            try
            {
                fWatcher.EnableRaisingEvents = false;
                var newLoc = ReadNewLines();
                //Pass new points to client
                var context = GlobalHost.ConnectionManager.GetHubContext<MapsHub>();
                context.Clients.All.addLocations(newLoc);
            }
            finally
            {
                fWatcher.EnableRaisingEvents = true;
            }

        }

        /// <summary>
        /// To read new lines 
        /// </summary>
        public static IEnumerable<Location> ReadNewLines()
        {
            XDocument xmlDoc;
            using (Stream s = File.OpenRead(xmlFilePath))
            {
                xmlDoc = XDocument.Load(s);
            }
            int total = objLoc.Count();
            var newLoc = (from item in xmlDoc.Descendants("location").Select((x, index) => new { x, index })
                          where item.index >= total
                          select new Location { Name = (String)item.x.Attribute("name"), Lat = (double)item.x.Attribute("lat"), Lng = (double)item.x.Attribute("lng") });
            objLoc.AddRange(newLoc);
            return newLoc;
        }

    }


In above, xml file is opened in readonly mode and new locations are fetched using LINQ in ReadNewLines method. When FileSystemWatcher fires change event, new points are passed to client.

call init method in Application_Start method of Global.asax:


 protected void Application_Start()
        {
		....
		FileWatcher.init(); 
        }

Hub:


 public class MapsHub : Hub
    {
        public IEnumerable<Location> GetLocations() {
            return FileWatcher.Data;
        }            
    }

The above method is called on page load to get all points to draw polylines with marker on current location.

Google Maps:

Add a controller and add its default view without any layout. Add jquery and signalR references:


  @Scripts.Render("~/bundles/jquery")
    <!--Reference the SignalR library. -->
    <script src="~/Scripts/jquery.signalR-1.1.2.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="~/signalr/hubs"></script>

In the view, reference script for Google maps and basic implementation of polyline and marker:


 <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
    <style>
        html, body
        {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #map-canvas, #map_canvas
        {
            height: 100%;
        }

        html, body
        {
            height: auto;
        }

        #map-canvas, #map_canvas
        {
            height: 650px;
        }

        #panel
        {
            background-color: #FFFFFF;
            border: 1px solid #999999;
            left: 50%;
            margin-left: -180px;
            padding: 5px;
            position: absolute;
            top: 5px;
            z-index: 5;
        }
    </style>
    <script>
        var googleMap,googlePolyLine,googleMarker;
        var googlePoints = new google.maps.MVCArray();       
        function initialize() {
            var myLatLng = new google.maps.LatLng(0, -180);

            var mapOptions = {
                zoom: 4,                
                mapTypeId: google.maps.MapTypeId.TERRAIN
            };

            googleMap = new google.maps.Map(document.getElementById('googleMap'), mapOptions);

            googleMarker = new google.maps.Marker({
                map: googleMap
            });

            googlePolyLine = new google.maps.Polyline({
                path: googlePoints,
                strokeColor: '#FF0000',
                strokeOpacity: 1.0,
                strokeWeight: 2
            });

            googlePolyLine.setMap(googleMap);
        }

        google.maps.event.addDomListener(window, 'load', initialize);

    </script>	

in body tag


  <h2>Google Maps:</h2>
  <div id="googleMap" style="position: relative; width: 600px; height: 400px;"></div>

To add points and marker:


 //To add Google Map Points
        function addGoogleMapPoints(loc) {
            var path;
            for (i = 0; i < loc.length; i++) {
                path = googlePolyLine.getPath();
                path.push(new google.maps.LatLng(loc[i].Lat, loc[i].Lng));
            }
            var pnt = loc[loc.length - 1];
            googleMarker.setPosition(new google.maps.LatLng(pnt.Lat, pnt.Lng));
            googleMap.panTo(new google.maps.LatLng(pnt.Lat, pnt.Lng));
        }

Bing Maps:


 <script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
    <script>
        var bingMap,bingPolylines,bingMarker;
        var bingPoints = [];       
        function OnPageLoad() {
            bingMap = new VEMap('bingMap');
            bingMap.LoadMap();
            bingMap.SetZoomLevel(4);
            var color = new VEColor(255, 0, 0, 1);
            var width = 3;
            var id = 'I70';

            bingPolylines = new VEPolyline(id, bingPoints, color, width);
            bingMap.AddPolyline(bingPolylines);
            bingMarker = new VEShape(VEShapeType.Pushpin, new VELatLong(0, 0));
            bingMap.AddShape(bingMarker);
        }
    </script>

call OnPageLoad method:


<body onload="OnPageLoad();">

in body tag


  <h2>Bing Maps:</h2>
  <div id="bingMap" style="position: relative; width: 600px; height: 400px;"></div>

To add points and markers:


 //To add Bing Map Points
        function addBingMapPoints(loc) {
            for (i = 0; i < loc.length; i++) {
                bingPoints.push(new VELatLong(loc[i].Lat, loc[i].Lng));
            }
            var pnt = loc[loc.length - 1];
            bingMarker.SetPoints(new VELatLong(pnt.Lat, pnt.Lng));
            bingMap.PanToLatLong(new VELatLong(pnt.Lat, pnt.Lng));
        }

SignalR Client:


   // Reference the auto-generated proxy for the hub.  
        var maps = $.connection.mapsHub;
        maps.client.addLocations = function (data) {
            addPoints(data);
        };

        $.connection.hub.start().done(function () {
            maps.server.getLocations().done(function (data) {
                addPoints(data);
            })
        })

        function addPoints(data) {
            if (data.length > 0) {
                addGoogleMapPoints(data);
                addBingMapPoints(data);
            }
        }

First, the existing points are retrieved through SignalR server side GetLocations method. When any new point is inserted in XML, FileSystemWatcher calls addLocations client method which adds location points in the existing polyline of Maps.

Demo:

This post covers following things also:

1. To draw polylines in Google and Bing maps

2. Add points dynamically in existing polyline

3. Move marker to current position

4. Implementation of FileSystemWatcher

5. An Application of SignalR

If you like this post, don’t forget to share. If you have any query, feel free to ask in comment box.