MDNS VE Drill Down Example v6.1

Overview

This example performs a drill down query.  That is, when the mouse hovers over a spot on the map, a spatial query is done, the found features are highlighted, and a Virtual Earth info box is shown with details.

Step-by-step Walkthrough

1. Customizing the JavaScript (in VEMap.js)

We need to need capture mouse move events for our drill down.  Therefore, we add this code to function MDNPostLoadVEMap(in veMap.js):

    // internal mousemove handler (supports IE, FF and NS)
    // don't use VE onmousemove because it is broken in VE5 3D
    if (!document.all && !document.getElementById) 
    {
        document.captureEvents(Event,MOUSEMOVE);
    }
    document.onmousemove=MDNMouseMoveHandlerOverride;

This simply captures the mouse event and passes it to the pre-defined handler "MDNMouseMoveHandlerOverride".  Next, we need to define a function that is specially defined for doing drill downs:

    // mouse hover drill-down
    function MDNDrillDownReady(curMouseMapPos)
    {
        HandleDrillDownIdentify(curMouseMapPos.Longitude, curMouseMapPos.Latitude,
 		MDNGetVEMap().GetZoomLevel());
    }


This calls a dynamically defined function "HandleDrillDownIdentify" which we add to our codebehind below. MDNGetVEMap is a method emitted by VEBase and used to obtain the Virtual Earth map object in use.

2. Add a MapQueryManager to Default.aspx 

This feature requires a MapQueryManager component.

3. Enhance the code-behind (Default.aspx.cs)

First, add namespaces (if necessary): 

        using System.Drawing;
        using ISC.MapDotNetServer.Controls.VirtualEarth.Version5;        

Next, we will override a base function RegisterTemplateScript which allows us to dynamically create javascript and register it on the client before the page has finished loading.  Don't forget to call the base method.

    #region Protected Methods


    /// <summary>
    /// Custom script to register.
    /// This is dynamic script based on server-determined values and settings.
    /// </summary>
    protected override void RegisterTemplateScript(MapControlBridge mcb)
    {
        // call base
        base.RegisterTemplateScript(mcb);

        // scripts-----------------------------------------
        string script = "<script type='text/javascript'>" +

        // handler for identify query
        "function HandleDrillDownIdentify(x, y, zl)" +
        "{" +
            "if (" + BlockingMutexName + "==0 && mdnDrillDownReady==true)" +
            "{" +
                BlockingFunctionName + "(true, 3000);" +
                
		// uncomment this if you have ProgressImageID set on the MapControlBridge
		//ShowProgressFunctionName + "(true);" +

                "CallbackMethods.HandleDrillDownIdentify(x, y, zl, " + OnMapStateChangeCompleteCallbackName + ");" +
             "}" +
             "else if(!mdnDrillDownReady){window.setTimeout('mdnDrillDownReady=true;', 1000)}" +

        "}" +

        "</script>";

        // register the client script
        Page.ClientScript.RegisterClientScriptBlock(GetType(), "TemplateScript2", script);
    }

    #endregion    
    

Here we defined HandleDrillDownIdentify which calls a Callback method (AJAX) with the Virtual Earth coordinates and zoom level that will handle the drill down.  Finally, we define this method (please read the code comments for extensive details of what is going on under the hood):

  /// <summary>
    /// Drill-down identify web method - used to query the appropriate layers as defined in the map file metadata
    /// and highlight the feature and display attribute information.
    /// </summary>
    /// <param name="x">x position over map in lat/lon.</param>
    /// <param name="y">y position over map in lat/lon.</param>
    /// <returns>MapClientStateUpdate.</returns>
    [ScriptMethod]
    public MapClientStateUpdate HandleDrillDownIdentify(double x, double y, int zl)
    {
        // Wrap this whole method in a try/catch to handle any server-side
        // exceptions gracefully
        try
        {
            // Check for a session timeout and cancel execution
            if (!MapControlBridge1.IsSessionTimedOut)
            {
                // Lock up this session
                lock (LockObjectForAjax)
                {
                    // build a string which will contain the javascript that is 
                    // passed to the client browser
                    StringBuilder sb = new StringBuilder();

                    // wrap javascript to handle client-side exceptions gracefully
                    sb.Append("try{");

                    // Hide any previous info boxes
                    sb.Append(VEShape.GenerateHideInfoBoxScript(VEMap));

                    // convert from WGS84 to calculate a reasonable search area
                    Point mapPoint84 = new Point(x, y);
                    Point mapPointMerc =
                        Tiles.ConvertDegreesPointToMercMeters(mapPoint84);

                    // based on current mapscale get a reasonable fraction of the current display envelope
                    double screenSegMeters = Tiles.GetMetersPerPixByZoomLevel(zl, y) * 8.0;

                    // disable check of map scale 
                    // for VE integrated apps, MapScale must be set manually
                    // see the code sample for a demonstration
                    MapQueryManager1.OnlyQueryInScaleLayers = false;
                    
                    // setup parameters on MapQueryMananger
                    // Note that this code assumes the underlying data in the database
                    // is unprojected (WGS84 particularly).  If your data is projected you will
                    // need to reproject this point to your correct projection.
                    // (see the Reference Library page "Transform" for an example)
                    MapQueryManager1.SpatialQueryShape = mapPoint84;
                    MapQueryManager1.SpatialQueryOperation = ShapeFilterOperations.DRILL_DOWN;

                    // the radius is based on 8 pixels
                    Point newPt84 =
                        Tiles.ConvertMercPointToDegrees(mapPointMerc.Offset(0, screenSegMeters));
                    MapQueryManager1.SpatialQueryBufferRadius = newPt84.Delta(mapPoint84);

                    // Execute the query
                    MapQueryManager1.ExecuteQuery();

                    // Grab the first shape we can find
                    if (MapQueryManager1.QueryResponse != null &&
                        MapQueryManager1.QueryResponse.QueryResultTables.Count > 0 &&
                        MapQueryManager1.QueryResponse.QueryResultTables[0].QueryResultRows.Count > 0)
                    {
                        // show first shape using VE
                        QueryResultRow row = MapQueryManager1.QueryResponse.QueryResultTables[0].QueryResultRows[0];
                        if (!string.IsNullOrEmpty(row.ShapeSerial))
                        {
                            // Provide a value for the VEShapeLayer
                            string vEHighlightLayer = "MyHighlightLayer";

                            // provide sensible default colors for highlight shape
                            Color fillColor = Color.FromArgb(128, 255, 0, 0);
                            Color outlineColor = Color.Red;

                            // get layer of feature being highlighted, and its highlighting metadata
                            Layer hLayer = MapQueryManager1.QueryRequest.Queries[0].Layer;
                            if (hLayer != null &&
                                hLayer.MetaData != null)
                            {
                                // These can be set int the mapefile in the METADATA section of the Layer:
                                //		HighlightFillColor	"80, 0, 255, 0"
                                // 		HighlightOutColor	"80, 0, 255, 0"
                                string[] colorData;
                                if (hLayer.MetaData.ContainsKey("HighlightFillColor"))
                                {
                                    colorData = hLayer.MetaData["HighlightFillColor"].Split(',');
                                    if (colorData.Length == 4)
                                    {
                                        fillColor = Color.FromArgb(
                                            Convert.ToByte(colorData[0]),
                                            Convert.ToByte(colorData[1]),
                                            Convert.ToByte(colorData[2]),
                                            Convert.ToByte(colorData[3]));
                                    }
                                }
                                if (hLayer.MetaData.ContainsKey("HighlightOutColor"))
                                {
                                    colorData = hLayer.MetaData["HighlightOutColor"].Split(',');
                                    if (colorData.Length == 4)
                                    {
                                        outlineColor = Color.FromArgb(
                                            Convert.ToByte(colorData[0]),
                                            Convert.ToByte(colorData[1]),
                                            Convert.ToByte(colorData[2]),
                                            Convert.ToByte(colorData[3]));
                                    }
                                }
                            }

                            // generate script to clear any previous highlight shapes
                            sb.Append(VEShape.GenerateDeleteLayerScript(VEMap, vEHighlightLayer));
                            sb.Append(VEShape.GenerateAddLayerScript(VEMap, vEHighlightLayer));

                            // deserialize shape from response
                            Shape shp = Shape.DeserializeShape(row.ShapeSerial);

                            // which feature class we are highlighting?
                            // convert points and polylines to appropriate highlight polygon
                            if (shp is Polyline)
                            {
                                Polyline pLine = shp as Polyline;
                                shp = pLine.ToPolygon(10.0);
                            }

                            // now the resultant feature should be a polygon
                            if (shp is Polygon)
                            {
                                Polygon polyG = shp as Polygon;

                                // if the shape is valid and all rings and holes can be closed
                                if (polyG.Validate())
                                {
                                    // reduce number of verticies to minimize traffic back to client
                                    // only perform this operation on complex shapes
                                    if (polyG[0].Count > 25)
                                    {
                                        // a reasonable tolerance to simplify (in decimal degrees)
                                        // (this will likely need to be changed or calculated based on mapscale)
                                        polyG.Simplify(.0001d);
                                    }

                                    // if simplification invalidates the poly, then use it's bounding box
                                    if (!polyG.Validate())
                                    {
                                        polyG = polyG.Bounds.ToPolygon();
                                    }

                                    // generate polygon/s for VE - supports multiple rings
                                    int sIdx = 0;
                                    List<VEShape> shapes = VEShape.CreateFromPolygon(polyG);
                                    foreach (VEShape shape in shapes)
                                    {
                                        // want the callout to appear where the mouse cursor is hovering
                                        shape.IconAnchor = mapPoint84;

                                        // only on the first shape, add a call-out
                                        // multi-ring polygons only get one callout anyway
                                        if (sIdx++ == 0)
                                        {
                                            shape.Title = "Info:";
                                            shape.ForceShowInfoBox(VEMap);

                                            // get back info on the first row
                                            shape.Description = MapQueryManager1.GetListHTML().Replace('\"', '\'');
                                        }

                                        // set style
                                        shape.FillColor = fillColor;
                                        shape.LineColor = outlineColor;
                                        shape.LineWidth = 2;

                                        // addshape script to eval
                                        sb.Append(shape.GenerateAddShapeScript(vEHighlightLayer));
                                    }
                                }
                            }
                            else if (shp is ISC.MapDotNetServer.Common.Point)
                            {
                                // create a VE pushpin for a point
                                ISC.MapDotNetServer.Common.Point pt = shp as ISC.MapDotNetServer.Common.Point;

                                VEShape pushpin =  VEShape.CreateFromPoint(pt);
                                pushpin.Title = "Info:";
                                pushpin.Description = MapQueryManager1.GetListHTML().Replace('\"', '\'');
                                sb.Append(pushpin.GenerateAddShapeScript(vEHighlightLayer));
                            }
                        }
                    }
                    else if (MapQueryManager1.QueryResponse != null &&
                             MapQueryManager1.QueryResponse.Error != null)
                    {
                        // Wrap any query errors as exceptions
                        throw new Exception(MapQueryManager1.QueryResponse.Error);
                    }

                    // Pass the script to the client to be processed
                    sb.Append("}catch(e){alert(e.description);}");
                    MapClientStateUpdate.EvalJavascript = sb.ToString();
                }
            }
        }
        catch (Exception ex)
        {
            // Let the client handle this error
            MapClientStateUpdate.Error = ex.Message;
        }

        return MapClientStateUpdate;
    }

This method runs the spatial query(s), generates the shape for highlighting, and populates the Virtual Earth info box.  All of this is passed back to the client as JavaScript.