-
-
Notifications
You must be signed in to change notification settings - Fork 643
/
mercator_coordinate.ts
157 lines (141 loc) · 4.99 KB
/
mercator_coordinate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import {LngLat, earthRadius} from '../geo/lng_lat';
import type {LngLatLike} from '../geo/lng_lat';
import {IMercatorCoordinate} from '@maplibre/maplibre-gl-style-spec';
/*
* The average circumference of the world in meters.
*/
const earthCircumfrence = 2 * Math.PI * earthRadius; // meters
/*
* The circumference at a line of latitude in meters.
*/
function circumferenceAtLatitude(latitude: number) {
return earthCircumfrence * Math.cos(latitude * Math.PI / 180);
}
export function mercatorXfromLng(lng: number) {
return (180 + lng) / 360;
}
export function mercatorYfromLat(lat: number) {
return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360;
}
export function mercatorZfromAltitude(altitude: number, lat: number) {
return altitude / circumferenceAtLatitude(lat);
}
export function lngFromMercatorX(x: number) {
return x * 360 - 180;
}
export function latFromMercatorY(y: number) {
const y2 = 180 - y * 360;
return 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90;
}
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 lat - Latitude
* @returns scale factor
*/
export function mercatorScale(lat: number) {
return 1 / Math.cos(lat * Math.PI / 180);
}
/**
* A `MercatorCoordinate` object represents a projected three dimensional position.
*
* `MercatorCoordinate` uses the web mercator projection ([EPSG:3857](https://epsg.io/3857)) with slightly different units:
*
* - the size of 1 unit is the width of the projected world instead of the "mercator meter"
* - the origin of the coordinate space is at the north-west corner instead of the middle
*
* For example, `MercatorCoordinate(0, 0, 0)` is the north-west corner of the mercator world and
* `MercatorCoordinate(1, 1, 0)` is the south-east corner. If you are familiar with
* [vector tiles](https://github.com/mapbox/vector-tile-spec) it may be helpful to think
* of the coordinate space as the `0/0/0` tile with an extent of `1`.
*
* The `z` dimension of `MercatorCoordinate` is conformal. A cube in the mercator coordinate space would be rendered as a cube.
*
* @group Geography and Geometry
*
* @example
* ```ts
* let nullIsland = new MercatorCoordinate(0.5, 0.5, 0);
* ```
* @see [Add a custom style layer](https://maplibre.org/maplibre-gl-js/docs/examples/custom-style-layer/)
*/
export class MercatorCoordinate implements IMercatorCoordinate {
x: number;
y: number;
z: number;
/**
* @param x - The x component of the position.
* @param y - The y component of the position.
* @param z - The z component of the position.
*/
constructor(x: number, y: number, z: number = 0) {
this.x = +x;
this.y = +y;
this.z = +z;
}
/**
* Project a `LngLat` to a `MercatorCoordinate`.
*
* @param lngLatLike - The location to project.
* @param altitude - The altitude in meters of the position.
* @returns The projected mercator coordinate.
* @example
* ```ts
* let coord = MercatorCoordinate.fromLngLat({ lng: 0, lat: 0}, 0);
* coord; // MercatorCoordinate(0.5, 0.5, 0)
* ```
*/
static fromLngLat(lngLatLike: LngLatLike, altitude: number = 0): MercatorCoordinate {
const lngLat = LngLat.convert(lngLatLike);
return new MercatorCoordinate(
mercatorXfromLng(lngLat.lng),
mercatorYfromLat(lngLat.lat),
mercatorZfromAltitude(altitude, lngLat.lat));
}
/**
* Returns the `LngLat` for the coordinate.
*
* @returns The `LngLat` object.
* @example
* ```ts
* let coord = new MercatorCoordinate(0.5, 0.5, 0);
* let lngLat = coord.toLngLat(); // LngLat(0, 0)
* ```
*/
toLngLat() {
return new LngLat(
lngFromMercatorX(this.x),
latFromMercatorY(this.y));
}
/**
* Returns the altitude in meters of the coordinate.
*
* @returns The altitude in meters.
* @example
* ```ts
* let coord = new MercatorCoordinate(0, 0, 0.02);
* coord.toAltitude(); // 6914.281956295339
* ```
*/
toAltitude(): number {
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 Distance of 1 meter in `MercatorCoordinate` units.
*/
meterInMercatorCoordinateUnits(): number {
// 1 meter / circumference at equator in meters * Mercator projection scale factor at this latitude
return 1 / earthCircumfrence * mercatorScale(latFromMercatorY(this.y));
}
}