Ricky's profileRicky's Bing Maps BlogBlogSkyDrive Tools Help

Blog


    1/18/2009

    Custom Client Side Clustering Algorithm

    In the latest release of Virtual Earth, version 6.2, client side clustering was included as a built in function for shape layers. You can assign a client side clustering algorithm to a shape layer so that it will automatically cluster the shapes when the map is zoomed. This is done by using the VEShapeLayer.SetClusteringConfiguration method. This method can take in two parameters. A clustering type and a VEClusteringOptions object. Currently there is only one built in clustering algorithm type available, grid clustering. Unfortunately there is limited flexibility in this algorithm in that the grid size can not be modified. This can be resolved by making a custom clustering algorithms. This post will show how to create a custom clustering algorithm that can be used with the VEShapeLayer.SetClusteringConfiguration method.

    According to the documentation (http://msdn.microsoft.com/en-us/library/cc966716.aspx) a custom clustering algorithm must take in a VEShapeLayer and return an array of VEClusterSpecification objects.

    A VEClusterSpecification objects has two properties. A Shapes property and a LatLong property. The Shapes object consists of an array shapes that are being clustered. The LatLong property is the coordinate used to place the clustered pushpin. (http://msdn.microsoft.com/en-us/library/cc966730.aspx)

    The custom clustering algorithm that will be created in this blog post will implement a grid clustering algorithm and place the clustered pushpins in the mean average coordinate of the clustered pins. This algorithm is an improvement upon the clustering algorithm described here: http://msdn.microsoft.com/en-us/library/cc161072.aspx

    Here is the custom clustering algorithm which grid clusters and places the clusters in the mean average coordinate of the clustered pins. You can modify the size of the grid used in the algorithm by changing the value of the gridSize variable.

    function meanAverageGridCluster(baseLayer)
    {
        var cluster = new Array();
        //the size of the grid in pixels   
        var gridSize = 30;                       

        //Calculate the size of the map in pixels
        var mapView = map.GetMapView();
        var bottomRight = map.LatLongToPixel(mapView.BottomRightLatLong);
        var mapWidth = parseInt(Math.ceil(bottomRight.x));
        var mapHeight = parseInt(Math.ceil(bottomRight.y));
        //break the map into a grid
        var numXCells = parseInt(Math.ceil(mapWidth / gridSize));
        var numYCells = parseInt(Math.ceil(mapHeight / gridSize));

        //create an array to store all the grid data.
        var gridCells = new Array(numXCells*numYCells);
        //Itirate through the shapes in the base layer
        for(var cnt = 0; cnt < baseLayer.GetShapeCount(); cnt++)
        {
            //convert the shapes latlong to a pixel location
            var shape = baseLayer.GetShapeByIndex(cnt);
            var latLong = (shape.GetPoints())[0];
            var pixel = map.LatLongToPixel(latLong);
            var xPixel = pixel.x;
            var yPixel = pixel.y;

            //check to see if the shape is within the bounds of the viewable map
            if(mapWidth >= xPixel && mapHeight >= yPixel && xPixel >= 0 && yPixel >= 0)
            {
                //calculate the grid position on the map of where the shape is located
                var i = Math.floor(xPixel/gridSize);
                var j = Math.floor(yPixel/gridSize);
                //calculates the grid location in the array
                var key = i+j*numXCells;

                if(gridCells[key]==null)
                {
                    gridCells[key] = new VEClusterSpecification();
                    gridCells[key].Shapes = new Array();
                    gridCells[key].Shapes.push(shape);
                }
                else
                {
                    gridCells[key].Shapes.push(shape);
                }
            }
        }
        //Iterate through the clustered data in the grid array
        for(var key = 0; key < gridCells.length; key++)
        {
            //calculate mean clustered coordinate
            if(gridCells[key] != null)
            {
                var size = gridCells[key].Shapes.length;
                var latSum = 0;
                var lonSum = 0;
                for(var i=0;i<size;i++)
                {
                    var point = gridCells[key].Shapes[i].GetPoints()[0];
                    latSum += point.Latitude;
                    lonSum += point.Longitude;
                }
                gridCells[key].LatLong = new VELatLong(latSum/size,lonSum/size);
                cluster.push(gridCells[key]);
            }
        }
        return cluster;   
    }

    The following code can be used to implement this customer clustering algorithm.

    var map = null;

    function GetMap()
    {
        map = new VEMap('myMap');
        map.LoadMap();

        var clusterLayer = new VEShapeLayer();
        clusterLayer.SetClusteringConfiguration(meanAverageGridCluster);
        map.AddShapeLayer(clusterLayer);


        //Import data from source. In this case a GeoRSS is used. 
        var veLayerSpec = new VEShapeSourceSpecification(VEDataType.GeoRSS, "georsstest.xml", clusterLayer);
        map.ImportShapeLayerData(veLayerSpec);
    }

     


    1/7/2009

    Birds Eye Imagery Extraction Via the Virtual Earth Web Services - Part 1

    Using the Imagery service available in the Virtual Earth Web Services, it is possible to retrieve a map image for a specific location (as specified by specific latitude and longitude values). This method however, is not available to the Birds Eye imagery. Rather further functionality must be applied within the Web Services in order to retrieve accurate Birds Eye Imagery Metadata. The information on the Birds Eye scene for the desired location is present in the URI that is used to retrieve the map tiles which make up the Birds Eye scene. To return the tile in which the specified coordinate exits currently a tile id for the map image must first be provided. The extraction of the correct tile id for alternate map styles, using the Quad Key notation is described in the article, Virtual Earth Tile System (http://msdn.microsoft.com/en-us/library/bb259689.aspx).

    The methodology described in the Virtual Earth Tile System article is unfortunately not applicable to Birds Eye imagery as a standard Birds Eye scene does not fit perfectly into a quad key frame (for example, a Birds Eye scene at zoom level 19 consists of 4 tiles in the x-axis and 6 tiles in the y-axis and at zoom level 20 there are 8 tiles in the x-axis and 12 tiles in the y-axis).  Furthermore the Birds Eye scenes do not populate the quad key format properly, rather tiles that are on the edge of a scene, only fill a portion of a standard map tile. This behavior is caused because the Birds Eye images are geo-referenced, high resolution images which are then overlayed onto the Virtual Earth map differently than a standard tile.

    The following (figure 1), is a Birds Eye scene shown over a standard road view in Virtual Earth at zoom level 19. The red boxes represent the Birds Eye tiles, and the black boxes represent the VE road tiles over the same area. As is evident, the Birds Eye tiles do not properly line up with the edges of the Virtual Earth tiles, which are placed on the map according to the quad key notation. The Birds Eye imagery therefore must be retrieved using a difference methodology than would be used for a standard VE tile.

    image001

    Figure 1 - Birds Eye over standard map tile at zoom level 19

    The Birds Eye geo-referencing information is stored in a database that can only be extracted using the backend web services the Virtual Earth Map Control implements. The data is returned as JavaScript, and can be extracted from the feed using another step, such as regular expressions. With the information retrieved, it is possible to then geo-reference the Birds Eye tiles, or calculate the specific tile id that a coordinate falls into. Note that this is a hack and is completely unsupported as it's possible that a change on VE's side will cause this URL or method to break.

    The following are the necessary steps to calculate the tile id based on an input coordinate:

    1)      Retrieve the Birdseye Imagery Metadata for a coordinate using the Virtual Earth Web Services.

    2)      Retrieve geo-referencing data for the Birdseye scene in which the coordinate resides using backend Virtual Earth web service.

    3)      Extract geo-reference data from web service response using regular expressions.

    4)      Convert LatLong to pixel coordinate, relative to top left corner of Birdseye scene.

    5)      Calculate the x and y tile positions.

    6)      Calculate tile id from x and y tile positions.

    7)       Retrieve Birds Eye tile in which the coordinate resides.

    Retrieve Birds Eye Imagery from Virtual Earth Web Services

     

    Retrieving the Imagery Metadata for a Birds Eye Scene in which a coordinate exists can be accomplished using the following simple example on how to build the request;

    Code Sample

    double latitude = 40.68;

    double longitude = -73.93;

    int zoom = 19;

    int heading = 0;

     

    ImageryMetadataRequest metadataRequest = new ImageryMetadataRequest();

     

    // Set credentials using a valid Virtual Earth Token

    metadataRequest.Credentials = new VEImageryService.Credentials();

    metadataRequest.Credentials.Token = clientToken;

     

    // Set the imagery metadata request options

    VEImageryService.Location centerLocation = new VEImageryService.Location();

    centerLocation.Latitude = latitude;

    centerLocation.Longitude = longitude;

     

    metadataRequest.Options = new ImageryMetadataOptions();

    metadataRequest.Options.Location = centerLocation;

     

    metadataRequest.Options.ZoomLevel = zoomLevel;

     

    metadataRequest.Options.Heading = new Heading();

    metadataRequest.Options.Heading.Orientation = heading;

     

    metadataRequest.Style = MapStyle.BirdseyeWithLabels;

     

    // Make the imagery metadata request

    ImageryServiceClient imageryService = new ImageryServiceClient();

    ImageryMetadataResponse metadataResponse = imageryService.GetImageryMetadata(metadataRequest);

     

    The ImageryMetadataResult object contains several properties; the following variables will be needed in either the calculations that follow or in creating the URI to the map tile:

    Variable

    Value

    ImageSize.Height

    Height of a single tile

    ImageSize.Width

    Width of a single tile

    ImageUriSubdomains

    An array of sub-domains that can be used to construct the URI

    ImageUri

    A string specifying the URI for the image

     

    Additional information on how to use the Virtual earth web services can be found in the article, Developing a .NET Application Using Virtual Earth Web Services (http://msdn.microsoft.com/en-us/library/dd221354.aspx).

    Retrieve Geo-Referencing Data for a Birds Eye scene

     

    Geo-Referencing data for Birds Eye scenes is not available through the Virtual Earth Web Services rather the information needs to be retrieved from a backend web service. The following is an example web service request that can be used to access the service:

    http://dev.virtualearth.net/services/v1/ImageryMetadataService/ImageryMetadataService.asmx/GetBirdsEyeSceneByLocation?latitude=40.77638178482896&longitude=-73.61663818359376&level=20&spinDirection=%22NoSpin%22&orientation=%22North%22&culture=%22en-us%22&format=json&rid=1227643110358&

    This request contains multiple variables:

    Variable

    Value

    latitude

    Latitude value

    longitude

    Longitude value

    level

    Zoom level of Birds Eye image

    orientation

    Direction of Orientation (North, South, East, West)

    culture

    The culture in which the labels should be in

    format

    This is the return format of the data. Currently “json” is the only available format.

    rid

    This is an id used for the request. This needs to be in the request however using the same rid for each request is acceptable.

     

    The following is an example of a response from the web service:

    Example Response

    function _f1227643110358(){return {"__type":"Microsoft.VirtualEarth.Engines.Core.ImageryMetadata.PublicTypes.BirdsEyeSearchResponse","Scene":{"S":8110562,"O":0,"Q":"03201011103","RI":4354,"L":20,"Fcx":16,"Fcy":12,"Hcx":8,"Hcy":6,"QA":-0.40797472410705138,"QB":20.928199325031251,"QC":165807.88732506905,"QD":0.22288172553678412,"QE":-11.587628391710947,"QF":-91840.5941131519,"QG":0.0054790346170970417,"QH":-0.28430088283524946,"QI":-2252.1714401186127,"XA":-207.10129411915017,"XB":-86.358261661444089,"XC":-11725.499006073529,"XD":-19.539468822757811,"XE":164.61435354728968,"XF":-8151.2791376682235,"XG":0.0019627160694678978,"XH":-0.020990038814928769,"XI":1},"ResponseSummary":{"Copyright":"Copyright © 2008 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.","StatusCode":0,"AuthResultCode":0,"ErrorMessage":null,"TraceId":"2cb7463b-dc46-4a21-8ba5-25bd825845fb"}};} if(typeof closeDependency !== 'undefined'){closeDependency('1227643110358');}

    Extract Geo-Reference Data from Web Service Response

     

    The web service response contains more information than necessary; the following is the only portion that is needed:

    {"S":8110562,"O":0,"Q":"03201011103","RI":4354,"L":20,"Fcx":16,"Fcy":12,"Hcx":8,"Hcy":6,"QA":-0.40797472410705138,"QB":20.928199325031251,"QC":165807.88732506905,"QD":0.22288172553678412,"QE":-11.587628391710947,"QF":-91840.5941131519,"QG":0.0054790346170970417,"QH":-0.28430088283524946,"QI":-2252.1714401186127,"XA":-207.10129411915017,"XB":-86.358261661444089,"XC":-11725.499006073529,"XD":-19.539468822757811,"XE":164.61435354728968,"XF":-8151.2791376682235,"XG":0.0019627160694678978,"XH":-0.020990038814928769,"XI":1}

     

    Each variable is defined with a variable name and value in the format:  “name”: value. An explanation as to what some variable names means is as follows:

    Variable

    Value

    S

    Scene ID

    O

    Direction in degrees (North, East, South, West)

    Q

    Quad key Birds Eye scene is contained in at zoom level 11

    L

    Maximum zoom level available for this scene

    Hcx

    Number of tiles used in the x-axis

    Fcy

    Number of tiles used in the y-axis

    QA - QI

    Geo-referencing constants needed to convert Pixel to LatLong

    XA - XI

    Geo-referencing constants needed to convert LatLong to Pixel

     

    These variables can be extracted using regular expressions similar to the following:

    C# Example

    Regex QArx = new Regex("\"QA\":(-?\\d+\\.?\\d*)");

    Match match = QArx.Match(response);

    double QA = double.Parse(match.Groups[1].Value.ToString());

     

    Java Example

    Pattern QArx = Pattern.compile("\"QA\":(-?\\d+\\.?\\d*)");

    Matcher match = QArx.matcher(response);

    double QA = Double.parseDouble(match.group(1));



    Birds Eye Imagery Extraction Via the Virtual Earth Web Services - Part 2

     

    Convert Latitude & Longitude to Pixel Coordinate

     

    The calculated pixel will be the pixel coordinates relative to the top left corner of the scene.  The first step in this calculation is to create a 1x3 matrix of the LatLong coordinate.

    image002

    Once the LatLong matrix is created a second matrix is needed to store the geo-referencing data XA-XI. This will require a 3x3 matrix.

     image003

    These matrices need to be multiplied together in the following fashion:

    image004

    Before the pixel coordinates can be calculated a zoom factor constant is needed.

    image005

    The following formula’s can then be used to calculate the pixel coordinates:

    image006

    image007

    By expanding out the calculations the pixel coordinates can be calculated as follows:


    image008

    image009

    Calculate X and Y Tile Positions

     

    The X tile position can be calculated by dividing the x pixel location by the width of a tile and rounding this number down to an integer value.

    image010

    The Y tile position can be calculated by dividing the y pixel location by the height of a tile and rounding this number down to an integer value.

    image011

    Calculate tile id from x and y tile positions

     

    The X and Y tile positions along with the tileId are calculated for zoom level 19 using the following grid pattern:

    X = 0

    Y = 0

    tileID = 0

    X = 1

    Y = 0

    tileID = 1

    X = 2

    Y = 0

    tileID = 2

    X = 3

    Y = 0

    tileID = 3

    X = 0

    Y = 1

    tileID = 4

    X = 1

    Y = 1

    tileID = 5

    X = 2

    Y = 1

    tileID = 6

    X = 3

    Y = 1

    tileID = 7

    X = 0

    Y = 2

    tileID =8

    X = 1

    Y = 2

    tileID = 9

    X = 2

    Y = 2

    tileID = 10

    X = 3

    Y = 2

    tileID = 11

    X = 0

    Y = 3

    tileID = 12

    X = 1

    Y = 3

    tileID = 13

    X = 2

    Y = 3

    tileID = 14

    X = 3

    Y = 3

    tileID = 15

    X = 0

    Y = 4

    tileID = 16

    X = 1

    Y = 4

    tileID = 17

    X = 2

    Y = 4

    tileID = 18

    X = 3

    Y = 4

    tileID = 19

    X = 0

    Y = 5

    tileID = 20

    X = 1

    Y = 5

    tileID = 21

    X = 2

    Y = 5

    tileID = 22

    X = 3

    Y = 5

    tileID = 23

     

    Zoom level 20 uses a similar method, however the grid is 8 tiles in the x-axis and 12 tiles in the y-axis. The number of tiles in the x-axis for a particular Birds Eye Scene is stored in the Hcx value and the number of tiles in the y-axis is stored in the Fcy value of the web response to the back end web service. The following formula calculates a tile id from the X and Y tile positions:

    image012

    Retrieve Birds Eye tile

     

    Here is an example of a URI to a hybrid Birds Eye tile that is provided by the ImageryMetadataResult.ImageUri property:

    http://{subdomain}.staging.tiles.virtualearth.net/tiles/cmd/ObliqueHybrid?a=03201011031-7384-19-{tileId}&g=213&token={token}

    There are three properties which have to be set; subdomain, tileId, and token.  The subdomain can be assigned any value that is in the ImageUriSubdomains array. The token value should be assigned as a valid client token. The tileId is the id of a specific tile in a Birds Eye scene.

    Birds Eye Imagery Extraction Via the Virtual Earth Web Services - Part 3

     

    Pixel to Latitude & Longitude

     

    The following calculation show how to convert a Pixel coordinate which is relative to the top left corner of the Birds Eye scene into a LatLong coordinate. The first step in this calculation is to calculate the zoom factor:

    image005

    Next a 1x3 matrix of the pixel coordinate is created.

     image013

    Once the pixel matrix is created a second matrix is needed to store the geo-referencing data QA-QI. This will require a 3x3 matrix.

    image014

    These matrices need to be multiplied together in the following fashion:

    image015

    The following formula’s can then be used to calculate the pixel coordinates:

    image016 

    image017

    By expanding out the calculations the pixel coordinates can be calculated as follows:

    image018 

    image019

    Application

     

    A sample application can be downloaded here: http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/VEImagery%5E_Birdseye.zip

    The following is an example of this application in use. The first image is the test coordinate. The second image is the output of the application.

    image020

    Figure 2 – Birdseye image of a coordinate in Virtual Earth

     

    image021

    Figure 3 - Birdseye tile of a coordinate generated using the VE Web Services