An ASP.NET MVC Truck Tracker Application Using Google Maps
In a previous post we built a MySQL database and a data access layer via a repository pattern implemented using Fluent LINQT to NHibernate. We also wired up this repository in our ASP.NET MVC application using Ninject. This post will build out the ASP.NET MVC application a bit to present the information. To help visualize the data we will be using the Google Maps v.3 jQuery plugin that we previously built.
MVC View
Here is a screen shot of the view we are going to build.
There is a header and footer on the page. The header contains application title and subtitle designed to look like a road. The footer contains information about the technology with links to the information.
The core of the application is a master / slave list and map view. Here is the markup that creates this view.
<head runat="server"> <title>Trucks</title> <link type="text/css" href="~/Content/css/Site.css" rel="Stylesheet" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script> <script type="text/javascript" src="<%= Url.Content("~/Content/scripts/jquery.gmap3.js") %>"></script> <script type="text/javascript" src="<%= Url.Content("~/Content/scripts/trucktracker.js") %>"></script> </head> <body> <div class="header"> <!-- Conent removed for brevity --> </div> <div id="wrapper"> <div id="truck-list" class="left"> <h3>Truck List</h3> <ul> <% foreach(Truck truck in Model) { %> <li> <a id="truck-<%= truck.Id %>" href="javascript:showTruck(<%= truck.Id %>)"> <span class="truck"> <span class="truck-id"><%= truck.Id %></span> <span class="truck-name"><%= truck.Name %></span> <span class="truck-type"><%= truck.Type %></span> <span class="truck-plate"><%= truck.PlateNumber %></span> </span> </a> </li> <% } %> </ul> </div> <div id="map"> <div id="map_canvas" class="line"></div> </div> <div id="footer"> <!-- Content removed for brevity --> </div> </div> </body>
The above code has been simplified by removing elements that do not map to functional requirements (header and footer areas) of the list view / map details. In the HTML ‘head’ element all the necessary CSS and scripts are pulled down. Notice the inclusion of jQuery and the Google maps API (both from the Google repository). The last two scripts include the previously created Google maps jQuery plugin and the application specific JavaScript (more later).
The bulk of the markup defines the layout for the list view (‘truck-list’ division). This division builds up the truck list using the supplied view-model (a list of ‘Truck’ objects). Each list element is an HTML anchor “obtrusively” wired up to the ‘showTruck’ JavaScript handler (more in a bit). The remaining elements create content and define CSS classes to allow each element to be styled.
The map markup is an empty HTML ‘div’. This map is dynamically created in JavaScript as the page loads. The ‘showTruck’ JavaScript handler dynamically updates the map area with content specific to a selected truck. Let’s look at the JavaScript.
JavaScript
This page leverages a bit of JavaScript to dynamically generate HTML. Lucky for us jQuery hides a lot of complexity that is required to keep the page cross-browser compliant. Here is the JavaScript for the list view / map area:
// Global map variable var map; // Main entry point...runs on document ready $(document).ready(function () { // Create and load the initial map map = $("#map_canvas").gmap3( { lat: 43.0566, lng: -89.4511, zoom: 2 }); map.setTypeRoadMap(); }); // AJAX fetch the last 50 GPS points function getHistory(id) { var url = 'Home/History'; $.getJSON(url, { truckId: id, count: 50 }, function (data) { map.addPath(data); }); } // AJAX fetch the truck info function getLocation(id) { var url = 'Home/Position'; $.getJSON(url, { truckId: id, count: 50 }, function (data) { map.setCenter(data.lat, data.lng); map.setZoom(6); // we have the lat/lng...geocode to get address $.fn.gmap3.geoCodeLatLng(data.lat, data.lng, function (address) { var latlng = data.lat + ", " + data.lng; map.addMarkerByLatLng(data.lat, data.lng, data.name, createInfo(data.name, data.plate, data.type, data.driver, latlng, address)); }); }); } // Helper to create the map info bubble function createInfo(title, plate, type, driver, latlng, location) { return '<div class="popup"><h1 class="popup-title">' + title + '</h1><div id="popup-body"><p><span class="popup-label">plate:</span> ' + plate + '</p><p><span class="popup-label">type:</span> ' + type + '</p><p><span class="popup-label">driver:</span> ' + driver + '</p><p><span class="popup-label">lat/lng:</span> ' + latlng + '</p><p><span class="popup-label">location:</span> ' + location + '</p></div></div>'; } // List object click handler function showTruck(id) { map.clear(); getLocation(id); getHistory(id); $(".selected").removeClass("selected"); $("#truck-" + id).addClass("selected"); }
The jQuery document ready event creates the initial map by using the before mentioned Google maps jQuery plugin. The map initial center, zoom, and type are set. Next are two JavaScript functions ‘getHistory’ and ‘getLocation’ that asynchronously (AJAX) fetch data from the server.
The ‘getHistory’ function fetches the last 50 lat / long GPS points for a truck. This information is returned in a JavaScript array of JSON formatted data. This information is in the format required by the map plugin to add a path. The ‘getHistory’ function uses the ‘History’ action method (discussed below) on the ‘Home’ controller.
The ‘getLocation’ function fetches detailed information about the truck. This information is used to reset the map center and zoom level. The detailed information includes the latest GPS lat / long point of the truck which is then geo-coded using Google’s map API to get an address. This information is then used to add a marker to the map via a function on the jQuery map plugin. This marker includes a popup that is dynamically created in the ‘createInfo’ function. The ‘getLocation’ function uses the ‘Position’ action method (discussed below) on the ‘Home’ controller.
The ‘showTruck’ function is the JavaScript event handler for the HTML anchors in the truck list view. When a truck is clicked in the list the map is cleared, location / history data is fetched and the CSS ‘selected’ class is managed. The CSS ‘selected’ class allows the selected element to be visually high lighted.
MVC Controller
The controller for the above view is defined by the following code:
public class HomeController : Controller { private readonly IIntKeyedRepository<Truck> _truckRepository; public HomeController(IIntKeyedRepository<Truck> truckRepository) { _truckRepository = truckRepository; } public ActionResult Index() { return View(_truckRepository.All()); } public ActionResult Position(int truckId) { JsonMapper mapper = new JsonMapper(_truckRepository); JsonTruckInfo jsonTruckInfo = mapper.MapToTruckInfo(truckId); if( jsonTruckInfo != null ) { return Json(jsonTruckInfo, JsonRequestBehavior.AllowGet); } return new EmptyResult(); } public ActionResult History(int truckId, int count) { JsonMapper mapper = new JsonMapper(_truckRepository); List<JsonLocation> locations = mapper.MapToLocations(truckId, count); if (locations != null) { return Json(locations, JsonRequestBehavior.AllowGet); } return new EmptyResult(); } }
As previously discussed, we are using Ninject as a Inversion of Control (IoC) container. In order to handle object lifetime, Ninject provides a controller factory for our ASP.NET MVC application. This controller factory does not require a default (parameter-less) constructor. The ‘HomeController’ constructor above accepts an ‘IIntKeyedRepository<Truck>’ instance. This repository instance is created by Ninject and provides access to the database.
The ‘Index’ action method simple provides an enumerable list of ‘Truck’ objects to the view. The ‘Position’ and ‘History’ action methods each use a mapper object to generate a view-model. The view-model objects are then serialized and returned in JSON format to the browser.
MVC Models
The following code defines the mapper class that is used by the controller to generate view-model objects:
public class JsonMapper { private readonly IIntKeyedRepository<Truck> _truckRepository; public JsonMapper(IIntKeyedRepository<Truck> truckRepository) { _truckRepository = truckRepository; } public JsonTruckInfo MapToTruckInfo(int truckId) { Truck truck = _truckRepository.FindBy(truckId); if (truck != null) { Location location = truck.Locations.OrderByDescending(c => c.Timestamp).First(); JsonTruckInfo jsonTruckInfo = new JsonTruckInfo { driver = truck.Driver.FirstName + " " + truck.Driver.LastName, lat = location.Latitude, lng = location.Longitude, name = truck.Name, plate = truck.PlateNumber, type = truck.Type }; return jsonTruckInfo; } return null; } public List<JsonLocation> MapToLocations(int truckId, int count) { Truck truck = _truckRepository.FindBy(truckId); if (truck != null) { List<JsonLocation> locations = truck.Locations.OrderByDescending(c => c.Timestamp).Take(count).Select( location => new JsonLocation { lat = location.Latitude, lng = location.Longitude }).ToList(); return locations; } return null; } }
An instance of the mapper object uses the injected repository to fetch data from the database and map that data into the one of the following view-models:
public class JsonTruckInfo { public double lat { get; set; } public double lng { get; set; } public string name { get; set; } public string type { get; set; } public string plate { get; set; } public string driver { get; set; } } public class JsonLocation { public double lat { get; set; } public double lng { get; set; } }
It should be noted that the ‘MapToLocations’ method returns a list of ‘JsonLocation’ objects which when serialized to JSON have the exact format required by the jQuery map plugin.
Summary
Adding a presentation layer to the truck tracker data was relatively easy. Especially since we previously created the database, repository, jQuery mapping plugin, and wired up these objects with our IOC container.
I am in the process of deploying this application to the internet. I will post again once that is deployed and will include a download of the solution at that time.
What’s next? We now need a way to get GPS location data into the database. I will be creating a web service that provides this feature. Check back as we explore the various web service options (ASMX, MVC, WCF…etc) to get data into our database.
How are you performing the commit at the end of the request?
Hi Scott,
Good question. The controller in this example is using Ninject to wire up the various pieces (see links in article). One piece that is being created is an instance of IUnitOfWork. In this particular example, nothing is being committed to the db. However, to add a new truck you would need to have IUnitOfWork injected into the ctor of the controller. Then you can call ‘Commit’ on that instance. This will then commit.
Bob
sir how you recieve gps data and save into database ?
Hi i liked your solution,
i am new on asp.net and i am developing one solution like for truck, i would like to have this solution joined on mine.
Did you find and gps, cant you use celphone?
Just ideas, i dont know well.
Best Ragards.
Thanks for the kind words. Location based services using a cell phone that is GPS aware is definitely feasible. Here is a link for a trip recorder that does just thus
http://blog.bobcravens.com/2010/10/trip-recorder-using-javascripts-geolocation-api-and-google-maps/
I am working on integrating this into the truck tracker example.
Hi Bob,
thanks for your blog post, it was quite helpful. I do, however, have a problem wiring it up with Ninject when it comes to UnitOfWork.
Let’s say I have a model
public class Model : IModel
{
private readonly IIntKeyedRepository _Repository;
private IUnitOfWork _UnitOfWork;
public Model(IIntKeyedRepository Repository, IUnitOfWork UnitOfWork)
{
_Repository = Repository;
_UnitOfWork = UnitOfWork;
}
public void insert()
{
Data d = new Data();
d.something = “text”;
_Repository.Add(d);
_UnitOfWork.Commit();
}
}
and I have wired up Ninject just like you did:
NHibernateHelper helper = new NHibernateHelper(connectionString);
Bind().ToConstant(helper.SessionFactory).InSingletonScope();
Bind().To().InTransientScope();
Bind().ToProvider(new SessionProvider());
Bind<IIntKeyedRepository>().To<Repository>().InTransientScope();
Bind().To().InTransientScope();
and I try to call
IModel m = (…..).kernel.Get();
m.insert();
then it does insert the Data-object into the repository, however NOT into the database. i.e. when I do something like Repository.All().Count() it shows that the size of the Repository increased by 1 (which is fine), but there is simply no new entry in my underlying SQLite database.
Also, wenn I try to call m.insert(); twice I get an error that there’s no active transaction.
Do you have any clue why? Am I doing something wrong?
It works perfectly fine when doing it directly with
NHibernateHelper helper = new NHibernateHelper(“Data Source=mydb.SQLite;Version=3;”);
UnitOfWork unitOfWork = new UnitOfWork(helper.SessionFactory);
Repository repository = new Repository(unitOfWork.Session);
Data d = new Data();
d.something = “text”;
repository.Add(d);
unitOfWork.Commit();
Thanks,
Tom
Come to think of it, the problem was binding IUnitOfWork to TransientScope. After binding it to SingletonScope, it worked for at least the first .Commit-transaction. After that I got the same problem saying that there is “no active transaction” when trying to committing again.
My solution was to keep binding it to singleton scope and and to change (in UnitOfWork):
private readonly ITransaction _transaction;
changed to
private ITransaction _transaction;
and and the end of the commit() method I added
_transaction = Session.BeginTransaction(IsolationLevel.ReadCommitted);
So after committing, I just begin another transaction.
Do you think that’s an acceptable solution or could you think of someting better?
Thanks again,
Tom
This is odd. Not certain what is happening. At first glance, I don’t see anything obvious. Unfortunately, I don’t have free clock cycles to look into this further right now. Sorry. I have a bit too much going on right now.
Your solution will probably work most of the time. I think it might have issues if two request come in at nearly the same time. Seems like both request will operate under the same unit of work. This is probably not what you want.
This repository solution has evolved into a bit more robust code base. I haven’t gotten a chance to blog about it yet, but you can find the code here: https://github.com/rcravens/GenericRepository. This is what I use for a number of projects (work and home).
Bob
Hi,
I have a database for this kind of application working fine from last 4 months my home made gps tracker..please let me know if someone need that.
I ma inserting location into database with a webservice.
Regards,
Ashwani Sihag
Hi Ashwani, can you please send me the database script??
Thanks.
Hi Bob, your project is very useful..as i am also working on vehicle tracking .But how did you store the lat /lang values ….did you use sql server 2005 to already store the available (known)lat/lng values or have you used gps modem to store dynamic data in to the database and then retrieve the data from there.
I did not use any built in database types to store the lat / lng values. That is probably the way to go. I wasn’t sure about portability between various db types (sql server, mysql…etc).
Bob
I am also using gmap3 to “manage” available sources , on ASP.NET MVC 4. …I used WCF web services to support my DAL layer to handle georeferenced resources on SQLServer…
Cause I am a newbie on ASP.NET MVC, I would like to explore your example very much, but I am unable to get it…is it possible to analyze your source code? For instance, Truck, IIntKeyedRepository objects are not explained…
Thanks in advance.
Luís Ferreira
All the truck tracker code is available on codeplex:
http://gpsnerd.codeplex.com/
Bob
Hi bob.
Thanks for great efforts.
I think http://gpsnerd.com/ is not working.
Its down. Fail to load Assembly file “Antlr3.Runtime” .
Can you look into please ?
Regards,
Pushkar
Thanks for the heads up. The site is back up now.
Just curious, has Google opened up their license to be able to use these internally without paying thousands? or does it still have to be on an external web site?
looks like a great app.
hai boi, when i’m trying to see the tracker code at http://gpsnerd.codeplex.com it not there anymore. Can u send me the code?
i’m new in mvc and GIS, wanna explore it. By the way, can we change the map rather than using google maps with other?
thanks before
It is still there. You need to go to this link (http://gpsnerd.codeplex.com/SourceControl/list/changesets) and click the ‘download’ button.
Hi Bob,
I am looking out for similar kind of implementation for my project. Thanks for sharing your wonderful work. Appreciate it.
I have a doubt in dataservice implementation. Actions in the controllers are directly calling dataservice methods. I wanted to implement interfaces for them too like IHomeDataService and call them via. interfaces. If there is any change in data retrieval mechanism, I can plugin them easily without changing any of the MVC application code.
Do you think this it’s better approach. If yes, where do IHomeDataService is best to implement, is it in Infra library?
Thanks,
Sundeep
Sundeep,
For larger applications, I recommend adding the complexity to abstract the services into interfaces and inject them into the controllers. This allows everything to be tested a lot easier. For this example, I chose not to add that extra complexity.
Bob
Hi, am trying make a site using the already provided sql database. but am unable to connect to the sql database using VB#(i have increased the tables provided. Please send me a connection string i need to use so that i can connect the database
Hi,
I’m doing website project in Asp.Net on Gps Based Asset tracking System.
my issue is i’m getting values from gps device like longitude,
latitude, speed & location on every 2 seconds and directly stored in DB table. I just want to display tracking position on google map on every 2 seconds….
I’m not using MVC in asp.net, using simple website
I hope can any one help me on this….
hi sachin how you save gps data into database.
I a friend of mine has a food truck and he wants a map on his own web site, so that people could be able to track him. would you please tell me what device I have to buy in order to integrate it with this application ?
Thank You.
Do you have the database scripts posted somewhere?
brother can you tell me about the link from where i can download this application?
Good job 🙂
Thanks.
i need the php code for truck tracker and real-time geolocation