diff --git a/src/geo/mercator_coordinate.js b/src/geo/mercator_coordinate.js index ee363142420..76b0e0d41fc 100644 --- a/src/geo/mercator_coordinate.js +++ b/src/geo/mercator_coordinate.js @@ -3,12 +3,16 @@ import LngLat from '../geo/lng_lat'; import type {LngLatLike} from '../geo/lng_lat'; +/* + * The circumference of the world in meters at the equator. + */ +const circumferenceAtEquator = 2 * Math.PI * 6378137; + /* * The circumference of the world in meters at the given latitude. */ function circumferenceAtLatitude(latitude: number) { - const circumference = 2 * Math.PI * 6378137; - return circumference * Math.cos(latitude * Math.PI / 180); + return circumferenceAtEquator * Math.cos(latitude * Math.PI / 180); } export function mercatorXfromLng(lng: number) { @@ -36,6 +40,19 @@ export function altitudeFromMercatorZ(z: number, y: number) { return z * circumferenceAtLatitude(latFromMercatorY(y)); } +/** + * Determine the Mercator scale factor for a given latitude, see + * https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor + * + * At the equator the scale factor will be 1, which increases at higher latitudes. + * + * @param {number} lat Latitude + * @returns {number} scale factor + */ +export function mercatorScale(lat: number) { + return 1 / Math.cos(lat * Math.PI / 180); +} + /** * A `MercatorCoordinate` object represents a projected three dimensional position. * @@ -113,6 +130,20 @@ class MercatorCoordinate { toAltitude() { return altitudeFromMercatorZ(this.z, this.y); } + + /** + * Returns the distance of 1 meter in `MercatorCoordinate` units at this latitude. + * + * For coordinates in real world units using meters, this naturally provides the scale + * to transform into `MercatorCoordinate`s. + * + * @returns {number} Distance of 1 meter in `MercatorCoordinate` units. + */ + meterInMercatorCoordinateUnits() { + // 1 meter / circumference at equator in meters * Mercator projection scale factor at this latitude + return 1 / circumferenceAtEquator * mercatorScale(latFromMercatorY(this.y)); + } + } export default MercatorCoordinate; diff --git a/test/unit/geo/mercator_coordinate.test.js b/test/unit/geo/mercator_coordinate.test.js index 4173296b199..d7acb1393da 100644 --- a/test/unit/geo/mercator_coordinate.test.js +++ b/test/unit/geo/mercator_coordinate.test.js @@ -1,6 +1,6 @@ import { test } from '../../util/test'; import LngLat from '../../../src/geo/lng_lat'; -import MercatorCoordinate from '../../../src/geo/mercator_coordinate'; +import MercatorCoordinate, { mercatorScale } from '../../../src/geo/mercator_coordinate'; test('LngLat', (t) => { t.test('#constructor', (t) => { @@ -27,5 +27,17 @@ test('LngLat', (t) => { t.end(); }); + t.test('#mercatorScale', (t) => { + t.equal(mercatorScale(0), 1, 'mercator scale at the equator'); + t.equal(mercatorScale(45), 1.414213562373095, 'mercator scale at 45 degrees latitude'); + t.end(); + }); + + t.test('#meterInMercatorCoordinateUnits', (t) => { + const nullIsland = new LngLat(0, 0); + t.equal(MercatorCoordinate.fromLngLat(nullIsland).meterInMercatorCoordinateUnits(), 2.495320233665337e-8, 'length of 1 meter in MercatorCoordinate units at the equator'); + t.end(); + }); + t.end(); });