Skip to content

Commit

Permalink
Move projection to style class (#4267)
Browse files Browse the repository at this point in the history
* Move projection to style class

* Fix lint

* Fix unit tests

* Increase build size

* Update docs, fix test

* Fix lint

* Add test to cover projection change

* Added more tests
  • Loading branch information
HarelM authored Jun 18, 2024
1 parent ba95d93 commit c343c43
Show file tree
Hide file tree
Showing 66 changed files with 214 additions and 161 deletions.
11 changes: 2 additions & 9 deletions src/geo/projection/projection_factory.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import {ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec';
import {warnOnce} from '../../util/util';
import {GlobeProjection} from './globe';
import {MercatorProjection} from './mercator';
import {Projection} from './projection';

/**
* Name of MapLibre's map projection. Can be:
*
* - `mercator` - A classic Web Mercator 2D map
* - 'globe' - A 3D spherical view of the planet when zoomed out, transitioning seamlessly to Web Mercator at high zoom levels.
*/
export type ProjectionName = 'mercator' | 'globe';

export function createProjectionFromName(name: ProjectionName): Projection {
export function createProjectionFromName(name: ProjectionSpecification['type']): Projection {
switch (name) {
case 'mercator':
return new MercatorProjection();
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function drawBackground(painter: Painter, sourceCache: SourceCache, layer

const context = painter.context;
const gl = context.gl;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const transform = painter.transform;
const tileSize = transform.tileSize;
const image = layer.paint.get('background-pattern');
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function drawCircles(painter: Painter, sourceCache: SourceCache, layer: C

const context = painter.context;
const gl = context.gl;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const transform = painter.transform;

const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly);
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_collision_debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ let quadTriangles: QuadTriangleArray;
export function drawCollisionDebug(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array<OverscaledTileID>, isText: boolean) {
const context = painter.context;
const gl = context.gl;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const program = painter.useProgram('collisionBox');
const tileBatches: Array<TileBatch> = [];
let circleCount = 0;
Expand Down
27 changes: 13 additions & 14 deletions src/render/draw_fill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,19 @@ describe('drawFill', () => {
painterMock.transform = {pitch: 0, labelPlaneMatrix: mat4.create()} as any as Transform;
painterMock.options = {} as any;
painterMock.style = {
map: {
projection: {
getProjectionData(_canonical, fallback) {
return {
'u_projection_matrix': fallback,
'u_projection_tile_mercator_coords': [0, 0, 1, 1],
'u_projection_clipping_plane': [0, 0, 0, 0],
'u_projection_transition': 0.0,
'u_projection_fallback_matrix': fallback,
};
},
translatePosition(transform: Transform, tile: Tile, translate: [number, number], translateAnchor: 'map' | 'viewport'): [number, number] {
return translatePosition(transform, tile, translate, translateAnchor);
}
map: {},
projection: {
getProjectionData(_canonical, fallback) {
return {
'u_projection_matrix': fallback,
'u_projection_tile_mercator_coords': [0, 0, 1, 1],
'u_projection_clipping_plane': [0, 0, 0, 0],
'u_projection_transition': 0.0,
'u_projection_fallback_matrix': fallback,
};
},
translatePosition(transform: Transform, tile: Tile, translate: [number, number], translateAnchor: 'map' | 'viewport'): [number, number] {
return translatePosition(transform, tile, translate, translateAnchor);
}
}
} as any as Style;
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function drawFillTiles(
const crossfade = layer.getCrossfadeParameters();
let drawMode, programName, uniformValues, indexBuffer, segments;

const projection = painter.style.map.projection;
const projection = painter.style.projection;

const propertyFillTranslate = layer.paint.get('fill-translate');
const propertyFillTranslateAnchor = layer.paint.get('fill-translate-anchor');
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_fill_extrusion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function drawExtrusionTiles(
const crossfade = layer.getCrossfadeParameters();
const opacity = layer.paint.get('fill-extrusion-opacity');
const constantPattern = patternProperty.constantOr(null);
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const globeCameraPosition = projection.cameraPosition;

for (const coord of coords) {
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: H
if (painter.renderPass === 'offscreen') {
const context = painter.context;
const gl = context.gl;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const transform = painter.transform;

// Allow kernels to be drawn across boundaries, so that
Expand Down
6 changes: 3 additions & 3 deletions src/render/draw_hillshade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function drawHillshade(painter: Painter, sourceCache: SourceCache, layer:
if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return;

const context = painter.context;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const useSubdivision = projection.useSubdivision;

const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly);
Expand Down Expand Up @@ -53,7 +53,7 @@ function renderHillshade(
colorMode: Readonly<ColorMode>,
useBorder: boolean
) {
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const context = painter.context;
const gl = context.gl;
const program = painter.useProgram('hillshade');
Expand All @@ -73,7 +73,7 @@ function renderHillshade(
gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get());

const posMatrix = terrainData ? coord.posMatrix : painter.transform.calculatePosMatrix(tile.tileID.toUnwrapped(), align);
const projectionData = painter.style.map.projection.getProjectionData(coord.canonical, posMatrix);
const projectionData = painter.style.projection.getProjectionData(coord.canonical, posMatrix);

program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW,
hillshadeUniformValues(painter, tile, layer), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments);
Expand Down
4 changes: 2 additions & 2 deletions src/render/draw_line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export function drawLine(painter: Painter, sourceCache: SourceCache, layer: Line

const rttCoord = terrainData ? coord : null;
const posMatrix = rttCoord ? rttCoord.posMatrix : tile.tileID.posMatrix;
const projectionData = painter.style.map.projection.getProjectionData(coord.canonical, posMatrix);
const pixelRatio = painter.style.map.projection.getPixelScale(painter.style.map.transform);
const projectionData = painter.style.projection.getProjectionData(coord.canonical, posMatrix);
const pixelRatio = painter.style.projection.getPixelScale(painter.style.map.transform);

const uniformValues = image ? linePatternUniformValues(painter, tile, layer, pixelRatio, crossfade) :
dasharray ? lineSDFUniformValues(painter, tile, layer, pixelRatio, dasharray, crossfade) :
Expand Down
4 changes: 2 additions & 2 deletions src/render/draw_raster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function drawRaster(painter: Painter, sourceCache: SourceCache, layer: Ra

const source = sourceCache.getSource();

const projection = painter.style.map.projection;
const projection = painter.style.projection;
const useSubdivision = projection.useSubdivision;

// When rendering globe (or any other subdivided projection), two passes are needed.
Expand Down Expand Up @@ -72,7 +72,7 @@ function drawTiles(
const gl = context.gl;
const program = painter.useProgram('raster');

const projection = painter.style.map.projection;
const projection = painter.style.projection;

const colorMode = painter.colorModeForRenderPass();
const align = !painter.options.moving;
Expand Down
14 changes: 5 additions & 9 deletions src/render/draw_symbol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ describe('drawSymbol', () => {
painterMock.transform = {pitch: 0, labelPlaneMatrix: mat4.create()} as any as Transform;
painterMock.options = {} as any;
painterMock.style = {
map: createMockMap()
map: {},
projection: new MercatorProjection()
} as any as Style;

const layerSpec = {
Expand Down Expand Up @@ -151,7 +152,8 @@ describe('drawSymbol', () => {
(sourceCacheMock.getTile as jest.Mock).mockReturnValue(tile);
sourceCacheMock.map = {showCollisionBoxes: false} as any as Map;
painterMock.style = {
map: createMockMap()
map: {},
projection: new MercatorProjection()
} as any as Style;

const spy = jest.spyOn(symbolProjection, 'updateLineLabels');
Expand All @@ -173,7 +175,7 @@ describe('drawSymbol', () => {
painterMock.transform = {pitch: 0, labelPlaneMatrix: mat4.create()} as any as Transform;
painterMock.options = {} as any;
painterMock.style = {
map: createMockMap()
projection: new MercatorProjection()
} as any as Style;

const layerSpec = {
Expand Down Expand Up @@ -222,9 +224,3 @@ describe('drawSymbol', () => {
});

});

function createMockMap() {
return {
projection: new MercatorProjection()
};
}
4 changes: 2 additions & 2 deletions src/render/draw_symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function updateVariableAnchors(coords: Array<OverscaledTileID>,
translateAnchor: 'map' | 'viewport',
variableOffsets: {[_ in CrossTileID]: VariableOffset}) {
const transform = painter.transform;
const projection = painter.style.map.projection;
const projection = painter.style.projection;
const terrain = painter.style.map.terrain;
const rotateWithMap = rotationAlignment === 'map';
const pitchWithMap = pitchAlignment === 'map';
Expand Down Expand Up @@ -310,7 +310,7 @@ function drawLayerSymbols(
const context = painter.context;
const gl = context.gl;
const tr = painter.transform;
const projection = painter.style.map.projection;
const projection = painter.style.projection;

const rotateWithMap = rotationAlignment === 'map';
const pitchWithMap = pitchAlignment === 'map';
Expand Down
6 changes: 3 additions & 3 deletions src/render/painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export class Painter {
_renderTileMasks(tileStencilRefs: {[_: string]: number}, tileIDs: Array<OverscaledTileID>, renderToTexture: boolean, useBorders: boolean) {
const context = this.context;
const gl = context.gl;
const projection = this.style.map.projection;
const projection = this.style.projection;

const program = this.useProgram('clippingMask');

Expand Down Expand Up @@ -486,7 +486,7 @@ export class Painter {
}

// Execute offscreen GPU tasks of the projection manager
this.style.map.projection.updateGPUdependent({
this.style.projection.updateGPUdependent({
context: this.context,
useProgram: (name: string) => this.useProgram(name)
});
Expand Down Expand Up @@ -674,7 +674,7 @@ export class Painter {
this.cache = this.cache || {};
const useTerrain = !!this.style.map.terrain;

const projection = this.style.map.projection;
const projection = this.style.projection;

const projectionPrelude = forceSimpleProjection ? shaders.projectionMercator : projection.shaderPreludeCode;
const projectionDefine = forceSimpleProjection ? MercatorShaderDefine : projection.shaderDefine;
Expand Down
2 changes: 1 addition & 1 deletion src/render/program/line_program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function calculateTileRatio(tile: Tile, transform: Transform) {

function calculateTranslation(painter: Painter, tile: Tile, layer: LineStyleLayer): [number, number] {
// Translate line points prior to any transformation
return painter.style.map.projection.translatePosition(
return painter.style.projection.translatePosition(
painter.transform,
tile,
layer.paint.get('line-translate'),
Expand Down
8 changes: 5 additions & 3 deletions src/source/geojson_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,11 @@ describe('GeoJSONSource#update', () => {
source.map = {
transform: {} as Transform,
getPixelRatio() { return 1; },
projection: {
get subdivisionGranularity() {
return SubdivisionGranularitySetting.noSubdivision;
style: {
projection: {
get subdivisionGranularity() {
return SubdivisionGranularitySetting.noSubdivision;
}
}
}
} as any;
Expand Down
2 changes: 1 addition & 1 deletion src/source/geojson_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export class GeoJSONSource extends Evented implements Source {
pixelRatio: this.map.getPixelRatio(),
showCollisionBoxes: this.map.showCollisionBoxes,
promoteId: this.promoteId,
subdivisionGranularity: this.map.projection.subdivisionGranularity
subdivisionGranularity: this.map.style.projection.subdivisionGranularity
};

tile.abortController = new AbortController();
Expand Down
14 changes: 8 additions & 6 deletions src/source/vector_tile_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ function createSource(options, transformCallback?, clearTiles = () => {}) {
transform: {showCollisionBoxes: false},
_getMapId: () => 1,
_requestManager: new RequestManager(transformCallback),
style: {sourceCaches: {id: {clearTiles}}},
getPixelRatio() { return 1; },
projection: {
get subdivisionGranularity() {
return SubdivisionGranularitySetting.noSubdivision;
style: {
sourceCaches: {id: {clearTiles}},
projection: {
get subdivisionGranularity() {
return SubdivisionGranularitySetting.noSubdivision;
}
}
}
},
getPixelRatio() { return 1; },
} as any as Map);

source.on('error', () => { }); // to prevent console log of errors
Expand Down
2 changes: 1 addition & 1 deletion src/source/vector_tile_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class VectorTileSource extends Evented implements Source {
pixelRatio: this.map.getPixelRatio(),
showCollisionBoxes: this.map.showCollisionBoxes,
promoteId: this.promoteId,
subdivisionGranularity: this.map.projection.subdivisionGranularity
subdivisionGranularity: this.map.style.projection.subdivisionGranularity
};
params.request.collectResourceTiming = this._collectResourceTiming;
let messageType: MessageType.loadTile | MessageType.reloadTile = MessageType.reloadTile;
Expand Down
40 changes: 40 additions & 0 deletions src/style/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,20 @@ describe('Style#_load', () => {
style._load(styleSpec, {validate: false});
expect(style._serializedLayers).toBeNull();
});

test('projection is mercator if not specified', () => {
const style = new Style(getStubMap());
const styleSpec = createStyleJSON({
layers: [{
id: 'background',
type: 'background'
}]
});

style._load(styleSpec, {validate: false});
expect(style.projection.name).toBe('mercator');
expect(style.serialize().projection).toBeUndefined();
});
});

describe('Style#_remove', () => {
Expand Down Expand Up @@ -695,6 +709,7 @@ describe('Style#setState', () => {
spys.push(jest.spyOn(style, 'setGeoJSONSourceData').mockImplementation((() => {}) as any));
spys.push(jest.spyOn(style, 'setGlyphs').mockImplementation((() => {}) as any));
spys.push(jest.spyOn(style, 'setSprite').mockImplementation((() => {}) as any));
spys.push(jest.spyOn(style, 'setProjection').mockImplementation((() => {}) as any));
spys.push(jest.spyOn(style.map, 'setTerrain').mockImplementation((() => {}) as any));

const newStyle = JSON.parse(JSON.stringify(styleJson)) as StyleSpecification;
Expand Down Expand Up @@ -723,6 +738,7 @@ describe('Style#setState', () => {
exaggeration: 0.5
};
newStyle.zoom = 2;
newStyle.projection = {type: 'globe'};
const didChange = style.setState(newStyle);
expect(didChange).toBeTruthy();
for (const spy of spys) {
Expand Down Expand Up @@ -2530,4 +2546,28 @@ describe('Style#serialize', () => {
await style.once('style.load');
expect(style.serialize().terrain).toBeUndefined();
});

test('include projection property when projection is defined in the style', async () => {
const style = new Style(getStubMap());
style.loadJSON(createStyleJSON({
projection: {
type: 'globe'
}
}));

await style.once('style.load');
expect(style.serialize().projection).toBeDefined();
expect(style.serialize().projection.type).toBe('globe');
});

test('include projection property when projection is set', async () => {
const style = new Style(getStubMap());
style.loadJSON(createStyleJSON());

await style.once('style.load');
style.setProjection({type: 'globe'});

expect(style.serialize().projection).toBeDefined();
expect(style.serialize().projection.type).toBe('globe');
});
});
Loading

0 comments on commit c343c43

Please sign in to comment.