MapBox - 聚类缩放
MapBox - Cluster Zooming
是否可以像 Craigslist Mapview 对 MapBox 那样重新创建单击以放大聚类?单击时,它会缩放到包含这些位置点的区域。
这是我的聚类代码:
map.on('load', function() {
map.addSource("location", {
type: "geojson",
data: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/73873/test.geojson",
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 100
});
map.addLayer({
id: "clusters",
type: "circle",
source: "location",
filter: ["has", "point_count"],
paint: {
"circle-color": {
property: "point_count",
type: "interval",
stops: [
[0, "#71AAC6"],
[100, "#71AAC6"],
[750, "#71AAC6"],
]
},
"circle-radius": {
property: "point_count",
type: "interval",
stops: [
[0, 20],
[100, 30],
[750, 40]
]
}
}
});
这是我的演示
我不认为你可以在纯 Mapbox 中做到这一点,至少不容易。
但是对于那些特定于集群的用法,Supercluster(一个官方的 Mapbox 地理空间点集群库)正是您想要的:
getClusterExpansionZoom(clusterId)
Returns the zoom on which the cluster expands into several children (useful for "click to zoom" feature) given the cluster's cluster_id
.
编辑:实际上你可以在没有 Supercluster 的情况下做到这一点:
使用此 JsFiddle example 您可以检索您单击的群集的点。
map.on('click', function(e) {
const cluster = map.queryRenderedFeatures(e.point, { layers: ["cluster"] });
if (cluster[0]) {
// features: from the added source that are clustered
const pointsInCluster = features.filter(f => {
const pointPixels = map.project(f.geometry.coordinates)
const pixelDistance = Math.sqrt(
Math.pow(e.point.x - pointPixels.x, 2) +
Math.pow(e.point.y - pointPixels.y, 2)
);
return Math.abs(pixelDistance) <= clusterRadius;
});
console.log(cluster, pointsInCluster);
}
});
然后你可以用所有这些点创建一个 mapboxgl.LngLatBounds
和 extend
它。
您将获得一个包含所有点的 LngLatBounds,因此您只需调用 fitBounds
就可以了。
正如@MeltedPenguin 所说。不用 SuperCluster
也可以做到。我搜索了几个答案,最后使用 coffeescript 完成了我自己的解决方案(您可以使用 http://js2.coffee/ 等工具将其转换回 JS):
@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)
在我的页面上,我有 2 个图层基于相同的数据源但具有不同的属性。一个定义所有点(没有簇),另一个定义点和簇。
对于我的显示,我使用带簇的 "markers_layer",为了计算距离和其他东西,我使用另一个作为点的 DB。
来源:
@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
图层:
##============================================================##
## 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://github.com/mapbox/mapbox-gl-js/issues/9707
const handleClusterClick = React.useCallback(
(event: LayerMouseEvent, sourceId: string) => {
if (event && mapRef.current) {
const properties = getFeaturePropertiesFromMapEvent(event);
const coordinates = event.lngLat.toArray() as [number, number];
// skip cluster logic, clicked no point
if (properties?.type && coordinates) {
handlePointClick(event);
return;
}
const feature = event?.features?.[0];
const source = mapRef.current.getSource(sourceId);
if (
feature &&
feature.properties !== null &&
// @ts-expect-error typings
typeof source.getClusterExpansionZoom === "function"
) {
source
// @ts-expect-error typings
.getClusterExpansionZoom(
feature.properties.cluster_id,
(err: Error, expansionZoom: number) => {
if (!err && mapRef.current) {
mapRef.current.flyTo({
// @ts-expect-error typings
center: feature.geometry.coordinates,
zoom: expansionZoom,
maxDuration: MAX_FLY_TO_DURATION_MS,
});
}
},
);
} else {
mapRef.current.flyTo({
maxDuration: MAX_FLY_TO_DURATION_MS,
center: event.lngLat,
zoom: mapRef.current.getZoom() + 3, // This is bad UX, requires multiple clicks
});
}
}
},
[handlePointClick],
);
是否可以像 Craigslist Mapview 对 MapBox 那样重新创建单击以放大聚类?单击时,它会缩放到包含这些位置点的区域。
这是我的聚类代码:
map.on('load', function() {
map.addSource("location", {
type: "geojson",
data: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/73873/test.geojson",
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 100
});
map.addLayer({
id: "clusters",
type: "circle",
source: "location",
filter: ["has", "point_count"],
paint: {
"circle-color": {
property: "point_count",
type: "interval",
stops: [
[0, "#71AAC6"],
[100, "#71AAC6"],
[750, "#71AAC6"],
]
},
"circle-radius": {
property: "point_count",
type: "interval",
stops: [
[0, 20],
[100, 30],
[750, 40]
]
}
}
});
这是我的演示
我不认为你可以在纯 Mapbox 中做到这一点,至少不容易。
但是对于那些特定于集群的用法,Supercluster(一个官方的 Mapbox 地理空间点集群库)正是您想要的:
getClusterExpansionZoom(clusterId)
Returns the zoom on which the cluster expands into several children (useful for "click to zoom" feature) given the cluster's
cluster_id
.
编辑:实际上你可以在没有 Supercluster 的情况下做到这一点:
使用此 JsFiddle example 您可以检索您单击的群集的点。
map.on('click', function(e) { const cluster = map.queryRenderedFeatures(e.point, { layers: ["cluster"] }); if (cluster[0]) { // features: from the added source that are clustered const pointsInCluster = features.filter(f => { const pointPixels = map.project(f.geometry.coordinates) const pixelDistance = Math.sqrt( Math.pow(e.point.x - pointPixels.x, 2) + Math.pow(e.point.y - pointPixels.y, 2) ); return Math.abs(pixelDistance) <= clusterRadius; }); console.log(cluster, pointsInCluster); } });
然后你可以用所有这些点创建一个
mapboxgl.LngLatBounds
和extend
它。您将获得一个包含所有点的 LngLatBounds,因此您只需调用
fitBounds
就可以了。
正如@MeltedPenguin 所说。不用 SuperCluster
也可以做到。我搜索了几个答案,最后使用 coffeescript 完成了我自己的解决方案(您可以使用 http://js2.coffee/ 等工具将其转换回 JS):
@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)
在我的页面上,我有 2 个图层基于相同的数据源但具有不同的属性。一个定义所有点(没有簇),另一个定义点和簇。 对于我的显示,我使用带簇的 "markers_layer",为了计算距离和其他东西,我使用另一个作为点的 DB。
来源:
@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
图层:
##============================================================##
## 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://github.com/mapbox/mapbox-gl-js/issues/9707
const handleClusterClick = React.useCallback(
(event: LayerMouseEvent, sourceId: string) => {
if (event && mapRef.current) {
const properties = getFeaturePropertiesFromMapEvent(event);
const coordinates = event.lngLat.toArray() as [number, number];
// skip cluster logic, clicked no point
if (properties?.type && coordinates) {
handlePointClick(event);
return;
}
const feature = event?.features?.[0];
const source = mapRef.current.getSource(sourceId);
if (
feature &&
feature.properties !== null &&
// @ts-expect-error typings
typeof source.getClusterExpansionZoom === "function"
) {
source
// @ts-expect-error typings
.getClusterExpansionZoom(
feature.properties.cluster_id,
(err: Error, expansionZoom: number) => {
if (!err && mapRef.current) {
mapRef.current.flyTo({
// @ts-expect-error typings
center: feature.geometry.coordinates,
zoom: expansionZoom,
maxDuration: MAX_FLY_TO_DURATION_MS,
});
}
},
);
} else {
mapRef.current.flyTo({
maxDuration: MAX_FLY_TO_DURATION_MS,
center: event.lngLat,
zoom: mapRef.current.getZoom() + 3, // This is bad UX, requires multiple clicks
});
}
}
},
[handlePointClick],
);