Skip to content

Commit

Permalink
Add Projection specifications (#687)
Browse files Browse the repository at this point in the history
* Add all that is requires for projection

* Fix lint and v8 doc comment.

* Update changelog

* Fix changelog

* Add space in changelog
  • Loading branch information
HarelM authored May 31, 2024
1 parent 139a225 commit a018340
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
## main

### ✨ Features and improvements

- Added `Projection` specifications [#687](https://github.com/maplibre/maplibre-style-spec/pull/687)
- Updated `Sky` specifications to support atmosphere and other required features [#686](https://github.com/maplibre/maplibre-style-spec/pull/686)
- _...Add new stuff here..._

### 🐞 Bug fixes

- Change `assert` to `with` in JSON modules. Requires Node.js 18.20.0 or later, and supports Node.js 22.0.0 or later - [#675](https://github.com/maplibre/maplibre-style-spec/pull/675)

## 20.2.0
Expand Down
2 changes: 2 additions & 0 deletions build/generate-style-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ ${objectDeclaration('SkySpecification', spec.sky)}
${objectDeclaration('TerrainSpecification', spec.terrain)}
${objectDeclaration('ProjectionSpecification', spec.projection)}
${spec.source.map(key => {
let str = objectDeclaration(sourceTypeName(key), spec[key]);
if (sourceTypeName(key) === 'GeoJSONSourceSpecification') {
Expand Down
12 changes: 12 additions & 0 deletions src/diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,16 @@ describe('diff', () => {
{command: 'setSky', args: [{'fog-color': 'green', 'fog-ground-blend': 0.2}]},
]);
});

test('set projection', () => {
expect(diffStyles({
} as StyleSpecification,
{
projection: {
type: 'globe'
}
} as StyleSpecification)).toEqual([
{command: 'setProjection', args: [{type: 'globe'}]},
]);
});
});
6 changes: 5 additions & 1 deletion src/diff.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import {GeoJSONSourceSpecification, LayerSpecification, LightSpecification, SkySpecification, SourceSpecification, SpriteSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from './types.g';
import {GeoJSONSourceSpecification, LayerSpecification, LightSpecification, ProjectionSpecification, SkySpecification, SourceSpecification, SpriteSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from './types.g';
import isEqual from './util/deep_equal';

/**
Expand Down Expand Up @@ -28,6 +28,7 @@ export type DiffOperationsMap = {
'setLight': [LightSpecification];
'setTerrain': [TerrainSpecification];
'setSky': [SkySpecification];
'setProjection': [ProjectionSpecification];
}

export type DiffOperations = keyof DiffOperationsMap;
Expand Down Expand Up @@ -304,6 +305,9 @@ function diffStyles(before: StyleSpecification, after: StyleSpecification): Diff
if (!isEqual(before.sky, after.sky)) {
commands.push({command: 'setSky', args: [after.sky]});
}
if (!isEqual(before.projection, after.projection)) {
commands.push({command: 'setProjection', args: [after.projection]});
}

// Handle changes to `sources`
// If a source is to be removed, we also--before the removeSource
Expand Down
22 changes: 22 additions & 0 deletions src/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@
]
}
},
"projection": {
"type": "projection",
"doc": "The projection configuration. **Note:** this definition is still experimental and is under development in maplibre-gl-js.",
"example": {
"type": "globe"
}
},
"terrain": {
"type": "terrain",
"doc": "The terrain configuration.",
Expand Down Expand Up @@ -4476,6 +4483,21 @@
}
}
},
"projection": {
"type": {
"type": "enum",
"doc": "The projection type.",
"default": "mercator",
"values": {
"mercator": {
"doc": "The Mercator projection."
},
"globe": {
"doc": "The globe projection."
}
}
}
},
"paint": [
"paint_fill",
"paint_line",
Expand Down
2 changes: 2 additions & 0 deletions src/validate/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import validatePadding from './validate_padding';
import validateVariableAnchorOffsetCollection from './validate_variable_anchor_offset_collection';
import validateSprite from './validate_sprite';
import ValidationError from '../error/validation_error';
import validateProjection from './validate_projection';

const VALIDATORS = {
'*'() {
Expand All @@ -45,6 +46,7 @@ const VALIDATORS = {
'light': validateLight,
'sky': validateSky,
'terrain': validateTerrain,
'projection': validateProjection,
'string': validateString,
'formatted': validateFormatted,
'resolvedImage': validateImage,
Expand Down
38 changes: 38 additions & 0 deletions src/validate/validate_projection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import validateProjection from './validate_projection';
import validateSpec from './validate';
import v8 from '../reference/v8.json' with {type: 'json'};
import {ProjectionSpecification} from '../types.g';

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

test('Should return error when value is not an object', () => {
const errors = validateProjection({validateSpec, value: '' as unknown as ProjectionSpecification, 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 = validateProjection({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 = validateProjection({validateSpec, value: {type: 1 as any}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(1);
expect(errors[0].message).toBe('type: expected one of [mercator, globe], 1 found');
});

test('Should pass if everything is according to spec', () => {
let errors = validateProjection({validateSpec, value: {type: 'globe'}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
errors = validateProjection({validateSpec, value: {type: 'mercator'}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});
});
43 changes: 43 additions & 0 deletions src/validate/validate_projection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ValidationError from '../error/validation_error';
import getType from '../util/get_type';
import v8 from '../reference/v8.json' with {type: 'json'};
import {ProjectionSpecification, StyleSpecification} from '../types.g';

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

export default function validateProjection(options: ValidateProjectionOptions) {
const projection = options.value;
const styleSpec = options.styleSpec;
const projectionSpec = styleSpec.projection;
const style = options.style;

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

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

return errors;
}

0 comments on commit a018340

Please sign in to comment.