592 lines
10 KiB
JavaScript
592 lines
10 KiB
JavaScript
// public/js/map/activities/activityClusters.js
|
|
// ===================================
|
|
// MAPBOX ACTIVITY CLUSTERS
|
|
// ===================================
|
|
|
|
(() => {
|
|
"use strict";
|
|
|
|
let mapboxMap = null;
|
|
|
|
const clusterMarkers = new Map();
|
|
|
|
let renderToken = 0;
|
|
|
|
const SOURCE_ID = "activities";
|
|
const CLUSTER_LAYER_ID = "activities-clusters";
|
|
const COUNT_LAYER_ID = "activities-cluster-count";
|
|
|
|
// ===================================
|
|
// SET MAP
|
|
// ===================================
|
|
|
|
function setMap(map) {
|
|
mapboxMap = map;
|
|
|
|
console.log("[ActivityClusters] setMap");
|
|
}
|
|
|
|
// ===================================
|
|
// CREA LAYER CLUSTER
|
|
// ===================================
|
|
|
|
function createLayer() {
|
|
if (!mapboxMap) {
|
|
return;
|
|
}
|
|
|
|
if (!mapboxMap.isStyleLoaded()) {
|
|
return;
|
|
}
|
|
|
|
if (!mapboxMap.getSource(SOURCE_ID)) {
|
|
console.warn(
|
|
"[ActivityClusters] source activities non presente",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// ---------------------------------
|
|
// CLUSTER LAYER
|
|
// ---------------------------------
|
|
|
|
if (!mapboxMap.getLayer(CLUSTER_LAYER_ID)) {
|
|
console.log(
|
|
"[ActivityClusters] creo layer cluster",
|
|
);
|
|
|
|
mapboxMap.addLayer({
|
|
id: CLUSTER_LAYER_ID,
|
|
|
|
type: "circle",
|
|
|
|
source: SOURCE_ID,
|
|
|
|
filter: ["has", "point_count"],
|
|
|
|
paint: {
|
|
"circle-color": "#ff3333",
|
|
|
|
"circle-radius": [
|
|
"step",
|
|
["get", "point_count"],
|
|
22,
|
|
10,
|
|
28,
|
|
50,
|
|
38,
|
|
],
|
|
|
|
"circle-stroke-color": "#ffffff",
|
|
|
|
"circle-stroke-width": 3,
|
|
|
|
// --------------------------------
|
|
// NASCONDI PALLINO MAPBOX
|
|
// --------------------------------
|
|
//
|
|
// Il layer rimane presente per
|
|
// queryRenderedFeatures().
|
|
//
|
|
|
|
"circle-opacity": 0,
|
|
|
|
"circle-stroke-opacity": 0,
|
|
},
|
|
});
|
|
}
|
|
|
|
// ---------------------------------
|
|
// COUNT LAYER
|
|
// ---------------------------------
|
|
|
|
if (!mapboxMap.getLayer(COUNT_LAYER_ID)) {
|
|
mapboxMap.addLayer({
|
|
id: COUNT_LAYER_ID,
|
|
|
|
type: "symbol",
|
|
|
|
source: SOURCE_ID,
|
|
|
|
filter: ["has", "point_count"],
|
|
|
|
layout: {
|
|
"text-field": "{point_count_abbreviated}",
|
|
|
|
"text-size": 14,
|
|
},
|
|
|
|
paint: {
|
|
// Anche il numero Mapbox viene
|
|
// sostituito dal marker HTML.
|
|
|
|
"text-color": "#ffffff",
|
|
|
|
"text-opacity": 0,
|
|
},
|
|
});
|
|
}
|
|
|
|
console.log(
|
|
"[ActivityClusters] layer creati",
|
|
);
|
|
}
|
|
|
|
// ===================================
|
|
// CLUSTER VISIBILI
|
|
// ===================================
|
|
|
|
function getRenderedClusters() {
|
|
if (!mapboxMap) {
|
|
return [];
|
|
}
|
|
|
|
if (!mapboxMap.getLayer(CLUSTER_LAYER_ID)) {
|
|
return [];
|
|
}
|
|
|
|
const rendered =
|
|
mapboxMap.queryRenderedFeatures({
|
|
layers: [CLUSTER_LAYER_ID],
|
|
});
|
|
|
|
const clusters = [];
|
|
|
|
const seen = new Set();
|
|
|
|
for (const feature of rendered) {
|
|
const clusterId =
|
|
feature.properties?.cluster_id;
|
|
|
|
if (
|
|
clusterId === undefined ||
|
|
clusterId === null
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const id = String(clusterId);
|
|
|
|
if (seen.has(id)) {
|
|
continue;
|
|
}
|
|
|
|
seen.add(id);
|
|
|
|
clusters.push(feature);
|
|
}
|
|
|
|
return clusters;
|
|
}
|
|
|
|
// ===================================
|
|
// CREA ELEMENTO HTML
|
|
// ===================================
|
|
|
|
function createClusterElement(cluster) {
|
|
const count =
|
|
Number(cluster.properties?.point_count) || 0;
|
|
|
|
const size = Math.min(
|
|
72,
|
|
Math.round(30 + Math.sqrt(count) * 5),
|
|
);
|
|
|
|
const cls =
|
|
count > 200
|
|
? "cluster-xl"
|
|
: count > 50
|
|
? "cluster-lg"
|
|
: count > 10
|
|
? "cluster-md"
|
|
: "cluster-sm";
|
|
|
|
const el =
|
|
document.createElement("div");
|
|
|
|
el.className =
|
|
"activity-cluster-wrapper";
|
|
|
|
el.style.width = `${size}px`;
|
|
el.style.height = `${size}px`;
|
|
|
|
el.innerHTML = `
|
|
<div
|
|
class="gp-cluster activity-cluster ${cls}"
|
|
style="
|
|
width:${size}px;
|
|
height:${size}px;
|
|
"
|
|
>
|
|
<div class="gp-count">
|
|
<span>${count}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
return el;
|
|
}
|
|
|
|
// ===================================
|
|
// CLICK CLUSTER
|
|
// ===================================
|
|
|
|
function handleClusterClick(
|
|
clusterId,
|
|
coordinates,
|
|
) {
|
|
if (!mapboxMap) {
|
|
return;
|
|
}
|
|
|
|
const source =
|
|
mapboxMap.getSource(SOURCE_ID);
|
|
|
|
if (!source) {
|
|
return;
|
|
}
|
|
|
|
console.log(
|
|
"[ActivityClusters] click cluster",
|
|
{
|
|
clusterId,
|
|
coordinates,
|
|
},
|
|
);
|
|
|
|
/*
|
|
* Prima proviamo ad espandere
|
|
* il cluster con lo zoom Mapbox.
|
|
*/
|
|
|
|
source.getClusterExpansionZoom(
|
|
Number(clusterId),
|
|
(err, expansionZoom) => {
|
|
if (err) {
|
|
console.error(
|
|
"[ActivityClusters] expansion zoom error",
|
|
err,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
const currentZoom =
|
|
mapboxMap.getZoom();
|
|
|
|
const targetZoom = Math.max(
|
|
expansionZoom,
|
|
currentZoom + 1,
|
|
);
|
|
|
|
mapboxMap.easeTo({
|
|
center: coordinates,
|
|
|
|
zoom: targetZoom,
|
|
|
|
duration: 600,
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
// ===================================
|
|
// CREA MARKER
|
|
// ===================================
|
|
|
|
function createClusterMarker(cluster) {
|
|
if (!mapboxMap) {
|
|
return null;
|
|
}
|
|
|
|
const coordinates =
|
|
cluster.geometry?.coordinates;
|
|
|
|
if (
|
|
!coordinates ||
|
|
coordinates.length < 2
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const clusterId =
|
|
String(
|
|
cluster.properties?.cluster_id,
|
|
);
|
|
|
|
const el =
|
|
createClusterElement(cluster);
|
|
|
|
// ---------------------------------
|
|
// CLICK
|
|
// ---------------------------------
|
|
|
|
el.addEventListener(
|
|
"click",
|
|
(event) => {
|
|
event.stopPropagation();
|
|
|
|
handleClusterClick(
|
|
Number(clusterId),
|
|
coordinates,
|
|
);
|
|
},
|
|
);
|
|
|
|
// ---------------------------------
|
|
// MARKER
|
|
// ---------------------------------
|
|
|
|
const marker =
|
|
new mapboxgl.Marker({
|
|
element: el,
|
|
|
|
anchor: "center",
|
|
})
|
|
.setLngLat(coordinates)
|
|
.addTo(mapboxMap);
|
|
|
|
marker.__clusterId = clusterId;
|
|
|
|
return marker;
|
|
}
|
|
|
|
// ===================================
|
|
// UPDATE
|
|
// ===================================
|
|
|
|
function update() {
|
|
if (!mapboxMap) {
|
|
return;
|
|
}
|
|
|
|
if (!mapboxMap.isStyleLoaded()) {
|
|
return;
|
|
}
|
|
|
|
if (!mapboxMap.getSource(SOURCE_ID)) {
|
|
return;
|
|
}
|
|
|
|
if (!mapboxMap.getLayer(CLUSTER_LAYER_ID)) {
|
|
createLayer();
|
|
}
|
|
|
|
const token = ++renderToken;
|
|
|
|
const clusters =
|
|
getRenderedClusters();
|
|
|
|
const currentIds =
|
|
new Set(
|
|
clusters.map((cluster) =>
|
|
String(
|
|
cluster.properties?.cluster_id,
|
|
),
|
|
),
|
|
);
|
|
|
|
console.log(
|
|
"[ActivityClusters] rendered:",
|
|
clusters.length,
|
|
);
|
|
|
|
// ===================================
|
|
// CREA / AGGIORNA
|
|
// ===================================
|
|
|
|
for (const cluster of clusters) {
|
|
const clusterId =
|
|
String(
|
|
cluster.properties?.cluster_id,
|
|
);
|
|
|
|
const coordinates =
|
|
cluster.geometry?.coordinates;
|
|
|
|
if (
|
|
!coordinates ||
|
|
coordinates.length < 2
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// ---------------------------------
|
|
// MARKER ESISTENTE
|
|
// ---------------------------------
|
|
|
|
if (
|
|
clusterMarkers.has(clusterId)
|
|
) {
|
|
const marker =
|
|
clusterMarkers.get(
|
|
clusterId,
|
|
);
|
|
|
|
marker.setLngLat(
|
|
coordinates,
|
|
);
|
|
|
|
marker
|
|
.getElement()
|
|
.style.display = "";
|
|
|
|
continue;
|
|
}
|
|
|
|
// ---------------------------------
|
|
// TOKEN
|
|
// ---------------------------------
|
|
|
|
if (token !== renderToken) {
|
|
continue;
|
|
}
|
|
|
|
// ---------------------------------
|
|
// CREA MARKER
|
|
// ---------------------------------
|
|
|
|
const marker =
|
|
createClusterMarker(cluster);
|
|
|
|
if (!marker) {
|
|
continue;
|
|
}
|
|
|
|
clusterMarkers.set(
|
|
clusterId,
|
|
marker,
|
|
);
|
|
}
|
|
|
|
// ===================================
|
|
// RIMUOVI NON VISIBILI
|
|
// ===================================
|
|
|
|
for (
|
|
const [
|
|
clusterId,
|
|
marker,
|
|
] of clusterMarkers
|
|
) {
|
|
if (
|
|
!currentIds.has(
|
|
String(clusterId),
|
|
)
|
|
) {
|
|
marker.remove();
|
|
|
|
clusterMarkers.delete(
|
|
clusterId,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===================================
|
|
// HIDE
|
|
// ===================================
|
|
|
|
function hide() {
|
|
for (
|
|
const marker of
|
|
clusterMarkers.values()
|
|
) {
|
|
marker
|
|
.getElement()
|
|
.style.display = "none";
|
|
}
|
|
}
|
|
|
|
// ===================================
|
|
// SHOW
|
|
// ===================================
|
|
|
|
function show() {
|
|
for (
|
|
const marker of
|
|
clusterMarkers.values()
|
|
) {
|
|
marker
|
|
.getElement()
|
|
.style.display = "";
|
|
}
|
|
}
|
|
|
|
// ===================================
|
|
// CLEAR
|
|
// ===================================
|
|
|
|
function clear() {
|
|
renderToken++;
|
|
|
|
for (
|
|
const marker of
|
|
clusterMarkers.values()
|
|
) {
|
|
marker.remove();
|
|
}
|
|
|
|
clusterMarkers.clear();
|
|
}
|
|
|
|
// ===================================
|
|
// SIZE
|
|
// ===================================
|
|
|
|
function size() {
|
|
return clusterMarkers.size;
|
|
}
|
|
|
|
// ===================================
|
|
// DEBUG
|
|
// ===================================
|
|
|
|
function debug() {
|
|
console.log(
|
|
"[ActivityClusters] debug",
|
|
{
|
|
markers:
|
|
clusterMarkers.size,
|
|
|
|
rendered:
|
|
getRenderedClusters().length,
|
|
|
|
layer:
|
|
!!mapboxMap?.getLayer(
|
|
CLUSTER_LAYER_ID,
|
|
),
|
|
|
|
source:
|
|
!!mapboxMap?.getSource(
|
|
SOURCE_ID,
|
|
),
|
|
},
|
|
);
|
|
}
|
|
|
|
// ===================================
|
|
// API PUBBLICA
|
|
// ===================================
|
|
|
|
window.MapboxActivityClusters = {
|
|
setMap,
|
|
|
|
createLayer,
|
|
|
|
update,
|
|
|
|
hide,
|
|
|
|
show,
|
|
|
|
clear,
|
|
|
|
size,
|
|
|
|
debug,
|
|
|
|
getRenderedClusters,
|
|
};
|
|
})();
|