Skip to content

Commit

Permalink
add support for non-mercator projections (#11124)
Browse files Browse the repository at this point in the history
* rough projection support

* projections stencil clipping and refactor (#410)

* Enable stencil clipping for line and fill layers

* Use buffers from tile

* Refactor tile bounds buffers

* Rename things to not exclusively be RasterBounds

* Create projections directory

* More refactoring

* Combine matrix calculations

* Cleanup

* Refactor projections to new folder

* Begin debug work

* Tile boundaries are working

* Refactor indexbuffer and segmentvector to per painter

* merge projectx and projecty functions

* nits

Co-authored-by: Ansis Brammanis <ansis@mapbox.com>

* Projections fix location issues (#414)

* Add debug page

* Add projection option

* Wire up projection code with worker

* Rename projections folder to projection

* Refactor and update free camera

* Add Projection type and fix center calculations

* Fix bug with transform._center

* Make Winkel projection noop for now

* Update demo HTML and CSS

* temp remove undistortion

Co-authored-by: Ansis Brammanis <ansis@mapbox.com>

* [projections] Adaptive geometry resampling for alternative projections (#10753)

* implement adaptive resampling of reprojected geometry

* address feedback

* Refactor projections code to get all tests passing (#10732)

* [projections] Simplify and optimize tile transform code (#10780)

* simplify projections tile transform

* skip resampling for mercator

* [projections] Fix performance regression in draw_background (#10747)

* separate tiles for background layers

* additional lint & flow fixes

* try fixing tests

* try fixing render tests

Co-authored-by: Ansis Brammanis <ansis@mapbox.com>

* Pin chrome to version 91 (#10887) (#10896)

* Pin to chrome version 91

* Pin chrome version for test-browser

Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>

* Refactor raw projections, handle projection options (#10913)

* refactor raw projections, handle projection options

* add unprojection to winkel tripel

* fix flow

* Pin chrome to version 91 (#10887)

* Pin to chrome version 91

* Pin chrome version for test-browser

* fix lint

* remove to superfluous sin calls

Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>

* Fix bearing for non-mercator projections (#10781)

* Use adaptive resampling with MARTINI & Earcut for non-Mercator tiles (#10980)

* use adaptive resampling and earcut for non-Mercator tile bounds

* fix unit test

* use adaptive MARTINI mesh for non-Mercator raster tiles

* Clamp unproject to valid geo range in alternate projections (#10992)

* clamp unproject to mercator bounds in all projections

* fix marker test

* avoid wrapping center for non-Mercator projections

* extend alt projections clamping to full lat range

* correct zoom, bearing and shear for projections (#10976)

* fix zoom, bearing and skew for projections

* refactor adjustments

* lint

* add comments

* Fix circle and heatmap on alternate projections (#11074)

* fix circle & heatmap on alternate projections (blunder)

* fix unit test

* fix pitch, line-width and other properties for projections (#11080)

and:
- fix fill-extrusions
- remove global projection variable to allow multiple maps on one page
- avoid recalculating tileTransform

* Add Equal Earth, Natural Earth and Lambert Conformal Conic projections (#11091)

* Fix constraining logic for alternate projections (#11092)

* adaptive bbox for projections, refactor resampling

* better precision for adaptive bounds

* remove leftover

* fix zoom/shear adjustments near poles

* optimize tile transform

* fix lint

* attempt to fix tests

* simplify, clarify and consolidate constraining logic

* minor renames in transform

* safer clamping for zoom adjustments

* Projections public API (#11002)

Co-authored-by: Ansis Brammanis <ansis@mapbox.com>

* fix conflicts

* fix seams around alternate-projected tiles (#11119)

* fix unit tests

* remove alaska

* Basic support for custom maxBounds in alternate projections (#11121)

* rudimentary support for custom maxBounds in alternate projections

* fix flow

* fix image and video sources in alternate projections (#11123)

* clean up debug pages

* remove uncessary deg <--> rad conversions

* fix filename casing

* fix queryRenderedFeatures for alternate projections (#11125)

* Projections fixups (#11127)

* disable terrain and fog for alternate projections (#11126)

* Lazily instantiate projected tile debug buffers and release projected buffers when tiles are unloaded (#11128)

* enable lod tile loading for projections (#11129)

* enable lod tile loading for projections

to significantly reduce the number of tiles at low zoom levels

* use Math.hypot(...)

Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>

* add comments

Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>

* allow map.setProjection(null)

* add limitations

* avoid recreating tile buffer

Co-authored-by: Karim Naaji <karim.naaji@gmail.com>

* fix assertion error

* fix requires

* center projections vertically

Center projections vertically in 0 to 1 range. This shouldn't matter but
there is some constraining behavior that is currently affected by this.

* Fix tile buffer destroyed but not reset (#11134)

* mention settin bounds in projection docs

Co-authored-by: Ryan Hamley <ryan.hamley@mapbox.com>
Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>
Co-authored-by: Arindam Bose <arindam.bose@mapbox.com>
Co-authored-by: Karim Naaji <karim.naaji@gmail.com>
  • Loading branch information
5 people authored Oct 19, 2021
1 parent f95b8fa commit 107a525
Show file tree
Hide file tree
Showing 90 changed files with 2,256 additions and 401 deletions.
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
4 changes: 4 additions & 0 deletions build/generate-flow-typed-style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ function flowType(property) {
return 'TerrainSpecification';
case 'fog':
return 'FogSpecification';
case 'projection':
return 'ProjectionSpecification';
case 'sources':
return '{[_: string]: SourceSpecification}';
case '*':
Expand Down Expand Up @@ -185,6 +187,8 @@ ${flowObjectDeclaration('TerrainSpecification', spec.terrain)}
${flowObjectDeclaration('FogSpecification', spec.fog)}
${flowObjectDeclaration('ProjectionSpecification', spec.projection)}
${spec.source.map(key => flowObjectDeclaration(flowSourceTypeName(key), spec[key])).join('\n\n')}
export type SourceSpecification =
Expand Down
9 changes: 6 additions & 3 deletions build/generate-struct-arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ function camelize (str) {
global.camelize = camelize;

import posAttributes from '../src/data/pos_attributes.js';
import rasterBoundsAttributes from '../src/data/raster_bounds_attributes.js';
import boundsAttributes from '../src/data/bounds_attributes.js';

createStructArrayType('pos', posAttributes);
createStructArrayType('raster_bounds', rasterBoundsAttributes);
createStructArrayType('raster_bounds', boundsAttributes);

import circleAttributes from '../src/data/bucket/circle_attributes.js';
import fillAttributes from '../src/data/bucket/fill_attributes.js';
Expand All @@ -130,6 +130,7 @@ import lineAttributesExt from '../src/data/bucket/line_attributes_ext.js';
import patternAttributes from '../src/data/bucket/pattern_attributes.js';
import dashAttributes from '../src/data/bucket/dash_attributes.js';
import skyboxAttributes from '../src/render/skybox_attributes.js';
import tileBoundsAttributes from '../src/data/bounds_attributes.js';
import {fillExtrusionAttributes, centroidAttributes} from '../src/data/bucket/fill_extrusion_attributes.js';

// layout vertex arrays
Expand Down Expand Up @@ -208,6 +209,9 @@ createStructArrayType('line_strip_index', createLayout([
// skybox vertex array
createStructArrayType(`skybox_vertex`, skyboxAttributes);

// tile bounds vertex array
createStructArrayType(`tile_bounds`, tileBoundsAttributes);

// paint vertex arrays

// used by SourceBinder for float properties
Expand Down Expand Up @@ -244,7 +248,6 @@ fs.writeFileSync('src/data/array_types.js',
import assert from 'assert';
import {Struct, StructArray} from '../util/struct_array.js';
import {register} from '../util/web_worker_transfer.js';
import Point from '@mapbox/point-geometry';
${layouts.map(structArrayLayoutJs).join('\n')}
${arraysWithStructAccessors.map(structArrayJs).join('\n')}
Expand Down
189 changes: 189 additions & 0 deletions debug/projections.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
.map-overlay {
background-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
font: bold 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
left: 10px;
padding: 10px;
position: absolute;
top: 10px;
width: 150px;
z-index: 1;
}

fieldset {
border: none;
padding: 5px 0;
}

select, input {
background-color: transparent;
border-radius: 3px;
margin: 0;
}

select {
width: 100%;
}
</style>
</head>

<body>
<div class="map-overlay top">
<fieldset>
<label>Projection:</label>
<select id="projName">
<option value="albers" selected>Albers USA</option>
<option value="equalEarth">Equal Earth</option>
<option value="equirectangular">Equirectangular</option>
<option value="lambertConformalConic">Lambert Conformal Conic</option>
<option value="mercator">Mercator</option>
<option value="naturalEarth">Natural Earth</option>
<option value="winkelTripel">Winkel Tripel</option>
</select>
</fieldset>
<fieldset>
<label>Style:</label>
<select id="styleName">
<option value="streets" selected>Streets</option>
<option value="satellite">Satellite</option>
</select>
</fieldset>
<fieldset>
<label>Graticule:</label>
<input id="graticule" type="checkbox">
</fieldset>
<fieldset>
<label>Show Debug Tiles:</label>
<input id="debug" type="checkbox" checked=true>
</fieldset>
</div>
<div id='map'></div>

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

let map;

const zooms = {
albers: 3,
lambertConformalConic: 3,
winkelTripel: 1.2
};
const centers = {
albers: [-122.414, 37.776],
lambertConformalConic: [-122.414, 37.776]
};

makeMap();

function makeMap() {
if (map) map.remove();
const el = document.getElementById('projName');
const projection = el.options[el.selectedIndex].value;
const zoom = zooms[projection] || 1;
const center = centers[projection] || [0, 0];
const styles = {
streets: 'mapbox://styles/mapbox/streets-v10',
satellite: 'mapbox://styles/mapbox/satellite-streets-v11'
};
const styleEl = document.getElementById('styleName');
const styleName = styleEl.options[styleEl.selectedIndex].value;
const style = styles[styleName];

map = new mapboxgl.Map({
projection,
container: 'map',
zoom,
center,
style,
hash: true
});
map.showTileBoundaries = true;
addGraticule();
}

map.on('click', function () {
console.log('projection', map.getProjection());
});

document.getElementById('projName').addEventListener('change', (e) => {
const el = document.getElementById('projName');
const projection = el.options[el.selectedIndex].value;
const zoom = zooms[projection] || 1;
const center = centers[projection] || [0, 0];
map.jumpTo({center, zoom});
map.setProjection(el.options[el.selectedIndex].value);
});

document.getElementById('styleName').addEventListener('change', (e) => {
makeMap();
});

document.getElementById('graticule').addEventListener('change', (e) => {
map.setPaintProperty('graticule', 'line-opacity', e.target.checked ? 1 : 0);
});

document.getElementById('debug').addEventListener('change', (e) => {
map.showTileBoundaries = e.target.checked;
});

function addGraticule() {
map.on('style.load', () => {
const fc = {
type: 'FeatureCollection',
features: []
};

for (let i = -170; i < 180; i += 10) {
fc.features.push({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [[i, -80], [i, 80]]
}
});
}
for (let i = -80; i < 80; i += 10) {
fc.features.push({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [[-180, i], [180, i]]
}
});
}

map.addSource('graticule', {
buffer: 0,
type: 'geojson',
data: fc
});

map.addLayer({
id: 'graticule',
source: 'graticule',
type: 'line',
paint: {
'line-width': 1,
'line-color': '#aaa',
'line-opacity': document.getElementById('graticule').checked ? 1 : 0
}
});
});
}

</script>
</body>
</html>
40 changes: 2 additions & 38 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -815,41 +815,6 @@ class StructArrayLayout1ui2 extends StructArray {
StructArrayLayout1ui2.prototype.bytesPerElement = 2;
register('StructArrayLayout1ui2', StructArrayLayout1ui2);

/**
* Implementation of the StructArray layout:
* [0]: Float32[5]
*
* @private
*/
class StructArrayLayout5f20 extends StructArray {
uint8: Uint8Array;
float32: Float32Array;

_refreshViews() {
this.uint8 = new Uint8Array(this.arrayBuffer);
this.float32 = new Float32Array(this.arrayBuffer);
}

emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number) {
const i = this.length;
this.resize(i + 1);
return this.emplace(i, v0, v1, v2, v3, v4);
}

emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number) {
const o4 = i * 5;
this.float32[o4 + 0] = v0;
this.float32[o4 + 1] = v1;
this.float32[o4 + 2] = v2;
this.float32[o4 + 3] = v3;
this.float32[o4 + 4] = v4;
return i;
}
}

StructArrayLayout5f20.prototype.bytesPerElement = 20;
register('StructArrayLayout5f20', StructArrayLayout5f20);

/**
* Implementation of the StructArray layout:
* [0]: Float32[2]
Expand Down Expand Up @@ -1227,7 +1192,6 @@ export {
StructArrayLayout1ul3ui12,
StructArrayLayout2ui4,
StructArrayLayout1ui2,
StructArrayLayout5f20,
StructArrayLayout2f8,
StructArrayLayout4f16,
StructArrayLayout2i4 as PosArray,
Expand All @@ -1251,6 +1215,6 @@ export {
StructArrayLayout3ui6 as TriangleIndexArray,
StructArrayLayout2ui4 as LineIndexArray,
StructArrayLayout1ui2 as LineStripIndexArray,
StructArrayLayout5f20 as GlobeVertexArray,
StructArrayLayout3f12 as SkyboxVertexArray
StructArrayLayout3f12 as SkyboxVertexArray,
StructArrayLayout4i8 as TileBoundsArray
};
File renamed without changes.
3 changes: 2 additions & 1 deletion src/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {FeatureStates} from '../source/source_state.js';
import type {ImagePosition} from '../render/image_atlas.js';
import type LineAtlas from '../render/line_atlas.js';
import type {CanonicalTileID} from '../source/tile_id.js';
import type {TileTransform} from '../geo/projection/tile_transform.js';

export type BucketParameters<Layer: TypedStyleLayer> = {
index: number,
Expand Down Expand Up @@ -78,7 +79,7 @@ export interface Bucket {
+layers: Array<any>;
+stateDependentLayers: Array<any>;
+stateDependentLayerIds: Array<string>;
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID): void;
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID, tileTransform: TileTransform): void;
update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array<string>, imagePositions: {[_: string]: ImagePosition}): void;
isEmpty(): boolean;

Expand Down
5 changes: 3 additions & 2 deletions src/data/bucket/circle_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type VertexBuffer from '../../gl/vertex_buffer.js';
import type Point from '@mapbox/point-geometry';
import type {FeatureStates} from '../../source/source_state.js';
import type {ImagePosition} from '../../render/image_atlas.js';
import type {TileTransform} from '../../geo/projection/tile_transform.js';

function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) {
layoutVertexArray.emplaceBack(
Expand Down Expand Up @@ -77,7 +78,7 @@ class CircleBucket<Layer: CircleStyleLayer | HeatmapStyleLayer> implements Bucke
this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);
}

populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID) {
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID, tileTransform: TileTransform) {
const styleLayer = this.layers[0];
const bucketFeatures = [];
let circleSortKey = null;
Expand All @@ -103,7 +104,7 @@ class CircleBucket<Layer: CircleStyleLayer | HeatmapStyleLayer> implements Bucke
type: feature.type,
sourceLayerIndex,
index,
geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature),
geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature, canonical, tileTransform),
patterns: {},
sortKey
};
Expand Down
5 changes: 3 additions & 2 deletions src/data/bucket/fill_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type VertexBuffer from '../../gl/vertex_buffer.js';
import type Point from '@mapbox/point-geometry';
import type {FeatureStates} from '../../source/source_state.js';
import type {ImagePosition} from '../../render/image_atlas.js';
import type {TileTransform} from '../../geo/projection/tile_transform.js';

class FillBucket implements Bucket {
index: number;
Expand Down Expand Up @@ -75,7 +76,7 @@ class FillBucket implements Bucket {
this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);
}

populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID) {
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID, tileTransform: TileTransform) {
this.hasPattern = hasPattern('fill', this.layers, options);
const fillSortKey = this.layers[0].layout.get('fill-sort-key');
const bucketFeatures = [];
Expand All @@ -96,7 +97,7 @@ class FillBucket implements Bucket {
type: feature.type,
sourceLayerIndex,
index,
geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature),
geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature, canonical, tileTransform),
patterns: {},
sortKey
};
Expand Down
Loading

0 comments on commit 107a525

Please sign in to comment.