Working with Virtual Earth Part 2 – Persisting and Retrieving Locations

In part one of this series, I described some of the features that make Live Maps a great application.  I then divided those features up into 2 categories.  The first category related to features that are intrinsic to Virtual Earth itself – such as the ability to zoom, pan, drag, and switch between different views.  The second category of features were those that the Live Maps team have implemented over the top of Virtual Earth – such as the ability to add and retrieve pins, work with saved collections of pins, and to perform searches over all pins in the database.  This series of articles shows how to get your own custom Virtual Earth map solution up and running.

VEMap

In this article, I will show how you can allow users to store, retrieve, and then display saved locations on the map.  The application that we’ll create to demonstrate this will use AJAX calls to a web service to save and retrieve location data.  For simplicity, our example will store saved locations in an XML file, but you can easily replace that logic with your own data persistence strategy.

Let’s start; create a new ASP.NET Website project, and add a ScriptManager to the Default.aspx page.  Within the ScriptReferences of the ScriptManager, add a reference to Virtual Earth and add a ServiceReference to a web service called MapService.asmx:

<asp:ScriptManager ID="sm" runat="server">
    <Scripts>
        <asp:ScriptReference Path=http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6
             ScriptMode="Release" />
    </Scripts>
    <Services>
        <asp:ServiceReference Path="~/WebServices/MapService.asmx" />
    </Services>
</asp:ScriptManager>

With our reference to the Virtual Earth map control in place, we can instantiate it in the page.  In doing so, we’ll wire up a couple of events on the map itself so that we can handle them to create pins and retrieve them:

var _map = null ;
var _layer = null ;

function pageLoad() {
    this._map = new VEMap(‘theMap’) ;
    this._map.LoadMap(null, 4) ;
    this._layer = new VEShapeLayer();        
    this._map.AddShapeLayer(this._layer);
}

As you can see from this code sample, we are instantiating the VEMap control, passing in the ID of an element on our page that will host the map control. 

Before we take a peek at any event handling code, let’s move back across to the server so that we can see how to create the MapService web service that we referenced in our ScriptManager control earlier.  The web service is the gateway between the page that the user uses to interact with the map, and the persistence medium where the locations will be stored.  The purpose of the web service will be to provide a method for saving new locations and to retrieve existing locations based on the currently visible location on the screen.  Before we dive into code though, here’s what our ‘database’ looks like:

<?xml version="1.0" encoding="utf-8"?>
<locations>
    <location latitude="-34.957995310867922" longitude="138.515625" />
    <location latitude="-34.971500333617328" longitude="138.60900878906247" />
</locations>

You’ll notice that, for the purposes of our example, we are simply storing the co-ordinates of each saved location.  In a real application there will be much more information that we’ll want to store, but I’m going to leave that for a future article.  Let’s look at those web methods for accessing our data, create a web service called MapService, and mark it with the ScriptService attribute so that ASP.NET Ajax knows to produce a client proxy for it:

[ScriptService]
public class MapService : WebService {

    string fileName = HttpContext.Current.Server.MapPath("~/App_Data/Locations.xml");

    public MapService () { }

}

The first method that we will add is a method that takes a LatLong and stores it, using Linq to XML, in our little XML database

[WebMethod]
[ScriptMethod]
public void CreateLocation(LatLong LL) {

    XElement doc = XElement.Load(fileName);

    XElement newElement = new XElement(
        "location",
        new XAttribute("latitude", LL.Lat),
        new XAttribute("longitude", LL.Lon)
        );

    doc.Add(new XElement[] {newElement}) ;
    doc.Save(fileName) ;
}

Going back to the page, we can now create the event handler for the map click event to invoke the call to our new web service method.  For our purposes, we’ll save the location whenever the user clicks on the map with their right mouse button.  First, add the following code in the pageLoad function to wire up the event handler for the mouse click:

function pageLoad() {
    this._map = new VEMap(‘theMap’) ;
    this._map.LoadMap(null, 4) ;
    this._layer = new VEShapeLayer();        
    this._map.AddShapeLayer(this._layer); 

    this.MapClickDelegate = Function.createDelegate(this, this._MapClicked); 
    this._map.AttachEvent("onclick", this.MapClickDelegate);
}

In this code we have created a delegate and attached it as the event handler for the click event of the map control.  It’s in this event handler function that we’ll implement the logic for saving a new location.  Let’s go ahead and create the _MapClicked function which handles the click event:

function _MapClicked(e) {
    if( e.rightMouseButton ) {
        var pixel = new VEPixel(e.mapX, e.mapY);
        var LL = this._map.PixelToLatLong(pixel); 
        MapService.CreateLocation(LL.Latitude, )LL.Longitude ;
    }
}

Pretty simple huh?  We get the latitude and longitude of the click event, and then call our web service directly from the page.  In reality you will probably not want to execute the web service code directly from within the handler like this, instead, you will typically want to display a context menu when the right mouse button is clicked which has a menu option for saving the current location as a favorite – other menu options are likely to include things such as various specialized zooming options.  We’ll see how to implement some of this other functionality in future articles.

Retrieving locations follows the same pattern of: handle a map event, call a web service to retrieve locations within a proximity of the current view, display the locations on the map.  Let’s create our web service method first:

public LatLong[] GetLocations(LatLong NW, LatLong SE, int zoom) {
    XElement doc = XElement.Load(fileName);

    var q = from location in doc.Descendants("location")
        .Within(NW, SE, zoom)
        select new LatLong(
            double.Parse(location.Attribute("latitude").Value),
            double.Parse(location.Attribute("longitude").Value)
           );

    return q.ToArray();
}

This web method takes the top-left corner and bottom-right co-ordinates and a zoom factor and then uses Linq to XML to query the database to retrieve all locations that are within that area.  Notice the custom .Within extension method that is used to filter the IEnumerable<XElement> collection from the initial query (doc.Descendants(…)).  Here’s the code for that extension method:

public static class EnumerableExtensions {
    public static IEnumerable<XElement> Within(this IEnumerable<XElement> elements, LatLong NW, LatLong SE, int zoom) {
        foreach (XElement element in elements) {
            double lat = double.Parse(element.Attribute("latitude").Value);
            double lon = double.Parse(element.Attribute("longitude").Value);
            if ((lat >= SE.Lat) && (lat <= NW.Lat) && (lon >= NW.Lon) && (lon <= SE.Lon)) {
                yield return element;
            }
        }
    }
}

The method is a Linq extension method which takes the IEnumarable<XElement> collection as its data source as well as the co-ordinates to use to filter on. 

Again, we wire-up our client-side event handling code.  This time we handle the event that fires whenever the map’s view changes – such as the user scrolling around or zooming in.  Whenever this happens, we need to display the locations that are relevant for the area that is currently being displayed.

function pageLoad() {
    this._map = new VEMap(‘theMap’) ;
    this._map.LoadMap(null, 4) ;
    this._layer = new VEShapeLayer();        
    this._map.AddShapeLayer(this._layer); 

    this.MapClickDelegate = Function.createDelegate(this, this._MapClicked);
    this._map.AttachEvent("onclick", this.MapClickDelegate);
    
    this.ViewChangeDelegate = Function.createDelegate(this, this._ViewChanged); 
    this._map.AttachEvent("onchangeview", this.ViewChangeDelegate); 
}

This time we handle the changeview event and map it to a handler function called _ViewChanged, which looks like this:

function _ViewChanged() {

    var view = this._map.GetMapView();
    var topLeft = view.TopLeftLatLong;
    var bottomRight = view.BottomRightLatLong;

    var NW = new LatLong() ;
    NW.Lat = topLeft.Latitude;
    NW.Lon = topLeft.Longitude ;

    var SE = new LatLong() ;
    SE.Lat = bottomRight.Latitude;
    SE.Lon = bottomRight.Longitude;

    var zoom = this._map.GetZoomLevel();

    this._layer.DeleteAllShapes();
    MapService.GetLocations(NW, SE, zoom, Function.createDelegate(this, _OnViewChangedSucceeded)) ;
}

We use the in-built Virtual Earth functions to get the current view and then to grab the top-left and bottom-right co-ordinates.  Finally, we send that data off to our web service method (GetLocations).  The last argument to the client-side proxy call to the web service, is a delegate which will handle the callback when the web service call completes.  It’s in this method that we’ll get the location data and use logic to display it on the map.  The code for the _OnViewChangedSucceeded function looks like so:

function _OnViewChangedSucceeded(results) {
    var newShapes = [];
    for(var i=0; i<results.length; i++) {
        var loc = results[i] ;
        var latlong = new VELatLong(loc.Lat, loc.Lon)
        var newShape = new VEShape(VEShapeType.Pushpin, latlong);
        newShapes.push(newShape) ;
    }
    this._layer.AddShape(newShapes);
}

Again, pretty simple!  We loop through each location that is returned and then add it to an array named newShapes, which in-turn,is passed to the AddShape method of the Virtual Earth layer that we created earlier.

It’s literally this simple to display locations on a map.  In a reality the only thing that you’d want to change is that you’d return slightly more information about each location other than its co-ordinates – maybe a Title, Description, and an optional Image.  Each of these values can then get added to the Pushpin when we create it so that the information is displayed to the user when they mouseover a pin. 

There it is.  The minimal amount of code used to save, retrieve, and display locations on your custom Virtual Earth map application.

About these ads

~ by D on November 2, 2007.

7 Responses to “Working with Virtual Earth Part 2 – Persisting and Retrieving Locations”

  1. Can you please send me the code sample of dynamic pushpins from XML?
    Thanks
    Serge

  2. Amberdigital Branch,Southern Stars Enterprises Co is specializing in the development and manufacturing of ad players, advertisement player and LCD advertisings. Established in 1996, we have explored and developed the international market with professionalism. We have built a widespread marketing network, and set up a capable management team dedicated to provide beyond-expectation services to our customers.

    amberdigital Contact Us
    Southern Stars Enterprises Co (Hong Kong Office)
    Add:3 Fl, No.2, Lane 2, Kam Tsin Tsuen, Sheung Shui, Hong Kong
    Tel:+852 2681 4099
    Fax:+852 2681 4586
    Southern Stars Enterprises Co (Shenzhen Office)
    Add:DE, 16/F, Building 2, Nanguo Tower, Sungang Road, Shenzhen, China
    Tel:+86 755 2592 9100
    Fax:+86 755 2592 7171
    E-mail:sstar@netvigator.com
    website:www.amberdigital.com.hk
    alibaba:http://amberdigital.en.alibaba.com%5Baagddeiacec

  3. Hi,Do you need advertising displays, screen advertisings, digital sign, digital signages and LCDs? Please go Here:www.amberdigital.com.hk(Amberdigital).we have explored and developed the international market with professionalism. We have built a widespread marketing network, and set up a capable management team dedicated to provide beyond-expectation services to our customers.
    amberdigital Contact Us
    website:www.amberdigital.com.hk
    alibaba:amberdigital.en.alibaba.com[cdajifbhhcicdg]

  4. I\’m trying to do your example and I\’m not having any luck. Also, what I\’m trying to do is I have an application that generates an xml file with positions (lat, lng) and I want to read that file and show the positions in Virtual Earth. I think it\’s the same cocept your doing here. Could you send me a sample of your code.

  5. no spam

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: