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

Add Sky spec #478

Merged
merged 4 commits into from
Jan 8, 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
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)

## 19.3.3

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