1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
<html>
<head>
<script src="https://unpkg.com/deck.gl@^8.8.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/carto@^8.8.0/dist.min.js"></script>
<script src="https://unpkg.com/supercluster@6.0.2/dist/supercluster.min.js"></script>
<script src="https://unpkg.com/maplibre-gl@2.1.9/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@2.1.9/dist/maplibre-gl.css" rel="stylesheet" />
</head>
<body style="margin: 0; padding: 0">
<div id="map" style="width: 100vw; height: 100vh;"></div>
</body>
<script type="text/javascript">
deck.carto.setDefaultCredentials({
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: 'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfbHFlM3p3Z3UiLCJqdGkiOiI1YjI0OWE2ZCJ9.Y7zB30NJFzq5fPv8W5nkoH5lPXFWQP0uywDtqUg8y8c'
});
(async ()=>{
const {DeckGL, CompositeLayer, IconLayer} = deck;
const {CartoSQLLayer, setDefaultCredentials} = deck.carto;
// Define your custom IconClusterLayer
function getIconName(size) {
if (size === 0) {
return '';
}
if (size < 10) {
return `marker-${size}`;
}
if (size < 100) {
return `marker-${Math.floor(size / 10)}0`;
}
return 'marker-100';
}
function getIconSize(size) {
return Math.min(100, size) / 100 + 1;
}
class IconClusterLayer extends CompositeLayer {
shouldUpdateState({changeFlags}) {
return changeFlags.somethingChanged;
}
updateState({props, oldProps, changeFlags}) {
const rebuildIndex = changeFlags.dataChanged || props.sizeScale !== oldProps.sizeScale;
if (rebuildIndex) {
const index = new Supercluster({maxZoom: 16, radius: props.sizeScale});
index.load(
props.data.map(d => ({
geometry: {coordinates: props.getPosition(d)},
properties: d
}))
);
this.setState({index});
}
const z = Math.floor(this.context.viewport.zoom);
if (rebuildIndex || z !== this.state.z) {
this.setState({
data: this.state.index.getClusters([-180, -85, 180, 85], z),
z
});
}
}
renderLayers() {
const {data} = this.state;
const {iconAtlas, iconMapping, sizeScale} = this.props;
return new IconLayer(
this.getSubLayerProps({
id: 'icon',
data,
iconAtlas,
iconMapping,
sizeScale,
getPosition: d => d.geometry.coordinates,
getIcon: d => getIconName(d.properties.cluster ? d.properties.point_count : 1),
getSize: d => getIconSize(d.properties.cluster ? d.properties.point_count : 1)
})
);
}
}
// Fetch Data from CARTO
const { data } = await deck.carto.fetchLayerData({
type: deck.carto.MAP_TYPES.QUERY,
source: `SELECT cartodb_id, geom FROM cartobq.public_account.meteorites`,
connection: 'bqconn',
format: deck.carto.FORMATS.GEOJSON
});
// Create a deck.gl map and use your IconClusterLayer with your CARTO dataset
const deckgl = new DeckGL({
container: 'map',
map: maplibregl,
mapStyle: deck.carto.BASEMAP.VOYAGER,
initialViewState: {
latitude: 0,
longitude: 0,
zoom: 1
},
controller: true,
layers: [
new IconClusterLayer({
id: 'icon-cluster',
data: data.features,
getPosition: d => d.geometry.coordinates,
iconAtlas: 'https://raw.githubusercontent.com/visgl/deck.gl/8.4-release/examples/website/icon/data/location-icon-atlas.png',
iconMapping: 'https://raw.githubusercontent.com/visgl/deck.gl/8.4-release/examples/website/icon/data/location-icon-mapping.json',
sizeScale: 60
})
]
});
})();
</script>
</html>
|