Skip to content
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
2 changes: 2 additions & 0 deletions build/generate-style-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ ${objectDeclaration('StyleSpecification', spec.$root)}

${objectDeclaration('LightSpecification', spec.light)}

${objectDeclaration('TerrainSpecification', spec.terrain)}

${spec.source.map(key => objectDeclaration(sourceTypeName(key), spec[key])).join('\n\n')}

export type SourceSpecification =
Expand Down
37 changes: 18 additions & 19 deletions debug/terrain.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,32 @@
<script src='../debug/access_token_generated.js'></script>
<script>

var map = window.map = new maplibregl.Map({
container: 'map',
zoom: 12.5,
center: [11.40416, 47.26475],
hash: true,
style: 'https://static.maptoolkit.net/styles/toursprung/terrain.json',
maxZoom: 18,
maxPitch: 85
});

map.on('load', () => {
map.addSource('terrain', {
fetch('https://static.maptoolkit.net/styles/toursprung/terrain.json').then((req) => req.json()).then((style) => {
Comment thread
HarelM marked this conversation as resolved.
style.sources.terrain = {
Comment thread
wipfli marked this conversation as resolved.
'type': 'raster-dem',
'tiles': ['https://vtc-cdn.maptoolkit.net/terrainrgb/{z}/{x}/{y}.webp'],
'encoding': 'mapbox',
'maxzoom': 14,
'minzoom': 4
});
map.setTerrain({source: 'terrain', exaggeration: 0.33});
});
};
style.terrain = {source: 'terrain', exaggeration: 0.33};

map.on('click', e => {
new maplibregl.Marker().setLngLat(e.lngLat).addTo(map);
});
var map = window.map = new maplibregl.Map({
container: 'map',
zoom: 12.5,
center: [11.40416, 47.26475],
hash: true,
style,
maxZoom: 18,
maxPitch: 85
});

map.addControl(new maplibregl.NavigationControl());
map.on('click', e => {
new maplibregl.Marker().setLngLat(e.lngLat).addTo(map);
});

map.addControl(new maplibregl.NavigationControl());
});
</script>
</body>
</html>
9 changes: 5 additions & 4 deletions src/render/terrain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import posAttributes from '../data/pos_attributes';
import SegmentVector from '../data/segment';
import VertexBuffer from '../gl/vertex_buffer';
import IndexBuffer from '../gl/index_buffer';
import Style, {TerrainOptions} from '../style/style';
import Style from '../style/style';
import Texture from '../render/texture';
import type Framebuffer from '../gl/framebuffer';
import Point from '@mapbox/point-geometry';
Expand All @@ -18,6 +18,7 @@ import TerrainSourceCache from '../source/terrain_source_cache';
import SourceCache from '../source/source_cache';
import EXTENT from '../data/extent';
import {number as mix} from '../style-spec/util/interpolate';
import type {TerrainSpecification} from '../style-spec/types.g';

/**
* This is the main class which handles most of the 3D Terrain logic. It has the follwing topics:
Expand Down Expand Up @@ -71,8 +72,8 @@ export default class Terrain {
style: Style;
// the sourcecache this terrain is based on
sourceCache: TerrainSourceCache;
// the TerrainOptions object passed to this instance
options: TerrainOptions;
// the TerrainSpecification object passed to this instance
options: TerrainSpecification;
// define the meshSize per tile.
meshSize: number;
// multiplicator for the elevation. Used to make terrain more "extrem".
Expand Down Expand Up @@ -114,7 +115,7 @@ export default class Terrain {
// remember all tiles which contains new data for a spezific source and tile-key.
_rerender: {[_: string]: {[_: number]: boolean}};

constructor(style: Style, sourceCache: SourceCache, options: TerrainOptions) {
constructor(style: Style, sourceCache: SourceCache, options: TerrainSpecification) {
this.style = style;
this.sourceCache = new TerrainSourceCache(sourceCache);
this.options = options;
Expand Down
2 changes: 1 addition & 1 deletion src/style-spec/error/validation_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default class ValidationError {
identifier: string;
line: number;

constructor(key: string, value: {
constructor(key: string, value: any & {
__line__: number;
}, message: string, identifier?: string | null) {
this.message = (key ? `${key}: ` : '') + message;
Expand Down
42 changes: 42 additions & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@
"intensity": 0.4
}
},
"terrain": {
"type": "terrain",
"doc": "The terrain configuration.",
"example": {
"source": "raster-dem-source",
"exaggeration": 0.5,
"elevationOffset": 100
}
},
"sources": {
"required": true,
"type": "sources",
Expand Down Expand Up @@ -3810,6 +3819,39 @@
}
}
},
"terrain": {
"source": {
"type": "string",
"doc": "The source for the terrain data.",
"required": true,
"sdk-support": {
"basic functionality": {
"js": "2.2.0"
}
}
},
"exaggeration": {
"type": "number",
"minimum": 0,
"doc": "The exaggeration of the terrain - how high it will look.",
"default": 1.0,
Comment thread
wipfli marked this conversation as resolved.
"sdk-support": {
"basic functionality": {
"js": "2.2.0"
}
}
},
"elevationOffset": {
"type": "number",
"doc": "The elevation offset.",
Comment thread
HarelM marked this conversation as resolved.
"default": 450,
"sdk-support": {
"basic functionality": {
"js": "2.2.0"
}
}
}
},
"paint": [
"paint_fill",
"paint_line",
Expand Down
2 changes: 2 additions & 0 deletions src/style-spec/validate/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import validateFilter from './validate_filter';
import validateLayer from './validate_layer';
import validateSource from './validate_source';
import validateLight from './validate_light';
import validateTerrain from './validate_terrain';
import validateString from './validate_string';
import validateFormatted from './validate_formatted';
import validateImage from './validate_image';
Expand All @@ -37,6 +38,7 @@ const VALIDATORS = {
'object': validateObject,
'source': validateSource,
'light': validateLight,
'terrain': validateTerrain,
'string': validateString,
'formatted': validateFormatted,
'resolvedImage': validateImage
Expand Down
46 changes: 46 additions & 0 deletions src/style-spec/validate/validate_terrain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import validateTerrain from './validate_terrain';
import v8 from '../reference/v8.json';

describe('Validate Terrain', () => {
test('Should return error in case terrain is not an object', () => {
const errors = validateTerrain({value: 1 as any, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(1);
expect(errors[0].message).toContain('number');
expect(errors[0].message).toContain('object');
expect(errors[0].message).toContain('terrain');
});

test('Should return error in case terrain source is not a string', () => {
const errors = validateTerrain({value: {source: 1 as any}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(1);
expect(errors[0].message).toContain('number');
expect(errors[0].message).toContain('string');
expect(errors[0].message).toContain('source');
});

test('Should return error in case of unknown property', () => {
const errors = validateTerrain({value: {a: 1} as any, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(1);
expect(errors[0].message).toContain('a');
expect(errors[0].message).toContain('unknown');
});

test('Should return errors according to spec violations', () => {
const errors = validateTerrain({value: {source: 1 as any, exaggeration: {} as any, elevationOffset: 'ex2' as any}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(3);
expect(errors[0].message).toContain('number');
expect(errors[0].message).toContain('string');
expect(errors[0].message).toContain('source');
expect(errors[1].message).toContain('number');
expect(errors[1].message).toContain('object');
expect(errors[1].message).toContain('exaggeration');
expect(errors[2].message).toContain('number');
expect(errors[2].message).toContain('string');
expect(errors[2].message).toContain('elevationOffset');
});

test('Should pass if everything is according to spec', () => {
const errors = validateTerrain({value: {source: 'source-id', elevationOffset: 1, exaggeration: 0.2}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});
});
41 changes: 41 additions & 0 deletions src/style-spec/validate/validate_terrain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import ValidationError from '../error/validation_error';
import getType from '../util/get_type';
import validate from './validate';
import type {StyleSpecification, TerrainSpecification} from '../types.g';
import type v8 from '../reference/v8.json';

export default function validateTerrain(
options: {value: TerrainSpecification; styleSpec: typeof v8; style: StyleSpecification}
): ValidationError[] {

const terrain = options.value;
const styleSpec = options.styleSpec;
const terrainSpec = styleSpec.terrain;
const style = options.style;

let errors = [];

const rootType = getType(terrain);
if (terrain === undefined) {
return errors;
} else if (rootType !== 'object') {
errors = errors.concat([new ValidationError('terrain', terrain, `object expected, ${rootType} found`)]);
return errors;
}

for (const key in terrain) {
if (terrainSpec[key]) {
errors = errors.concat(validate({
key,
value: terrain[key],
valueSpec: terrainSpec[key],
style,
styleSpec
}));
} else {
errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${key}"`)]);
}
}

return errors;
}
2 changes: 2 additions & 0 deletions src/style-spec/validate_style.min.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import validateGlyphsURL from './validate/validate_glyphs_url';

import validateSource from './validate/validate_source';
import validateLight from './validate/validate_light';
import validateTerrain from './validate/validate_terrain';
import validateLayer from './validate/validate_layer';
import validateFilter from './validate/validate_filter';
import validatePaintProperty from './validate/validate_paint_property';
Expand Down Expand Up @@ -58,6 +59,7 @@ function validateStyleMin(style, styleSpec = latestStyleSpec) {

validateStyleMin.source = wrapCleanErrors(validateSource);
validateStyleMin.light = wrapCleanErrors(validateLight);
validateStyleMin.terrain = wrapCleanErrors(validateTerrain);
validateStyleMin.layer = wrapCleanErrors(validateLayer);
validateStyleMin.filter = wrapCleanErrors(validateFilter);
validateStyleMin.paintProperty = wrapCleanErrors(validatePaintProperty);
Expand Down
1 change: 1 addition & 0 deletions src/style-spec/validate_style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default function validateStyle(style, styleSpec = v8) {

export const source = validateStyleMin.source;
export const light = validateStyleMin.light;
export const terrain = validateStyleMin.terrain;
export const layer = validateStyleMin.layer;
export const filter = validateStyleMin.filter;
export const paintProperty = validateStyleMin.paintProperty;
Expand Down
16 changes: 16 additions & 0 deletions src/style/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,22 @@ describe('Style#loadJSON', () => {
style._layers.background.fire(new Event('error', {mapLibre: true}));
});
});

test('sets terrain if defined', (done) => {
const map = getStubMap();
const style = new Style(map);
map.transform.updateElevation = jest.fn();
style.loadJSON(createStyleJSON({
sources: {'source-id': createGeoJSONSource()},
terrain: {source: 'source-id', exaggeration: 0.33}
}));

style.on('style.load', () => {
expect(style.terrain).toBeDefined();
expect(map.transform.updateElevation).toHaveBeenCalled();
done();
});
});
});

describe('Style#_remove', () => {
Expand Down
15 changes: 6 additions & 9 deletions src/style/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ import type {
FilterSpecification,
StyleSpecification,
LightSpecification,
SourceSpecification
SourceSpecification,
TerrainSpecification
} from '../style-spec/types.g';
import type {CustomLayerInterface} from './style_layer/custom_style_layer';
import type {Validator} from './validate_style';
Expand Down Expand Up @@ -104,12 +105,6 @@ export type StyleSetterOptions = {
validate?: boolean;
};

export type TerrainOptions = {
source: string;
exaggeration?: number;
elevationOffset?: number;
};

/**
* @private
*/
Expand Down Expand Up @@ -288,6 +283,8 @@ class Style extends Evented {

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

this.setTerrain(this.stylesheet.terrain);

this.fire(new Event('data', {dataType: 'style'}));
this.fire(new Event('style.load'));
}
Expand Down Expand Up @@ -490,9 +487,9 @@ class Style extends Evented {

/**
* Loads a 3D terrain mesh, based on a "raster-dem" source.
* @param {TerrainOptions} [options] Options object.
* @param {TerrainSpecification} [options] Options object.
*/
setTerrain(options?: TerrainOptions) {
setTerrain(options?: TerrainSpecification) {
Comment thread
HarelM marked this conversation as resolved.
// clear event handlers
if (this._terrainDataCallback) this.off('data', this._terrainDataCallback);
if (!options) {
Expand Down
2 changes: 2 additions & 0 deletions src/style/validate_style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type ValidateStyle = {
source: Validator;
layer: Validator;
light: Validator;
terrain: Validator;
filter: Validator;
paintProperty: Validator;
layoutProperty: Validator;
Expand All @@ -25,6 +26,7 @@ export const validateStyle = (validateStyleMin as ValidateStyle);

export const validateSource = validateStyle.source;
export const validateLight = validateStyle.light;
export const validateTerrain = validateStyle.terrain;
export const validateFilter = validateStyle.filter;
export const validatePaintProperty = validateStyle.paintProperty;
export const validateLayoutProperty = validateStyle.layoutProperty;
Expand Down
6 changes: 3 additions & 3 deletions src/ui/control/terrain_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {bindAll} from '../../util/util';

import type Map from '../map';
import type {IControl} from './control';
import type {TerrainOptions} from '../../style/style';
import type {TerrainSpecification} from '../../style-spec/types.g';

/**
* An `TerrainControl` control adds a button to turn terrain on and off.
Expand All @@ -19,12 +19,12 @@ import type {TerrainOptions} from '../../style/style';
* }));
*/
class TerrainControl implements IControl {
options: TerrainOptions;
options: TerrainSpecification;
_map: Map;
_container: HTMLElement;
_terrainButton: HTMLButtonElement;

constructor(options: TerrainOptions) {
constructor(options: TerrainSpecification) {
this.options = options;

bindAll([
Expand Down
Loading