Search code examples
azure-mapsazuremapscontrol

Adding more pins to a SymbolLayer


When I have the map up with a bunch of pins (SymbolLayer) and the user scrolls sideways, I now have some more pins to display. My select from the scroll is to get all pins in the new bounding box because searching against regions of just new areas struck me as not a good idea - a single select is generally faster.

So now I have an existing layer, some of which is still visible, and a collection of items to display that is all items now visible. What is the best approach here:

  1. Delete the layer and build a new one?
  2. Clear() the layer and add all the pins.
  3. Add only the pins from the new list to the existing layer, retaining the existing ones.

And if the optimum approach is #3, should I then remove the no longer visible pins in the layer? Or leave them?

If the optimum is #3 and leave the off the box pins in the layer, then I can track the box for the layer, and don't need to do a query if after they scrolld left, they now scroll back right.


Solution

  • If I understand correctly, you have a bunch of pins in your data source, the map only displays what's in view, and you want to list only the pins in view.

    Unfortunately, this is a bit more complicated than I'd like due to the map supporting rotating and pitching (a bounding box won't property represent the map viewable area). There is a code sample for this scenario here: https://samples.azuremaps.com/?sample=get-points-in-current-map-view

    It does the following:

    1. Monitors the maps moveend event to trigger an update to the list.
    2. Calculates the rotated/pitches polygon area for the map view based on the pixel coordinates of the map container.
    3. Loops through all points in the data source and finds which shapes intersect with the polygon.

    Here is the main code blocks:

    //Monitor for when the map is done moving.
    map.events.add('moveend', mapChangedView);
    
    function mapChangedView() {
        //Calculate a polygon for the map view.
        var viewPolygon = getMapViewPolygon(map);
    
        //Get all shapes in the datasource.
        var shapes = datasource.getShapes();
                    
        var pointsInView = [];
    
        //Do something with the points in view.
        //For demo purposes, we will simply output the name of each pin.
        var html = [];
    
        //Search for all point shapes that intersect the polygon.
        for (var i = 0; i < pins.length; i++) {
            if (shapes[i].getType() === 'Point' && isPointInPolygon(shapes[i].getCoordinates(), viewPolygon)) {
                pointsInView.push(shapes[i]);
                html.push(shapes[i].getProperties().name, '<br/>');
            }
        }
    
        document.getElementById('output').innerHTML = pointsInView.length + ' pins in view:<br/><br/>' + html.join('');
    }
    
    /**
     * Generates an array of polygons to create a polygon that represents the map view. 
     * Coordinates are converted into mercator piels so we can do pixel accurate calculations.
     * @param map The map to calculate the view polygon for. 
     */
    function getMapViewPolygon(map) {
        //Simply using the bounding box of the map will not generate a polygon that represents the map area when it is rotated and pitched.
        //Instead we need to calculate the coordinates of the corners of the map.
        var mapRect = map.getCanvasContainer().getBoundingClientRect();
        var width = mapRect.width;
        var height = mapRect.height;
    
        //Calculate positions from the corners of the map.
        var pos = map.pixelsToPositions([
            //Top Left corner
            [0, 0],
    
            //Top right corner.
            [width, 0],
    
            //Bottom Right corner.
            [width, height],
    
            //Bottom left corner.
            [0, height]
        ]);
    
        //Convert the positions to mercator pixels at zoom level 22.
        return atlas.math.mercatorPositionsToPixels(pos, 22);
    }
    
    /**
     * Checks to see if a position is within a mercator polygon.
     * @param position A position point to determine if it is in a polygon.
     * @param mercatorPolygon Array of Mercator coordinates that form a polygon.
     */
    function isPointInPolygon(position, mercatorPolygon) {
    
        //Convert point into a mercator pixel at zoom level 22 for pixel accurate calculations. 
        var p = atlas.math.mercatorPositionsToPixels([position], 22)[0];
        var x = p[0];
        var y = p[1];
    
        var inside = false;
        for (var i = 0, j = mercatorPolygon.length - 1; i < mercatorPolygon.length; j = i++) {
            var xi = mercatorPolygon[i][0], yi = mercatorPolygon[i][1];
            var xj = mercatorPolygon[j][0], yj = mercatorPolygon[j][1];
    
            if (((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
                inside = !inside;
            }
        }
    
        return inside;
    }
    

    As an optimization, the code uses Mercator pixel coordinates at zoom level 22 (3cm/1" accuracy) so that it can use a basic geometry point in polygon algorithm rather than a more complex spatial algorithm using latitude/longitude which would require taking into consideration the curvature of the earth.