Skip to content

Commit 6563f75

Browse files
authored
Add altitude support in Marker and Popup (#13335) (h/t @yangtanyu)
1 parent 6304f71 commit 6563f75

File tree

8 files changed

+263
-31
lines changed

8 files changed

+263
-31
lines changed

debug/markers-altitude.html

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Mapbox GL JS debug page</title>
5+
<meta charset='utf-8'>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
7+
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
8+
<style>
9+
body { margin: 0; padding: 0; }
10+
html, body, #map { height: 100%; }
11+
#controls { position: absolute; top: 0; left: 0; }
12+
</style>
13+
</head>
14+
15+
<body>
16+
<div id='map'></div>
17+
<div id='controls'>
18+
<button id="animate">Animate</button><br />
19+
<label><input id='terrain-checkbox' type='checkbox'> terrain</label><br />
20+
<div>
21+
<label>Projection:</label>
22+
<select id="projName">
23+
<option value="mercator" selected>Mercator</option>
24+
<option value="globe">Globe</option>
25+
<option value="albers">Albers USA</option>
26+
<option value="equalEarth">Equal Earth</option>
27+
<option value="equirectangular">Equirectangular</option>
28+
<option value="lambertConformalConic">Lambert Conformal Conic</option>
29+
<option value="naturalEarth">Natural Earth</option>
30+
<option value="winkelTripel">Winkel Tripel</option>
31+
</select>
32+
</div>
33+
</div>
34+
<script src='../dist/mapbox-gl-dev.js'></script>
35+
<script src='../debug/access_token_generated.js'></script>
36+
<script>
37+
38+
var map = window.map = new mapboxgl.Map({
39+
container: 'map',
40+
devtools: true,
41+
zoom: 12.5,
42+
center: [-77.01866, 38.888],
43+
style: 'mapbox://styles/mapbox/streets-v9',
44+
hash: true
45+
});
46+
47+
let lngLatArr = [];
48+
const spinningMarkers = [];
49+
['auto', 'map', 'viewport', 'horizon'].forEach((rotationAlignment, i) => {
50+
['auto', 'map', 'viewport'].forEach((pitchAlignment, j) => {
51+
var bounds = map.getBounds();
52+
var s = bounds.getSouth();
53+
var n = bounds.getNorth();
54+
var w = bounds.getWest();
55+
var e = bounds.getEast();
56+
57+
var lng = w + (e - w) * ((i + .5) / 4);
58+
var lat = s + (n - s) * ((j + .5) / 3);
59+
60+
var height = (i * 2000 + j*2000) * (e - w);
61+
// var height = 0.0;
62+
63+
64+
// if(!(i===2 && j===0))return
65+
66+
const popupHtml = `<div>
67+
Pitch Alignment: ${pitchAlignment}<br>
68+
Rotation Alignment: ${rotationAlignment}<br>
69+
lng:${lng}<br>
70+
lat:${lat}<br>
71+
height:${height}<br>
72+
</div>`
73+
74+
var popup = new mapboxgl.Popup().setHTML(popupHtml);
75+
76+
var r = Math.round(Math.random() * 255);
77+
var g = Math.round(Math.random() * 255);
78+
var b = Math.round(Math.random() * 255);
79+
80+
var sc = Math.random() * 2.5 + 0.5;
81+
82+
var marker = new mapboxgl.Marker({
83+
color: `rgb(${r}, ${g}, ${b})`,
84+
scale: sc,
85+
draggable: true,
86+
rotationAlignment,
87+
pitchAlignment,
88+
altitude:height,
89+
})
90+
.setLngLat([lng, lat])
91+
.setPopup(popup)
92+
.addTo(map);
93+
94+
marker.togglePopup();
95+
96+
lngLatArr.push({
97+
lngLat:[lng, lat],
98+
lngOffset:(e - w),
99+
latOffset:(n - s),
100+
height
101+
})
102+
103+
spinningMarkers.push(marker);
104+
});
105+
});
106+
107+
108+
map.on('style.load',()=>{
109+
addFillExtrusionLayer(lngLatArr)
110+
})
111+
112+
113+
114+
function addFillExtrusionLayer(data=[]){
115+
116+
const dataArr = data.map(a=>{
117+
let {lngOffset,latOffset} = a;
118+
lngOffset = lngOffset/100;
119+
latOffset = latOffset/100;
120+
const [lng, lat] = a.lngLat;
121+
const aObj = {
122+
...a,
123+
"type": "Feature",
124+
"properties": {
125+
"height": a.height,
126+
"color": `rgba(${a.height%255},${a.height%150},${255 - a.height%255},1)`,
127+
},
128+
lngLatArr:[
129+
[lng-lngOffset,lat+latOffset],
130+
[lng+lngOffset,lat+latOffset],
131+
[lng+lngOffset,lat-latOffset],
132+
[lng-lngOffset,lat-latOffset],
133+
]
134+
}
135+
136+
aObj.geometry = {
137+
"type": "Polygon",
138+
"coordinates": [aObj.lngLatArr]
139+
}
140+
141+
return aObj
142+
});
143+
144+
let layer = {
145+
id:'FillExtrusionLayerId',
146+
'type': 'fill-extrusion',
147+
'source': {
148+
type: 'geojson',
149+
data: {
150+
"type": "FeatureCollection",
151+
"features":dataArr,
152+
}
153+
},
154+
155+
'paint': {
156+
'fill-extrusion-opacity':0.6,
157+
// 'fill-extrusion-color': 'rgba(255,10,10,0.8)',
158+
'fill-extrusion-color': ["get", "color"],
159+
'fill-extrusion-height': ["get", "height"],
160+
'fill-extrusion-base': 0,
161+
}
162+
163+
164+
165+
166+
}
167+
168+
map.addLayer(layer);
169+
170+
}
171+
172+
let animate = false;
173+
document.getElementById('animate').addEventListener('click', () => {
174+
animate = !animate;
175+
if (animate) {
176+
spinMarkers();
177+
}
178+
});
179+
180+
let rotation = 0;
181+
function spinMarkers() {
182+
rotation++;
183+
spinningMarkers.forEach((marker) => {
184+
marker.setRotation(rotation);
185+
});
186+
if (animate) {
187+
window.requestAnimationFrame(spinMarkers);
188+
}
189+
}
190+
window.requestAnimationFrame(spinMarkers);
191+
192+
map.addControl(new mapboxgl.NavigationControl());
193+
194+
map.on('load', function() {
195+
map.addSource('mapbox-dem', {
196+
"type": "raster-dem",
197+
"url": "mapbox://mapbox.mapbox-terrain-dem-v1",
198+
"tileSize": 512,
199+
"maxzoom": 14
200+
});
201+
202+
map.setFog({});
203+
});
204+
205+
document.getElementById('terrain-checkbox').onclick = function() {
206+
map.setTerrain(this.checked ? {"source": "mapbox-dem", "exaggeration": 10} : null);
207+
};
208+
209+
document.getElementById('projName').addEventListener('change', (e) => {
210+
const el = document.getElementById('projName');
211+
map.setProjection(el.options[el.selectedIndex].value);
212+
});
213+
</script>
214+
215+
</body>
216+
</html>

src/geo/projection/globe.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ export default class Globe extends Mercator {
5252
return {x: pos[0], y: pos[1], z: pos[2]};
5353
}
5454

55-
override locationPoint(tr: Transform, lngLat: LngLat): Point {
55+
override locationPoint(tr: Transform, lngLat: LngLat, terrain: boolean = true, altitude?: number): Point {
5656
const pos = latLngToECEF(lngLat.lat, lngLat.lng);
5757
const up = vec3.normalize([] as any, pos);
5858

59-
const elevation = tr.elevation ?
60-
tr.elevation.getAtPointOrZero(tr.locationCoordinate(lngLat), tr._centerAltitude) :
61-
tr._centerAltitude;
59+
let elevation = 0;
60+
if (altitude) {
61+
elevation = tr._centerAltitude + altitude;
62+
} else {
63+
elevation = tr.elevation ?
64+
tr.elevation.getAtPointOrZero(tr.locationCoordinate(lngLat), tr._centerAltitude) :
65+
tr._centerAltitude;
66+
}
6267

6368
const upScale = mercatorZfromAltitude(1, 0) * EXTENT * elevation;
6469
vec3.scaleAndAdd(pos, pos, up, upScale);

src/geo/projection/projection.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export default class Projection {
7171
return {x, y, z: 0};
7272
}
7373

74-
locationPoint(tr: Transform, lngLat: LngLat, terrain: boolean = true): Point {
75-
return tr._coordinatePoint(tr.locationCoordinate(lngLat), terrain);
74+
locationPoint(tr: Transform, lngLat: LngLat, terrain: boolean = true, altitude?: number): Point {
75+
return tr._coordinatePoint(tr.locationCoordinate(lngLat, altitude), terrain);
7676
}
7777

7878
pixelsPerMeter(lat: number, worldSize: number): number {

src/geo/transform.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1529,8 +1529,8 @@ class Transform {
15291529
* @returns {Point} screen point
15301530
* @private
15311531
*/
1532-
locationPoint3D(lnglat: LngLat): Point {
1533-
return this.projection.locationPoint(this, lnglat, true);
1532+
locationPoint3D(lnglat: LngLat, altitude?: number): Point {
1533+
return this.projection.locationPoint(this, lnglat, true, (altitude || 0));
15341534
}
15351535

15361536
/**
@@ -1551,8 +1551,8 @@ class Transform {
15511551
* @returns {LngLat} lnglat location
15521552
* @private
15531553
*/
1554-
pointLocation3D(p: Point): LngLat {
1555-
return this.coordinateLocation(this.pointCoordinate3D(p));
1554+
pointLocation3D(p: Point, altitude?: number): LngLat {
1555+
return this.coordinateLocation(this.pointCoordinate3D(p, (altitude || 0)));
15561556
}
15571557

15581558
/**
@@ -1674,12 +1674,12 @@ class Transform {
16741674
* @param {Point} p top left origin screen point, in pixels.
16751675
* @private
16761676
*/
1677-
pointCoordinate3D(p: Point): MercatorCoordinate {
1678-
if (!this.elevation) return this.pointCoordinate(p);
1677+
pointCoordinate3D(p: Point, altitude?: number): MercatorCoordinate {
1678+
if (!this.elevation) return this.pointCoordinate(p, (altitude || 0));
16791679
let raycast: vec3 | null | undefined = this.projection.pointCoordinate3D(this, p.x, p.y);
16801680
if (raycast) return new MercatorCoordinate(raycast[0], raycast[1], raycast[2]);
16811681
let start = 0, end = this.horizonLineFromTop();
1682-
if (p.y > end) return this.pointCoordinate(p); // holes between tiles below horizon line or below bottom.
1682+
if (p.y > end) return this.pointCoordinate(p, (altitude || 0)); // holes between tiles below horizon line or below bottom.
16831683
const samples = 10;
16841684
const threshold = 0.02 * end;
16851685
const r = p.clone();

src/ui/map.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1516,13 +1516,14 @@ export class Map extends Camera {
15161516
* the `x` and `y` components of the returned {@link Point} are set to Number.MAX_VALUE.
15171517
*
15181518
* @param {LngLatLike} lnglat The geographical location to project.
1519+
* @param {number} altitude The height above sea level.
15191520
* @returns {Point} The {@link Point} corresponding to `lnglat`, relative to the map's `container`.
15201521
* @example
15211522
* const coordinate = [-122.420679, 37.772537];
15221523
* const point = map.project(coordinate);
15231524
*/
1524-
project(lnglat: LngLatLike): Point {
1525-
return this.transform.locationPoint3D(LngLat.convert(lnglat));
1525+
project(lnglat: LngLatLike, altitude?: number): Point {
1526+
return this.transform.locationPoint3D(LngLat.convert(lnglat), (altitude || 0));
15261527
}
15271528

15281529
/**
@@ -1532,15 +1533,16 @@ export class Map extends Camera {
15321533
* to the point.
15331534
*
15341535
* @param {PointLike} point The pixel coordinates to unproject.
1536+
*@param {number} altitude The height above sea level.
15351537
* @returns {LngLat} The {@link LngLat} corresponding to `point`.
15361538
* @example
15371539
* map.on('click', (e) => {
15381540
* // When the map is clicked, get the geographic coordinate.
15391541
* const coordinate = map.unproject(e.point);
15401542
* });
15411543
*/
1542-
unproject(point: PointLike): LngLat {
1543-
return this.transform.pointLocation3D(Point.convert(point));
1544+
unproject(point: PointLike, altitude?: number): LngLat {
1545+
return this.transform.pointLocation3D(Point.convert(point), (altitude || 0));
15441546
}
15451547

15461548
/** @section {Movement state} */

0 commit comments

Comments
 (0)