Skip to content

Commit

Permalink
Add Sky spec (#478)
Browse files Browse the repository at this point in the history
* Style Spec for sky and fog (#298)

* Copy style-spec pieces from maplibre/maplibre-gl-js#1713

* add missing SkySpecification

* fix lint error

* Add tests, improve documentation, add to the style-spec docs

* Fix lint

* Update changelog

---------

Co-authored-by: Andrew Calcutt <acalcutt@techidiots.net>
  • Loading branch information
HarelM and acalcutt authored Jan 8, 2024
1 parent 8d3b078 commit 397b5e0
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Add terrain to diff method and improve type. This also removes the `operations` from the API [#460](https://github.com/maplibre/maplibre-style-spec/pull/460)
* Improve the type of `data` in the `GeoJSONSourceSpecification` for TypeScript [#463](https://github.com/maplibre/maplibre-style-spec/pull/463).
* Add expression tests to this repo [#476](https://github.com/maplibre/maplibre-style-spec/pull/476)
* Add Sky spec, this is only the definition, no implementation at this point, only validation [#478](https://github.com/maplibre/maplibre-style-spec/pull/478)

### 🐞 Bug fixes

Expand Down
4 changes: 4 additions & 0 deletions build/generate-style-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function propertyType(property) {
}
case 'light':
return 'LightSpecification';
case 'sky':
return 'SkySpecification';
case 'sources':
return '{[_: string]: SourceSpecification}';
case '*':
Expand Down Expand Up @@ -312,6 +314,8 @@ ${objectDeclaration('StyleSpecification', spec.$root)}
${objectDeclaration('LightSpecification', spec.light)}
${objectDeclaration('SkySpecification', spec.sky)}
${objectDeclaration('TerrainSpecification', spec.terrain)}
${spec.source.map(key => {
Expand Down
1 change: 1 addition & 0 deletions docs/src/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const pages: {path: string; title: string}[] = [
{title: 'Sources', path: 'sources/'},
{title: 'Sprite', path: 'sprite/'},
{title: 'Terrain', path: 'terrain/'},
{title: 'Sky', path: 'sky/'},
{title: 'Transition', path: 'transition/'},
{title: 'Types', path: 'types/'},
{title: 'Deprecations', path: 'deprecations/'},
Expand Down
3 changes: 2 additions & 1 deletion docs/src/routes/glyphs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Markdown} from '~/components/markdown/markdown';
import ref from '../../../../src/reference/latest';

function glyphs() {
const md = `
Expand All @@ -7,7 +8,7 @@ function glyphs() {
A style's \`glyphs\` property provides a URL template for loading signed-distance-field glyph sets in PBF format.
\`\`\`json
"glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf"
"glyphs": ${JSON.stringify(ref.$root.glyphs.example, null, 2)}
\`\`\`
This URL template should include two tokens:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/routes/light/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ A style's \`light\` property provides a global light source for that style. Sinc
<div>
<Markdown content={md} />

<Items headingLevel='2' entry={ref.light} />
<Items headingLevel='3' entry={ref.light} />
</div>
);
}
Expand Down
26 changes: 26 additions & 0 deletions docs/src/routes/sky/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Markdown} from '~/components/markdown/markdown';
import ref from '../../../../src/reference/latest';
import {Items} from '../../components/items/items';
function Root() {

const md = `# Sky
Add sky and fog to the map.
This feautre is still under development and is not yet available in the latest release.
\`\`\`json
"sky": ${JSON.stringify(ref.$root.sky.example, null, 2)}
\`\`\`
`;

return (
<div>
<Markdown content={md} />
<Items headingLevel='3' entry={ref.sky} />
</div>
);
}

export default Root;
7 changes: 6 additions & 1 deletion docs/src/routes/terrain/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ function Root() {

const md = `# Terrain
Add 3D terrain to the map.`;
Add 3D terrain to the map.
\`\`\`json
"terrain": ${JSON.stringify(ref.$root.terrain.example, null, 2)}
\`\`\`
`;
return (
<div>
<Markdown content={md} />
Expand Down
68 changes: 68 additions & 0 deletions src/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@
"intensity": 0.4
}
},
"sky": {
"type": "sky",
"doc": "The map's sky configuration.",
"example": {
"sky-color": "#199EF3",
"fog-color": "#00ff00",
"horizon-blend": 0.5,
"fog-blend": 0.6
}
},
"terrain": {
"type": "terrain",
"doc": "The terrain configuration.",
Expand Down Expand Up @@ -3902,6 +3912,64 @@
}
}
},
"sky": {
"sky-color": {
"type": "color",
"property-type": "data-constant",
"default": "#88C6FC",
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "The base color for the sky."
},
"fog-color": {
"type": "color",
"property-type": "data-constant",
"default": "#ffffff",
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "The base color for the fog."
},
"fog-blend": {
"type": "number",
"property-type": "data-constant",
"default": 0.5,
"minimum": 0,
"maximum": 1,
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "How to blend the fog over the 3D terrain. A value between 0 and 1. Where 0 is the map center and 1 is the horizon"
},
"horizon-blend": {
"type": "number",
"property-type": "data-constant",
"default": 0.8,
"minimum": 0,
"maximum": 1,
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "How to blend the fog and sky color at the horizon. A value between 0 and 1. Where 0 is the horizon and 1 is map-height / 2"
}
},
"terrain": {
"source": {
"type": "string",
Expand Down
2 changes: 2 additions & 0 deletions src/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 validateSky from './validate_sky';
import validateTerrain from './validate_terrain';
import validateString from './validate_string';
import validateFormatted from './validate_formatted';
Expand All @@ -41,6 +42,7 @@ const VALIDATORS = {
'object': validateObject,
'source': validateSource,
'light': validateLight,
'sky': validateSky,
'terrain': validateTerrain,
'string': validateString,
'formatted': validateFormatted,
Expand Down
40 changes: 40 additions & 0 deletions src/validate/validate_sky.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import validateSky from './validate_sky';
import validateSpec from './validate';
import v8 from '../reference/v8.json' assert {type: 'json'};
import {SkySpecification} from '../types.g';

describe('Validate sky', () => {
it('Should pass when value is undefined', () => {
const errors = validateSky({validateSpec, value: undefined, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});

test('Should return error when value is not an object', () => {
const errors = validateSky({validateSpec, value: '' as unknown as SkySpecification, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(1);
expect(errors[0].message).toContain('object');
expect(errors[0].message).toContain('expected');
});

test('Should return error in case of unknown property', () => {
const errors = validateSky({validateSpec, 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 = validateSky({validateSpec, value: {'sky-color': 1 as any, 'fog-color': 2 as any, 'horizon-blend': {} as any, 'fog-blend': 'foo' as any}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(4);
expect(errors[0].message).toBe('sky-color: color expected, number found');
expect(errors[1].message).toBe('fog-color: color expected, number found');
expect(errors[2].message).toBe('horizon-blend: missing required property "stops"');
expect(errors[3].message).toBe('fog-blend: number expected, string found');
});

test('Should pass if everything is according to spec', () => {
const errors = validateSky({validateSpec, value: {'sky-color': 'red', 'fog-color': '#123456', 'horizon-blend': 1, 'fog-blend': 0}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});
});

44 changes: 44 additions & 0 deletions src/validate/validate_sky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ValidationError from '../error/validation_error';
import getType from '../util/get_type';
import validate from './validate';
import v8 from '../reference/v8.json' assert {type: 'json'};
import {SkySpecification, StyleSpecification} from '../types.g';

interface ValidateSkyOptions {
sourceName?: string;
value: SkySpecification;
styleSpec: typeof v8;
style: StyleSpecification;
validateSpec: Function;
}

export default function validateSky(options: ValidateSkyOptions) {
const sky = options.value;
const styleSpec = options.styleSpec;
const skySpec = styleSpec.sky;
const style = options.style;

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

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

return errors;
}
2 changes: 2 additions & 0 deletions src/validate_style.min.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import latestStyleSpec from './reference/latest';

import validateSource from './validate/validate_source';
import validateLight from './validate/validate_light';
import validateSky from './validate/validate_sky';
import validateTerrain from './validate/validate_terrain';
import validateLayer from './validate/validate_layer';
import validateFilter from './validate/validate_filter';
Expand Down Expand Up @@ -65,6 +66,7 @@ validateStyleMin.source = wrapCleanErrors(injectValidateSpec(validateSource));
validateStyleMin.sprite = wrapCleanErrors(injectValidateSpec(validateSprite));
validateStyleMin.glyphs = wrapCleanErrors(injectValidateSpec(validateGlyphsUrl));
validateStyleMin.light = wrapCleanErrors(injectValidateSpec(validateLight));
validateStyleMin.sky = wrapCleanErrors(injectValidateSpec(validateSky));
validateStyleMin.terrain = wrapCleanErrors(injectValidateSpec(validateTerrain));
validateStyleMin.layer = wrapCleanErrors(injectValidateSpec(validateLayer));
validateStyleMin.filter = wrapCleanErrors(injectValidateSpec(validateFilter));
Expand Down
1 change: 1 addition & 0 deletions src/validate_style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function validateStyle(style: StyleSpecification | string | Buffe

export const source = validateStyleMin.source;
export const light = validateStyleMin.light;
export const sky = validateStyleMin.sky;
export const terrain = validateStyleMin.terrain;
export const layer = validateStyleMin.layer;
export const filter = validateStyleMin.filter;
Expand Down

0 comments on commit 397b5e0

Please sign in to comment.