@geospatial-sdk/legend 0.0.5-dev.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +35 -0
- package/dist/create-legend/from-layer.d.ts +19 -0
- package/dist/create-legend/from-layer.d.ts.map +1 -0
- package/dist/create-legend/from-layer.js +129 -0
- package/dist/create-legend/index.d.ts +2 -0
- package/dist/create-legend/index.d.ts.map +1 -0
- package/dist/create-legend/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/lib/create-legend/from-layer.test.ts +148 -0
- package/lib/create-legend/from-layer.ts +159 -0
- package/lib/create-legend/index.ts +1 -0
- package/lib/index.ts +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Camptocamp
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# `legend`
|
|
2
|
+
|
|
3
|
+
> A library to get legend graphics from the map-context.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To install the package, use npm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @geospatial-sdk/legend
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createLegendFromLayer } from "@geospatial-sdk/legend";
|
|
17
|
+
|
|
18
|
+
const layer = {
|
|
19
|
+
type: "wms",
|
|
20
|
+
url: "https://example.com/wms",
|
|
21
|
+
name: "test-layer",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
createLegendFromLayer(layer).then((legendDiv) => {
|
|
25
|
+
document.body.appendChild(legendDiv);
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
For more detailed API documentation, see the [documentation website](https://camptocamp.github.io/geospatial-sdk/docs/).
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
For examples and demos, see the [examples website](https://camptocamp.github.io/geospatial-sdk/).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MapContextLayer } from "@geospatial-sdk/core";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for legend generation
|
|
4
|
+
*/
|
|
5
|
+
interface LegendOptions {
|
|
6
|
+
format?: string;
|
|
7
|
+
widthPxHint?: number;
|
|
8
|
+
heightPxHint?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a legend from a layer.
|
|
12
|
+
*
|
|
13
|
+
* @param {MapContextLayer} layer - The layer to create the legend from.
|
|
14
|
+
* @param {LegendOptions} [options] - The options to create the legend.
|
|
15
|
+
* @returns {Promise<HTMLElement | null>} A promise that resolves to the legend element or `null` if the legend could not be created.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createLegendFromLayer(layer: MapContextLayer, options?: LegendOptions): Promise<HTMLElement | null>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=from-layer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-layer.d.ts","sourceRoot":"","sources":["../../lib/create-legend/from-layer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAIhB,MAAM,sBAAsB,CAAC;AAG9B;;GAEG;AACH,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAoED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAiE7B"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { removeSearchParams, } from "@geospatial-sdk/core";
|
|
11
|
+
import { WmtsEndpoint } from "@camptocamp/ogc-client";
|
|
12
|
+
/**
|
|
13
|
+
* Create a legend URL for a WMS layer
|
|
14
|
+
*
|
|
15
|
+
* @param layer - The MapContextLayer to create a legend URL for
|
|
16
|
+
* @param options - Optional configuration for legend generation
|
|
17
|
+
* @returns A URL for the WMS legend graphic
|
|
18
|
+
*/
|
|
19
|
+
function createWmsLegendUrl(layer, options = {}) {
|
|
20
|
+
const { format = "image/png", widthPxHint, heightPxHint } = options;
|
|
21
|
+
const legendUrl = new URL(removeSearchParams(layer.url, [
|
|
22
|
+
"SERVICE",
|
|
23
|
+
"REQUEST",
|
|
24
|
+
"FORMAT",
|
|
25
|
+
"LAYER",
|
|
26
|
+
"LAYERTITLE",
|
|
27
|
+
"WIDTH",
|
|
28
|
+
"HEIGHT",
|
|
29
|
+
]));
|
|
30
|
+
legendUrl.searchParams.set("SERVICE", "WMS");
|
|
31
|
+
legendUrl.searchParams.set("REQUEST", "GetLegendGraphic");
|
|
32
|
+
legendUrl.searchParams.set("FORMAT", format);
|
|
33
|
+
legendUrl.searchParams.set("LAYER", layer.name);
|
|
34
|
+
legendUrl.searchParams.set("LAYERTITLE", false.toString()); // Disable layer title for QGIS Server
|
|
35
|
+
if (widthPxHint) {
|
|
36
|
+
legendUrl.searchParams.set("WIDTH", widthPxHint.toString());
|
|
37
|
+
}
|
|
38
|
+
if (heightPxHint) {
|
|
39
|
+
legendUrl.searchParams.set("HEIGHT", heightPxHint.toString());
|
|
40
|
+
}
|
|
41
|
+
return legendUrl;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a legend URL for a WMTS layer
|
|
45
|
+
*
|
|
46
|
+
* @param layer - The MapContextLayer to create a legend URL for
|
|
47
|
+
* @returns A URL for the WMTS legend graphic or null if not available
|
|
48
|
+
*/
|
|
49
|
+
function createWmtsLegendUrl(layer) {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const endpoint = yield new WmtsEndpoint(layer.url).isReady();
|
|
52
|
+
const layerByName = endpoint.getLayerByName(layer.name);
|
|
53
|
+
console.log("layerByName");
|
|
54
|
+
console.log(layerByName);
|
|
55
|
+
if (layerByName.styles &&
|
|
56
|
+
layerByName.styles.length > 0 &&
|
|
57
|
+
layerByName.styles[0].legendUrl) {
|
|
58
|
+
return layerByName.styles[0].legendUrl;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates a legend from a layer.
|
|
65
|
+
*
|
|
66
|
+
* @param {MapContextLayer} layer - The layer to create the legend from.
|
|
67
|
+
* @param {LegendOptions} [options] - The options to create the legend.
|
|
68
|
+
* @returns {Promise<HTMLElement | null>} A promise that resolves to the legend element or `null` if the legend could not be created.
|
|
69
|
+
*/
|
|
70
|
+
export function createLegendFromLayer(layer, options = {}) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
if ((layer.type !== "wms" && layer.type !== "wmts") ||
|
|
73
|
+
!layer.url ||
|
|
74
|
+
!layer.name) {
|
|
75
|
+
console.error("Invalid layer for legend creation");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Create a container for the legend
|
|
79
|
+
const legendDiv = document.createElement("div");
|
|
80
|
+
legendDiv.id = "legend";
|
|
81
|
+
legendDiv.setAttribute("role", "region");
|
|
82
|
+
legendDiv.setAttribute("aria-label", "Map Layer Legend");
|
|
83
|
+
legendDiv.classList.add("geosdk--legend-container");
|
|
84
|
+
const layerDiv = document.createElement("div");
|
|
85
|
+
layerDiv.classList.add("geosdk--legend-layer");
|
|
86
|
+
const layerTitle = document.createElement("h4");
|
|
87
|
+
layerTitle.textContent = layer.name;
|
|
88
|
+
layerTitle.classList.add("geosdk--legend-layer-label");
|
|
89
|
+
layerDiv.appendChild(layerTitle);
|
|
90
|
+
const img = document.createElement("img");
|
|
91
|
+
img.alt = `Legend for ${layer.name}`;
|
|
92
|
+
img.classList.add("geosdk--legend-layer-image");
|
|
93
|
+
// Error handling for failed image loading
|
|
94
|
+
img.onerror = (e) => {
|
|
95
|
+
console.warn(`Failed to load legend for layer: ${layer.name}`, e);
|
|
96
|
+
const errorMessage = document.createElement("span");
|
|
97
|
+
errorMessage.textContent = `Legend not available for ${layer.name}`;
|
|
98
|
+
layerDiv.replaceChild(errorMessage, img);
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
let legendUrl = null;
|
|
102
|
+
// Determine legend URL based on layer type
|
|
103
|
+
if (layer.type === "wms") {
|
|
104
|
+
legendUrl = createWmsLegendUrl(layer, options).toString();
|
|
105
|
+
}
|
|
106
|
+
else if (layer.type === "wmts") {
|
|
107
|
+
legendUrl = yield createWmtsLegendUrl(layer);
|
|
108
|
+
}
|
|
109
|
+
// If legend URL is available, set the image source
|
|
110
|
+
if (legendUrl) {
|
|
111
|
+
img.src = legendUrl;
|
|
112
|
+
layerDiv.appendChild(img);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const errorMessage = document.createElement("span");
|
|
116
|
+
errorMessage.textContent = `Legend not available for ${layer.name}`;
|
|
117
|
+
layerDiv.appendChild(errorMessage);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Error creating legend for layer ${layer.name}:`, error);
|
|
122
|
+
const errorMessage = document.createElement("span");
|
|
123
|
+
errorMessage.textContent = `Error loading legend for ${layer.name}`;
|
|
124
|
+
layerDiv.appendChild(errorMessage);
|
|
125
|
+
}
|
|
126
|
+
legendDiv.appendChild(layerDiv);
|
|
127
|
+
return legendDiv;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/create-legend/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createLegendFromLayer } from "./from-layer";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./create-legend";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createLegendFromLayer } from "./from-layer";
|
|
2
|
+
import {
|
|
3
|
+
MapContextLayer,
|
|
4
|
+
MapContextLayerWms,
|
|
5
|
+
MapContextLayerWmts,
|
|
6
|
+
} from "@geospatial-sdk/core";
|
|
7
|
+
import { WmtsEndpoint } from "@camptocamp/ogc-client";
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
vi.mock("@camptocamp/ogc-client", () => ({
|
|
11
|
+
WmtsEndpoint: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("createLegendFromLayer", () => {
|
|
15
|
+
const baseWmsLayer: MapContextLayerWms = {
|
|
16
|
+
type: "wms",
|
|
17
|
+
url: "https://example.com/wms",
|
|
18
|
+
name: "test-layer",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const baseWmtsLayer: MapContextLayerWmts = {
|
|
22
|
+
type: "wmts",
|
|
23
|
+
url: "https://example.com/wmts",
|
|
24
|
+
name: "test-wmts-layer",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
// Clear all mocks before each test
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("creates a legend for a valid WMS layer", async () => {
|
|
33
|
+
const result = await createLegendFromLayer(baseWmsLayer);
|
|
34
|
+
|
|
35
|
+
expect(result).toBeInstanceOf(HTMLElement);
|
|
36
|
+
|
|
37
|
+
const legendDiv = result as HTMLElement;
|
|
38
|
+
const img = legendDiv.querySelector("img");
|
|
39
|
+
const title = legendDiv.querySelector("h4");
|
|
40
|
+
|
|
41
|
+
expect(title?.textContent).toBe("test-layer");
|
|
42
|
+
expect(img).toBeTruthy();
|
|
43
|
+
expect(img?.src).toContain("REQUEST=GetLegendGraphic");
|
|
44
|
+
expect(img?.alt).toBe("Legend for test-layer");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("creates a legend for a valid WMS layer with custom options", async () => {
|
|
48
|
+
const result = await createLegendFromLayer(baseWmsLayer, {
|
|
49
|
+
format: "image/jpeg",
|
|
50
|
+
widthPxHint: 200,
|
|
51
|
+
heightPxHint: 100,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const img = (result as HTMLElement).querySelector("img");
|
|
55
|
+
|
|
56
|
+
expect(img?.src).toContain("FORMAT=image%2Fjpeg");
|
|
57
|
+
expect(img?.src).toContain("WIDTH=200");
|
|
58
|
+
expect(img?.src).toContain("HEIGHT=100");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("creates a legend for a valid WMTS layer with legend URL", async () => {
|
|
62
|
+
const mockLegendUrl = "https://example.com/legend.png";
|
|
63
|
+
const mockIsReady = {
|
|
64
|
+
getLayerByName: () => ({
|
|
65
|
+
styles: [{ legendUrl: mockLegendUrl }],
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Mock WmtsEndpoint
|
|
70
|
+
(WmtsEndpoint as any).mockImplementation(() => ({
|
|
71
|
+
isReady: () => Promise.resolve(mockIsReady),
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
const result = await createLegendFromLayer(baseWmtsLayer);
|
|
75
|
+
|
|
76
|
+
const img = (result as HTMLElement).querySelector("img");
|
|
77
|
+
|
|
78
|
+
expect(img?.src).toBe(mockLegendUrl);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("handles WMTS layer without legend URL", async () => {
|
|
82
|
+
const mockIsReady = {
|
|
83
|
+
getLayerByName: () => ({
|
|
84
|
+
styles: [],
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Mock WmtsEndpoint
|
|
89
|
+
(WmtsEndpoint as any).mockImplementation(() => ({
|
|
90
|
+
isReady: () => Promise.resolve(mockIsReady),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
const result = await createLegendFromLayer(baseWmtsLayer);
|
|
94
|
+
|
|
95
|
+
const errorSpan = (result as HTMLElement).querySelector("span");
|
|
96
|
+
|
|
97
|
+
expect(result).toBeInstanceOf(HTMLElement);
|
|
98
|
+
expect(errorSpan?.textContent).toBe(
|
|
99
|
+
"Legend not available for test-wmts-layer",
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns null for invalid layer type", async () => {
|
|
104
|
+
const invalidLayer = { ...baseWmsLayer, type: "invalid" as any };
|
|
105
|
+
|
|
106
|
+
const result = await createLegendFromLayer(invalidLayer);
|
|
107
|
+
|
|
108
|
+
expect(result).toBe(null);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns null for layer without URL", async () => {
|
|
112
|
+
const layerWithoutUrl = { ...baseWmsLayer, url: "" };
|
|
113
|
+
|
|
114
|
+
const result = await createLegendFromLayer(layerWithoutUrl);
|
|
115
|
+
|
|
116
|
+
expect(result).toBe(null);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("returns null for layer without name", async () => {
|
|
120
|
+
const layerWithoutName = { ...baseWmsLayer, name: "" };
|
|
121
|
+
|
|
122
|
+
const result = await createLegendFromLayer(layerWithoutName);
|
|
123
|
+
|
|
124
|
+
expect(result).toBe(null);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("handles image load error", async () => {
|
|
128
|
+
const result = await createLegendFromLayer(baseWmsLayer);
|
|
129
|
+
const img = (result as HTMLElement).querySelector("img");
|
|
130
|
+
|
|
131
|
+
if (img) {
|
|
132
|
+
const errorEvent = new Event("error");
|
|
133
|
+
img.dispatchEvent(errorEvent);
|
|
134
|
+
|
|
135
|
+
const errorSpan = (result as HTMLElement).querySelector("span");
|
|
136
|
+
expect(errorSpan?.textContent).toBe(
|
|
137
|
+
"Legend not available for test-layer",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("adds accessibility attributes", async () => {
|
|
143
|
+
const result = await createLegendFromLayer(baseWmsLayer);
|
|
144
|
+
|
|
145
|
+
expect(result.getAttribute("role")).toBe("region");
|
|
146
|
+
expect(result.getAttribute("aria-label")).toBe("Map Layer Legend");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MapContextLayer,
|
|
3
|
+
MapContextLayerWms,
|
|
4
|
+
MapContextLayerWmts,
|
|
5
|
+
removeSearchParams,
|
|
6
|
+
} from "@geospatial-sdk/core";
|
|
7
|
+
import { WmtsEndpoint } from "@camptocamp/ogc-client";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for legend generation
|
|
11
|
+
*/
|
|
12
|
+
interface LegendOptions {
|
|
13
|
+
format?: string;
|
|
14
|
+
widthPxHint?: number;
|
|
15
|
+
heightPxHint?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a legend URL for a WMS layer
|
|
20
|
+
*
|
|
21
|
+
* @param layer - The MapContextLayer to create a legend URL for
|
|
22
|
+
* @param options - Optional configuration for legend generation
|
|
23
|
+
* @returns A URL for the WMS legend graphic
|
|
24
|
+
*/
|
|
25
|
+
function createWmsLegendUrl(
|
|
26
|
+
layer: MapContextLayerWms,
|
|
27
|
+
options: LegendOptions = {},
|
|
28
|
+
): URL {
|
|
29
|
+
const { format = "image/png", widthPxHint, heightPxHint } = options;
|
|
30
|
+
|
|
31
|
+
const legendUrl = new URL(
|
|
32
|
+
removeSearchParams(layer.url, [
|
|
33
|
+
"SERVICE",
|
|
34
|
+
"REQUEST",
|
|
35
|
+
"FORMAT",
|
|
36
|
+
"LAYER",
|
|
37
|
+
"LAYERTITLE",
|
|
38
|
+
"WIDTH",
|
|
39
|
+
"HEIGHT",
|
|
40
|
+
]),
|
|
41
|
+
);
|
|
42
|
+
legendUrl.searchParams.set("SERVICE", "WMS");
|
|
43
|
+
legendUrl.searchParams.set("REQUEST", "GetLegendGraphic");
|
|
44
|
+
legendUrl.searchParams.set("FORMAT", format);
|
|
45
|
+
legendUrl.searchParams.set("LAYER", layer.name);
|
|
46
|
+
legendUrl.searchParams.set("LAYERTITLE", false.toString()); // Disable layer title for QGIS Server
|
|
47
|
+
|
|
48
|
+
if (widthPxHint) {
|
|
49
|
+
legendUrl.searchParams.set("WIDTH", widthPxHint.toString());
|
|
50
|
+
}
|
|
51
|
+
if (heightPxHint) {
|
|
52
|
+
legendUrl.searchParams.set("HEIGHT", heightPxHint.toString());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return legendUrl;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a legend URL for a WMTS layer
|
|
60
|
+
*
|
|
61
|
+
* @param layer - The MapContextLayer to create a legend URL for
|
|
62
|
+
* @returns A URL for the WMTS legend graphic or null if not available
|
|
63
|
+
*/
|
|
64
|
+
async function createWmtsLegendUrl(
|
|
65
|
+
layer: MapContextLayerWmts,
|
|
66
|
+
): Promise<string | null> {
|
|
67
|
+
const endpoint = await new WmtsEndpoint(layer.url).isReady();
|
|
68
|
+
|
|
69
|
+
const layerByName = endpoint.getLayerByName(layer.name);
|
|
70
|
+
console.log("layerByName");
|
|
71
|
+
console.log(layerByName);
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
layerByName.styles &&
|
|
75
|
+
layerByName.styles.length > 0 &&
|
|
76
|
+
layerByName.styles[0].legendUrl
|
|
77
|
+
) {
|
|
78
|
+
return layerByName.styles[0].legendUrl;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates a legend from a layer.
|
|
86
|
+
*
|
|
87
|
+
* @param {MapContextLayer} layer - The layer to create the legend from.
|
|
88
|
+
* @param {LegendOptions} [options] - The options to create the legend.
|
|
89
|
+
* @returns {Promise<HTMLElement | null>} A promise that resolves to the legend element or `null` if the legend could not be created.
|
|
90
|
+
*/
|
|
91
|
+
export async function createLegendFromLayer(
|
|
92
|
+
layer: MapContextLayer,
|
|
93
|
+
options: LegendOptions = {},
|
|
94
|
+
): Promise<HTMLElement | null> {
|
|
95
|
+
if (
|
|
96
|
+
(layer.type !== "wms" && layer.type !== "wmts") ||
|
|
97
|
+
!layer.url ||
|
|
98
|
+
!layer.name
|
|
99
|
+
) {
|
|
100
|
+
console.error("Invalid layer for legend creation");
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Create a container for the legend
|
|
105
|
+
const legendDiv = document.createElement("div");
|
|
106
|
+
legendDiv.id = "legend";
|
|
107
|
+
legendDiv.setAttribute("role", "region");
|
|
108
|
+
legendDiv.setAttribute("aria-label", "Map Layer Legend");
|
|
109
|
+
legendDiv.classList.add("geosdk--legend-container");
|
|
110
|
+
|
|
111
|
+
const layerDiv = document.createElement("div");
|
|
112
|
+
layerDiv.classList.add("geosdk--legend-layer");
|
|
113
|
+
|
|
114
|
+
const layerTitle = document.createElement("h4");
|
|
115
|
+
layerTitle.textContent = layer.name;
|
|
116
|
+
layerTitle.classList.add("geosdk--legend-layer-label");
|
|
117
|
+
layerDiv.appendChild(layerTitle);
|
|
118
|
+
|
|
119
|
+
const img = document.createElement("img");
|
|
120
|
+
img.alt = `Legend for ${layer.name}`;
|
|
121
|
+
img.classList.add("geosdk--legend-layer-image");
|
|
122
|
+
|
|
123
|
+
// Error handling for failed image loading
|
|
124
|
+
img.onerror = (e) => {
|
|
125
|
+
console.warn(`Failed to load legend for layer: ${layer.name}`, e);
|
|
126
|
+
const errorMessage = document.createElement("span");
|
|
127
|
+
errorMessage.textContent = `Legend not available for ${layer.name}`;
|
|
128
|
+
layerDiv.replaceChild(errorMessage, img);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
let legendUrl: string | null = null;
|
|
133
|
+
|
|
134
|
+
// Determine legend URL based on layer type
|
|
135
|
+
if (layer.type === "wms") {
|
|
136
|
+
legendUrl = createWmsLegendUrl(layer, options).toString();
|
|
137
|
+
} else if (layer.type === "wmts") {
|
|
138
|
+
legendUrl = await createWmtsLegendUrl(layer);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If legend URL is available, set the image source
|
|
142
|
+
if (legendUrl) {
|
|
143
|
+
img.src = legendUrl;
|
|
144
|
+
layerDiv.appendChild(img);
|
|
145
|
+
} else {
|
|
146
|
+
const errorMessage = document.createElement("span");
|
|
147
|
+
errorMessage.textContent = `Legend not available for ${layer.name}`;
|
|
148
|
+
layerDiv.appendChild(errorMessage);
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`Error creating legend for layer ${layer.name}:`, error);
|
|
152
|
+
const errorMessage = document.createElement("span");
|
|
153
|
+
errorMessage.textContent = `Error loading legend for ${layer.name}`;
|
|
154
|
+
layerDiv.appendChild(errorMessage);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
legendDiv.appendChild(layerDiv);
|
|
158
|
+
return legendDiv;
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createLegendFromLayer } from "./from-layer";
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./create-legend";
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geospatial-sdk/legend",
|
|
3
|
+
"version": "0.0.5-dev.31+6f70b18",
|
|
4
|
+
"description": "Get legend graphic from the map-context",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"legend"
|
|
7
|
+
],
|
|
8
|
+
"author": "ronitjadhav <ronit.jadhav@camptocamp.com>",
|
|
9
|
+
"homepage": "https://github.com/camptocamp/geospatial-sdk#readme",
|
|
10
|
+
"license": "BSD-3-Clause",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"typings": "dist/index.d.ts",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"directories": {
|
|
18
|
+
"lib": "lib"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/camptocamp/geospatial-sdk.git"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"build": "tsc"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@geospatial-sdk/core": "^0.0.5-dev.31+6f70b18"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/camptocamp/geospatial-sdk/issues"
|
|
37
|
+
},
|
|
38
|
+
"gitHead": "6f70b189e8ec6200579da5266000f4840e1bd430"
|
|
39
|
+
}
|