diff --git a/package.json b/package.json index 51a5f6b..af379f8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "leaflet-html", "type": "module", - "version": "0.13.5", + "version": "0.13.6", "description": "Leaflet maps expressed in HTML suitable for HTMX", "keywords": [ "leaflet", diff --git a/src/grid-layer.js b/src/grid-layer.js new file mode 100644 index 0000000..aac74a5 --- /dev/null +++ b/src/grid-layer.js @@ -0,0 +1,16 @@ +// Helpers to support GridLayer inherited Leaflet functions +import { point } from "leaflet"; + +/** + * @param {Element} el + * @returns {import("leaflet").GridLayerOptions} + */ +export const gridLayerOptions = (el) => { + const options = {}; + const text = el.getAttribute("tile-size"); + if (text) { + const number = parseInt(text); + options["tileSize"] = isNaN(number) ? point(JSON.parse(text)) : number; + } + return options; +}; diff --git a/src/l-tile-layer-wms.js b/src/l-tile-layer-wms.js index 5cbd327..ebc7e6e 100644 --- a/src/l-tile-layer-wms.js +++ b/src/l-tile-layer-wms.js @@ -3,6 +3,7 @@ import { tileLayer } from "leaflet"; import LLayer from "./l-layer.js"; import { layerConnected } from "./events.js"; import { htmlAttribute, optional, parse, partial } from "./parse.js"; +import { gridLayerOptions } from "./grid-layer.js"; class LTileLayerWMS extends LLayer { static get observedAttributes() { @@ -69,10 +70,14 @@ class LTileLayerWMS extends LLayer { paneOptions["pane"] = this.parentElement.getAttribute("name"); } + // GridLayer options + const gridOptions = gridLayerOptions(this); + this.layer = tileLayer.wms(urlTemplate, { ...standardOptions, ...nonStandardOptions(), ...paneOptions, + ...gridOptions, }); const event = new CustomEvent(layerConnected, { detail: { name, layer: this.layer }, diff --git a/src/l-tile-layer-wms.test.js b/src/l-tile-layer-wms.test.js index 8663534..8bece42 100644 --- a/src/l-tile-layer-wms.test.js +++ b/src/l-tile-layer-wms.test.js @@ -1,5 +1,5 @@ // @vitest-environment happy-dom -import { tileLayer } from "leaflet"; +import { point, tileLayer } from "leaflet"; import { it, expect } from "vitest"; import { layerConnected } from "./events"; import "./index"; @@ -167,3 +167,20 @@ it("should not reload the layer when non-options attributes are changed", async expect(detail.layer.options.bbox).toBe("coords ere"); expect(layerConnectedEventEmittedCount).toBe(1); // initial layer creation only }); + +it.each([ + ["512", 512], + ["[256, 512]", point({ x: 256, y: 512 })], + ['{"x": 256, "y": 512}', point({ x: 256, y: 512 })], +])("should support tile-size attribute", (text, tileSize) => { + const baseUrl = "/"; + const layers = "layer-1"; + const el = document.createElement("l-tile-layer-wms"); + el.setAttribute("url-template", baseUrl); + el.setAttribute("layers", layers); + el.setAttribute("tile-size", text); + document.body.appendChild(el); + const actual = el.layer; + const expected = tileLayer.wms(baseUrl, { layers, tileSize }); + expect(actual).toEqual(expected); +}); diff --git a/src/l-tile-layer.js b/src/l-tile-layer.js index 8185645..b8de34a 100644 --- a/src/l-tile-layer.js +++ b/src/l-tile-layer.js @@ -3,6 +3,7 @@ import { tileLayer } from "leaflet"; import { layerConnected } from "./events.js"; import LLayer from "./l-layer.js"; import { htmlAttribute, optional, parse, partial } from "./parse.js"; +import { gridLayerOptions } from "./grid-layer.js"; class LTileLayer extends LLayer { constructor() { @@ -11,33 +12,42 @@ class LTileLayer extends LLayer { } connectedCallback() { - const urlTemplate = parse(htmlAttribute("url-template"), this) - + const urlTemplate = parse(htmlAttribute("url-template"), this); + // Template attributes - const urlAttributes = LTileLayer.parseTemplateAttributes(urlTemplate) - const templateOptions = {} + const urlAttributes = LTileLayer.parseTemplateAttributes(urlTemplate); + const templateOptions = {}; for (const attribute of urlAttributes) { - const value = this.getAttribute(attribute) + const value = this.getAttribute(attribute); if (value !== null) { - templateOptions[attribute] = value + templateOptions[attribute] = value; } } // Pane options - const paneOptions = {} + const paneOptions = {}; // Support parent element if (this.parentElement.tagName.toLowerCase() === "l-pane") { - paneOptions["pane"] = this.parentElement.getAttribute("name") + paneOptions["pane"] = this.parentElement.getAttribute("name"); } - + // Options const name = this.getAttribute("name"); const schema = partial({ attribution: optional(htmlAttribute("attribution")), - errorTileUrl: optional(htmlAttribute("error-tile-url")) - }) - const options = parse(schema, this) - this.layer = tileLayer(urlTemplate, { ...templateOptions, ...paneOptions, ...options }); + errorTileUrl: optional(htmlAttribute("error-tile-url")), + }); + const options = parse(schema, this); + + // GridLayer options + const gridOptions = gridLayerOptions(this); + + this.layer = tileLayer(urlTemplate, { + ...templateOptions, + ...paneOptions, + ...options, + ...gridOptions, + }); const event = new CustomEvent(layerConnected, { detail: { name, layer: this.layer }, bubbles: true, @@ -50,8 +60,8 @@ class LTileLayer extends LLayer { * @returns {string[]} */ static parseTemplateAttributes(urlTemplate) { - const regex = /{(.*?)}/g - return [...urlTemplate.matchAll(regex)].map(match => match[1]) + const regex = /{(.*?)}/g; + return [...urlTemplate.matchAll(regex)].map((match) => match[1]); } } diff --git a/src/l-tile-layer.test.js b/src/l-tile-layer.test.js index cf1940b..fa00595 100644 --- a/src/l-tile-layer.test.js +++ b/src/l-tile-layer.test.js @@ -27,27 +27,52 @@ it("should cover l-tile-layer", async () => { it.each([ ["/tile/{z}/{x}/{y}.png?key={key}", "key", "value"], ["/tile/{z}/{x}/{y}.png?key={camelCase}", "camelCase", "value"], - ["/tile/{z}/{x}/{y}.png?key={kebab-case}", "kebab-case", "value"] + ["/tile/{z}/{x}/{y}.png?key={kebab-case}", "kebab-case", "value"], ])("should perform arbitrary templating %s %s", (urlTemplate, key, value) => { const el = document.createElement("l-tile-layer"); el.setAttribute("url-template", urlTemplate); - el.setAttribute(key, value) + el.setAttribute(key, value); document.body.appendChild(el); - const actual = el.layer - const expected = tileLayer(urlTemplate, { [key]: value }) - expect(actual).toEqual(expected) -}) + const actual = el.layer; + const expected = tileLayer(urlTemplate, { [key]: value }); + expect(actual).toEqual(expected); +}); it("should support error-tile-url", () => { - const urlTemplate = "/{z}/{x}/{y}.png" - const errorTileUrl = "/error.png" + const urlTemplate = "/{z}/{x}/{y}.png"; + const errorTileUrl = "/error.png"; const el = document.createElement("l-tile-layer"); el.setAttribute("url-template", urlTemplate); - el.setAttribute("error-tile-url", errorTileUrl) + el.setAttribute("error-tile-url", errorTileUrl); document.body.appendChild(el); - const actual = el.layer - const expected = tileLayer(urlTemplate, { errorTileUrl }) - expect(actual).toEqual(expected) -}) + const actual = el.layer; + const expected = tileLayer(urlTemplate, { errorTileUrl }); + expect(actual).toEqual(expected); +}); + +it.each([ + ["512", 512], + ["[256, 512]", { x: 256, y: 512 }], + ['{"x": 256, "y": 512}', { x: 256, y: 512 }], +])("should support tile-size attribute", (text, value) => { + const urlTemplate = "/"; + const el = document.createElement("l-tile-layer"); + el.setAttribute("url-template", urlTemplate); + el.setAttribute("tile-size", text); + document.body.appendChild(el); + const actual = el.layer; + const expected = tileLayer(urlTemplate, { tileSize: value }); + expect(actual).toEqual(expected); +}); + +it("should support tile-size attribute default value", () => { + const urlTemplate = "/"; + const el = document.createElement("l-tile-layer"); + el.setAttribute("url-template", urlTemplate); + document.body.appendChild(el); + const actual = el.layer; + const expected = tileLayer(urlTemplate, {}); + expect(actual).toEqual(expected); +});