becomap 1.7.30 → 1.7.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/becomap.js +1 -1
- package/maplibre-threejs-demo.html +340 -0
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>MapLibre + Three.js Demo - Extruded Polygons + 3D Markers</title>
|
|
7
|
+
|
|
8
|
+
<!-- MapLibre GL CSS -->
|
|
9
|
+
<link href="https://unpkg.com/maplibre-gl@4.0.0/dist/maplibre-gl.css" rel="stylesheet" />
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
font-family: Arial, sans-serif;
|
|
16
|
+
}
|
|
17
|
+
#map {
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
bottom: 0;
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
.marker-label {
|
|
24
|
+
font-size: 14px;
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
color: #333;
|
|
27
|
+
pointer-events: none;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
text-shadow:
|
|
33
|
+
-1px -1px 0 #fff,
|
|
34
|
+
1px -1px 0 #fff,
|
|
35
|
+
-1px 1px 0 #fff,
|
|
36
|
+
1px 1px 0 #fff,
|
|
37
|
+
0 0 4px rgba(255,255,255,0.8);
|
|
38
|
+
}
|
|
39
|
+
.marker-icon {
|
|
40
|
+
width: 24px;
|
|
41
|
+
height: 24px;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
border: 3px solid #fff;
|
|
44
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
font-weight: bold;
|
|
51
|
+
color: #fff;
|
|
52
|
+
}
|
|
53
|
+
.info {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: 10px;
|
|
56
|
+
left: 10px;
|
|
57
|
+
background: rgba(255, 255, 255, 0.9);
|
|
58
|
+
padding: 10px;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
z-index: 1;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<div id="map"></div>
|
|
66
|
+
<div class="info">
|
|
67
|
+
<strong>Demo:</strong> MapLibre extruded polygons + Three.js markers with text
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- MapLibre GL JS -->
|
|
71
|
+
<script src="https://unpkg.com/maplibre-gl@4.0.0/dist/maplibre-gl.js"></script>
|
|
72
|
+
|
|
73
|
+
<!-- Import map for Three.js modules -->
|
|
74
|
+
<script type="importmap">
|
|
75
|
+
{
|
|
76
|
+
"imports": {
|
|
77
|
+
"three": "https://unpkg.com/three@0.172.0/build/three.module.js",
|
|
78
|
+
"three/addons/": "https://unpkg.com/three@0.172.0/examples/jsm/"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<script type="module">
|
|
84
|
+
import * as THREE from 'three';
|
|
85
|
+
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
86
|
+
|
|
87
|
+
// Initialize MapLibre map
|
|
88
|
+
const map = new maplibregl.Map({
|
|
89
|
+
container: 'map',
|
|
90
|
+
style: 'https://demotiles.maplibre.org/style.json',
|
|
91
|
+
center: [-122.4194, 37.7749], // San Francisco
|
|
92
|
+
zoom: 16,
|
|
93
|
+
pitch: 60,
|
|
94
|
+
bearing: -17.6,
|
|
95
|
+
antialias: true
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
map.on('load', () => {
|
|
99
|
+
// Add 4 extruded polygons (buildings)
|
|
100
|
+
const buildings = {
|
|
101
|
+
'type': 'FeatureCollection',
|
|
102
|
+
'features': [
|
|
103
|
+
{
|
|
104
|
+
'type': 'Feature',
|
|
105
|
+
'properties': { 'height': 50, 'color': '#4A90E2' },
|
|
106
|
+
'geometry': {
|
|
107
|
+
'type': 'Polygon',
|
|
108
|
+
'coordinates': [[
|
|
109
|
+
[-122.4204, 37.7759],
|
|
110
|
+
[-122.4194, 37.7759],
|
|
111
|
+
[-122.4194, 37.7749],
|
|
112
|
+
[-122.4204, 37.7749],
|
|
113
|
+
[-122.4204, 37.7759]
|
|
114
|
+
]]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
'type': 'Feature',
|
|
119
|
+
'properties': { 'height': 70, 'color': '#E94B3C' },
|
|
120
|
+
'geometry': {
|
|
121
|
+
'type': 'Polygon',
|
|
122
|
+
'coordinates': [[
|
|
123
|
+
[-122.4184, 37.7759],
|
|
124
|
+
[-122.4174, 37.7759],
|
|
125
|
+
[-122.4174, 37.7749],
|
|
126
|
+
[-122.4184, 37.7749],
|
|
127
|
+
[-122.4184, 37.7759]
|
|
128
|
+
]]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
'type': 'Feature',
|
|
133
|
+
'properties': { 'height': 40, 'color': '#50C878' },
|
|
134
|
+
'geometry': {
|
|
135
|
+
'type': 'Polygon',
|
|
136
|
+
'coordinates': [[
|
|
137
|
+
[-122.4204, 37.7739],
|
|
138
|
+
[-122.4194, 37.7739],
|
|
139
|
+
[-122.4194, 37.7729],
|
|
140
|
+
[-122.4204, 37.7729],
|
|
141
|
+
[-122.4204, 37.7739]
|
|
142
|
+
]]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
'type': 'Feature',
|
|
147
|
+
'properties': { 'height': 60, 'color': '#F39C12' },
|
|
148
|
+
'geometry': {
|
|
149
|
+
'type': 'Polygon',
|
|
150
|
+
'coordinates': [[
|
|
151
|
+
[-122.4184, 37.7739],
|
|
152
|
+
[-122.4174, 37.7739],
|
|
153
|
+
[-122.4174, 37.7729],
|
|
154
|
+
[-122.4184, 37.7729],
|
|
155
|
+
[-122.4184, 37.7739]
|
|
156
|
+
]]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Add source for buildings
|
|
163
|
+
map.addSource('buildings', {
|
|
164
|
+
'type': 'geojson',
|
|
165
|
+
'data': buildings
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Add extruded polygon layer
|
|
169
|
+
map.addLayer({
|
|
170
|
+
'id': 'buildings-3d',
|
|
171
|
+
'type': 'fill-extrusion',
|
|
172
|
+
'source': 'buildings',
|
|
173
|
+
'paint': {
|
|
174
|
+
'fill-extrusion-color': ['get', 'color'],
|
|
175
|
+
'fill-extrusion-height': ['get', 'height'],
|
|
176
|
+
'fill-extrusion-base': 0,
|
|
177
|
+
'fill-extrusion-opacity': 0.8
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Add Three.js custom layer for markers
|
|
182
|
+
addThreeJSMarkers();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
function addThreeJSMarkers() {
|
|
186
|
+
// Marker positions (lng, lat, height)
|
|
187
|
+
const markers = [
|
|
188
|
+
{ position: [-122.4199, 37.7754, 55], label: 'Building A', color: 0x4A90E2 },
|
|
189
|
+
{ position: [-122.4179, 37.7754, 75], label: 'Building B', color: 0xE94B3C },
|
|
190
|
+
{ position: [-122.4199, 37.7734, 45], label: 'Building C', color: 0x50C878 },
|
|
191
|
+
{ position: [-122.4179, 37.7734, 65], label: 'Building D', color: 0xF39C12 }
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const customLayer = {
|
|
195
|
+
id: 'threejs-markers',
|
|
196
|
+
type: 'custom',
|
|
197
|
+
renderingMode: '3d',
|
|
198
|
+
|
|
199
|
+
onAdd: function(map, gl) {
|
|
200
|
+
// Three.js setup
|
|
201
|
+
this.camera = new THREE.Camera();
|
|
202
|
+
this.scene = new THREE.Scene();
|
|
203
|
+
|
|
204
|
+
// WebGL renderer
|
|
205
|
+
this.renderer = new THREE.WebGLRenderer({
|
|
206
|
+
canvas: map.getCanvas(),
|
|
207
|
+
context: gl,
|
|
208
|
+
antialias: true
|
|
209
|
+
});
|
|
210
|
+
this.renderer.autoClear = false;
|
|
211
|
+
|
|
212
|
+
// CSS2D renderer for text labels
|
|
213
|
+
this.labelRenderer = new CSS2DRenderer();
|
|
214
|
+
this.labelRenderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight);
|
|
215
|
+
this.labelRenderer.domElement.style.position = 'absolute';
|
|
216
|
+
this.labelRenderer.domElement.style.top = '0';
|
|
217
|
+
this.labelRenderer.domElement.style.pointerEvents = 'none';
|
|
218
|
+
map.getContainer().appendChild(this.labelRenderer.domElement);
|
|
219
|
+
|
|
220
|
+
// Add lighting
|
|
221
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
|
222
|
+
this.scene.add(ambientLight);
|
|
223
|
+
|
|
224
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
225
|
+
directionalLight.position.set(10, 50, 10);
|
|
226
|
+
this.scene.add(directionalLight);
|
|
227
|
+
|
|
228
|
+
// Create markers
|
|
229
|
+
markers.forEach(markerData => {
|
|
230
|
+
const [lng, lat, height] = markerData.position;
|
|
231
|
+
const mercator = maplibregl.MercatorCoordinate.fromLngLat([lng, lat], height);
|
|
232
|
+
const modelScale = mercator.meterInMercatorCoordinateUnits();
|
|
233
|
+
|
|
234
|
+
// Create icon sprite (circular marker)
|
|
235
|
+
const canvas = document.createElement('canvas');
|
|
236
|
+
canvas.width = 256;
|
|
237
|
+
canvas.height = 256;
|
|
238
|
+
const ctx = canvas.getContext('2d');
|
|
239
|
+
|
|
240
|
+
// Draw circular icon with shadow
|
|
241
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
|
242
|
+
ctx.shadowBlur = 10;
|
|
243
|
+
ctx.shadowOffsetX = 0;
|
|
244
|
+
ctx.shadowOffsetY = 5;
|
|
245
|
+
|
|
246
|
+
ctx.fillStyle = '#' + markerData.color.toString(16).padStart(6, '0');
|
|
247
|
+
ctx.beginPath();
|
|
248
|
+
ctx.arc(128, 128, 80, 0, Math.PI * 2);
|
|
249
|
+
ctx.fill();
|
|
250
|
+
|
|
251
|
+
// Draw white border
|
|
252
|
+
ctx.shadowColor = 'transparent';
|
|
253
|
+
ctx.strokeStyle = '#ffffff';
|
|
254
|
+
ctx.lineWidth = 12;
|
|
255
|
+
ctx.stroke();
|
|
256
|
+
|
|
257
|
+
// Draw letter "P" or first letter of label
|
|
258
|
+
ctx.fillStyle = '#ffffff';
|
|
259
|
+
ctx.font = 'bold 100px Arial';
|
|
260
|
+
ctx.textAlign = 'center';
|
|
261
|
+
ctx.textBaseline = 'middle';
|
|
262
|
+
ctx.fillText(markerData.label.charAt(0), 128, 128);
|
|
263
|
+
|
|
264
|
+
// Create sprite from canvas
|
|
265
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
266
|
+
const spriteMaterial = new THREE.SpriteMaterial({
|
|
267
|
+
map: texture,
|
|
268
|
+
sizeAttenuation: true,
|
|
269
|
+
depthTest: true,
|
|
270
|
+
depthWrite: false
|
|
271
|
+
});
|
|
272
|
+
const sprite = new THREE.Sprite(spriteMaterial);
|
|
273
|
+
|
|
274
|
+
// Scale sprite to be visible (in meters)
|
|
275
|
+
const spriteSize = 15; // 15 meters
|
|
276
|
+
sprite.scale.set(spriteSize * modelScale, spriteSize * modelScale, 1);
|
|
277
|
+
|
|
278
|
+
// Position sprite
|
|
279
|
+
sprite.position.set(
|
|
280
|
+
mercator.x,
|
|
281
|
+
mercator.y,
|
|
282
|
+
mercator.z
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
this.scene.add(sprite);
|
|
286
|
+
|
|
287
|
+
// Create text label with icon using CSS2D
|
|
288
|
+
const labelDiv = document.createElement('div');
|
|
289
|
+
labelDiv.className = 'marker-label';
|
|
290
|
+
|
|
291
|
+
// Create icon element
|
|
292
|
+
const iconDiv = document.createElement('div');
|
|
293
|
+
iconDiv.className = 'marker-icon';
|
|
294
|
+
iconDiv.style.backgroundColor = '#' + markerData.color.toString(16).padStart(6, '0');
|
|
295
|
+
iconDiv.textContent = markerData.label.charAt(0); // First letter
|
|
296
|
+
|
|
297
|
+
// Create text element
|
|
298
|
+
const textSpan = document.createElement('span');
|
|
299
|
+
textSpan.textContent = markerData.label;
|
|
300
|
+
|
|
301
|
+
// Append icon and text to label
|
|
302
|
+
labelDiv.appendChild(iconDiv);
|
|
303
|
+
labelDiv.appendChild(textSpan);
|
|
304
|
+
|
|
305
|
+
const label = new CSS2DObject(labelDiv);
|
|
306
|
+
// Position label to the left of the marker
|
|
307
|
+
const labelOffset = 20 * modelScale; // 20 meters to the left
|
|
308
|
+
label.position.set(
|
|
309
|
+
mercator.x - labelOffset,
|
|
310
|
+
mercator.y,
|
|
311
|
+
mercator.z
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
this.scene.add(label);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
this.map = map;
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
render: function(gl, matrix) {
|
|
321
|
+
// Update camera matrix
|
|
322
|
+
this.camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
|
|
323
|
+
|
|
324
|
+
// Render Three.js scene
|
|
325
|
+
this.renderer.resetState();
|
|
326
|
+
this.renderer.render(this.scene, this.camera);
|
|
327
|
+
|
|
328
|
+
// Render CSS2D labels
|
|
329
|
+
this.labelRenderer.render(this.scene, this.camera);
|
|
330
|
+
|
|
331
|
+
// Trigger map repaint
|
|
332
|
+
this.map.triggerRepaint();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
map.addLayer(customLayer);
|
|
337
|
+
}
|
|
338
|
+
</script>
|
|
339
|
+
</body>
|
|
340
|
+
</html>
|