Calculate distance between two coordinates on earth

This is part of the latest feature of our Advanced Sitecore Google Maps (WCAG Edition). I have added a new feature to find the nearest markers based on an address.

Find Nearest Markers

Find Nearest Markers

The caclculation is based on the approximate birds-flight distance between 2 latitude/longitude coordinates. Google Maps has a JavaScript function to give you the numbers, but since our Google Maps implementation will work without JavaScript I have to do the math in C#.

The calculation needs to return the distance in either kilometers or miles, so I have created an enumeration for this:

 public enum MeasureUnits { Miles, Kilometers }; 

 The rest is pure math and is based on an approximated earth radius of 3960 miles (or 6371 kilometers). The class is basically the same Coordinate class I used in the Google Maps Polyline Encoding article. The class is extended with the DistanceFrom function that calculates the distance to another Coordinate.

namespace PT.GoogleMaps.Core
{
  public class Coordinate
  {
    /// <summary>
    /// The units of measure when calculating distance between 2 coordinates
    /// </summary>
    public enum MeasureUnits { Miles, Kilometers };
    /// <summary>
    /// Gets or sets the latitude of the coordinate.
    /// </summary>
    public double Latitude { get; set; }

    /// <summary>
    /// Gets or sets the longitude of the coordinate.
    /// </summary>
    public double Longitude { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="Coordinate"/> class.
    /// </summary>
    /// <param name="latitude">The latitude.</param>
    /// <param name="longitude">The longitude.</param>
    public Coordinate(double latitude, double longitude)
    {
      Latitude = latitude;
      Longitude = longitude;
    }

    /// <summary>
    /// Calculates the approximate birds-flight distance between this coordinate and the coordinate in the parameter
    /// </summary>
    /// <param name="coordinate">The coordinate.</param>
    /// <param name="units">The units to measure in</param>
    /// <returns></returns>
    public double DistanceFrom(Coordinate coordinate, MeasureUnits units)
    {
      double earthRadius = (units == MeasureUnits.Miles) ? 3960 : 6371;
      double dLat = Deg2Rad(coordinate.Latitude - Latitude);
      double dLon = Deg2Rad(coordinate.Longitude - Longitude);
      double a = Math.Sin(dLat / 2) *
                 Math.Sin(dLat / 2) +
                 Math.Cos(Deg2Rad(Latitude)) *
                 Math.Cos(Deg2Rad(coordinate.Latitude)) *
                 Math.Sin(dLon / 2) *
                 Math.Sin(dLon / 2);
      double c = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));
      double d = earthRadius * c;
      return d;
    }

    /// <summary>
    /// Converts from Degrees to Radians
    /// </summary>
    /// <param name="deg">The degrees</param>
    /// <returns>The radians</returns>
    private double Deg2Rad(double deg)
    {
      return (deg * Math.PI / 180.0);
    }

  }
}

Advanced Sitecore Google Maps (WCAG Edition)

Advanced Sitecore Google Maps (WCAG Edition) is a new module for Sitecore. The module provides maps, that are accessible and meet the Section 508 requirements. The module is very easy to use for your Sitecore editors.

Google Maps from Google is a great tool, that you can make to your tool with this module. Users are familiar with Google Maps, from many other sites. They know how to zoom in and out and switch between various map types. There is a possibility to show the geodesic maps, terrain maps, satellite maps and combinations.

It is becoming increasingly more important that websites are accessible. The Advanced Sitecore Google Maps module expands Google Maps features to also be accessible and meet the requirements of WCAG 2.0 level AA and Section 508. This module is the only map solution for Sitecore, that meets the WCAG 2.0 Level AA.

The module provides multiple ways of viewing a Google Map, including several filters and accessibility lists. As a developer, you can develop your own views:

Google Maps view including 3-level filter and accessibility list

Google Maps view including 3-level filter and accessibility list

The module is configured from Sitecore using custom Sitecore editors directly on the item that show the map. You can enter an address, or move the map marker to find the map center:

Create a map in Sitecore

Create a map in Sitecore

When creating a map you have full control over the zoom control, the map types to include (including moon, mars and sky views) and how the map should be displayed. You can even add the overview map and the Google search box to your map.

Markers, polygons and polylines are called “overlays”. These overlays can be grouped, and these groups can be used to filter overlays from the map, or to inherit marker icons. All overlays contain titles, text, alternative text and address information that can be used when presenting the overlay.

Markers are created similar to creating a map, by entering an address or moving a marker to the designated position.

Creating a marker in Sitecore

Creating a marker in Sitecore

Polygons and polylines are created in a similar fashion, simply by clicking on the map untill the polygon or polyline is drawn. The polygon and polyline colors, line thickness and transparency are fully controllable:

Adding a polygon to the overlays collection

Adding a polygon to the overlays collection

In order to use Google Maps, you need to register your URL at Google. If your website has many URL’s, you need to register each of them, including sub-domains (www.mysite.com and mysite.com for example). The module contains a key manager where you add all of your Google Maps keys, and the module automatically finds the correct key:

Multiple Google Maps key management

Multiple Google Maps key management

Click here to read more about the module, or Click here to see a demo of the module.

Google Maps polyline encoding in C#

One of the new features in the Advanced Sitecore Google Maps (WCAG edition) is the ability to draw polylines. Polylines in Google Maps can be drawn using 2 methods: By creating a new GPolyline with the array of coordinates to draw, or by using the GPolyline.fromEncoded function which takes an encoded string of coordinates and renders this as a line.

The encoded polyline function has the advantage of being faster and more compressed compared with feeding the GPolyline with an array of coordinates. However, some of the precision is lost, as the encoding algorithm only uses 5 digit coordinates. 5 digits is still a pretty good precision, so I decided to go with the encoded string.

I was looking for a method of encoded my coordinates in C# when i stumbled upon a solution on the SoulSolutions webpage, where the developer demonstrates a Javascript and C# method to encode and decode polyline coordinates.

This is the C# method re-written. SoulSolutions should be credited for the code. However, his code examples are incomplete, so I rewrote them. First I need a Coordinate class, which contains the latitude and longitude of a coordinate set (I have removed comments and other stuff to compress the code):

public class Coordinate
{
  public double Latitude { get; set; }
  public double Longitude { get; set; }
}

The following function EncodeCoordinates encodes a list of coordinates into an encoded string:

/// <summary>
/// Encodes the list of coordinates to a Google Maps encoded coordinate string.
/// </summary>
/// <param name="coordinates">The coordinates.</param>
/// <returns>Encoded coordinate string</returns>
public static string EncodeCoordinates(List<Coordinate> coordinates)
{
  int plat = 0;
  int plng = 0;
  StringBuilder encodedCoordinates = new StringBuilder();
  foreach (Coordinate coordinate in coordinates)
  {
    // Round to 5 decimal places and drop the decimal
    int late5 = (int)(coordinate.Latitude * 1e5);
    int lnge5 = (int)(coordinate.Longitude * 1e5);
    // Encode the differences between the coordinates
    encodedCoordinates.Append(EncodeSignedNumber(late5 - plat));
    encodedCoordinates.Append(EncodeSignedNumber(lnge5 - plng));
    // Store the current coordinates
    plat = late5;
    plng = lnge5;
  }
  return encodedCoordinates.ToString();
}
/// <summary>
/// Encode a signed number in the encode format.
/// </summary>
/// <param name="num">The signed number</param>
/// <returns>The encoded string</returns>
private static string EncodeSignedNumber(int num)
{
  int sgn_num = num << 1; //shift the binary value
  if (num < 0) //if negative invert
  {
    sgn_num = ~(sgn_num);
  }
  return (EncodeNumber(sgn_num));
}

/// <summary>
/// Encode an unsigned number in the encode format.
/// </summary>
/// <param name="num">The unsigned number</param>
/// <returns>The encoded string</returns>
private static string EncodeNumber(int num)
{
  StringBuilder encodeString = new StringBuilder();
  while (num >= 0x20)
  {
    encodeString.Append((char)((0x20 | (num & 0x1f)) + 63));
    num >>= 5;
  }
  encodeString.Append((char)(num + 63));
  // All backslashes needs to be replaced with double backslashes
  // before being used in a Javascript string.
  return encodeString.ToString().Replace(@"\", @"\\");
}

Using the encoded string, I can draw a line surrounding Denmark like this:

var line = new GPolyline.fromEncoded({
               color: "#0000FF",
               weight: 5,
               opacity: 0.5,
               points: "kpgiI}{wt@{xeDfxv@ylqC|flAyfyDidPiioBoabD_fkAyjfDooZ{nyCtw_BihCnzlBzbyAl}XehjBfp`B_se@vdgAhdPya_BoabDipHoabDngiAsen@jz}@htcAzqt@itcAnha@|~eBdzh@qqnBf~w@zrlCjkx@fppAy{u@zflA{zRpeuC`zWh`]bmx@}byAlwn@ny{DncNn}nDsxd@uqG",
               levels: "BBBBBBBBBBBBBBBBBBBBBBBBBBBB",
               zoomFactor: 32,
               numLevels: 4 });

The line looks like this using 28 coordinates:

Line drawing using GPolyline.fromEncoded

Line drawing using GPolyline.fromEncoded

Save Sitecore Item from .aspx editor

Sitecore is an extendable platform, no doubt about it. For example it allows you to add an external (.aspx) page, to an item:

Advanced Sitecore Google Maps Editor

Advanced Sitecore Google Maps Editor

The page will then show up on your item. But what if you wish to modify the actual Sitecore item from the page? Like in this case:
This is one of the editors for the new Advanced Sitecore Google Maps (WCAG Edition) module. The page allows a user to apply a Google Maps map to a Sitecore item. When the user press “Save map”, the map center and zoom-level is saved to the Sitecore item:

Save map center and zoom to the current Sitecore item

Save map center and zoom to the current Sitecore item

This can only be achieved by hooking into some of Sitecore’s Javascript classes. When the “Save map” button is pressed I hook into the window.parent.scForm Javascript object and call item:load and item:refreshchildren.

All of this can be achieved from code-behind. On the button’s On_Click event I modify the current Sitecore item, and then use RegisterStartupScript to register the Javascript events:

protected void btnSave_Click(object sender, EventArgs e)
{
  // Get the Sitecore item to store map center and zoom level
  Sitecore.Data.Database db = Sitecore.Configuration.Factory.GetDatabase(Request.QueryString["database"]);
  Item item = db.GetItem(Request.QueryString["id"]);
  item.Fields["zoom"] = myZoomLevel;
  item.Fields["center"] = myCenter;
  // Now register the client startup script to be executed
  // When page posts back to save the modified Sitecore item
  SitecoreHelper.RegisterRefreshScript(Page, Request.QueryString["id"]);
}

The final function, SitecoreHelper.RegisterRefreshScript, is my own helper function that will register the startup script:

public static void RegisterRefreshScript(Page page, string id)
{
  StringBuilder message = new StringBuilder();
  message.Append("var parentScForm = window.parent.scForm;" + Environment.NewLine);
  message.Append(string.Format("parentScForm.postRequest('','','','item:load(id={0})');", id) + Environment.NewLine);
  message.Append(string.Format("parentScForm.postRequest('','','','item:refreshchildren(id={0})');", id) + Environment.NewLine);
  if (!page.ClientScript.IsStartupScriptRegistered("RefreshItem"))
  {
    page.ClientScript.RegisterStartupScript(typeof(string), "RefreshItem", message.ToString(), true);
  }
}

Thanks to the Sitecore support team to help me out with this one. BTW: It works with both Sitecore 5 and Sitecore 6.

Waiting for gg.google.com

I am developing a Google Maps module for Sitecore, and when I test it in Firefox my Google Javascript won’t load. All I get is: “Waiting for gg.google.com…”.

Waiting for gg.google.com

Waiting for gg.google.com

It seemes to be a problem with certain versions of Firefox and Firebug. If Firebug has Script debugging enabled, the Google Maps script will not load.

Disabling Script debugging in Firebug solves the problem:

Disable Script Debugging in Firebug

Disable Script Debugging in Firebug

But now I cannot debug the Javascript I am creating using Firefox. Well… I’m glad I got Google Chrome.

Follow

Get every new post delivered to your Inbox.

Join 92 other followers