Using Google map API with Umbraco

Create a location search API controller

In part 4 of the series we created an API controller with a method/service that returns all the location content from Umbraco as JSON/XML. Then in part 5 we created a template that uses some JQuery to make a request to the service and display the locations on a map. Whilst this is useful, it’s limited in its application as it returns all the locations. What if you only wanted to display locations within a specific area? A more useful approach is to allow a user to input the name of a place and then only return locations that are within a certain distance from that point.

A common example of this functionality would be a store finder page on a website that allows you to enter a location (postcode or town) which it then uses to work out if there are stores nearby and if so, displays their locations on a map.

This part of the series is about how this functionality can be achieved by adding some extra code to the Location API Controller that we created in part 4 of the series.

So what’s required to make this work? To start with we need the coordinates (latitude and longitude) of a location that will be the centre point of the search area. Next we need a numeric value that will be the distance, or radius, from the centre point which defines the perimeter of the search area. So that’s three items of data: latitude, longitude and distance. Once we have that data then it can be used to calculate which locations stored in Umbraco fall within that area.

To perform the spatial calculations that are necessary for this functionality we need to reference a specific assembly in our Umbraco Visual Studio project. Or add the assembly dll file to the sites bin folder.

Reference the System.Data.Entity assembly

In order to be able to calculate the distance from one point to another the first thing that needs doing is to add a reference to the System.Data.Entity assembly. If you have version 4.5 of the .Net framework installed then you should be able to browse to it on your machine. Alternatively, if you add the Entity Framework to your project via NuGet then you should be able to use the one that comes with that instead. Once that assembly has been referenced, the LocationController class created in part 4 of the series can be updated with the relevant functionality. If you are not using Visual Studio you can locate a copy of the dll and just add it to the sites bin folder instead of referencing it. 

Updating the locationApiController

Start by adding the following using statement to the top of the controller.

using System.Data.Spatial;

Next add the following method to the LocationController class

private DbGeography GetPoint(double longitude, double latitude)
{
    return DbGeography.FromText(string.Format("POINT({0} {1})", longitude, latitude));
}

This method is used to create a DbGeography object that contains data about a location based on the latitude and longitude arguments passed into the method. Once we have a DbGeography object of a location we can use the objects Distance method to calculate the distance between it and another DbGeography object which is passed in as an argument to the Distance method. This is what enables us to calculate the distance between two locations.

Now all that’s left to do is to add the code that performs the distance calculations against each item of location data in Umbraco and returns any locations that are within the search area.

For this we’ll add a new method called GetLocationsNear:

public IEnumerable<Location> GetLocationsNear(double lat, double lng, int radiusInMiles)
{                    
    return;
}

If we look at the method signature we see that it returns IEnumerable list of the Location model and takes three arguments; latitude, longitude and radiusInMiles. The latitude and longitude values will be the coordinates of the location that will be at the centre of the search area. The radiusInMiles value will be the distance (in miles) from the centre to the edge of the search area. We want to return any locations that fall within this search area.

The first line we add to the method is:

var radiusInMeters = radiusInMiles * 1604.344;

This converts the radiusInMiles arguments value into meters and assigns it to a variable called radiusInMeters. This is necessary because the DbGeoghraphy.Distance method returns its value in meters, so any comparisons can be done using the same unit of distance. 

The second line of code we add to the method is:

var searchLocation = GetPoint(lng, lat);

The latitude and longitude arguments are passed into the GetPoint method which returns a DbGeography object for that location and assigns it to a variable called searchLocation.

Now that we have the location that we want to use as the centre of the search area we have all the data we need to do a location search. The search calculations can be done using a single Linq query:

var locations = from l in _locationContent
                let geolocation = JsonConvert.DeserializeObject<Geolocation>(l.GetValue("geocoder").ToString())
                let distance = GetPoint(geolocation.Latitude, geolocation.Longitude).Distance(searchLocation)
                where distance < radiusInMeters
                select new Location()
                {
                    Name = l.Name,
                    Id = l.Id,
                    Geolocation = geolocation
                };

What this query does is it takes the _locationContent field that is set in the constructor of the class and iterates over the location content that it contains. For each iteration it does the following:

First it takes the value of the content geocoder field, deserializes it into a Geolocation object and assigns it to a variable called geolocation:

let geolocation = JsonConvert.DeserializeObject<Geolocation>(l.GetValue("geocoder").ToString()) 

Next it passes the geolocation objects latitude and longitude properties to the GetPoint method which returns a DbGeography object and then passes the searchLocation to its Distance method. This method returns the distance (in meters) between the searchLocation and the current iteration of the location content. Which is then assigned to a variable called distance: 

let distance = GetPoint(geolocation.Latitude,geolocation.Longitude).Distance(searchLocation) 

It then compares the distance to the radiusInMeters, and if the distance is less than the radiusInMeters then the location is within the search area:

where distance < radiusInMeters 

If the location is in the search area then a new Location object is created and added to a list of Location objects. When all the iterations of the location content have been completed the list of Location objects is assigned to the locations variable. This is then returned from the method as JSON or XML.

Full code for the method:

public IEnumerable<Location> GetLocationsNear(double lat, double lng, int radiusInMiles)
{
    var radiusInMeters = radiusInMiles * 1604.344
    var searchLocation = GetPoint(lng, lat);
   
    var locations = from l in _locationContent
                    let geolocation = JsonConvert.DeserializeObject<Geolocation>(l.GetValue("geocoder").ToString())
                    let distance = GetPoint(geolocation.Latitude, geolocation.Longitude).Distance(searchLocation)
                    where distance < radiusInMeters
                    select new Location()
                    {
                        Name = l.Name,
                        Id = l.Id,
                        Geolocation = geolocation
                    };
                    
    return locations;
}

Try it out

Once you have saved it and restarted your site you should be able to access the web service by appending /Umbraco/Api/location/GetLocationsNear after the sites domain name e.g.

http://yoursitename/Umbraco/Api/location/GetLocationsNear

If you then add valid values for latitude, longitude and radiusInMiles to the querystring the service should return some locations, if any are found within the search area defined by those values. For example, the URL below will return any locations that are within 1000 miles of London.

http://jo3.co.uk/Umbraco/Api/location/GetLocationsNear?lat=51.5073509&lng=-0.1277582&radiusInMiles=1000

Next article: Create a location search form

Comments

  • sorenbj - 18/08/17 10:59

    Great tutorial. I had to change the GetPoint method to the following to make it work. private DbGeography GetPoint(double longitude, double latitude) { var point = string.Format(CultureInfo.InvariantCulture.NumberFormat, "POINT({0} {1})", longitude, latitude); return DbGeography.PointFromText(point, 4326); }

To be able to comment you need to login using a Google or Facebook account.