Skip to content

Commit

Permalink
Add raster-dem encoding parameters to RasterDEMSourceSpecification (#337
Browse files Browse the repository at this point in the history
)

* Add parameters

* Update hillshade docs

* Add changelog

* Changes names to *Factor and baseShift

* Add custom encoding validation and tests

* Properly validate raster-dem sources

* Remove unneeded validation definition

* Add additional tests
  • Loading branch information
ibesora authored Sep 26, 2023
1 parent 08a0f07 commit 58eb347
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Document `raster-fade-duration` property's effect on rendering a video. [#297](https://github.com/maplibre/maplibre-style-spec/pull/297)
* Add and expose `isZoomExpression`. [#267](https://github.com/maplibre/maplibre-style-spec/issues/267)
* Add raster dem source `redFactor`, `greenFactor`, `blueFactor`, `baseShift` properties [#326](https://github.com/maplibre/maplibre-style-spec/issues/326)

## 19.3.0

Expand Down
27 changes: 25 additions & 2 deletions src/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,34 @@
},
"mapbox": {
"doc": "Mapbox Terrain RGB tiles. See https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb for more info."
},
"custom": {
"doc": "Decodes tiles using the redFactor, blueFactor, greenFactor, baseShift parameters."
}
},
"default": "mapbox",
"doc": "The encoding used by this source. Mapbox Terrain RGB is used by default"
"doc": "The encoding used by this source. Mapbox Terrain RGB is used by default."
},
"redFactor": {
"type": "number",
"default": 1.0,
"doc": "Value that will be multiplied by the red channel value when decoding. Only used on custom encodings."
},
"blueFactor": {
"type": "number",
"default": 1.0,
"doc": "Value that will be multiplied by the blue channel value when decoding. Only used on custom encodings."
},
"greenFactor": {
"type": "number",
"default": 1.0,
"doc": "Value that will be multiplied by the green channel value when decoding. Only used on custom encodings."
},
"baseShift": {
"type": "number",
"default": 0.0,
"doc": "Value that will be added to the encoding mix when decoding. Only used on custom encodings."
},
"volatile": {
"type": "boolean",
"default": false,
Expand Down Expand Up @@ -598,7 +621,7 @@
}
},
"hillshade": {
"doc": "Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB and Mapzen Terrarium tiles.",
"doc": "Client-side hillshading visualization based on DEM data. The implementation supports Mapbox Terrain RGB, Mapzen Terrarium tiles and custom encodings.",
"sdk-support": {
"basic functionality": {
"js": "0.43.0",
Expand Down
67 changes: 67 additions & 0 deletions src/validate/validate_raster_dem_source.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import validateSpec from './validate';
import v8 from '../reference/v8.json' assert {type: 'json'};
import validateRasterDEMSource from './validate_raster_dem_source';
import {RasterDEMSourceSpecification} from '../types.g';

function checkErrorMessage(message: string, key: string, expectedType: string, foundType: string) {
expect(message).toContain(key);
expect(message).toContain(expectedType);
expect(message).toContain(foundType);
}

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

test('Should return error when value is not an object', () => {
const errors = validateRasterDEMSource({validateSpec, value: '' as unknown as RasterDEMSourceSpecification, 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 = validateRasterDEMSource({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 = validateRasterDEMSource({validateSpec, value: {type: 'raster-dem', url: {} as any, tiles: {} as any, encoding: 'foo' as any}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(3);
checkErrorMessage(errors[0].message, 'url', 'string', 'object');
checkErrorMessage(errors[1].message, 'tiles', 'array', 'object');
checkErrorMessage(errors[2].message, 'encoding', '[terrarium, mapbox, custom]', 'foo');
});

test('Should return errors when custom encoding values are set but encoding is "mapbox"', () => {
const errors = validateRasterDEMSource({validateSpec, value: {type: 'raster-dem', encoding: 'mapbox', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(4);
checkErrorMessage(errors[0].message, 'redFactor', 'custom', 'mapbox');
checkErrorMessage(errors[1].message, 'greenFactor', 'custom', 'mapbox');
checkErrorMessage(errors[2].message, 'blueFactor', 'custom', 'mapbox');
checkErrorMessage(errors[3].message, 'baseShift', 'custom', 'mapbox');
});

test('Should return errors when custom encoding values are set but encoding is "terrarium"', () => {
const errors = validateRasterDEMSource({validateSpec, value: {type: 'raster-dem', encoding: 'terrarium', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(4);
checkErrorMessage(errors[0].message, 'redFactor', 'custom', 'terrarium');
checkErrorMessage(errors[1].message, 'greenFactor', 'custom', 'terrarium');
checkErrorMessage(errors[2].message, 'blueFactor', 'custom', 'terrarium');
checkErrorMessage(errors[3].message, 'baseShift', 'custom', 'terrarium');
});

test('Should pass when custom encoding values are set and encoding is "custom"', () => {
const errors = validateRasterDEMSource({validateSpec, value: {type: 'raster-dem', encoding: 'custom', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});

test('Should pass if everything is according to spec', () => {
const errors = validateRasterDEMSource({validateSpec, value: {type: 'raster-dem'}, styleSpec: v8, style: {} as any});
expect(errors).toHaveLength(0);
});
});
58 changes: 58 additions & 0 deletions src/validate/validate_raster_dem_source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import ValidationError from '../error/validation_error';
import getType from '../util/get_type';
import type {RasterDEMSourceSpecification, StyleSpecification} from '../types.g';
import v8 from '../reference/v8.json' assert {type: 'json'};
import {unbundle} from '../util/unbundle_jsonlint';

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

export default function validateRasterDEMSource(
options: ValidateRasterDENSourceOptions
): ValidationError[] {

const sourceName = options.sourceName ?? '';
const rasterDEM = options.value;
const styleSpec = options.styleSpec;
const rasterDEMSpec = styleSpec.source_raster_dem;
const style = options.style;

let errors = [];

const rootType = getType(rasterDEM);
if (rasterDEM === undefined) {
return errors;
} else if (rootType !== 'object') {
errors.push(new ValidationError('source_raster_dem', rasterDEM, `object expected, ${rootType} found`));
return errors;
}

const encoding = unbundle(rasterDEM.encoding);
const isCustomEncoding = encoding === 'custom';
const customEncodingKeys = ['redFactor', 'greenFactor', 'blueFactor', 'baseShift'];
const encodingName = options.value.encoding ? `"${options.value.encoding}"` : 'Default';

for (const key in rasterDEM) {
if (!isCustomEncoding && customEncodingKeys.includes(key)) {
errors.push(new ValidationError(key, rasterDEM[key], `In "${sourceName}": "${key}" is only valid when "encoding" is set to "custom". ${encodingName} encoding found`));
} else if (rasterDEMSpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: rasterDEM[key],
valueSpec: rasterDEMSpec[key],
validateSpec: options.validateSpec,
style,
styleSpec
}));
} else {
errors.push(new ValidationError(key, rasterDEM[key], `unknown property "${key}"`));
}
}

return errors;
}
11 changes: 10 additions & 1 deletion src/validate/validate_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import validateEnum from './validate_enum';
import validateExpression from './validate_expression';
import validateString from './validate_string';
import getType from '../util/get_type';
import validateRasterDEMSource from './validate_raster_dem_source';

const objectElementValidators = {
promoteId: validatePromoteId
Expand All @@ -28,7 +29,6 @@ export default function validateSource(options) {
switch (type) {
case 'vector':
case 'raster':
case 'raster-dem':
errors = validateObject({
key,
value,
Expand All @@ -39,6 +39,15 @@ export default function validateSource(options) {
validateSpec,
});
return errors;
case 'raster-dem':
errors = validateRasterDEMSource({
sourceName: key,
value,
style: options.style,
styleSpec,
validateSpec,
});
return errors;

case 'geojson':
errors = validateObject({
Expand Down

0 comments on commit 58eb347

Please sign in to comment.