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
|
<html>
<head>
<script src="https://unpkg.com/deck.gl@^8.4.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/carto@^8.4.0/dist.min.js"></script>
<script src="https://unpkg.com/supercluster@6.0.2/dist/supercluster.min.js"></script>
<script src="https://libs.cartocdn.com/mapbox-gl/v1.13.0/mapbox-gl.js"></script>
<link href="https://libs.cartocdn.com/mapbox-gl/v1.13.0/mapbox-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">
(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
// Notice that you can use any Deck.gl layer with CARTO datasets getting GeoJSON data from CARTO's API. This method is recommended for complex layers with datasets below 50Mb
const dataResponse = await fetch('https://public.carto.com/api/v2/sql?q=SELECT cartodb_id, the_geom FROM meteorites&format=geojson');
const data = await dataResponse.json();
// Create a Deck.gl map and use your IconClusterLayer with your CARTO dataset
const deckgl = new DeckGL({
container: 'map',
mapStyle: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
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>
|