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

Move projection to style class #4267

Merged
merged 11 commits into from
Jun 18, 2024
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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps It could be interesting to check if the default value of projection is well mercator

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Basically all the non globe render tests check that, but I don't mind adding a small test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a few more tests related to serialization and default mercator.

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