@binary-constructions/semantic-map
Version:
A markup wrapper for the Leaflet map API
147 lines (123 loc) • 5.16 kB
JavaScript
const GEOJSON_GEOMETRY_TYPES = [
"Point",
"LineString",
"Polygon",
"MultiPoint",
"MultiLineString",
"MultiPolygon",
"GeometryCollection",
];
function mapNumber(value, context) {
const parsed = Number(value);
if (parsed === NaN) {
throw "doesn't parse as a number";
}
return parsed;
}
function mapSelected(value, context, selection) {
if (!selection.some(s => s === value)) {
throw "not one of the allowed values";
}
return value;
}
function escapeHtml(text) {
var map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
const attribute_transforms_ordered = [
[ "disable-doubleclick-default", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
[ "enable-clustering", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
[ "features", (value, context) => {
const parsed = JSON.parse(value);
let features = undefined;
if (Array.isArray(parsed)) {
features = parsed;
} else if (typeof parsed === 'object') {
if (parsed.type === "FeatureCollection") {
if (Array.isArray(parsed.features)) {
features = parsed.features;
} else {
throw "FeatureCollection does not define features";
}
} else if (parsed.type === "Feature") {
features = [ parsed ];
} else if (GEOJSON_GEOMETRY_TYPES.includes(parsed.type)) {
features = [ {
type: "Feature",
geometry: parsed,
} ];
} else {
throw "invalid or missing value of member 'type'";
}
} else {
throw "neither an object nor an array";
}
if (features.some(f => (typeof f !== 'object' || f.type !== "Feature"))) {
// TODO: fully validate GeoJSON objects?
throw "invalid feature definitions found";
}
return features;
} ],
[ "fit-features", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
[ "geometry-coordinates", (value, context) => {
const parsed = JSON.parse(value);
// TODO: properly validate dependent on GeoJSON type?
if (!Array.isArray(parsed)) {
throw "not a (nested) array of coordinates";
}
return parsed;
} ],
[ "geometries", (value, context) => {
const parsed = JSON.parse(value);
// TODO: properly validate as GeoJSON geometry?
if (!Array.isArray(parsed) || parsed.some(g => (typeof g !== 'object' || !g.type || !GEOJSON_GEOMETRY_TYPES.includes(g.type)))) {
throw "not an array of objects with a valid GeoJSON 'type' property";
}
return parsed;
} ],
[ "geometry-type", (value, context) => mapSelected(value, context, GEOJSON_GEOMETRY_TYPES) ],
//[ "icon-name", (value, context) => value],
[ "hide-zoom-control", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
[ "icon-size", (value, context) => mapNumber(value, context) ],
//[ "id", (value, context) => value],
[ "initial-center", (value, context) => {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed) || parsed.length !== 2 || parsed.some(n => typeof n !== "number")) {
throw "doesn't parse as an array of two numbers";
}
return parsed;
} ],
[ "initial-zoom", (value, context) => mapNumber(value, context) ],
[ "max-zoom", (value, context) => mapNumber(value, context) ],
[ "max-native-zoom", (value, context) => mapNumber(value, context) ],
[ "min-zoom", (value, context) => mapNumber(value, context) ],
[ "properties", (value, context) => {
const parsed = JSON.parse(value);
if (typeof parsed !== 'object' || !Object.keys(parsed).length) {
throw "doesn't parse as an object with at least one property";
}
return parsed;
} ],
//[ "property-key", (value, context) => value],
[ "property-parse", (value, context) => mapSelected(value, context, [ "literal", "escaped", "json" ]) ],
[ "property-value", (value, context) =>
context["property-parse"] === "escaped" ? escapeHtml(value)
: context["property-parse"] === "json" ? JSON.parse(value)
: value
],
//[ "ref", (value, context) => value ],
[ "show-user-location", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
[ "show-user-location-accuracy", (value, context) => mapSelected(value, context, [ "true", "false" ]) === "true" ],
//[ "tile-url-template", (value, context) => value ],
];
export const attribute_transforms = attribute_transforms_ordered.reduce((a, t) => {
a[t[0]] = t[1];
return a;
}, {});
export const attribute_transforms_order = attribute_transforms_ordered.map(t => t[0]);