locked
How can I make Polygon/Polyline draggable in Bing Maps 8? RRS feed

  • Question

  • Hi,

    To make polygon/polyline draggable in the map I made the following steps.

    1 I have created polygon and polyline on the map.

    2 I have a functionality to select polygon/polyline on the map and when I select them I set my javascript object "dragee" to the selected polygon/polyline.

    3 I add an event handler to the bing maps for "mousemove" event. So when this event is triggered and if I have "dragee object" then I change the location of my "draggee" (polygon/polyline) object. And it works correctly only on specific initial zoom. The issue appears when I zoom in/out on the map, then If I try to make mousemove on my object, object stops to move and the entire map starts to move.

    My code looks in this way:

            _MM.Events.addHandler(_bm, 'mousemove', 
                function(e)
                {
                    if (dragee) {
    
                        var latLon = _bm.tryPixelToLocation(new _MM.Point(e.getX(), e.getY()));
                        var offsetX = _lastMouseLatLon.longitude - latLon.longitude;
                        var offsetY = _lastMouseLatLon.latitude - latLon.latitude;
                        _dragee.UpdateLocation(offsetX, offsetY);
    
                    }
                });

    _MM = Microsoft Maps; _bm = bing maps; dragee is my object (which is set when we select polyline/polygon). This works perfectly for pushpins.

    My question is:

    Why it works only on specific map zoom for polygon/polyline? And if I try to drag my object after it the entire bing maps is moved (I want only my object to be moved)?

    It will be great if you can provide me a sample example :)





    • Edited by Emo Stack Wednesday, September 14, 2016 7:16 PM add more explanations
    Wednesday, September 14, 2016 12:02 PM

Answers

  • Dragging pushpins is easy as there is a draggable option in the pushpin options. Dragging other shapes is a bit more difficult and requires a lot more calculations. There is also two different ways to drag shapes. You can drag them and keep them pixel accurate so that they always look the same. This is visually appealing but isn't geospatially accurate. The other option is to shift the coordinates based on heading and distance. For example, if the user moves the mouse left and up a bit, then this might mean each coordinate needs to moved 1 mile in a specific direction. Since the map is flat and square and the world is a sphere, this can cause some odd looking effects. Primarily you will see the shape appear to deform as you move it. This is because the map is flattened and stretched into a square using the Mercator projection. The shape, however should still represent the same size of area.

    I've put together a code sample that provides dragging functionalities for all shapes in Bing Maps and gives you the option to switch between pixel accurate and geospatially accurate dragging. There are a few minor issues, primarily that if you push the shape up against the north or south pole the shape will become a line as latitude coordinates are clipped at the poles as 90 degree coordinates cause divide by 0 errors. The other issue is the V8 has limited world wrap support at the moment, so you won't be able to drag a shape across the anti-meridian (-180/180 longitude). It may also turn into a line if you try to do this. Other than these two edge cases this appears to work fine. Here is the code sample I put together:

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <meta charset="utf-8" />
    
        <script type='text/javascript'
                src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap'
                async defer></script>
    
        <script type='text/javascript'>
        var map;
        
    
        function GetMap()
        {
            map = new Microsoft.Maps.Map('#myMap', {
                credentials: 'Your Bing Maps Key',
                zoom:3
            });
    
            var polygon = Microsoft.Maps.TestDataGenerator.getPolygons(1, map.getBounds());
            map.entities.push(polygon);
    
            Microsoft.Maps.loadModule('Microsoft.Maps.SpatialMath', function () {
                var dragManager = new DraggableShapeManager(map, true);
                dragManager.makeDraggable(polygon);
            });
        }
    
        var DraggableShapeManager = function(map, pixelAccurate){
            var currentLocation;
            var currentShape;
            var currentPoint;
    
            this.makeDraggable = function (shape) {
                if (shape instanceof Microsoft.Maps.Pushpin) {
                    //Pushpins already support dragging.
                    shape.setOptions({ draggable: true });
                } else {
                    Microsoft.Maps.Events.addHandler(shape, 'mousedown', function (e) {
                        //Lock map so it doesn't move when dragging.
                        map.setOptions({ disablePanning: true });
    
                        //Set the current draggable shape.
                        currentShape = e.target;
    
                        //Capture the mouse start location and pixel point.
                        if (pixelAccurate) {
                            currentLocation = Microsoft.Maps.SpatialMath.Tiles.locationToGlobalPixel(e.location, 19);
                        } else {
                            currentLocation = e.location;
                        }
    
                        currentPoint = e.point;
                    });
    
                    //Update the shape as the mouse moves on themap.
                    Microsoft.Maps.Events.addHandler(map, 'mousemove', updateShape);
    
                    Microsoft.Maps.Events.addHandler(shape, 'mouseup', function (e) {
                        //Unlock map panning.
                        map.setOptions({ disablePanning: false });
    
                        //Set current shape to null, so that no updates will happen will mouse is up.
                        currentShape = null;
                    });
                }
            };
    
            function updateShape(e) {
                if (currentShape) {
                    //As an optimization, only update the shape if the mouse has moved atleast 5 pixels.
                    //This will significantly reduce the number of recalculations and provide much better performance.
                    var dx = currentPoint.x - e.point.x;
                    var dy = currentPoint.y - e.point.y;
    
                    if (dx * dx + dy * dy <= 25) {
                        return;
                    }
    
                    if (pixelAccurate) {
                        pixelAccurateUpdate(e);
                    } else {
                        geoAccurateUpdate(e);
                    }
                }
            }
    
            function geoAccurateUpdate(e){
                var newLoc = e.location;
    
                //Calculate the distance and heading from the last mouse location used to update the shape to the new location.
                var distance = Microsoft.Maps.SpatialMath.getDistanceTo(currentLocation, newLoc);
                var heading = Microsoft.Maps.SpatialMath.getHeading(currentLocation, newLoc);
    
                //Check if the shape has a getRings function (likely a polygon)
                if (currentShape.getRings) {
                    var rings = currentShape.getRings();
    
                    for (var i = 0, len = rings.length; i < len; i++) {
                        rings[i] = shiftLocations(rings[i], heading, distance);
                    }
    
                    //Update the in rings ina polygon.
                    currentShape.setRings(rings);
                } else if (currentShape.getLocations) {
                    var locs = currentShape.getLocations();
    
                    //Update the locations of a polyline.
                    currentShape.setLocations(shiftLocations(locs, heading, distance));
                } else if (currentShape.getLocation) {
                    //Although not needed, for completeness, this supports dragging of pushpins.
                    var locs = shiftLocations([currentShape.getLocation()], heading, distance);
                    currentShape.setLocation(locs[0]);
                }
    
                //Store the new mouse location and point that was used to update the shape.
                currentLocation = newLoc;
                currentPoint = e.point;
            }
    
            function pixelAccurateUpdate(e) {
                //Shift the shape based on pixel offset.
                var newLoc = Microsoft.Maps.SpatialMath.Tiles.locationToGlobalPixel(e.location, 19);
    
                var dx = currentLocation.x - newLoc.x;
                var dy = currentLocation.y - newLoc.y;
    
                //Check if the shape has a getRings function (likely a polygon)
                if (currentShape.getRings) {
                    var rings = currentShape.getRings();
    
                    for (var i = 0, len = rings.length; i < len; i++) {
                        rings[i] = pixelShiftLocations(rings[i], dx, dy);
                    }
    
                    //Update the in rings ina polygon.
                    currentShape.setRings(rings);
                } else if(currentShape.getLocations){
                    var locs = currentShape.getLocations();
    
                    //Update the locations of a polyline.
                    currentShape.setLocations(pixelShiftLocations(locs, dx, dy));
                } else if (currentShape.getLocation) {
                    //Although not needed, for completeness, this supports dragging of pushpins.
                    var locs = pixelShiftLocations([currentShape.getLocation()], dx, dy);
                    currentShape.setLocation(locs[0]);
                }
    
                //Store the new mouse location and point that was used to update the shape.
                currentLocation = newLoc;
                currentPoint = e.point;
            }
    
            function shiftLocations(locs, heading, distance) {
                //Based on the distance and heading, shift all locations in the array accordingly.
                for (var i = 0, len = locs.length; i < len; i++) {
                    locs[i] = Microsoft.Maps.SpatialMath.getDestination(locs[i], heading, distance);
                }
    
                return locs;
            }
    
            function pixelShiftLocations(locs, dx, dy) {
                //Based on the distance and heading, shift all locations in the array accordingly.
                for (var i = 0, len = locs.length; i < len; i++) {
                    var p = Microsoft.Maps.SpatialMath.Tiles.locationToGlobalPixel(locs[i], 19);
                    p.x -= dx;
                    p.y -= dy;
    
                    locs[i] = Microsoft.Maps.SpatialMath.Tiles.globalPixelToLocation(p, 19);
                }
    
                return locs;
            }
        };
        </script>
    </head>
    <body>
        <div id="myMap" style=";width:600px;height:400px;"></div>
    </body>
    </html>


    [Blog] [twitter] [LinkedIn]

    • Proposed as answer by Ricky_Brundritt Wednesday, September 14, 2016 9:35 PM
    • Marked as answer by Emo Stack Thursday, September 15, 2016 10:14 AM
    Wednesday, September 14, 2016 9:35 PM