UNPKG

geostyler-openlayers-parser

Version:

GeoStyler Style Parser implementation for OpenLayers styles

944 lines (943 loc) 34.1 kB
import { parseFont as L } from "css-font-parser"; import { isGeoStylerBooleanFunction as b, isGeoStylerFunction as d, isSprite as k, isIconSymbolizer as x, isMarkSymbolizer as R, isGeoStylerStringFunction as E } from "geostyler-style"; import M from "ol/ImageState.js"; import P from "ol/geom/Point.js"; import T from "ol/style/Style.js"; import W from "ol/style/Image.js"; import A from "ol/style/Stroke.js"; import K from "ol/style/Text.js"; import z from "ol/style/Circle.js"; import I from "ol/style/Fill.js"; import D from "ol/style/Icon.js"; import G from "ol/style/RegularShape.js"; import _ from "ol/geom/LineString.js"; import U from "ol/geom/MultiLineString.js"; import j from "ol/geom/Polygon.js"; import H from "ol/geom/MultiPolygon.js"; import { METERS_PER_UNIT as Y } from "ol/proj/Units.js"; import a, { DEGREES_TO_RADIANS as C } from "./Util/OlStyleUtil.js"; import { isPointDefinedAsSvg as B, getPointSvg as X, cleanWellKnownName as $ } from "./Util/OlSvgPoints.js"; import { toContext as J } from "ol/render.js"; import V from "ol/Feature.js"; import { fillPatternSvg as q } from "./Util/OlSvgPatterns.js"; import { getDecodedSvg as Q, getSvgProperties as Z, getEncodedSvg as tt, LINE_WELLKNOWNNAMES as et, NOFILL_WELLKNOWNNAMES as ot, drawSvgToCanvas as rt } from "./Util/OlSvgUtil.js"; import v from "./Util/OlGraphicStrokeUtil.js"; class It { /** * The name of the OlStyleParser. */ static title = "OpenLayers Style Parser"; unsupportedProperties = { Symbolizer: { MarkSymbolizer: { avoidEdges: "none", blur: "none", offsetAnchor: "none", pitchAlignment: "none", pitchScale: "none" }, FillSymbolizer: { antialias: "none", opacity: { support: "none", info: "Use fillOpacity instead." } }, IconSymbolizer: { allowOverlap: "none", anchor: "none", avoidEdges: "none", color: "none", haloBlur: "none", haloColor: "none", haloWidth: "none", keepUpright: "none", offsetAnchor: "none", size: { support: "partial", info: "Will set/get the width of the ol Icon." }, optional: "none", padding: "none", pitchAlignment: "none", rotationAlignment: "none", textFit: "none", textFitPadding: "none" }, LineSymbolizer: { blur: "none", gapWidth: "none", gradient: "none", miterLimit: "none", roundLimit: "none", spacing: "none", graphicFill: "none", perpendicularOffset: "none" }, RasterSymbolizer: "none", TextSymbolizer: { anchor: "none", placement: { support: "partial", info: "point and line supported. line-center will be mapped to line." } } }, Function: { double2bool: { support: "none", info: "Always returns false" }, atan2: { support: "none", info: "Currently returns the first argument" }, rint: { support: "none", info: "Currently returns the first argument" }, numberFormat: { support: "none", info: "Currently returns the first argument" }, strAbbreviate: { support: "none", info: "Currently returns the first argument" } } }; title = "OpenLayers Style Parser"; olIconStyleCache = {}; OlStyleConstructor = T; OlStyleImageConstructor = W; OlStyleFillConstructor = I; OlStyleStrokeConstructor = A; OlStyleTextConstructor = K; OlStyleCircleConstructor = z; OlStyleIconConstructor = D; OlStyleRegularshapeConstructor = G; OlLineStringContructor = _; OlMultiLineStringConstructor = U; OlPolygonConstructor = j; OlMultiPolygonConstructor = H; OlPointConstructor = P; constructor(t) { t && (this.OlStyleConstructor = t.style.Style, this.OlStyleImageConstructor = t.style.Image, this.OlStyleFillConstructor = t.style.Fill, this.OlStyleStrokeConstructor = t.style.Stroke, this.OlStyleTextConstructor = t.style.Text, this.OlStyleCircleConstructor = t.style.Circle, this.OlStyleIconConstructor = t.style.Icon, this.OlStyleRegularshapeConstructor = t.style.RegularShape, this.OlLineStringContructor = t.geom.LineString, this.OlMultiLineStringConstructor = t.geom.MultiLineString, this.OlPolygonConstructor = t.geom.Polygon, this.OlMultiPolygonConstructor = t.geom.MultiPolygon, this.OlPointConstructor = t.geom.Point); } isOlParserStyleFct = (t) => typeof t == "function"; /** * Get the GeoStyler-Style PointSymbolizer from an OpenLayers Style object. * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style PointSymbolizer */ getPointSymbolizerFromOlStyle(t) { let o; if (t.getImage() instanceof this.OlStyleCircleConstructor) { const e = t.getImage(), r = e.getFill(), n = e.getStroke(), s = e.getDisplacement(); o = { kind: "Mark", wellKnownName: "circle", color: r ? a.getHexColor(r.getColor()) : void 0, opacity: e.getOpacity() !== 1 ? e.getOpacity() : void 0, fillOpacity: r ? a.getOpacity(r.getColor()) : void 0, radius: e.getRadius() !== 0 ? e.getRadius() : 5, strokeColor: n ? n.getColor() : void 0, strokeOpacity: n ? a.getOpacity(n.getColor()) : void 0, strokeWidth: n ? n.getWidth() : void 0, offset: s[0] || s[1] ? s : void 0 }; } else if (t.getImage() instanceof this.OlStyleRegularshapeConstructor) { const e = t.getImage(), r = e.getFill(), n = e.getStroke(), s = e.getRadius(), i = e.getRadius2(), l = e.getPoints(), g = e.getAngle(), u = e.getDisplacement(), c = { kind: "Mark", color: r ? a.getHexColor(r.getColor()) : void 0, opacity: e.getOpacity() !== 1 ? e.getOpacity() : void 0, fillOpacity: r ? a.getOpacity(r.getColor()) : void 0, strokeColor: n ? n.getColor() : void 0, strokeOpacity: n ? a.getOpacity(n.getColor()) : void 0, strokeWidth: n ? n.getWidth() : void 0, radius: s !== 0 ? s : 5, // Rotation in openlayers is radians while we use degree rotate: e.getRotation() / C, offset: u[0] || u[1] ? u : void 0 }; switch (l) { case 2: switch (g) { case 0: c.wellKnownName = "shape://vertline"; break; case Math.PI / 2: c.wellKnownName = "shape://horline"; break; case Math.PI / 4: c.wellKnownName = "shape://slash"; break; case 2 * Math.PI - Math.PI / 4: c.wellKnownName = "shape://backslash"; break; } break; case 3: switch (g) { case 0: c.wellKnownName = "triangle"; break; case Math.PI / 2: c.wellKnownName = "shape://carrow"; break; } break; case 4: Number.isFinite(i) ? e.getAngle() === 0 ? e.getRadius2() === 0 ? c.wellKnownName = "shape://plus" : c.wellKnownName = "star_diamond" : c.wellKnownName = "x" : e.getAngle() === 0 ? c.wellKnownName = "diamond" : c.wellKnownName = "square"; break; case 5: Number.isFinite(i) ? c.wellKnownName = "star" : c.wellKnownName = "pentagon"; break; case 6: c.wellKnownName = "hexagon"; break; case 8: c.wellKnownName = "octagon"; break; case 10: c.wellKnownName = "decagon"; break; default: throw new Error("Could not parse OlStyle."); } o = c; } else if (t.getText() instanceof this.OlStyleTextConstructor) { const e = t.getText(), r = e.getFill(), n = e.getStroke(), s = e.getRotation(); let i = e.getText() || "a"; const l = e.getFont() || "10px sans-serif", g = a.getFontNameFromOlFont(l), u = a.getSizeFromOlFont(l), c = [e.getOffsetX(), e.getOffsetY()]; Array.isArray(i) && (i = i[0]), o = { kind: "Mark", wellKnownName: `ttf://${g}#0x${i.charCodeAt(0).toString(16)}`, color: r ? a.getHexColor(r.getColor()) : void 0, opacity: r ? a.getOpacity(r.getColor()) : void 0, strokeColor: n ? n.getColor() : void 0, strokeOpacity: n ? a.getOpacity(n.getColor()) : void 0, strokeWidth: n ? n.getWidth() : void 0, radius: u !== 0 ? u : 5, // Rotation in openlayers is radians while we use degree rotate: s ? s / C : 0, offset: c[0] || c[1] ? c : void 0 }; } else { const e = t.getImage(), r = e.getDisplacement(), n = e.getWidth(), s = e.getRotation() / C, i = e.getOpacity(), l = e.getSrc(); let g; l && l.startsWith("data:image/svg+xml") ? g = Q(l) : l && l.trim().startsWith("<svg") && (g = l.trim()), g ? o = { ...Z(g), ...a.checkOpacity(i) && { opacity: i }, ...s && { rotate: s }, ...(r[0] || r[1]) && { offset: r } } : o = { kind: "Icon", image: this.getImageFromIconStyle(e), size: n, ...a.checkOpacity(i) && { opacity: i }, ...s && { rotate: s }, ...(r[0] || r[1]) && { offset: r } }; } return o; } /** * * @param olIconStyle An ol style Icon representation * @returns A string or Sprite configuration */ getImageFromIconStyle(t) { const o = t.getSize(); if (Array.isArray(o)) { const e = t.offset_, r = t.offsetOrigin_; if (r && r !== "top-left") throw new Error(`Offset origin ${r} not supported`); return { source: t.getSrc(), position: e, size: o }; } else return t.getSrc() ? t.getSrc() : void 0; } /** * Get the GeoStyler-Style LineSymbolizer from an OpenLayers Style object. * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style LineSymbolizer */ getLineSymbolizerFromOlStyle(t) { const o = t.getStroke(), e = o ? o.getLineDash() : void 0; return { kind: "Line", color: o ? a.getHexColor(o.getColor()) : void 0, opacity: o ? a.getOpacity(o.getColor()) : void 0, width: o ? o.getWidth() : void 0, cap: o ? o.getLineCap() : "butt", join: o ? o.getLineJoin() : "miter", dasharray: e || void 0, dashOffset: o ? o.getLineDashOffset() : void 0 }; } /** * Get the GeoStyler-Style FillSymbolizer from an OpenLayers Style object. * * PolygonSymbolizer Stroke is just partially supported. * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style FillSymbolizer */ getFillSymbolizerFromOlStyle(t) { const o = t.getFill(), e = t.getStroke(), r = e ? e.getLineDash() : void 0, n = { kind: "Fill" }; return o && (n.color = a.getHexColor(o.getColor())), o && (n.fillOpacity = a.getOpacity(o.getColor())), e && (n.outlineColor = a.getHexColor(e.getColor())), r && (n.outlineDasharray = r), e && (n.outlineOpacity = a.getOpacity(e.getColor())), e && e.getWidth() && (n.outlineWidth = e.getWidth()), n; } /** * Get the GeoStyler-Style TextSymbolizer from an OpenLayers Style object. * * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style TextSymbolizer */ getTextSymbolizerFromOlStyle(t) { const o = t.getText(); if (!o) throw new Error("Could not get text from olStyle."); const e = o.getFill(), r = o.getStroke(), n = o.getOffsetX(), s = o.getOffsetY(), i = o.getFont(), l = o.getRotation(), g = o.getOverflow() ? o.getOverflow() : void 0, u = o.getPlacement(), c = o.getRepeat(), f = o.getText(), y = Array.isArray(f) ? f[0] : f; let S = 1 / 0, h, O, F; if (i) { const p = L(i); p["font-weight"] && (O = p["font-weight"]), p["font-size"] && (S = parseInt(p["font-size"], 10)), p["font-family"] && (h = p["font-family"]?.map((m) => m.includes(" ") ? '"' + m + '"' : m)), p["font-style"] && (F = p["font-style"]); } return { kind: "Text", label: y, placement: u, repeat: c, allowOverlap: g, color: e ? a.getHexColor(e.getColor()) : void 0, size: isFinite(S) ? S : void 0, font: h, fontWeight: O || void 0, fontStyle: F || void 0, offset: n !== void 0 && s !== void 0 ? [n, s] : [0, 0], haloColor: r && r.getColor() ? a.getHexColor(r.getColor()) : void 0, haloWidth: r ? r.getWidth() : void 0, rotate: l !== void 0 ? l / C : void 0 }; } /** * Get the GeoStyler-Style Symbolizer from an OpenLayers Style object. * * @param olStyles The OpenLayers Style object * @return The GeoStyler-Style Symbolizer array */ getSymbolizersFromOlStyle(t) { const o = []; return t.forEach((e) => { let r; switch (this.getStyleTypeFromOlStyle(e)) { case "Point": e.getText() && !a.getIsMarkSymbolizerFont(e.getText().getFont()) ? r = this.getTextSymbolizerFromOlStyle(e) : r = this.getPointSymbolizerFromOlStyle(e); break; case "Line": r = this.getLineSymbolizerFromOlStyle(e); break; case "Fill": r = this.getFillSymbolizerFromOlStyle(e); break; default: throw new Error("Failed to parse SymbolizerKind from OpenLayers Style"); } o.push(r); }), o; } /** * Get the GeoStyler-Style Rule from an OpenLayers Style object. * * @param olStyles The OpenLayers Style object * @return The GeoStyler-Style Rule */ getRuleFromOlStyle(t) { let o; const e = "OL Style Rule 0"; return Array.isArray(t) ? o = this.getSymbolizersFromOlStyle(t) : o = this.getSymbolizersFromOlStyle([t]), { name: e, symbolizers: o }; } /** * Get the GeoStyler-Style Symbolizer from an OpenLayers Style object. * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style Symbolizer */ getStyleTypeFromOlStyle(t) { let o; if (t.getImage() instanceof this.OlStyleImageConstructor) o = "Point"; else if (t.getText() instanceof this.OlStyleTextConstructor) o = "Point"; else if (t.getFill() instanceof this.OlStyleFillConstructor) o = "Fill"; else if (t.getStroke() && !t.getFill()) o = "Line"; else throw new Error("StyleType could not be detected"); return o; } /** * Get the GeoStyler-Style Style from an OpenLayers Style object. * * @param olStyle The OpenLayers Style object * @return The GeoStyler-Style Style */ olStyleToGeoStylerStyle(t) { const o = "OL Style", e = this.getRuleFromOlStyle(t); return { name: o, rules: [e] }; } /** * The readStyle implementation of the GeoStyler-Style StyleParser interface. * It reads an OpenLayers Style, an array of OpenLayers Styles or an olParserStyleFct and returns a Promise. * * The Promise itself resolves with a GeoStyler-Style Style. * * @param olStyle The style to be parsed * @return The Promise resolving with the GeoStyler-Style Style */ readStyle(t) { return new Promise((o) => { try { if (this.isOlParserStyleFct(t)) o({ output: t.__geoStylerStyle }); else { t = t; const e = this.olStyleToGeoStylerStyle(t), r = this.checkForUnsupportedProperties(e); o({ output: e, unsupportedProperties: r }); } } catch (e) { o({ errors: [e] }); } }); } /** * The writeStyle implementation of the GeoStyler-Style StyleParser interface. * It reads a GeoStyler-Style Style and returns a Promise. * The Promise itself resolves one of three types * * 1. OlStyle if input Style consists of * one rule with one symbolizer, no filter, no scaleDenominator, no TextSymbolizer * 2. OlStyle[] if input Style consists of * one rule with multiple symbolizers, no filter, no scaleDenominator, no TextSymbolizer * 3. OlParserStyleFct for everything else * * @param geoStylerStyle A GeoStyler-Style Style. * @return The Promise resolving with one of above mentioned style types. */ writeStyle(t) { return new Promise((o) => { const e = structuredClone(t), r = this.checkForUnsupportedProperties(e); try { const n = this.getOlStyleTypeFromGeoStylerStyle(e); o({ output: n, unsupportedProperties: r, warnings: r && ["Your style contains unsupportedProperties!"] }); } catch (n) { o({ errors: [n] }); } }); } checkForUnsupportedProperties(t) { const o = (r) => r[0].toUpperCase() + r.slice(1), e = {}; if (t.rules.forEach((r) => { r.symbolizers.forEach((n) => { const s = o(`${n.kind}Symbolizer`), i = this.unsupportedProperties?.Symbolizer?.[s]; i && (typeof i == "string" ? (e.Symbolizer || (e.Symbolizer = {}), e.Symbolizer[s] = i) : Object.keys(n).forEach((l) => { i[l] && (e.Symbolizer || (e.Symbolizer = {}), e.Symbolizer[s] || (e.Symbolizer[s] = {}), e.Symbolizer[s][l] = i[l]); })); }); }), Object.keys(e).length > 0) return e; } /** * Decides which OlStyleType should be returned depending on given geoStylerStyle. * Three OlStyleTypes are possible: * * 1. OlStyle if input Style consists of * one rule with one symbolizer, no filter, no scaleDenominator, no TextSymbolizer * 2. OlStyle[] if input Style consists of * one rule with multiple symbolizers, no filter, no scaleDenominator, no TextSymbolizer * 3. OlParserStyleFct for everything else * * @param geoStylerStyle A GeoStyler-Style Style */ getOlStyleTypeFromGeoStylerStyle(t) { const o = t.rules; if (o.length === 1) { const r = t?.rules?.[0]?.filter !== void 0, n = t?.rules?.[0]?.scaleDenominator?.min !== void 0, s = t?.rules?.[0]?.scaleDenominator?.max !== void 0, i = !!(n || s), l = a.containsGeoStylerFunctions(t), g = v.containsGraphicStroke(t), u = t.rules[0].symbolizers.length, c = o[0].symbolizers.some((y) => y.kind === "Text"), f = o[0].symbolizers.some((y) => y.kind === "Icon" && typeof y.image == "string" && y.image.includes("{{")); return !r && !i && !c && !f && !l && !g ? u === 1 ? this.geoStylerStyleToOlStyle(t) : this.geoStylerStyleToOlStyleArray(t) : this.geoStylerStyleToOlParserStyleFct(t); } else return this.geoStylerStyleToOlParserStyleFct(t); } /** * Parses the first symbolizer of the first rule of a GeoStyler-Style Style. * * @param geoStylerStyle GeoStyler-Style Style * @return An OpenLayers Style Object */ geoStylerStyleToOlStyle(t) { const e = t.rules[0].symbolizers[0]; return this.getOlSymbolizerFromSymbolizer(e); } /** * Parses all symbolizers of the first rule of a GeoStyler-Style Style. * * @param geoStylerStyle GeoStyler-Style Style * @return An array of OpenLayers Style Objects */ geoStylerStyleToOlStyleArray(t) { const o = t.rules[0], e = []; return o.symbolizers.forEach((r) => { const n = this.getOlSymbolizerFromSymbolizer(r); e.push(n); }), e; } /** * Get the OpenLayers Style object from an GeoStyler-Style Style * * @param geoStylerStyle A GeoStyler-Style Style. * @return An OlParserStyleFct */ geoStylerStyleToOlParserStyleFct(t) { const o = structuredClone(t.rules), r = (n, s) => { let i = !1; const l = [], g = 25.4 / 0.28, u = Y.m, f = s * u * 39.37 * g; return o.forEach((y) => { let S = y?.scaleDenominator?.min, h = y?.scaleDenominator?.max, O = !0; (S || h) && (S = d(S) ? a.evaluateFunction(S, n) : S, h = d(h) ? a.evaluateFunction(h, n) : h, S && f < S && (O = !1), h && f >= h && (O = !1)); let F = !1; if (!y.filter) F = !0; else try { F = this.geoStylerFilterToOlParserFilter(n, y.filter); } catch { F = !1; } if (O && F) { if (y.elseRule && i) return; i = !0, y.symbolizers.forEach((p) => { p.visibility === !1 && l.push(null), b(p.visibility) && (a.evaluateFunction(p.visibility, n) || l.push(null)); const w = this.getOlSymbolizerFromSymbolizer(p, n, s); if (typeof w == "function") { const m = w(n, s); l.push(m); } else Array.isArray(w) ? w.forEach((m) => { if (typeof m == "function") { const N = m(n, s); l.push(N); } else l.push(m); }) : l.push(w); }); } }), l; }; return r.__geoStylerStyle = t, r; } /** * Checks if a feature matches given filter expression(s) * @param feature ol.Feature * @param filter Filter * @return boolean true if feature matches filter expression */ geoStylerFilterToOlParserFilter(t, o) { const e = { "&&": !0, "||": !0, "!": !0 }; let r = !0; if (b(o)) return a.evaluateBooleanFunction(o, t); if (o === !0 || o === !1) return o; const n = o[0]; let s = !1; e[n] && (s = !0); try { if (s) { let i, l; switch (o[0]) { case "&&": i = !0, l = o.slice(1), l.forEach((g) => { this.geoStylerFilterToOlParserFilter(t, g) || (i = !1); }), r = i; break; case "||": i = !1, l = o.slice(1), l.forEach((g) => { this.geoStylerFilterToOlParserFilter(t, g) && (i = !0); }), r = i; break; case "!": r = !this.geoStylerFilterToOlParserFilter(t, o[1]); break; default: throw new Error("Cannot parse Filter. Unknown combination or negation operator."); } } else { let i; d(o[1]) ? i = a.evaluateFunction(o[1], t) : i = t.get(o[1]); let l; switch (d(o[2]) ? l = a.evaluateFunction(o[2], t) : l = o[2], o[0]) { case "==": r = "" + i == "" + l; break; case "*=": typeof l == "string" && typeof i == "string" && (l.length > i.length ? r = !1 : r = i.indexOf(l) !== -1); break; case "!=": r = "" + i != "" + l; break; case "<": r = Number(i) < Number(l); break; case "<=": r = Number(i) <= Number(l); break; case ">": r = Number(i) > Number(l); break; case ">=": r = Number(i) >= Number(l); break; default: throw new Error("Cannot parse Filter. Unknown comparison operator."); } } } catch { throw new Error("Cannot parse Filter. Invalid structure."); } return r; } /** * Get the OpenLayers Style object or an OL StyleFunction from an * GeoStyler-Style Symbolizer. * * @param symbolizer A GeoStyler-Style Symbolizer. * @return The OpenLayers Style object or a StyleFunction */ getOlSymbolizerFromSymbolizer(t, o, e) { let r; switch (t = structuredClone(t), t.kind) { case "Mark": r = this.getOlPointSymbolizerFromMarkSymbolizer(t, o); break; case "Icon": r = this.getOlIconSymbolizerFromIconSymbolizer(t, o); break; case "Text": r = this.getOlTextSymbolizerFromTextSymbolizer(t, o); break; case "Line": r = this.getOlLineSymbolizerFromLineSymbolizer(t, o, e); break; case "Fill": r = this.getOlPolygonSymbolizerFromFillSymbolizer(t, o); break; default: { const n = new this.OlStyleFillConstructor({ color: "rgba(255,255,255,0.4)" }), s = new this.OlStyleStrokeConstructor({ color: "#3399CC", width: 1.25 }); r = new this.OlStyleConstructor({ image: new this.OlStyleCircleConstructor({ fill: n, stroke: s, radius: 5 }), fill: n, stroke: s }); break; } } return r; } /** * Get the OL Style object from an GeoStyler-Style MarkSymbolizer. * * @param markSymbolizer A GeoStyler-Style MarkSymbolizer. * @return The OL Style object */ getOlPointSymbolizerFromMarkSymbolizer(t, o) { for (const c of Object.keys(t)) d(t[c]) && (t[c] = a.evaluateFunction(t[c], o)); const e = t.strokeColor, r = t.strokeOpacity, n = t.strokeWidth, s = t.color, i = t.fillOpacity, l = Number(t.rotate) * C, g = t.offset; let u; if (B(t.wellKnownName)) { const c = t.opacity, f = X(t); u = new this.OlStyleConstructor({ image: new this.OlStyleIconConstructor({ crossOrigin: "anonymous", ...g && { displacement: g }, ...a.checkOpacity(c) && { opacity: c }, ...l && { rotation: l }, scale: 1, src: tt(f) }) }); } else if (a.getIsFontGlyphBased(t)) { const c = e && a.checkOpacity(r) ? a.getRgbaColor(e, r) : e, f = s && a.checkOpacity(i) ? a.getRgbaColor(s, i) : s, y = new this.OlStyleStrokeConstructor({ ...c && { color: c }, ...n && { width: n } }), S = new this.OlStyleFillConstructor({ ...f && { color: f } }); u = new this.OlStyleConstructor({ text: new this.OlStyleTextConstructor({ text: a.getCharacterForMarkSymbolizer(t), font: a.getTextFontForMarkSymbolizer(t), ...S && { fill: S }, ...g && { offsetX: g[0] }, ...g && { offsetY: g[1] }, ...y && { stroke: y }, ...l && { rotation: l } }) }); } else throw new Error("MarkSymbolizer cannot be parsed. Unsupported WellKnownName."); return u; } /** * Get the OL Style object from an GeoStyler-Style IconSymbolizer. * * @param symbolizer A GeoStyler-Style IconSymbolizer. * @return The OL Style object */ getOlIconSymbolizerFromIconSymbolizer(t, o) { for (const l of Object.keys(t)) d(t[l]) && (t[l] = a.evaluateFunction(t[l], o)); const e = { src: k(t.image) ? t.image.source : t.image, crossOrigin: "anonymous", opacity: t.opacity, width: t.size, // Rotation in openlayers is radians while we use degree rotation: typeof t.rotate == "number" ? t.rotate * C : void 0, displacement: t.offset, size: k(t.image) ? t.image.size : void 0, offset: k(t.image) ? t.image.position : void 0 }, r = "\\{\\{", n = "\\}\\}", s = new RegExp(r + ".*?" + n, "g"); return (typeof t.image == "string" ? t.image.match(s) : null) ? (g) => { let u = a.resolveAttributeTemplate(g, t.image, ""); u || (u = t.image + ""); let c; return this.olIconStyleCache[u] ? (c = this.olIconStyleCache[u], e.rotation !== void 0 && c.setRotation(e.rotation), e.opacity !== void 0 && c.setOpacity(e.opacity)) : (c = new this.OlStyleIconConstructor({ ...e, src: u // order is important }), this.olIconStyleCache[u] = c), new this.OlStyleConstructor({ image: c }); } : new this.OlStyleConstructor({ image: new this.OlStyleIconConstructor({ ...e }) }); } /** * Get the OL Style object from an GeoStyler-Style LineSymbolizer. * * @param symbolizer A GeoStyler-Style LineSymbolizer. * @return The OL Style object */ getOlLineSymbolizerFromLineSymbolizer(t, o, e) { if (t.graphicStroke) return this.getOlGraphicStrokeFromGraphicStroke(t, o, e); for (const i of Object.keys(t)) d(t[i]) && (t[i] = a.evaluateFunction(t[i], o)); const r = t.color, n = t.opacity, s = r && n !== null && n !== void 0 ? a.getRgbaColor(r, n) : r; return new this.OlStyleConstructor({ stroke: new this.OlStyleStrokeConstructor({ color: s, width: t.width, lineCap: t.cap, lineJoin: t.join, lineDash: t.dasharray, lineDashOffset: t.dashOffset }) }); } getOlGraphicStrokeFromGraphicStroke(t, o, e) { const r = v.getLineStringsFromGeometry(o.getGeometry(), { LineString: this.OlLineStringContructor, MultiLineString: this.OlMultiLineStringConstructor, Polygon: this.OlPolygonConstructor, MultiPolygon: this.OlMultiPolygonConstructor }), n = t.graphicStroke, s = this.getSymbolSizeFromGraphicStroke(n, o); if (s <= 0) return console.warn("Symbol size must be greater than zero for graphic stroke. No graphic will be drawn."), []; const i = n.rotate, l = d(i) ? a.evaluateFunction(i, o) : i ?? 0, g = t.dasharray, u = t.dashOffset || 0, c = d(u) ? a.evaluateFunction(u, o) : u, f = (y) => this.getOlSymbolizerFromSymbolizer(y, o, e); return r.flatMap( (y) => v.processLineStringGraphicStroke( y, s, e, g, c, l, n, f, this.OlPointConstructor ) ); } /** * Get the size of a symbol from graphicStroke. * @param graphicStroke The graphicStroke from the LineSymbolizer. * @param feat The feature. * @returns The size of the symbol in pixels. */ getSymbolSizeFromGraphicStroke(t, o) { let e = 0; if (t.kind === "Mark") { const r = t.radius; if (d(r) ? e = a.evaluateFunction(r, o) * 2 : e = (r ?? 0) * 2, e <= 0) return e; const n = t.strokeWidth; d(n) ? e += a.evaluateFunction(n, o) : e += n ?? 0; } else if (t.kind === "Icon") { const r = t.size; d(r) ? e = a.evaluateFunction(r, o) : e = r ?? 0; } return e; } /** * Get the OL Style object from an GeoStyler-Style FillSymbolizer. * * @param symbolizer A GeoStyler-Style FillSymbolizer. * @return The OL Style object */ getOlPolygonSymbolizerFromFillSymbolizer(t, o) { for (const f of Object.keys(t)) d(t[f]) && (t[f] = a.evaluateFunction(t[f], o)); const e = t.color, r = t.fillOpacity, n = e && Number.isFinite(r) ? a.getRgbaColor(e, r) : e; let s = e ? new this.OlStyleFillConstructor({ color: n }) : void 0; const i = t.outlineColor, l = t.outlineOpacity, g = i && Number.isFinite(l) ? a.getRgbaColor(i, l) : i, u = i || t.outlineWidth ? new this.OlStyleStrokeConstructor({ color: g, width: t.outlineWidth, lineDash: t.outlineDasharray }) : void 0, c = new T({ fill: s, stroke: u }); if (t.graphicFill) { const f = this.getOlPatternFromGraphicFill(t.graphicFill); s || (s = new I({})), f && s.setColor(f), c.setFill(s); } return c; } /** * Get the pattern for a graphicFill. * * This creates a CanvasPattern based on the * properties of the given PointSymbolizer. Currently, * only IconSymbolizer and MarkSymbolizer are supported. * * @param graphicFill The Symbolizer that holds the pattern config. * @returns The created CanvasPattern, or null. */ getOlPatternFromGraphicFill(t) { if (!x(t) && !R(t)) return null; const o = document.createElement("canvas"), e = o.getContext("2d"); let r, n = [8, 8], s = 1.5, i = 1; if (x(t)) { const h = this.getOlIconSymbolizerFromIconSymbolizer(t), O = h?.getImage(); if (O?.load(), O?.getImageState() !== M.LOADED) return null; n = h.getImage().getSize(), r = n.map((m) => m * s), o.width = r[0], o.height = r[1]; const F = J(e, { size: r, pixelRatio: 1 }), p = r.map((m) => m / 2), w = new V(new P(p)); return F.drawFeature(w, h), e.createPattern(o, "repeat"); } const l = q(t), g = et.includes(String(t.wellKnownName)), u = $(t.wellKnownName), c = !t.strokeWidth && ot.includes(u), f = u.startsWith("dense"), y = t.rotate || 0; (g || c || f) && (y / C % 1 !== 0 && (i = Math.sin(y * C)), s = 1); const S = (t.radius ?? 4) * 2; return n = [S, S], r = n.map((h) => h * s * i), o.width = r[0], o.height = r[1], rt(l, e, r[0], y), e.createPattern(o, "repeat"); } /** * Get the OL StyleFunction object from an GeoStyler-Style TextSymbolizer. * * @param {TextSymbolizer} textSymbolizer A GeoStyler-Style TextSymbolizer. * @return {object} The OL StyleFunction */ getOlTextSymbolizerFromTextSymbolizer(t, o) { for (const h of Object.keys(t)) d(t[h]) && (t[h] = a.evaluateFunction(t[h], o)); const e = t.color; let r = t.placement; r || (r = "point"), r === "line-center" && (r = "line"); const n = t.opacity, s = e && Number.isFinite(n) ? a.getRgbaColor(e, n) : e, i = t.haloColor, l = t.haloWidth, g = i && Number.isFinite(n) ? a.getRgbaColor(i, n) : i, u = { font: a.getTextFont(t), fill: new this.OlStyleFillConstructor({ color: s }), stroke: new this.OlStyleStrokeConstructor({ color: g, width: l || 0 }), overflow: t.allowOverlap, offsetX: t.offset ? t.offset[0] : 0, offsetY: t.offset ? t.offset[1] : 0, rotation: typeof t.rotate == "number" ? t.rotate * C : void 0, placement: r, repeat: t.repeat // TODO check why props match // textAlign: symbolizer.pitchAlignment, // textBaseline: symbolizer.anchor }, c = "\\{\\{", f = "\\}\\}", y = new RegExp(c + ".*?" + f, "g"); let S; return E(t.label) || (S = t.label ? t.label.match(y) : null), S ? (O) => { const F = new this.OlStyleTextConstructor({ text: a.resolveAttributeTemplate(O, t.label, ""), ...u }); return new this.OlStyleConstructor({ text: F }); } : new this.OlStyleConstructor({ text: new this.OlStyleTextConstructor({ text: t.label, ...u }) }); } } export { It as OlStyleParser, It as default };