Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add MercatorCoordinate type #7488

Merged
merged 10 commits into from
Oct 29, 2018
Merged
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
1 change: 1 addition & 0 deletions docs/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ toc:
- LngLatBoundsLike
- Point
- PointLike
- MercatorCoordinate
- name: User Interface
description: |
Controls, markers, and popups add new user interface elements to the map.
Expand Down
21 changes: 12 additions & 9 deletions docs/pages/example/custom-style-layer.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<script>
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 4,
center: [0, 0],
zoom: 3,
center: [7.5, 58],
style: 'mapbox://styles/mapbox/light-v9'
});

Expand All @@ -17,7 +17,7 @@
"uniform mat4 u_matrix;" +
"attribute vec2 a_pos;" +
"void main() {" +
" gl_Position = vec4(a_pos, 0.0, 1.0);" +
" gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);" +
"}";

var fragmentSource = "" +
Expand All @@ -39,13 +39,16 @@

this.aPos = gl.getAttribLocation(this.program, "a_pos");

var helsinki = mapboxgl.MercatorCoordinate.fromLngLat({ lng: 25.004, lat: 60.239 });
var berlin = mapboxgl.MercatorCoordinate.fromLngLat({ lng: 13.403, lat: 52.562 });
var kyiv = mapboxgl.MercatorCoordinate.fromLngLat({ lng: 30.498, lat: 50.541 });

this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-0.5, -0.5,
-0.5, 0.5,
0.5, -0.5,
0.5, 0.5
helsinki.x, helsinki.y,
berlin.x, berlin.y,
kyiv.x, kyiv.y,
]), gl.STATIC_DRAW);
},

Expand All @@ -57,11 +60,11 @@
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
};

map.on('load', function() {
map.addLayer(highlightLayer, 'waterway-river-canal');
map.addLayer(highlightLayer, 'building');
});
</script>
79 changes: 0 additions & 79 deletions src/geo/coordinate.js

This file was deleted.

118 changes: 118 additions & 0 deletions src/geo/mercator_coordinate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// @flow

import LngLat from '../geo/lng_lat';
import type {LngLatLike} from '../geo/lng_lat';

/*
* 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);
}

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));
}

/**
* 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`.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does there need to be a caveat about precision here? I.e. When using a mercator co-ordinate to place something on a custom layer, it may not match the exact location at higher zooms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those issues are more on the rendering side of things than the MercatorCoordinate side. We should probably add caveats and work arounds in the docs for custom layers. #7268 kind of covers this

* The `z` dimension of `MercatorCoordinate` is conformal. A cube in the mercator coordinate space would be rendered as a cube.
*
* @param {number} x The x component of the position.
* @param {number} y The y component of the position.
* @param {number} z The z component of the position.
* @example
* var nullIsland = new mapboxgl.MercatorCoordinate(0.5, 0.5, 0);
*
* @see [Add a custom style layer](https://www.mapbox.com/mapbox-gl-js/example/custom-style-layer/)
*/
class MercatorCoordinate {
x: number;
y: number;
z: number;

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} lngLatLike The location to project.
* @param {number} altitude The altitude in meters of the position.
* @returns {MercatorCoordinate} The projected mercator coordinate.
* @example
* var coord = mapboxgl.MercatorCoordinate.fromLngLat({ lng: 0, lat: 0}, 0);
* coord; // MercatorCoordinate(0.5, 0.5, 0)
*/
static fromLngLat(lngLatLike: LngLatLike, altitude: number = 0) {
const lngLat = LngLat.convert(lngLatLike);

return new MercatorCoordinate(
mercatorXfromLng(lngLat.lng),
mercatorYfromLat(lngLat.lat),
mercatorZfromAltitude(altitude, lngLat.lat));
}

/**
* Returns the `LatLng` for the coordinate.
*
* @returns {LngLat} The `LngLat` object.
* @example
* var coord = new mapboxgl.MercatorCoordinate(0.5, 0.5, 0);
* var latLng = coord.toLatLng(); // LngLat(0, 0)
*/
toLngLat() {
return new LngLat(
lngFromMercatorX(this.x),
latFromMercatorY(this.y));
}

/**
* Returns the altitude in meters of the coordinate.
*
* @returns {number} The altitude in meters.
* @example
* var coord = new mapboxgl.MercatorCoordinate(0, 0, 0.02);
* coord.toAltitude(); // 6914.281956295339
*/
toAltitude() {
return altitudeFromMercatorZ(this.z, this.y);
}
}

export default MercatorCoordinate;
Loading