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

Refactor raw projections, handle projection options #10913

Merged
merged 7 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------

Contains a portion of d3-color https://github.com/d3/d3-color
Contains a portion of d3-geo https://github.com/d3/d3-geo
Contains a portion of d3-geo-projection https://github.com/d3/d3-geo-projection

Copyright 2010-2016 Mike Bostock
Copyright 2010-2021 Mike Bostock
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
Expand Down
2 changes: 0 additions & 2 deletions debug/projections.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,13 @@
alaska: 4,
winkel: 1.2,
mercator: 1.0,
sinusoidal: 1.0,
wgs84: 1.0
};
const centers = {
albers: [-122.414, 37.776],
alaska: [-154, 63],
winkel: [0, 0],
mercator: [0, 0],
sinusoidal: [0, 0],
wgs84: [0, 0]
};
const projection = el.options[el.selectedIndex].value;
Expand Down
8 changes: 4 additions & 4 deletions src/data/load_geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {warnOnce, clamp} from '../util/util.js';

import EXTENT from './extent.js';
import {lngFromMercatorX, latFromMercatorY} from '../geo/mercator_coordinate.js';
import projections from '../geo/projection/index.js';
import getProjection from '../geo/projection/index.js';
import tileTransform from '../geo/projection/tile_transform.js';
import Point from '@mapbox/point-geometry';
import type {CanonicalTileID} from '../source/tile_id.js';
Expand All @@ -19,8 +19,8 @@ const MIN = -MAX - 1;

let projection;

export function setProjection(projectionName: string) {
projection = projections[projectionName];
export function setProjection(config: {name: string} | string) {
projection = getProjection(config);
}

function clampPoint(point: Point) {
Expand Down Expand Up @@ -52,7 +52,7 @@ export default function loadGeometry(feature: VectorTileFeature, canonical?: Can
const scale = EXTENT / featureExtent;
let cs, z2;
if (canonical) {
cs = tileTransform(canonical, projection.project);
cs = tileTransform(canonical, projection);
z2 = Math.pow(2, canonical.z);
}

Expand Down
92 changes: 33 additions & 59 deletions src/geo/projection/albers.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,48 @@
// @flow
import LngLat from '../lng_lat.js';

const albersConstants = {
refLng: -96,
p1r: 29.5,
p2r: 45.5
};

const alaskaConstants = {
refLng: -154,
p1r: 55,
p2r: 65
};

function project(lng, lat, constants) {
const p1 = constants.p1r / 180 * Math.PI;
const p2 = constants.p2r / 180 * Math.PI;
const n = 0.5 * (Math.sin(p1) + Math.sin(p2));
const theta = n * ((lng - constants.refLng) / 180 * Math.PI);
const c = Math.pow(Math.cos(p1), 2) + 2 * n * Math.sin(p1);
const r = 0.5;
const a = r / n * Math.sqrt(c - 2 * n * Math.sin(lat / 180 * Math.PI));
const b = r / n * Math.sqrt(c - 2 * n * Math.sin(0 / 180 * Math.PI));
const x = a * Math.sin(theta);
const y = b - a * Math.cos(theta);
const ret = {x: 0.5 + 0.5 * x, y: 0.5 + 0.5 * -y};
ret.x += 0.5;
ret.y += 0.5;

return ret;
}

function unproject(x, y, constants) {
const p1 = constants.p1r / 180 * Math.PI;
const p2 = constants.p2r / 180 * Math.PI;
const n = 0.5 * (Math.sin(p1) + Math.sin(p2));
const c = Math.pow(Math.cos(p1), 2) + 2 * n * Math.sin(p1);
const r = 0.5;
const b = r / n * Math.sqrt(c - 2 * n * Math.sin(0 / 180 * Math.PI));
const x__ = x - 0.5;
const y__ = y - 0.5;
const x_ = (x__ - 0.5) * 2;
const y_ = (y__ - 0.5) * -2;
const y2 = -(y_ - b);
const theta = Math.atan2(x_, y2);
const lng = (theta / n * 180 / Math.PI) + constants.refLng;
const a = x_ / Math.sin(theta);
const lat = Math.asin((Math.pow(a / r * n, 2) - c) / (-2 * n)) * 180 / Math.PI;

return new LngLat(lng, lat);
}

export const albers = {
name: 'albers',
range: [3.5, 7],

center: [-96, 37.5],
parallels: [29.5, 45.5],

project(lng: number, lat: number) {
return project(lng, lat, albersConstants);
const p1 = this.parallels[0] / 180 * Math.PI;
const p2 = this.parallels[1] / 180 * Math.PI;
const n = 0.5 * (Math.sin(p1) + Math.sin(p2));
const theta = n * ((lng - this.center[0]) / 180 * Math.PI);
const c = Math.pow(Math.cos(p1), 2) + 2 * n * Math.sin(p1);
const r = 0.5;
const a = r / n * Math.sqrt(c - 2 * n * Math.sin(lat / 180 * Math.PI));
const b = r / n * Math.sqrt(c - 2 * n * Math.sin(0 / 180 * Math.PI));
const x = a * Math.sin(theta);
const y = b - a * Math.cos(theta);
return {x: 1 + 0.5 * x, y: 1 - 0.5 * y};
},
unproject: (x: number, y: number) => {
return unproject(x, y, albersConstants);
unproject(x: number, y: number) {
const p1 = this.parallels[0] / 180 * Math.PI;
const p2 = this.parallels[1] / 180 * Math.PI;
const n = 0.5 * (Math.sin(p1) + Math.sin(p2));
const c = Math.pow(Math.cos(p1), 2) + 2 * n * Math.sin(p1);
const r = 0.5;
const b = r / n * Math.sqrt(c - 2 * n * Math.sin(0 / 180 * Math.PI));
const x_ = (x - 1) * 2;
const y_ = (y - 1) * -2;
const y2 = -(y_ - b);
const theta = Math.atan2(x_, y2);
const lng = (theta / n * 180 / Math.PI) + this.center[0];
const a = x_ / Math.sin(theta);
const lat = Math.asin((Math.pow(a / r * n, 2) - c) / (-2 * n)) * 180 / Math.PI;
return new LngLat(lng, lat);
}
};

export const alaska = {
...albers,
name: 'alaska',
range: [4, 7],
project(lng: number, lat: number) {
return project(lng, lat, alaskaConstants);
},
unproject: (x: number, y: number) => {
return unproject(x, y, alaskaConstants);
}
center: [-154, 50],
parallels: [55, 65]
};
16 changes: 11 additions & 5 deletions src/geo/projection/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
// @flow
import {albers, alaska} from './albers.js';
import mercator from './mercator.js';
import sinusoidal from './sinusoidal.js';
import wgs84 from './wgs84.js';
import winkel from './winkelTripel.js';
import LngLat from '../lng_lat.js';

export type Projection = {
name: string,
range: Array<number>,
project: (lng: number, lat: number, options?: Object) => {x: number, y: number},
center: [number, number],
range?: Array<number>,
project: (lng: number, lat: number) => {x: number, y: number},
unproject: (x: number, y: number) => LngLat
};

export default {
const projections = {
albers,
alaska,
mercator,
sinusoidal,
wgs84,
winkel
};

export default function getProjection(config: {name: string} | string) {
if (typeof config === 'string') {
return projections[config];
}
return {...projections[config.name], ...config};
}
11 changes: 8 additions & 3 deletions src/geo/projection/mercator.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// @flow
import MercatorCoordinate, {mercatorXfromLng, mercatorYfromLat} from '../mercator_coordinate.js';
import {mercatorXfromLng, mercatorYfromLat, lngFromMercatorX, latFromMercatorY} from '../mercator_coordinate.js';
import LngLat from '../lng_lat.js';

export default {
name: 'mercator',
range: [],
center: [0, 0],
project(lng: number, lat: number) {
const x = mercatorXfromLng(lng);
const y = mercatorYfromLat(lat);
return {x, y};
},
unproject: (x: number, y: number) => new MercatorCoordinate(x, y).toLngLat()
unproject(x: number, y: number) {
return new LngLat(
lngFromMercatorX(x),
latFromMercatorY(y));
}
};
17 changes: 0 additions & 17 deletions src/geo/projection/sinusoidal.js

This file was deleted.

11 changes: 6 additions & 5 deletions src/geo/projection/tile_transform.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow
import {lngFromMercatorX, latFromMercatorY} from '../mercator_coordinate.js';
import {number as interpolate} from '../../style-spec/util/interpolate.js';
import type {Projection} from './index.js';

export default function tileTransform(id: Object, project: function) {
export default function tileTransform(id: Object, projection: Projection) {
const s = Math.pow(2, -id.z);
const x1 = (id.x) * s;
const x2 = (id.x + 1) * s;
Expand All @@ -22,10 +23,10 @@ export default function tileTransform(id: Object, project: function) {
const lng = lngFromMercatorX(interpolate(x1, x2, i / n));
const lat = latFromMercatorY(interpolate(y1, y2, i / n));

const p0 = project(lng, lat1);
const p1 = project(lng, lat2);
const p2 = project(lng1, lat);
const p3 = project(lng2, lat);
const p0 = projection.project(lng, lat1);
const p1 = projection.project(lng, lat2);
const p2 = projection.project(lng1, lat);
const p3 = projection.project(lng2, lat);

minX = Math.min(minX, p0.x, p1.x, p2.x, p3.x);
maxX = Math.max(maxX, p0.x, p1.x, p2.x, p3.x);
Expand Down
9 changes: 7 additions & 2 deletions src/geo/projection/wgs84.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// @flow
import LngLat from '../lng_lat.js';

export default {
name: 'wgs84',
range: [],
center: [0, 0],
project(lng: number, lat: number) {
const x = 0.5 + lng / 360;
const y = 0.5 - lat / 360;
return {x, y};
},
unproject: () => {}
unproject(x: number, y: number) {
return new LngLat(
(x - 0.5) * 360,
(0.5 - y) * 360);
}
};
34 changes: 31 additions & 3 deletions src/geo/projection/winkelTripel.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
// @flow
// leaving Winkel Tripel methods as noop for now while we decide on implementation issues
import LngLat from '../lng_lat.js';

function sinc(x) {
return Math.sin(x) / x;
}

function s(n) {
return (n / (Math.PI) + 0.5) * 0.5;
}

export default {
name: 'winkel',
center: [0, 0],
range: [3.5, 7],
project: () => {},
unproject: () => {}

project(lng: number, lat: number) {
lat = lat / 180 * Math.PI;
lng = lng / 180 * Math.PI;
const phi1 = Math.acos(2 / Math.PI);
const alpha = Math.acos(Math.cos(lat) * Math.cos(lng / 2));
const x = 0.5 * (lng * Math.cos(phi1) + (2 * Math.cos(lat) * Math.sin(lng / 2)) / sinc(alpha)) || 0;
const y = 0.5 * (lat + Math.sin(lat) / sinc(alpha)) || 0;
return {
x: s(x),
y: 1 - s(y)
};
},

unproject(x: number, y: number) {
// temporary — replace with a proper implementation
return new LngLat(
(x - 0.5) * 360,
(0.5 - y) * 90);
}
};
10 changes: 6 additions & 4 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import LngLat from './lng_lat.js';
import LngLatBounds from './lng_lat_bounds.js';
import MercatorCoordinate, {mercatorXfromLng, mercatorYfromLat, mercatorZfromAltitude, latFromMercatorY} from './mercator_coordinate.js';
import projections from './projection/index.js';
import getProjection from './projection/index.js';
import tileTransform from '../geo/projection/tile_transform.js';
import Point from '@mapbox/point-geometry';
import {wrap, clamp, radToDeg, degToRad, getAABBPointSquareDist, furthestTileCorner} from '../util/util.js';
Expand Down Expand Up @@ -93,6 +93,7 @@ class Transform {
cameraElevationReference: ElevationReference;
fogCullDistSq: ?number;
_averageElevation: number;
projectionOptions: {name: string} | string;
projection: Projection;
_elevation: ?Elevation;
_fov: number;
Expand Down Expand Up @@ -129,7 +130,8 @@ class Transform {
this.setMaxBounds();

if (!projection) projection = 'mercator';
this.projection = projections[projection];
this.projectionOptions = projection;
this.projection = getProjection(projection);

this.width = 0;
this.height = 0;
Expand Down Expand Up @@ -678,7 +680,7 @@ class Transform {
const minRange = options.isTerrainDEM ? -maxRange : this._elevation ? this._elevation.getMinElevationBelowMSL() : 0;

const aabbForTile = (z, x, y, wrap, min, max) => {
const tt = tileTransform({z, x, y}, this.projection.project);
const tt = tileTransform({z, x, y}, this.projection);
const tx = tt.x / tt.scale;
const ty = tt.y / tt.scale;
const tx2 = tt.x2 / tt.scale;
Expand Down Expand Up @@ -1265,7 +1267,7 @@ class Transform {
scaledX = unwrappedX * scale;
scaledY = canonical.y * scale;
} else {
const cs = tileTransform(canonical, this.projection.project);
const cs = tileTransform(canonical, this.projection);
scale = 1;
scaledX = cs.x;
scaledY = cs.y;
Expand Down
2 changes: 1 addition & 1 deletion src/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ class Tile {
const s = Math.pow(2, -this.tileID.canonical.z);
const x1 = (this.tileID.canonical.x) * s;
const y1 = (this.tileID.canonical.y) * s;
const cs = tileTransform(this.tileID.canonical, projection.project);
const cs = tileTransform(this.tileID.canonical, projection);
const increment = s / denominator;
const x2 = x1 + x * increment;
const y2 = y1 + y * increment;
Expand Down
4 changes: 2 additions & 2 deletions src/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export default class Worker {
callback();
}

setProjection(mapId: string, projectionName: string) {
setProjection(projectionName);
setProjection(mapId: string, config: {name: string} | string) {
setProjection(config);
}

setLayers(mapId: string, layers: Array<LayerSpecification>, callback: WorkerTileCallback) {
Expand Down
2 changes: 1 addition & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class Style extends Evented {
this._serializedLayers[layer.id] = layer.serialize();
this._updateLayerCount(layer, true);
}
this.dispatcher.broadcast('setProjection', this.map.transform.projection.name);
this.dispatcher.broadcast('setProjection', this.map.transform.projectionOptions);
this.dispatcher.broadcast('setLayers', this._serializeLayers(this._order));

this.light = new Light(this.stylesheet.light);
Expand Down