Skip to content

Commit

Permalink
Add altitude support in Marker and Popup (#13335) (h/t @yangtanyu)
Browse files Browse the repository at this point in the history
  • Loading branch information
yangtanyu authored Feb 17, 2025
1 parent 6304f71 commit 6563f75
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 31 deletions.
216 changes: 216 additions & 0 deletions debug/markers-altitude.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#controls { position: absolute; top: 0; left: 0; }
</style>
</head>

<body>
<div id='map'></div>
<div id='controls'>
<button id="animate">Animate</button><br />
<label><input id='terrain-checkbox' type='checkbox'> terrain</label><br />
<div>
<label>Projection:</label>
<select id="projName">
<option value="mercator" selected>Mercator</option>
<option value="globe">Globe</option>
<option value="albers">Albers USA</option>
<option value="equalEarth">Equal Earth</option>
<option value="equirectangular">Equirectangular</option>
<option value="lambertConformalConic">Lambert Conformal Conic</option>
<option value="naturalEarth">Natural Earth</option>
<option value="winkelTripel">Winkel Tripel</option>
</select>
</div>
</div>
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = window.map = new mapboxgl.Map({
container: 'map',
devtools: true,
zoom: 12.5,
center: [-77.01866, 38.888],
style: 'mapbox://styles/mapbox/streets-v9',
hash: true
});

let lngLatArr = [];
const spinningMarkers = [];
['auto', 'map', 'viewport', 'horizon'].forEach((rotationAlignment, i) => {
['auto', 'map', 'viewport'].forEach((pitchAlignment, j) => {
var bounds = map.getBounds();
var s = bounds.getSouth();
var n = bounds.getNorth();
var w = bounds.getWest();
var e = bounds.getEast();

var lng = w + (e - w) * ((i + .5) / 4);
var lat = s + (n - s) * ((j + .5) / 3);

var height = (i * 2000 + j*2000) * (e - w);
// var height = 0.0;


// if(!(i===2 && j===0))return

const popupHtml = `<div>
Pitch Alignment: ${pitchAlignment}<br>
Rotation Alignment: ${rotationAlignment}<br>
lng:${lng}<br>
lat:${lat}<br>
height:${height}<br>
</div>`

var popup = new mapboxgl.Popup().setHTML(popupHtml);

var r = Math.round(Math.random() * 255);
var g = Math.round(Math.random() * 255);
var b = Math.round(Math.random() * 255);

var sc = Math.random() * 2.5 + 0.5;

var marker = new mapboxgl.Marker({
color: `rgb(${r}, ${g}, ${b})`,
scale: sc,
draggable: true,
rotationAlignment,
pitchAlignment,
altitude:height,
})
.setLngLat([lng, lat])
.setPopup(popup)
.addTo(map);

marker.togglePopup();

lngLatArr.push({
lngLat:[lng, lat],
lngOffset:(e - w),
latOffset:(n - s),
height
})

spinningMarkers.push(marker);
});
});


map.on('style.load',()=>{
addFillExtrusionLayer(lngLatArr)
})



function addFillExtrusionLayer(data=[]){

const dataArr = data.map(a=>{
let {lngOffset,latOffset} = a;
lngOffset = lngOffset/100;
latOffset = latOffset/100;
const [lng, lat] = a.lngLat;
const aObj = {
...a,
"type": "Feature",
"properties": {
"height": a.height,
"color": `rgba(${a.height%255},${a.height%150},${255 - a.height%255},1)`,
},
lngLatArr:[
[lng-lngOffset,lat+latOffset],
[lng+lngOffset,lat+latOffset],
[lng+lngOffset,lat-latOffset],
[lng-lngOffset,lat-latOffset],
]
}

aObj.geometry = {
"type": "Polygon",
"coordinates": [aObj.lngLatArr]
}

return aObj
});

let layer = {
id:'FillExtrusionLayerId',
'type': 'fill-extrusion',
'source': {
type: 'geojson',
data: {
"type": "FeatureCollection",
"features":dataArr,
}
},

'paint': {
'fill-extrusion-opacity':0.6,
// 'fill-extrusion-color': 'rgba(255,10,10,0.8)',
'fill-extrusion-color': ["get", "color"],
'fill-extrusion-height': ["get", "height"],
'fill-extrusion-base': 0,
}




}

map.addLayer(layer);

}

let animate = false;
document.getElementById('animate').addEventListener('click', () => {
animate = !animate;
if (animate) {
spinMarkers();
}
});

let rotation = 0;
function spinMarkers() {
rotation++;
spinningMarkers.forEach((marker) => {
marker.setRotation(rotation);
});
if (animate) {
window.requestAnimationFrame(spinMarkers);
}
}
window.requestAnimationFrame(spinMarkers);

map.addControl(new mapboxgl.NavigationControl());

map.on('load', function() {
map.addSource('mapbox-dem', {
"type": "raster-dem",
"url": "mapbox://mapbox.mapbox-terrain-dem-v1",
"tileSize": 512,
"maxzoom": 14
});

map.setFog({});
});

document.getElementById('terrain-checkbox').onclick = function() {
map.setTerrain(this.checked ? {"source": "mapbox-dem", "exaggeration": 10} : null);
};

document.getElementById('projName').addEventListener('change', (e) => {
const el = document.getElementById('projName');
map.setProjection(el.options[el.selectedIndex].value);
});
</script>

</body>
</html>
13 changes: 9 additions & 4 deletions src/geo/projection/globe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,18 @@ export default class Globe extends Mercator {
return {x: pos[0], y: pos[1], z: pos[2]};
}

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

const elevation = tr.elevation ?
tr.elevation.getAtPointOrZero(tr.locationCoordinate(lngLat), tr._centerAltitude) :
tr._centerAltitude;
let elevation = 0;
if (altitude) {
elevation = tr._centerAltitude + altitude;
} else {
elevation = tr.elevation ?
tr.elevation.getAtPointOrZero(tr.locationCoordinate(lngLat), tr._centerAltitude) :
tr._centerAltitude;
}

const upScale = mercatorZfromAltitude(1, 0) * EXTENT * elevation;
vec3.scaleAndAdd(pos, pos, up, upScale);
Expand Down
4 changes: 2 additions & 2 deletions src/geo/projection/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default class Projection {
return {x, y, z: 0};
}

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

pixelsPerMeter(lat: number, worldSize: number): number {
Expand Down
14 changes: 7 additions & 7 deletions src/geo/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1529,8 +1529,8 @@ class Transform {
* @returns {Point} screen point
* @private
*/
locationPoint3D(lnglat: LngLat): Point {
return this.projection.locationPoint(this, lnglat, true);
locationPoint3D(lnglat: LngLat, altitude?: number): Point {
return this.projection.locationPoint(this, lnglat, true, (altitude || 0));
}

/**
Expand All @@ -1551,8 +1551,8 @@ class Transform {
* @returns {LngLat} lnglat location
* @private
*/
pointLocation3D(p: Point): LngLat {
return this.coordinateLocation(this.pointCoordinate3D(p));
pointLocation3D(p: Point, altitude?: number): LngLat {
return this.coordinateLocation(this.pointCoordinate3D(p, (altitude || 0)));
}

/**
Expand Down Expand Up @@ -1674,12 +1674,12 @@ class Transform {
* @param {Point} p top left origin screen point, in pixels.
* @private
*/
pointCoordinate3D(p: Point): MercatorCoordinate {
if (!this.elevation) return this.pointCoordinate(p);
pointCoordinate3D(p: Point, altitude?: number): MercatorCoordinate {
if (!this.elevation) return this.pointCoordinate(p, (altitude || 0));
let raycast: vec3 | null | undefined = this.projection.pointCoordinate3D(this, p.x, p.y);
if (raycast) return new MercatorCoordinate(raycast[0], raycast[1], raycast[2]);
let start = 0, end = this.horizonLineFromTop();
if (p.y > end) return this.pointCoordinate(p); // holes between tiles below horizon line or below bottom.
if (p.y > end) return this.pointCoordinate(p, (altitude || 0)); // holes between tiles below horizon line or below bottom.
const samples = 10;
const threshold = 0.02 * end;
const r = p.clone();
Expand Down
10 changes: 6 additions & 4 deletions src/ui/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1516,13 +1516,14 @@ export class Map extends Camera {
* the `x` and `y` components of the returned {@link Point} are set to Number.MAX_VALUE.
*
* @param {LngLatLike} lnglat The geographical location to project.
* @param {number} altitude The height above sea level.
* @returns {Point} The {@link Point} corresponding to `lnglat`, relative to the map's `container`.
* @example
* const coordinate = [-122.420679, 37.772537];
* const point = map.project(coordinate);
*/
project(lnglat: LngLatLike): Point {
return this.transform.locationPoint3D(LngLat.convert(lnglat));
project(lnglat: LngLatLike, altitude?: number): Point {
return this.transform.locationPoint3D(LngLat.convert(lnglat), (altitude || 0));
}

/**
Expand All @@ -1532,15 +1533,16 @@ export class Map extends Camera {
* to the point.
*
* @param {PointLike} point The pixel coordinates to unproject.
*@param {number} altitude The height above sea level.
* @returns {LngLat} The {@link LngLat} corresponding to `point`.
* @example
* map.on('click', (e) => {
* // When the map is clicked, get the geographic coordinate.
* const coordinate = map.unproject(e.point);
* });
*/
unproject(point: PointLike): LngLat {
return this.transform.pointLocation3D(Point.convert(point));
unproject(point: PointLike, altitude?: number): LngLat {
return this.transform.pointLocation3D(Point.convert(point), (altitude || 0));
}

/** @section {Movement state} */
Expand Down
Loading

0 comments on commit 6563f75

Please sign in to comment.