Azure-Samples / azure-maps-html-marker-layer

An Azure Maps Web SDK module that provides a layer that renders point data from a data source as HTML elements on the map.
MIT License
5 stars 9 forks source link

HtmlMarkerLayer loses data points on clustering #6

Closed mitikov closed 2 years ago

mitikov commented 2 years ago

This issue is for a: (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request
- [ ] documentation issue or request
- [ ] regression (a behavior that used to work and stopped in a new release)

Minimal steps to reproduce

Use clusted datasource (data.json.txt) and render both bubble & HtmlMarkerLayer.

Place diagnostic code into markerCallback:

                if (properties.cluster){
                      datasource.getClusterLeaves(properties.cluster_id).then(leafs => {
                        const props = leafs.reduce((a, v) => {
                            const { data } = v;
                            a.push(data.properties.Ip);
                            return a;
                        }, []);
                          console.log(props.join(', '));
                      });
                }
                else
                {
                    console.log(properties.Ip);
                }
...
return new atlas.PieChartMarker(..)

Load the map & open console, screenshot attached:

lost_datapoint

Ip 51 is never picked/sent to markerCallback, despite getting shown by bubble & agenda. However, it appears on super-high zoom. Happens for other data points as well.

Any log messages given by the failure

Nope

Expected/desired behavior

No data for display is lost

OS and Version?

Windows 10

Versions

Latest as of May 25th

Mention any other details that might be useful

The props from .getOptions():

DataSourceOptions {buffer: 128, maxZoom: 18, cluster: true, clusterRadius: 35, clusterMaxZoom: undefined, …}
buffer: 128
cluster: true
clusterMaxZoom: undefined
clusterProperties: {total: Array(2), cluster_blockcount: Array(2), cluster_hits: Array(2)}
clusterRadius: 35
lineMetrics: false
maxZoom: 18
tolerance: 0.1

args.state.mapInfo.bubbleLayer.getOptions()
BubbleLayerOptions {filter: undefined, minZoom: 0, maxZoom: 24, visible: true, source: DataSource, …}
blur: 0
color: "#A3A6BA"
filter: undefined
maxZoom: 24
minZoom: 0
opacity: 0.9
pitchAlignment: "viewport"
radius: (7) ['interpolate', Array(2), Array(4), 5, 22, 1000, 43]
source: DataSource {listeners: Map(0), id: 'logcount-datasource', requestId: undefined, options: DataSourceOptions, shapes: Array(42), …}
sourceLayer: undefined
strokeColor: "grey"
strokeOpacity: 0.9
strokeWidth: (7) ['interpolate', Array(1), Array(4), 0, 1, 500, 5]
visible: true

args.state.mapInfo.freshPie.getOptions()
{minZoom: 0, maxZoom: 24, visible: true, updateWhileMoving: true, filter: Array(2), …}
markerCallback: ƒ (id, position, properties)
maxZoom: 24
minZoom: 0
visible: true

Discussion is also opened on doc.ms.com QA

rbrundritt commented 2 years ago

Where did you get the html marker JavaScript file from? I just noticed that the Azure Maps sample site appears to be using an older version that is buggy, while the files in the dist folder of the HTML Marker module project are more up to date. I suspect this is the issue as I'm seeing different less markers when using the version from the sample site.

mitikov commented 2 years ago

Hi @rbrundritt, thanks for the hint, indeed pulling later version from dist folder made things slightly better.

However, still a portion of data seem to be lost, like point 14, please refer to attached log: A) Initial load is over, no point 14; I print "render over, no point 14" into console B) I zoom in and observe point 14 to appear in German cluster

Expected - it is processed on initial load at once. Actual - Missed on initial load.

point_14_is_lost.log

rbrundritt commented 2 years ago

Think I found a possible issue in your analyzing code, the getClusterLeaves function returns the first 10 leaves by default. Looking at your log where the issue occurs, there are two clusters that have ten leaves listed, thus there is a good chance this is the issue. This function has the following signature getClusterLeaves(clusterId: number, limit: number, offset: number). You can step through groups of leaves, or set the limit to Infinity, and it will return all leaves. So, try modifying your call to getClusterLeaves to the following:

datasource.getClusterLeaves(properties.cluster_id).then(leafs => {
                     const props = leafs.reduce((a, v) => {
                         const { data } = v;
                         a.push(data.properties.Ip);
                         return a;
                     }, []);
                       const message = props.join(', ');
                       console.log(message);
                   }, Infinity);
mitikov commented 2 years ago

Hi @rbrundritt, you are right, specifying the limit led to showing all the points.

I wanted to invite you the general question on map testing, while I see you've already answered that one as well ;)

Thanks.

rbrundritt commented 2 years ago

Yes, this is an experimental extension to Azure Maps. It wasn't developed by the Azure Maps engineering team and doesn't go through any of their testing frameworks.

As part of debugging this issue I also looked at alternatives. Under the hood Azure Maps uses Supercluster library to do the clustering. So I looked at using that directly without a data source and layer monitoring. It's fairly easy to get working, although it isn't as seamless as this library as this library added a lot of logic to limit the adding/removing of markers to minimize markers flashing into view or fidgeting when moving the map. If you want to try it out, here is the code:

<!DOCTYPE html>
<html lang="en">
<head>
    <title></title>

    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    <!-- Add references to the Atlas Map control JavaScript and CSS files. -->
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>

    <script src="https://unpkg.com/supercluster@7.1.2/dist/supercluster.min.js"></script>

    <script>
        var map, superCluster;

        function GetMap() {
            //Initialize a map instance.
            map = new atlas.Map('myMap', {
                style: 'grayscale_light',
                view: 'Auto',

                //Add authentication details for connecting to Azure Maps.
                authOptions: {
                    //Use Azure Active Directory authentication.
                    /*authType: 'anonymous',
                    clientId: 'e6b6ab59-eb5d-4d25-aa57-581135b927f0', //Your Azure Maps client id for accessing your Azure Maps account.
                    getToken: function (resolve, reject, map) {
                        //URL to your authentication service that retrieves an Azure Active Directory Token.
                        var tokenServiceUrl = "https://samples.azuremaps.com/api/GetAzureMapsToken";

                        fetch(tokenServiceUrl).then(r => r.text()).then(token => resolve(token));
                    }*/

                    //Alternatively, use an Azure Maps key. Get an Azure Maps key at https://azure.com/maps. NOTE: The primary key should be used as the key.
                    authType: 'subscriptionKey',
                    subscriptionKey: '<Your Azure Maps Key>'
                }
            });

            //Wait until the map resources are ready.
            map.events.add('ready', function () {
                //Add your post map load code here.

                superCluster = new Supercluster({
                    radius: 35,
                    maxZoom: 18
                });
                superCluster.load(sourceData.features);

                map.events.add('move', render);
                render();
            });
        }

        function render() {
            var cam = map.getCamera();
            var points = superCluster.getClusters(cam.bounds, Math.round(cam.zoom));
            var markers = [];
            points.forEach(p => {
                markers.push(new atlas.HtmlMarker({
                    position: p.geometry.coordinates,
                    text: (p.properties.cluster) ? 'c' + p.properties.cluster_id : p.properties.Ip
                }));
            });

            map.markers.clear();
            map.markers.add(markers);
        }

        var sourceData = {
            "type": "FeatureCollection",
            "features": [
                //Your data.
            ]
        };

    </script>
</head>
<body onload="GetMap()">
    <div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>

    This uses uses the <a href="https://github.com/mapbox/supercluster">Supercluster</a> library to cluster points and render them as HTML markers.
</body>
</html>