Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modules/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export {default as DeckRenderer} from './lib/deck-renderer';

// Viewports
export {default as Viewport} from './viewports/viewport';
export {default as WebMercatorViewport} from './viewports/web-mercator-viewport';
export {
default as WebMercatorViewport,
getZoomFromElevation,
getElevationFromZoom
} from './viewports/web-mercator-viewport';
export {default as _GlobeViewport} from './viewports/globe-viewport';
export {default as OrbitViewport} from './viewports/orbit-viewport';
export {default as OrthographicViewport} from './viewports/orthographic-viewport';
Expand Down
59 changes: 59 additions & 0 deletions modules/core/src/viewports/web-mercator-viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getProjectionParameters,
altitudeToFovy,
fovyToAltitude,
getMeterZoom,
fitBounds,
getBounds
} from '@math.gl/web-mercator';
Expand Down Expand Up @@ -324,3 +325,61 @@ export default class WebMercatorViewport extends Viewport {
return new WebMercatorViewport({width, height, longitude, latitude, zoom});
}
}

/**
* Returns the zoom level that will position the camera at the given elevation above the ground.
* Can be used to create a WebMercatorViewport from a camera at a known physical elevation (e.g. for 3D tileset traversal).
*
* @param options
* @param options.elevation - Physical camera elevation in meters above the ground
* @param options.latitude - Latitude of the viewport center in degrees
* @param options.height - Height of the viewport in pixels
* @param options.pitch - Tilt of the camera in degrees. Default `0`
* @param options.fovy - Camera field of view in degrees. If provided, overrides `altitude`
* @param options.altitude - Camera altitude relative to the viewport height. Default `1.5`
* @returns Zoom level for use in WebMercatorViewport
*/
export function getZoomFromElevation(options: {
elevation: number;
latitude: number;
height: number;
pitch?: number;
fovy?: number;
altitude?: number;
}): number {
const {elevation, latitude, height, pitch = 0, fovy, altitude = 1.5} = options;
const altitudeRatio = fovy ? fovyToAltitude(fovy) : altitude;
return (
getMeterZoom({latitude}) +
Math.log2((altitudeRatio * Math.cos(pitch * (Math.PI / 180)) * height) / elevation)
);
}

/**
* Returns the camera elevation in meters for the given zoom level.
* This is the inverse of `getZoomFromElevation`.
*
* @param options
* @param options.zoom - Zoom level
* @param options.latitude - Latitude of the viewport center in degrees
* @param options.height - Height of the viewport in pixels
* @param options.pitch - Tilt of the camera in degrees. Default `0`
* @param options.fovy - Camera field of view in degrees. If provided, overrides `altitude`
* @param options.altitude - Camera altitude relative to the viewport height. Default `1.5`
* @returns Camera elevation in meters above the ground
*/
export function getElevationFromZoom(options: {
zoom: number;
latitude: number;
height: number;
pitch?: number;
fovy?: number;
altitude?: number;
}): number {
const {zoom, latitude, height, pitch = 0, fovy, altitude = 1.5} = options;
const altitudeRatio = fovy ? fovyToAltitude(fovy) : altitude;
return (
(altitudeRatio * Math.cos(pitch * (Math.PI / 180)) * height) /
Math.pow(2, zoom - getMeterZoom({latitude}))
);
}
3 changes: 3 additions & 0 deletions modules/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export {
OrbitViewport,
OrthographicViewport,
FirstPersonViewport,
// Viewport utilities
getZoomFromElevation,
getElevationFromZoom,
// Controllers
Controller,
MapController,
Expand Down
48 changes: 47 additions & 1 deletion test/modules/core/viewports/web-mercator-viewport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import test from 'tape-promise/tape';
import {equals, config, Vector3} from '@math.gl/core';
import {WebMercatorViewport} from 'deck.gl';
import {WebMercatorViewport, getZoomFromElevation, getElevationFromZoom} from 'deck.gl';
import {Matrix4} from '@math.gl/core';

// Adjust sensitivity of math.gl's equals
Expand Down Expand Up @@ -304,6 +304,52 @@ test('WebMercatorViewport#constructor#fovy', t => {
t.end();
});

test('getZoomFromElevation and getElevationFromZoom', t => {
const testCases = [
{latitude: 0, height: 600, altitude: 1.5, elevation: 1000},
{latitude: 45, height: 600, altitude: 1.5, elevation: 5000},
{latitude: 60, height: 800, altitude: 2.0, elevation: 500},
{latitude: 37.8, height: 600, altitude: 1.5, elevation: 10000, pitch: 30},
{latitude: 37.8, height: 600, fovy: 50, elevation: 2000}
];

for (const tc of testCases) {
const zoom = getZoomFromElevation(tc);
t.ok(Number.isFinite(zoom), `getZoomFromElevation returns a number for ${JSON.stringify(tc)}`);

const roundtrippedElevation = getElevationFromZoom({...tc, zoom});
t.ok(
Math.abs(roundtrippedElevation - tc.elevation) < 1e-6,
`getElevationFromZoom is the inverse of getZoomFromElevation for ${JSON.stringify(tc)}`
);
}

// Verify the zoom produces the correct camera height in the viewport
const latitude = 37.8;
const height = 600;
const altitude = 1.5;
const desiredElevation = 5000;
const zoom = getZoomFromElevation({elevation: desiredElevation, latitude, height, altitude});
const viewport = new WebMercatorViewport({
latitude,
longitude: -122,
zoom,
height,
width: 800,
altitude
});
const cameraZ = viewport.cameraPosition[2];
// cameraPosition[2] is in common space; convert to meters
const distanceScales = viewport.getDistanceScales();
const cameraElevationMeters = cameraZ / distanceScales.unitsPerMeter[2];
t.ok(
Math.abs(cameraElevationMeters - desiredElevation) / desiredElevation < 0.01,
'camera elevation matches desired elevation (within 1%)'
);

t.end();
});

function getCulling(p, planes) {
let outDir = null;
p = new Vector3(p);
Expand Down