A kind of Mapbox GL JS learning Exploration Series (4) – solutions for marker overlap

Time:2020-8-11

brief introduction

A kind of Mapbox GL JS learning Exploration Series (4) - solutions for marker overlap
Compared with layer, marker has a more flexible rendering mode, which is suitable for more complex annotation display on the map. At the same time, marker is rendered by Dom and then superimposed on the map layer, so its performance is inferior to layer. In practical application scenarios, when the map needs to render a large number of complex structural annotations, layer usually can not fully meet the needs, and marker has become one of the alternatives. However, marker does not have as many configuration items as layer to satisfy the location relationship between markers or between markers and maps. In this paper, we use source’sclusterAttribute, focus on solving the problem of marker display overlap in the map.

Basic usage

var popup = new mapboxgl.Popup({ offset: 25 })
.setText('popUpText');
var marker = new mapboxgl.Marker({
element: element,
draggable: true,
offset: [10, 0],
})
.setLngLat([0, 0])
.setPopup(popup)
.addTo(map);

The marker receives a DOM element as the display unit, which is an SVG positioning icon by default. Options also supports configuration offset and drag and drop configuration, and can add a pop-up window to the marker.

Marker overlay display solution

In mapbox, it is difficult to achieve the effect of marker edge detection directly. The current idea is to control the display and hiding of markers by calculating the distance between markers to avoid overlapping. But this method has too much calculation and poor feasibility. Therefore, we need a self-adaptive map display scheme similar to layer to solve the overlapping display problem of markers.

this.map.addSource("build-marker-source", {
                                "type": "geojson",
                                "data": {
                                    "type": "FeatureCollection",
                                    "features": []
                                },
                                "cluster": true,
                                "clusterRadius": 35
                            })

By setting the source of aggregation attribute for layer, the display and hiding of marker are controlled indirectly.When the cluster is set to true in the source, the edge detection effect can be obtained between the markers of the current layer, which will automatically aggregate into one of the markers when they collide and cover(There is a certain deviation between the longitude and latitude coordinates of the aggregation target and the original data), clusterradius to set the radius size of the aggregation target.

this.map.addSource("build-marker-source", {
                                "type": "geojson",
                                "data": {
                                    "type": "FeatureCollection",
                                    "features": []
                                },
                                "cluster": true,
                                "Clusterradius": 35 // polymerization radius
                            })

By monitoring the data update of the map, the marker with the same display status as the layer is drawn in real time.

this.map.on("data", (e) => {
                            if (e.sourceId !== "build-marker-source" || !e.isSourceLoaded) return;

                            this.map.on("move", updateMarkers);
                            this.map.on("moveend", updateMarkers);
                            updateMarkers();
                        });

In the process of monitoring map data update, the data changes of non operation marker and the status of data not loaded are filtered out, and map annotation display is updated only when the update conditions are met.

                    var markers = {};
                    var markersOnScreen = {};
                    const updateMarkers = () => {
                        let sourceObj = this.map.getSource("build-marker-source")
                        var newMarkers = {};
                        var features = this.map.querySourceFeatures("build-marker-source");
                        for (var i = 0; i < features.length; i++) {
                            let coords = features[i].geometry.coordinates;
                            let props = features[i].properties;
                            let name = ""                           
                             if (!props.cluster) continue;
                            var id = props.cluster_id;
                            if (id) {
                                sourceObj.getClusterLeaves(id, 10, 0, (e, f) => {
                                    name = f[0].properties.name
                                })
                            } else {
                                id = props.id
                                name = props.name
                            }
                            var marker = markers[id];
                            if (!marker && name) {
                                let el = document.createElement("div");
                                el.className = "popUpBox";
                                el.innerText = name
                                marker = markers[id] = new creeper.Marker({element: el}).setLngLat(coords);
                            }

                            newMarkers[id] = marker;

                            if (!markersOnScreen[id])
                                marker && marker.addTo(this.map);
                        }
                        // for every marker we've added previously, remove those that are no longer visible
                        for (id in markersOnScreen) {
                            if (!newMarkers[id])
                                markersOnScreen[id] && markersOnScreen[id].remove();
                        }
                        markersOnScreen = newMarkers;
                    }

flow chart:
A kind of Mapbox GL JS learning Exploration Series (4) - solutions for marker overlap

variable describe
markers The current map annotates the total set, and the UI is defined as the primary key through aggregation ID or resource
markersOnScreen The last round of map data change annotation set, that is, before the current round of data change, the map display annotation set
newMarkers This round of map data changes the annotation set, and the annotation set to be displayed on the current map

A kind of Mapbox GL JS learning Exploration Series (4) - solutions for marker overlap

utilizethis.map.querySourceFeatures("build-marker-source")Obtain the visible annotation information data set of the current map, and check whether the current visible marker is an aggregate class by traversing the collection. If it is a non aggregation class, the current marker data is the original data, which can be directly marked in the map. If the traversal target is an aggregate class, the current visible marker data in the resource object should be usedgetClusterLeavesMethod, through the cluster_ ID is used to find the original data source. Because the aggregated marker coordinates lose the original properties and are replaced by the aggregated content properties, a second query is needed to obtain the name and original longitude and latitude of the marker.
A kind of Mapbox GL JS learning Exploration Series (4) - solutions for marker overlap
Through the uid in the custom attribute, or cluster_ ID to find whether the current marker has been instantiated in the markers. In each round of visual feature traversal, the newmarkers are reset, and the markers that meet the visual conditions are assigned to newmarkers in the way of key value. In markersonscreen, whether the old marker exists in newmarkers is searched. If not, it is removed from the current map. At the end of the logic, assign newmarkers to markersonscreen and wait for the next data update to judge whether the relevant markers are displayed or hidden.

summary

At this point, through the cluster configuration on the source, the problem of overlapping display of map markers is solved, and the adaptive annotation points can be displayed through map zooming. If there is a better way, please exchange views.

Recommended Today

Statistics In PostgreSQL

This article is similar to the source reading of an article, a preliminary introduction to the PostgreSQL statistical information module. The reason why we choose PostgreSQL instead of other databases is that PostgreSQL always performs well in various papers when we see some comparison of design estimation. Statistics collected in PG In PostgreSQL, the collected […]