问题
I'm trying to show all the markers when the user click on a cluster.
This is what I have done so far:
map.on('click', function (e) {
var cluster_features = map.queryRenderedFeatures(e.point, {
layers: [
'cluster-0',
'cluster-1',
'cluster-2'
]
});
var cluster_feature = cluster_features[0];
if (cluster_feature && cluster_feature.properties.cluster) {
map.jumpTo({
around: e.lngLat,
zoom: map.getZoom() + 2
});
}
});
This is adding 2 levels of zoom every time the user click on a marker. It works but sometimes I need to zoom even more to see the markers.
Any suggestion about how I could zoom to the actual markers with a single click?
回答1:
This is a bit of a complicated solution given the current version of Mapbox-gl 0.37.0
.
I'm trying to show all the markers when the user click on a cluster
There are two possible solutions given this one statement.
- show markers plus clusters in the next zoom level or
- show all markers (irrespective of the zoom level).
In mapbox-gl
, the clustering functionality is provided by supercluster.
As of 0.37.0
, there is no intuitive API to customise how supercluster works when you set source via map.addSource...
therefore you may need to use/import supercluster as a library dependency in your entry file where mapbox-gl
is used (either via npm or otherwise).
1. Using supercluster to find next zoom level for when markers uncluster.
In supercluster, you can use the method getClusterExpansionZoom(clusterId, clusterZoom)
, this gives you the next zoom for the selected cluster from where markers can be seen (whether that is 1,2,4,n
zoomlevels
from currentZoomLevel
.
var supercluster = require('supercluster');
let features;
map.on('load', function(e) {
features = supercluster().load(FEATURES);
});
// helper function
function findNearestCluster(map, marker, i) {
let clusterSelected = marker;
// get bounds
let south = map.getBounds()._sw;
let north = map.getBounds()._ne;
let bounds = [south.lng, south.lat, north.lng, north.lat];
let currentClusters = i.getClusters(bounds, Math.floor(map.getZoom()));
let compare = {
lng: clusterSelected.geometry.coordinates[0],
lat: clusterSelected.geometry.coordinates[1]
};
let minClusters = currentClusters.map(cluster => {
let lng = cluster.geometry.coordinates[0];
let lat = cluster.geometry.coordinates[1];
return {
id: cluster.properties.cluster_id,
geometry: cluster.geometry,
value: Math.pow(compare.lng - lng,2) * Math.pow(compare.lat-lat,2)
};
});
return minClusters.sort(function(a,b) {
return a.value - b.value;
});
}
map.on('click', function (e) {
var cluster_features = map.queryRenderedFeatures(e.point, {
layers: [
'cluster-0',
'cluster-1',
'cluster-2'
]
});
var cluster_feature = cluster_features[0];
// we need to find the nearest cluster as
// we don't know the clusterid associated within supercluster/map
// we use findNearestCluster to find the 'nearest'
// according to the distance from the click
// and the center point of the cluster at the respective map zoom
var clusters = findNearestCluster(map, cluster_feature, features);
var nearestCluster = clusters[0];
var currentZoom = Math.floor(map.getZoom());
var nextZoomLevel = supercluster()
.getClusterExpansionZoom(nearestCluster.id, currentZoom);
if (cluster_feature && cluster_feature.properties.cluster) {
map.jumpTo({
around: e.lngLat,
zoom: nextZoomLevel
});
}
});
2. show all markers (irrespective of the zoom level).
We do the a similar thing as above, but instead of using just nextZoomLevel
/ getClusterExpansionZoom
, we can use the getLeaves
.
getLeaves returns all markers from the cluster -
var clusters = findNearestCluster(map, cluster_feature, features);
var nearestCluster = clusters[0];
var currentZoom = Math.floor(map.getZoom());
var getLeaves = supercluster()
.getLeaves(nearestCluster.id, currentZoom, Infinity);
From here, you can render the leaves as mapbox.Markers
if required, similar to leaflet.markercluster
.
The problem with the second solution is that you need to remove/update the markers whenever the view has been changed, in order to reflect the current position of the mapview.
From a top-down perspective, this is ok, but if you start pivoting, the view render gets a bit jank, so I wouldn't recommend that from a UI perspective.
回答2:
What if you constructed a lineString from the coordinates of each point in the cluster_features and then did a zoomTo-lingstring
回答3:
As @MeltedPenguin said (in MapBox - Cluster Zooming) . You can do it without SuperCluster
. I search for several answers and finally did my own solution using coffeescript (you can convert it back to JS with tools like http://js2.coffee/):
@clusterRadius = 30
@map.on 'click', (e) =>
features = @map.queryRenderedFeatures(e.point, { layers: ['markers_layer'] });
if features && features.length > 0
if features[0].properties.cluster
cluster = features[0].properties
allMarkers = @map.queryRenderedFeatures(layers:['markers_layer_dot']);
self = @ #just a way to use 'this' un a function, its more verbose then =>
#Get all Points of a Specific Cluster
pointsInCluster = allMarkers.filter((mk) ->
pointPixels = self.map.project(mk.geometry.coordinates) #get the point pixel
#Get the distance between the Click Point and the Point we are evaluating from the Matrix of All point
pixelDistance = Math.sqrt((e.point.x - (pointPixels.x)) ** 2 + (e.point.y - (pointPixels.y)) ** 2)
#If the distant is greater then the disance that define a cluster, then the point si in the cluster
# add it to the boundaries
Math.abs(pixelDistance) <= self.clusterRadius
)
#build the bounding box with the selected points coordinates
bounds = new (mapboxgl.LngLatBounds)
pointsInCluster.forEach (feature) ->
bounds.extend feature.geometry.coordinates
return
#Move the map to fit the Bounding Box (BBox)
@map.fitBounds bounds, {padding:45, maxZoom: 16}
else
window.open('/en/ad/' + features[0].properties.propertyId)
On my page I have 2 layers based on the same source of data but with different attributes. One that define all dots (without clusters) and another one which define dots and clusters. For my display I use the "markers_layer" with clusters, and for calculate distance and stuff I use the other, as a DB of dots.
SOURCE:
@map.addSource "markers_source_wo_cluster",
type: "geojson"
data:
type: "FeatureCollection"
features: []
cluster: false
clusterMaxZoom: 10
clusterRadius: @clusterRadius
@map.addSource "markers_source",
type: "geojson"
data:
type: "FeatureCollection"
features: []
cluster: true
clusterMaxZoom: 60
clusterRadius: @clusterRadius
LAYER:
##============================================================##
## Add marker layer (Layer for QueryRender all dot without cluster)
##============================================================##
@map.addLayer
id: 'markers_layer_dot'
source: 'markers_source_wo_cluster'
type: "circle"
paint:
"circle-radius": 0 #This are 1 pixel dot for ref only
##============================================================##
## Add marker layer
##============================================================##
@map.addLayer
id: 'markers_layer'
source: 'markers_source'
type: 'symbol'
layout:
'icon-allow-overlap': true
'icon-image':'pin_map'
'icon-size':
stops: [[0,0.4], [40,0.4]]
来源:https://stackoverflow.com/questions/37460056/mapbox-gl-cluster-zoom