tile-stencil
Version:
Load a MapLibre style document and parse it into Javascript functions
962 lines (850 loc) • 27.3 kB
JavaScript
function expandStyleURL(url, token) {
const prefix = /^mapbox:\/\/styles\//;
if (!url.match(prefix)) return url;
const apiRoot = "https://api.mapbox.com/styles/v1/";
return url.replace(prefix, apiRoot) + "?access_token=" + token;
}
function expandSpriteURLs(url, pixRatio, token) {
// Returns an array containing urls to .png and .json files
const ratio = Math.floor(Math.min(Math.max(1.0, pixRatio), 4.0));
const ratioStr = (ratio > 1)
? "@" + ratio + "x"
: "";
const prefix = /^mapbox:\/\/sprites\//;
if (!url.match(prefix)) return {
image: url + ratioStr + ".png",
meta: url + ratioStr + ".json",
};
// We have a Mapbox custom url. Expand to an absolute URL, as per the spec
const apiRoot = "https://api.mapbox.com/styles/v1/";
url = url.replace(prefix, apiRoot) + "/sprite";
const tokenString = "?access_token=" + token;
return {
image: url + ratioStr + ".png" + tokenString,
meta: url + ratioStr + ".json" + tokenString,
};
}
function expandTileURL(url, token) {
const prefix = /^mapbox:\/\//;
if (!url.match(prefix)) return url;
const apiRoot = "https://api.mapbox.com/v4/";
return url.replace(prefix, apiRoot) + ".json?secure&access_token=" + token;
}
function expandGlyphURL(url, token) {
const prefix = /^mapbox:\/\/fonts\//;
if (!url.match(prefix)) return url;
const apiRoot = "https://api.mapbox.com/fonts/v1/";
return url.replace(prefix, apiRoot) + "?access_token=" + token;
}
function getGeoJSON(data) {
const dataPromise = (typeof data === "object" && data !== null)
? Promise.resolve(data)
: getJSON(data); // data may be a URL. Try loading it
return dataPromise.then(json => {
// Is it valid GeoJSON? For now, just check for a .type property
return (json.type)
? json
: Promise.reject(Error("invalid GeoJSON: " + JSON.stringify(json)));
});
}
function getJSON(href) {
return (typeof href === "string" && href.length)
? fetch(href).then(checkFetch)
: Promise.reject(Error("invalid URL: " + JSON.stringify(href)));
}
function checkFetch(response) {
if (!response.ok) {
const { status, statusText, url } = response;
const message = `HTTP ${status} ${statusText} for URL ${url}`;
return Promise.reject(Error(message));
}
return response.json();
}
function getImage(href) {
const img = new Image();
return new Promise( (resolve, reject) => {
img.onerror = () => reject(Error("Failed to retrieve image from " + href));
img.onload = () => (img.complete && img.naturalWidth !== 0)
? resolve(img)
: reject(Error("Incomplete image received from " + href));
img.crossOrigin = "anonymous";
img.src = href;
});
}
function warn(message) {
console.log("tile-stencil had a problem loading part of the style document");
console.log(" " + message);
console.log(" Not a fatal error. Proceeding with the rest of the style...");
}
function define(constructor, factory, prototype) {
constructor.prototype = factory.prototype = prototype;
prototype.constructor = constructor;
}
function extend(parent, definition) {
var prototype = Object.create(parent.prototype);
for (var key in definition) prototype[key] = definition[key];
return prototype;
}
function Color() {}
var darker = 0.7;
var brighter = 1 / darker;
var reI = "\\s*([+-]?\\d+)\\s*",
reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",
reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",
reHex = /^#([0-9a-f]{3,8})$/,
reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"),
reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"),
reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"),
reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"),
reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"),
reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$");
var named = {
aliceblue: 0xf0f8ff,
antiquewhite: 0xfaebd7,
aqua: 0x00ffff,
aquamarine: 0x7fffd4,
azure: 0xf0ffff,
beige: 0xf5f5dc,
bisque: 0xffe4c4,
black: 0x000000,
blanchedalmond: 0xffebcd,
blue: 0x0000ff,
blueviolet: 0x8a2be2,
brown: 0xa52a2a,
burlywood: 0xdeb887,
cadetblue: 0x5f9ea0,
chartreuse: 0x7fff00,
chocolate: 0xd2691e,
coral: 0xff7f50,
cornflowerblue: 0x6495ed,
cornsilk: 0xfff8dc,
crimson: 0xdc143c,
cyan: 0x00ffff,
darkblue: 0x00008b,
darkcyan: 0x008b8b,
darkgoldenrod: 0xb8860b,
darkgray: 0xa9a9a9,
darkgreen: 0x006400,
darkgrey: 0xa9a9a9,
darkkhaki: 0xbdb76b,
darkmagenta: 0x8b008b,
darkolivegreen: 0x556b2f,
darkorange: 0xff8c00,
darkorchid: 0x9932cc,
darkred: 0x8b0000,
darksalmon: 0xe9967a,
darkseagreen: 0x8fbc8f,
darkslateblue: 0x483d8b,
darkslategray: 0x2f4f4f,
darkslategrey: 0x2f4f4f,
darkturquoise: 0x00ced1,
darkviolet: 0x9400d3,
deeppink: 0xff1493,
deepskyblue: 0x00bfff,
dimgray: 0x696969,
dimgrey: 0x696969,
dodgerblue: 0x1e90ff,
firebrick: 0xb22222,
floralwhite: 0xfffaf0,
forestgreen: 0x228b22,
fuchsia: 0xff00ff,
gainsboro: 0xdcdcdc,
ghostwhite: 0xf8f8ff,
gold: 0xffd700,
goldenrod: 0xdaa520,
gray: 0x808080,
green: 0x008000,
greenyellow: 0xadff2f,
grey: 0x808080,
honeydew: 0xf0fff0,
hotpink: 0xff69b4,
indianred: 0xcd5c5c,
indigo: 0x4b0082,
ivory: 0xfffff0,
khaki: 0xf0e68c,
lavender: 0xe6e6fa,
lavenderblush: 0xfff0f5,
lawngreen: 0x7cfc00,
lemonchiffon: 0xfffacd,
lightblue: 0xadd8e6,
lightcoral: 0xf08080,
lightcyan: 0xe0ffff,
lightgoldenrodyellow: 0xfafad2,
lightgray: 0xd3d3d3,
lightgreen: 0x90ee90,
lightgrey: 0xd3d3d3,
lightpink: 0xffb6c1,
lightsalmon: 0xffa07a,
lightseagreen: 0x20b2aa,
lightskyblue: 0x87cefa,
lightslategray: 0x778899,
lightslategrey: 0x778899,
lightsteelblue: 0xb0c4de,
lightyellow: 0xffffe0,
lime: 0x00ff00,
limegreen: 0x32cd32,
linen: 0xfaf0e6,
magenta: 0xff00ff,
maroon: 0x800000,
mediumaquamarine: 0x66cdaa,
mediumblue: 0x0000cd,
mediumorchid: 0xba55d3,
mediumpurple: 0x9370db,
mediumseagreen: 0x3cb371,
mediumslateblue: 0x7b68ee,
mediumspringgreen: 0x00fa9a,
mediumturquoise: 0x48d1cc,
mediumvioletred: 0xc71585,
midnightblue: 0x191970,
mintcream: 0xf5fffa,
mistyrose: 0xffe4e1,
moccasin: 0xffe4b5,
navajowhite: 0xffdead,
navy: 0x000080,
oldlace: 0xfdf5e6,
olive: 0x808000,
olivedrab: 0x6b8e23,
orange: 0xffa500,
orangered: 0xff4500,
orchid: 0xda70d6,
palegoldenrod: 0xeee8aa,
palegreen: 0x98fb98,
paleturquoise: 0xafeeee,
palevioletred: 0xdb7093,
papayawhip: 0xffefd5,
peachpuff: 0xffdab9,
peru: 0xcd853f,
pink: 0xffc0cb,
plum: 0xdda0dd,
powderblue: 0xb0e0e6,
purple: 0x800080,
rebeccapurple: 0x663399,
red: 0xff0000,
rosybrown: 0xbc8f8f,
royalblue: 0x4169e1,
saddlebrown: 0x8b4513,
salmon: 0xfa8072,
sandybrown: 0xf4a460,
seagreen: 0x2e8b57,
seashell: 0xfff5ee,
sienna: 0xa0522d,
silver: 0xc0c0c0,
skyblue: 0x87ceeb,
slateblue: 0x6a5acd,
slategray: 0x708090,
slategrey: 0x708090,
snow: 0xfffafa,
springgreen: 0x00ff7f,
steelblue: 0x4682b4,
tan: 0xd2b48c,
teal: 0x008080,
thistle: 0xd8bfd8,
tomato: 0xff6347,
turquoise: 0x40e0d0,
violet: 0xee82ee,
wheat: 0xf5deb3,
white: 0xffffff,
whitesmoke: 0xf5f5f5,
yellow: 0xffff00,
yellowgreen: 0x9acd32
};
define(Color, color, {
copy: function(channels) {
return Object.assign(new this.constructor, this, channels);
},
displayable: function() {
return this.rgb().displayable();
},
hex: color_formatHex, // Deprecated! Use color.formatHex.
formatHex: color_formatHex,
formatHsl: color_formatHsl,
formatRgb: color_formatRgb,
toString: color_formatRgb
});
function color_formatHex() {
return this.rgb().formatHex();
}
function color_formatHsl() {
return hslConvert(this).formatHsl();
}
function color_formatRgb() {
return this.rgb().formatRgb();
}
function color(format) {
var m, l;
format = (format + "").trim().toLowerCase();
return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000
: l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00
: l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000
: l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000
: null) // invalid hex
: (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
: (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)
: (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
: (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)
: (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
: (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
: named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
: format === "transparent" ? new Rgb(NaN, NaN, NaN, 0)
: null;
}
function rgbn(n) {
return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);
}
function rgba(r, g, b, a) {
if (a <= 0) r = g = b = NaN;
return new Rgb(r, g, b, a);
}
function rgbConvert(o) {
if (!(o instanceof Color)) o = color(o);
if (!o) return new Rgb;
o = o.rgb();
return new Rgb(o.r, o.g, o.b, o.opacity);
}
function rgb(r, g, b, opacity) {
return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);
}
function Rgb(r, g, b, opacity) {
this.r = +r;
this.g = +g;
this.b = +b;
this.opacity = +opacity;
}
define(Rgb, rgb, extend(Color, {
brighter: function(k) {
k = k == null ? brighter : Math.pow(brighter, k);
return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
},
darker: function(k) {
k = k == null ? darker : Math.pow(darker, k);
return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
},
rgb: function() {
return this;
},
displayable: function() {
return (-0.5 <= this.r && this.r < 255.5)
&& (-0.5 <= this.g && this.g < 255.5)
&& (-0.5 <= this.b && this.b < 255.5)
&& (0 <= this.opacity && this.opacity <= 1);
},
hex: rgb_formatHex, // Deprecated! Use color.formatHex.
formatHex: rgb_formatHex,
formatRgb: rgb_formatRgb,
toString: rgb_formatRgb
}));
function rgb_formatHex() {
return "#" + hex(this.r) + hex(this.g) + hex(this.b);
}
function rgb_formatRgb() {
var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
return (a === 1 ? "rgb(" : "rgba(")
+ Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", "
+ Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", "
+ Math.max(0, Math.min(255, Math.round(this.b) || 0))
+ (a === 1 ? ")" : ", " + a + ")");
}
function hex(value) {
value = Math.max(0, Math.min(255, Math.round(value) || 0));
return (value < 16 ? "0" : "") + value.toString(16);
}
function hsla(h, s, l, a) {
if (a <= 0) h = s = l = NaN;
else if (l <= 0 || l >= 1) h = s = NaN;
else if (s <= 0) h = NaN;
return new Hsl(h, s, l, a);
}
function hslConvert(o) {
if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
if (!(o instanceof Color)) o = color(o);
if (!o) return new Hsl;
if (o instanceof Hsl) return o;
o = o.rgb();
var r = o.r / 255,
g = o.g / 255,
b = o.b / 255,
min = Math.min(r, g, b),
max = Math.max(r, g, b),
h = NaN,
s = max - min,
l = (max + min) / 2;
if (s) {
if (r === max) h = (g - b) / s + (g < b) * 6;
else if (g === max) h = (b - r) / s + 2;
else h = (r - g) / s + 4;
s /= l < 0.5 ? max + min : 2 - max - min;
h *= 60;
} else {
s = l > 0 && l < 1 ? 0 : h;
}
return new Hsl(h, s, l, o.opacity);
}
function hsl(h, s, l, opacity) {
return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);
}
function Hsl(h, s, l, opacity) {
this.h = +h;
this.s = +s;
this.l = +l;
this.opacity = +opacity;
}
define(Hsl, hsl, extend(Color, {
brighter: function(k) {
k = k == null ? brighter : Math.pow(brighter, k);
return new Hsl(this.h, this.s, this.l * k, this.opacity);
},
darker: function(k) {
k = k == null ? darker : Math.pow(darker, k);
return new Hsl(this.h, this.s, this.l * k, this.opacity);
},
rgb: function() {
var h = this.h % 360 + (this.h < 0) * 360,
s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
l = this.l,
m2 = l + (l < 0.5 ? l : 1 - l) * s,
m1 = 2 * l - m2;
return new Rgb(
hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
hsl2rgb(h, m1, m2),
hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
this.opacity
);
},
displayable: function() {
return (0 <= this.s && this.s <= 1 || isNaN(this.s))
&& (0 <= this.l && this.l <= 1)
&& (0 <= this.opacity && this.opacity <= 1);
},
formatHsl: function() {
var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
return (a === 1 ? "hsl(" : "hsla(")
+ (this.h || 0) + ", "
+ (this.s || 0) * 100 + "%, "
+ (this.l || 0) * 100 + "%"
+ (a === 1 ? ")" : ", " + a + ")");
}
}));
/* From FvD 13.37, CSS Color Module Level 3 */
function hsl2rgb(h, m1, m2) {
return (h < 60 ? m1 + (m2 - m1) * h / 60
: h < 180 ? m2
: h < 240 ? m1 + (m2 - m1) * (240 - h) / 60
: m1) * 255;
}
function buildInterpolator(stops, base = 1) {
if (!stops || stops.length < 2 || stops[0].length !== 2) return;
// Confirm stops are all the same type, and convert colors to arrays
const type = getType(stops[0][1]);
if (!stops.every(s => getType(s[1]) === type)) return;
stops = stops.map(([x, y]) => [x, convertIfColor(y)]);
const izm = stops.length - 1;
const scale = getScale(base);
const interpolate = getInterpolator(type);
return function(x) {
const iz = stops.findIndex(stop => stop[0] > x);
if (iz === 0) return stops[0][1]; // x is below first stop
if (iz < 0) return stops[izm][1]; // x is above last stop
const [x0, y0] = stops[iz - 1];
const [x1, y1] = stops[iz];
return interpolate(y0, scale(x0, x, x1), y1);
};
}
function getType(v) {
return color(v) ? "color" : typeof v;
}
function convertIfColor(val) {
// Convert CSS color strings to clamped RGBA arrays for WebGL
if (!color(val)) return val;
const c = rgb(val);
return [c.r / 255, c.g / 255, c.b / 255, c.opacity];
}
function getScale(base) {
// Return a function to find the relative position of x between a and b
// Exponential scale follows mapbox-gl-js, style-spec/function/index.js
// NOTE: https://github.com/mapbox/mapbox-gl-js/issues/2698 not addressed!
const scale = (base === 1)
? (a, x, b) => (x - a) / (b - a) // Linear scale
: (a, x, b) => (Math.pow(base, x - a) - 1) / (Math.pow(base, b - a) - 1);
// Add check for zero range
return (a, x, b) => (a === b)
? 0
: scale(a, x, b);
}
function getInterpolator(type) {
// Return a function to find an interpolated value between end values v1, v2,
// given relative position t between the two end positions
switch (type) {
case "number": // Linear interpolator
return (v1, t, v2) => v1 + t * (v2 - v1);
case "color": // Interpolate RGBA
return (v1, t, v2) =>
v1.map((v, i) => v + t * (v2[i] - v));
default: // Assume step function
return (v1) => v1;
}
}
function autoGetters(properties = {}, defaults) {
return Object.entries(defaults).reduce((d, [key, val]) => {
d[key] = buildStyleFunc(properties[key], val);
return d;
}, {});
}
function buildStyleFunc(style, defaultVal) {
if (style === undefined) {
return getConstFunc(defaultVal);
} else if (typeof style !== "object" || Array.isArray(style)) {
return getConstFunc(style);
} else {
return getStyleFunc(style);
} // NOT IMPLEMENTED: zoom-and-property functions
}
function getConstFunc(rawVal) {
const val = convertIfColor(rawVal);
const func = () => val;
return Object.assign(func, { type: "constant" });
}
function getStyleFunc(style) {
const { type, property = "zoom", base = 1, stops } = style;
const getArg = (property === "zoom")
? (zoom) => zoom
: (zoom, feature) => feature.properties[property];
const getVal = (type === "identity")
? convertIfColor
: buildInterpolator(stops, base);
if (!getVal) return console.log("style: " + JSON.stringify(style) +
"\nERROR in tile-stencil: unsupported style!");
const styleFunc = (zoom, feature) => getVal(getArg(zoom, feature));
return Object.assign(styleFunc, {
type: (property === "zoom") ? "zoom" : "property",
property,
});
}
const layoutDefaults = {
"background": {
"visibility": "visible",
},
"fill": {
"visibility": "visible",
},
"line": {
"visibility": "visible",
"line-cap": "butt",
"line-join": "miter",
"line-miter-limit": 2,
"line-round-limit": 1.05,
},
"symbol": {
"visibility": "visible",
"symbol-placement": "point",
"symbol-spacing": 250,
"symbol-avoid-edges": false,
"symbol-sort-key": undefined,
"symbol-z-order": "auto",
"icon-allow-overlap": false,
"icon-ignore-placement": false,
"icon-optional": false,
"icon-rotation-alignment": "auto",
"icon-size": 1,
"icon-text-fit": "none",
"icon-text-fit-padding": [0, 0, 0, 0],
"icon-image": undefined,
"icon-rotate": 0,
"icon-padding": 2,
"icon-keep-upright": false,
"icon-offset": [0, 0],
"icon-anchor": "center",
"icon-pitch-alignment": "auto",
"text-pitch-alignment": "auto",
"text-rotation-alignment": "auto",
"text-field": "",
"text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
"text-size": 16,
"text-max-width": 10,
"text-line-height": 1.2,
"text-letter-spacing": 0,
"text-justify": "center",
"text-radial-offset": 0,
"text-variable-anchor": undefined,
"text-anchor": "center",
"text-max-angle": 45,
"text-rotate": 0,
"text-padding": 2.0,
"text-keep-upright": true,
"text-transform": "none",
"text-offset": [0, 0],
"text-allow-overlap": false,
"text-ignore-placement": false,
"text-optional": false,
},
"raster": {
"visibility": "visible",
},
"circle": {
"visibility": "visible",
},
"fill-extrusion": {
"visibility": "visible",
},
"heatmap": {
"visibility": "visible",
},
"hillshade": {
"visibility": "visible",
},
};
const paintDefaults = {
"background": {
"background-color": "#000000",
"background-opacity": 1,
"background-pattern": undefined,
},
"fill": {
"fill-antialias": true,
"fill-opacity": 1,
"fill-color": "#000000",
"fill-outline-color": undefined,
"fill-outline-width": 1, // non-standard!
"fill-translate": [0, 0],
"fill-translate-anchor": "map",
"fill-pattern": undefined,
},
"line": {
"line-opacity": 1,
"line-color": "#000000",
"line-translate": [0, 0],
"line-translate-anchor": "map",
"line-width": 1,
"line-gap-width": 0,
"line-offset": 0,
"line-blur": 0,
"line-dasharray": [0, 0, 0, 0],
"line-pattern": undefined,
"line-gradient": undefined,
},
"symbol": {
"icon-opacity": 1,
"icon-color": "#000000",
"icon-halo-color": "rgba(0, 0, 0, 0)",
"icon-halo-width": 0,
"icon-halo-blur": 0,
"icon-translate": [0, 0],
"icon-translate-anchor": "map",
"text-opacity": 1,
"text-color": "#000000",
"text-halo-color": "rgba(0, 0, 0, 0)",
"text-halo-width": 0,
"text-halo-blur": 0,
"text-translate": [0, 0],
"text-translate-anchor": "map",
},
"raster": {
"raster-opacity": 1,
"raster-hue-rotate": 0,
"raster-brighness-min": 0,
"raster-brightness-max": 1,
"raster-saturation": 0,
"raster-contrast": 0,
"raster-resampling": "linear",
"raster-fade-duration": 300,
},
"circle": {
"circle-radius": 5,
"circle-color": "#000000",
"circle-blur": 0,
"circle-opacity": 1,
"circle-translate": [0, 0],
"circle-translate-anchor": "map",
"circle-pitch-scale": "map",
"circle-pitch-alignment": "viewport",
"circle-stroke-width": 0,
"circle-stroke-color": "#000000",
"circle-stroke-opacity": 1,
},
"fill-extrusion": {
"fill-extrusion-opacity": 1,
"fill-extrusion-color": "#000000",
"fill-extrusion-translate": [0, 0],
"fill-extrusion-translate-anchor": "map",
"fill-extrusion-height": 0,
"fill-extrusion-base": 0,
"fill-extrusion-vertical-gradient": true,
},
"heatmap": {
"heatmap-radius": 30,
"heatmap-weight": 1,
"heatmap-intensity": 1,
"heatmap-color": [
"interpolate", ["linear"], ["heatmap-density"],
0, "rgba(0, 0, 255,0)", 0.1, "royalblue", 0.3, "cyan",
0.5, "lime", 0.7, "yellow", 1, "red"
],
"heatmap-opacity": 1,
},
"hillshade": {
"hillshade-illumination-direction": 335,
"hillshade-illumination-anchor": "viewport",
"hillshade-exaggeration": 0.5,
"hillshade-shadow-color": "#000000",
"hillshade-highlight-color": "#FFFFFF",
"hillshade-accent-color": "#000000",
},
};
const refProperties = ["type", "minzoom", "maxzoom",
"source", "source-layer", "filter", "layout"];
function derefLayers(layers) {
// Some layers in Mapbox styles contain a non-standard "ref" property,
// pointing to the "id" of another layer.
// Augment these layers with properties from the referenced layer
const map = layers.reduce((m, l) => (m[l.id] = l, m), {});
return layers.map(l => ("ref" in l) ? deref(l, map[l.ref]) : l);
}
function deref(layer, parent) {
const result = Object.assign({}, layer);
delete result.ref;
refProperties.forEach(k => {
if (k in parent) result[k] = parent[k];
});
return result;
}
function loadLinks(styleDoc, mapboxToken) {
const { sources: rawSources, glyphs, sprite, layers } = styleDoc;
styleDoc.layers = derefLayers(layers);
if (glyphs) {
styleDoc.glyphs = expandGlyphURL(glyphs, mapboxToken);
}
return Promise.all([
expandSources(rawSources, mapboxToken),
loadSprite(sprite, mapboxToken),
]).then(([sources, spriteData]) => {
return Object.assign(styleDoc, { sources, spriteData });
});
}
function expandSources(rawSources, token) {
const expandPromises = Object.entries(rawSources).map(expandSource);
function expandSource([key, source]) {
const { type, url } = source;
const infoPromise =
(type === "geojson") ? getGeoJSON(source.data).then(data => ({ data })) :
(url) ? getJSON(expandTileURL(url, token)) : // Get linked TileJSON
Promise.resolve({}); // No linked info
return infoPromise.then(
val => ({ [key]: Object.assign({}, source, val, { type }) }),
err => (warn("sources." + key + ": " + err.message), ({}))
);
}
return Promise.all(expandPromises).then(results => {
return results.reduce((a, c) => Object.assign(a, c), {});
});
}
function loadSprite(sprite, token) {
if (!sprite) return;
const notWorker = (window && window.devicePixelRatio);
const pixRatio = (notWorker) ? window.devicePixelRatio : 1.0;
const urls = expandSpriteURLs(sprite, pixRatio, token);
return Promise.all([getImage(urls.image), getJSON(urls.meta)]).then(
([image, meta]) => ({ image, meta }),
err => warn("sprite: " + err.message)
);
}
function buildFeatureFilter(filterObj) {
// filterObj is a filter definition following the 'deprecated' syntax:
// https://maplibre.org/maplibre-gl-js-docs/style-spec/other/#other-filter
if (!filterObj) return () => true;
const [type, ...vals] = filterObj;
// If this is a combined filter, the vals are themselves filter definitions
switch (type) {
case "all": {
const filters = vals.map(buildFeatureFilter); // Iteratively recursive!
return (d) => filters.every( filt => filt(d) );
}
case "any": {
const filters = vals.map(buildFeatureFilter);
return (d) => filters.some( filt => filt(d) );
}
case "none": {
const filters = vals.map(buildFeatureFilter);
return (d) => filters.every( filt => !filt(d) );
}
default:
return getSimpleFilter(filterObj);
}
}
function getSimpleFilter(filterObj) {
const [type, key, ...vals] = filterObj;
const getVal = initFeatureValGetter(key);
switch (type) {
// Existential Filters
case "has":
return d => !!getVal(d); // !! forces a Boolean return
case "!has":
return d => !getVal(d);
// Comparison Filters
case "==":
return d => getVal(d) === vals[0];
case "!=":
return d => getVal(d) !== vals[0];
case ">":
return d => getVal(d) > vals[0];
case ">=":
return d => getVal(d) >= vals[0];
case "<":
return d => getVal(d) < vals[0];
case "<=":
return d => getVal(d) <= vals[0];
// Set Membership Filters
case "in" :
return d => vals.includes( getVal(d) );
case "!in" :
return d => !vals.includes( getVal(d) );
default:
console.log("prepFilter: unknown filter type = " + filterObj[0]);
}
// No recognizable filter criteria. Return a filter that is always true
return () => true;
}
function initFeatureValGetter(key) {
switch (key) {
case "$type":
// NOTE: data includes MultiLineString, MultiPolygon, etc-NOT IN SPEC
return f => {
const t = f.geometry.type;
if (t === "MultiPoint") return "Point";
if (t === "MultiLineString") return "LineString";
if (t === "MultiPolygon") return "Polygon";
return t;
};
case "$id":
return f => f.id;
default:
return f => f.properties[key];
}
}
function getStyleFuncs(inputLayer) {
const layer = Object.assign({}, inputLayer); // Leave input unchanged
// Replace rendering properties with functions
layer.layout = autoGetters(layer.layout, layoutDefaults[layer.type]);
layer.paint = autoGetters(layer.paint, paintDefaults[layer.type] );
return layer;
}
function loadStyle(style, mapboxToken) {
// Loads a style document and any linked information
const getStyle = (typeof style === "object")
? Promise.resolve(style) // style is JSON already
: getJSON( expandStyleURL(style, mapboxToken) ); // Get from URL
return getStyle.then(checkStyle)
.then( styleDoc => loadLinks(styleDoc, mapboxToken) );
}
function checkStyle(doc) {
const { version, sources, layers } = doc;
const noSource =
typeof sources !== "object" ||
sources === null ||
Array.isArray(sources);
const error =
noSource ? "missing sources object" :
(!Array.isArray(layers)) ? "missing layers array" :
(version !== 8) ? "unsupported version number" :
null;
return (error) ? Promise.reject(error) : doc;
}
export { buildFeatureFilter, getStyleFuncs, loadStyle };