ogm-viewer 0.5.1 → 0.6.1
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/dist/cjs/{lerc-IU-tWkVO.js → lerc-DQmE0Pfk.js} +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BAGJpqMB.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-B_nGMI1e.js} +12000 -11333
- package/dist/cjs/ogm-alerts_11.cjs.entry.js +1 -1
- package/dist/cjs/ogm-viewer.cjs.js +1 -1
- package/dist/collection/components/ogm-attributes/ogm-attributes.js +1 -1
- package/dist/collection/components/ogm-attributes/ogm-attributes.test.js +24 -0
- package/dist/collection/components/ogm-image/ogm-image.js +14 -14
- package/dist/collection/components/ogm-map/ogm-map.js +135 -53
- package/dist/collection/components/ogm-preview/ogm-preview.js +13 -13
- package/dist/collection/components/ogm-preview/ogm-preview.test.js +5 -5
- package/dist/collection/components/ogm-previews/ogm-previews.js +29 -26
- package/dist/collection/components/ogm-previews/ogm-previews.test.js +1 -1
- package/dist/collection/lib/geometry.js +35 -1
- package/dist/collection/lib/geometry.test.js +90 -1
- package/dist/collection/lib/previewers/cog-deck.js +4 -4
- package/dist/collection/lib/previewers/cog.js +15 -11
- package/dist/collection/lib/previewers/cog.test.js +61 -0
- package/dist/collection/lib/previewers/geojson.js +15 -7
- package/dist/collection/lib/previewers/geojson.test.js +81 -0
- package/dist/collection/lib/previewers/map.js +51 -0
- package/dist/collection/lib/previewers/openindexmap.js +2 -2
- package/dist/collection/lib/previewers/pmtiles-raster.js +13 -9
- package/dist/collection/lib/previewers/pmtiles-vector.js +12 -51
- package/dist/collection/lib/previewers/pmtiles.test.js +62 -0
- package/dist/collection/lib/previewers/previewer.js +4 -3
- package/dist/collection/lib/previewers/raster.js +15 -11
- package/dist/collection/lib/previewers/tiled-vector.js +47 -0
- package/dist/collection/lib/previewers/tilejson-raster.js +27 -0
- package/dist/collection/lib/previewers/tilejson-vector.js +18 -0
- package/dist/collection/lib/previewers/tilejson.test.js +140 -0
- package/dist/collection/lib/previewers/vector.js +7 -4
- package/dist/collection/lib/previewers/wms.js +86 -6
- package/dist/collection/lib/previewers/wms.test.js +133 -0
- package/dist/collection/lib/previewers/wmts.js +31 -0
- package/dist/collection/lib/previewers/wmts.test.js +108 -0
- package/dist/collection/lib/{sources → resources}/cog.js +2 -2
- package/dist/collection/lib/{sources → resources}/geojson.js +3 -3
- package/dist/collection/lib/{sources → resources}/iiif-manifest.js +2 -2
- package/dist/collection/lib/{sources → resources}/iiif-manifest.test.js +3 -3
- package/dist/collection/lib/{sources → resources}/iiif.js +2 -2
- package/dist/collection/lib/{sources/maplibre.js → resources/map.js} +2 -2
- package/dist/collection/lib/{sources → resources}/openindexmap.js +2 -2
- package/dist/collection/lib/{sources → resources}/pmtiles.js +2 -2
- package/dist/collection/lib/{sources → resources}/raster.js +2 -2
- package/dist/collection/lib/{sources/source.js → resources/resource.js} +3 -3
- package/dist/collection/lib/{sources → resources}/tilejson.js +21 -4
- package/dist/collection/lib/{sources → resources}/tms.js +2 -2
- package/dist/collection/lib/{sources → resources}/vector.js +2 -2
- package/dist/collection/lib/{sources → resources}/wms.js +46 -6
- package/dist/collection/lib/resources/wms.test.js +63 -0
- package/dist/collection/lib/resources/wmts.js +317 -0
- package/dist/collection/lib/resources/wmts.test.js +235 -0
- package/dist/collection/lib/{sources → resources}/xyz.js +2 -2
- package/dist/esm/{lerc-yC3LNu-h.js → lerc-BQHa9iiK.js} +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BpVupgSB.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-DSguoh85.js} +12000 -11333
- package/dist/esm/ogm-alerts_11.entry.js +1 -1
- package/dist/esm/ogm-viewer.js +1 -1
- package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
- package/dist/ogm-viewer/p-667JHO1P.js +2822 -0
- package/dist/ogm-viewer/{p-_eTjrSO2.js → p-9T5uP6M8.js} +1 -1
- package/dist/ogm-viewer/{p-11cf948e.entry.js → p-c95acce0.entry.js} +1 -1
- package/dist/types/components/ogm-image/ogm-image.d.ts +2 -2
- package/dist/types/components/ogm-map/ogm-map.d.ts +12 -9
- package/dist/types/components/ogm-preview/ogm-preview.d.ts +2 -2
- package/dist/types/components/ogm-previews/ogm-previews.d.ts +2 -2
- package/dist/types/components.d.ts +6 -6
- package/dist/types/lib/geometry.d.ts +5 -1
- package/dist/types/lib/previewers/cog-deck.d.ts +3 -3
- package/dist/types/lib/previewers/cog.d.ts +6 -5
- package/dist/types/lib/previewers/cog.test.d.ts +1 -0
- package/dist/types/lib/previewers/geojson.d.ts +8 -4
- package/dist/types/lib/previewers/geojson.test.d.ts +1 -0
- package/dist/types/lib/previewers/{maplibre.d.ts → map.d.ts} +10 -7
- package/dist/types/lib/previewers/openindexmap.d.ts +2 -2
- package/dist/types/lib/previewers/pmtiles-raster.d.ts +2 -2
- package/dist/types/lib/previewers/pmtiles-vector.d.ts +4 -11
- package/dist/types/lib/previewers/pmtiles.test.d.ts +1 -0
- package/dist/types/lib/previewers/previewer.d.ts +3 -3
- package/dist/types/lib/previewers/raster.d.ts +10 -7
- package/dist/types/lib/previewers/tiled-vector.d.ts +11 -0
- package/dist/types/lib/previewers/tilejson-raster.d.ts +8 -0
- package/dist/types/lib/previewers/tilejson-vector.d.ts +6 -0
- package/dist/types/lib/previewers/tilejson.test.d.ts +1 -0
- package/dist/types/lib/previewers/vector.d.ts +9 -5
- package/dist/types/lib/previewers/wms.d.ts +17 -5
- package/dist/types/lib/previewers/wms.test.d.ts +1 -0
- package/dist/types/lib/previewers/wmts.d.ts +11 -0
- package/dist/types/lib/previewers/wmts.test.d.ts +1 -0
- package/dist/types/lib/{sources → resources}/cog.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/geojson.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/iiif-manifest.d.ts +2 -2
- package/dist/types/lib/resources/iiif-manifest.test.d.ts +1 -0
- package/dist/types/lib/resources/iiif.d.ts +5 -0
- package/dist/types/lib/{sources/maplibre.d.ts → resources/map.d.ts} +2 -2
- package/dist/types/lib/resources/openindexmap.d.ts +5 -0
- package/dist/types/lib/{sources → resources}/pmtiles.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/raster.d.ts +2 -2
- package/dist/types/lib/{sources/source.d.ts → resources/resource.d.ts} +1 -1
- package/dist/types/lib/{sources → resources}/tilejson.d.ts +5 -2
- package/dist/types/lib/resources/tms.d.ts +5 -0
- package/dist/types/lib/{sources → resources}/vector.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/wms.d.ts +15 -5
- package/dist/types/lib/resources/wms.test.d.ts +1 -0
- package/dist/types/lib/resources/wmts.d.ts +59 -0
- package/dist/types/lib/resources/wmts.test.d.ts +1 -0
- package/dist/types/lib/resources/xyz.d.ts +5 -0
- package/package.json +1 -1
- package/readme.md +1 -1
- package/dist/collection/lib/previewers/maplibre.js +0 -56
- package/dist/ogm-viewer/p-DPOGJxir.js +0 -2822
- package/dist/types/lib/sources/iiif.d.ts +0 -5
- package/dist/types/lib/sources/openindexmap.d.ts +0 -5
- package/dist/types/lib/sources/tms.d.ts +0 -5
- package/dist/types/lib/sources/xyz.d.ts +0 -5
- /package/dist/types/{lib/sources/iiif-manifest.test.d.ts → components/ogm-attributes/ogm-attributes.test.d.ts} +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@stencil/vitest";
|
|
2
|
+
import WmtsPreviewer from "./wmts";
|
|
3
|
+
import WmtsResource from "../resources/wmts";
|
|
4
|
+
// Just enough of a MapLibre map to record what the previewer adds
|
|
5
|
+
class FakeMap {
|
|
6
|
+
sources = new Map();
|
|
7
|
+
layers = new Map();
|
|
8
|
+
getSource(id) {
|
|
9
|
+
return this.sources.get(id);
|
|
10
|
+
}
|
|
11
|
+
addSource(id, spec) {
|
|
12
|
+
this.sources.set(id, spec);
|
|
13
|
+
}
|
|
14
|
+
removeSource(id) {
|
|
15
|
+
this.sources.delete(id);
|
|
16
|
+
}
|
|
17
|
+
getLayer(id) {
|
|
18
|
+
return this.layers.get(id);
|
|
19
|
+
}
|
|
20
|
+
addLayer(layer) {
|
|
21
|
+
// MapLibre refuses a layer whose source hasn't been added yet
|
|
22
|
+
if (!this.sources.has(layer.source))
|
|
23
|
+
throw new Error(`No source ${layer.source} for layer ${layer.id}`);
|
|
24
|
+
this.layers.set(layer.id, layer);
|
|
25
|
+
}
|
|
26
|
+
removeLayer(id) {
|
|
27
|
+
this.layers.delete(id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Reading the capabilities document is the resource's job and is tested there
|
|
31
|
+
class StubWmtsResource extends WmtsResource {
|
|
32
|
+
layers = [];
|
|
33
|
+
async getLayers() {
|
|
34
|
+
return this.layers;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// The previewer only reads the opacity
|
|
38
|
+
const style = { opacity: 0.8 };
|
|
39
|
+
// A layer served from several tile hosts, and one on a grid with its own size and limits
|
|
40
|
+
const LAYERS = [
|
|
41
|
+
{
|
|
42
|
+
id: 'lights',
|
|
43
|
+
title: 'Night Lights',
|
|
44
|
+
tileUrls: ['https://one.example.org/lights/{z}/{y}/{x}.png', 'https://two.example.org/lights/{z}/{y}/{x}.png'],
|
|
45
|
+
tileSize: 256,
|
|
46
|
+
minzoom: 0,
|
|
47
|
+
maxzoom: 8,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'ortho',
|
|
51
|
+
title: 'Orthophoto',
|
|
52
|
+
tileUrls: ['https://one.example.org/ortho/{z}/{y}/{x}.jpeg'],
|
|
53
|
+
tileSize: 512,
|
|
54
|
+
minzoom: 5,
|
|
55
|
+
maxzoom: 20,
|
|
56
|
+
bounds: [16.17, 48.1, 16.58, 48.33],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
let map;
|
|
60
|
+
let resource;
|
|
61
|
+
let previewer;
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
map = new FakeMap();
|
|
64
|
+
resource = new StubWmtsResource('night-lights', 'https://example.org/wmts/1.0.0/WMTSCapabilities.xml', { layerIds: [] });
|
|
65
|
+
resource.layers = LAYERS;
|
|
66
|
+
previewer = new WmtsPreviewer(resource, map, style);
|
|
67
|
+
await previewer.preview();
|
|
68
|
+
});
|
|
69
|
+
describe('WmtsPreviewer#preview', () => {
|
|
70
|
+
it('adds a source and a layer for each layer of the service', () => {
|
|
71
|
+
expect([...map.sources.keys()]).toEqual(['night-lights-lights', 'night-lights-ortho']);
|
|
72
|
+
expect([...map.layers.keys()]).toEqual(['night-lights-lights', 'night-lights-ortho']);
|
|
73
|
+
expect(map.layers.get('night-lights-ortho')?.source).toEqual('night-lights-ortho');
|
|
74
|
+
});
|
|
75
|
+
it('keeps every tile host the service offers', () => {
|
|
76
|
+
expect(map.sources.get('night-lights-lights')?.tiles).toEqual(LAYERS[0].tileUrls);
|
|
77
|
+
});
|
|
78
|
+
it('draws the tiles as XYZ rasters', () => {
|
|
79
|
+
const source = map.sources.get('night-lights-lights');
|
|
80
|
+
expect(source?.type).toEqual('raster');
|
|
81
|
+
expect(source?.scheme).toEqual('xyz');
|
|
82
|
+
});
|
|
83
|
+
it('carries the tile size of the grid rather than letting MapLibre default it to 512', () => {
|
|
84
|
+
expect(map.sources.get('night-lights-lights')?.tileSize).toEqual(256);
|
|
85
|
+
expect(map.sources.get('night-lights-ortho')?.tileSize).toEqual(512);
|
|
86
|
+
});
|
|
87
|
+
it('bounds the source to the zooms the grid defines', () => {
|
|
88
|
+
// Without these MapLibre keeps asking past the end of the grid instead of overzooming
|
|
89
|
+
// the deepest level it has
|
|
90
|
+
expect(map.sources.get('night-lights-lights')?.maxzoom).toEqual(8);
|
|
91
|
+
expect(map.sources.get('night-lights-ortho')?.minzoom).toEqual(5);
|
|
92
|
+
expect(map.sources.get('night-lights-ortho')?.maxzoom).toEqual(20);
|
|
93
|
+
});
|
|
94
|
+
it('bounds the source to the layer extent, and omits the key when there is none', () => {
|
|
95
|
+
// MapLibre validates the sources it's handed, so an absent extent has to be absent rather
|
|
96
|
+
// than undefined
|
|
97
|
+
expect(map.sources.get('night-lights-ortho')?.bounds).toEqual([16.17, 48.1, 16.58, 48.33]);
|
|
98
|
+
expect(map.sources.get('night-lights-lights')).not.toHaveProperty('bounds');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('WmtsPreviewer#clearPreview', () => {
|
|
102
|
+
it('removes every source and layer it added', async () => {
|
|
103
|
+
await previewer.clearPreview();
|
|
104
|
+
expect(map.sources.size).toEqual(0);
|
|
105
|
+
expect(map.layers.size).toEqual(0);
|
|
106
|
+
expect(previewer.layerIds).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import geojsonExtent from "@mapbox/geojson-extent";
|
|
2
|
-
import
|
|
2
|
+
import VectorResource from "./vector";
|
|
3
3
|
import { fetchOrThrow } from "../errors";
|
|
4
|
-
export default class
|
|
4
|
+
export default class GeoJsonResource extends VectorResource {
|
|
5
5
|
// Data parsed from GeoJSON document
|
|
6
6
|
data;
|
|
7
7
|
// Fetch and memoize data
|
|
@@ -15,7 +15,7 @@ export default class GeoJSONSource extends VectorSource {
|
|
|
15
15
|
label() {
|
|
16
16
|
return 'GeoJSON';
|
|
17
17
|
}
|
|
18
|
-
// GeoJSON is always a vector
|
|
18
|
+
// GeoJSON is always a vector resource
|
|
19
19
|
async isVector() {
|
|
20
20
|
return true;
|
|
21
21
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import IIIFResource from "./iiif";
|
|
2
2
|
import { fetchOrThrow } from "../errors";
|
|
3
3
|
// A manifest containing multiple IIIF image URLs for preview
|
|
4
|
-
export default class
|
|
4
|
+
export default class IIIFManifestResource extends IIIFResource {
|
|
5
5
|
// The parsed manifest contents
|
|
6
6
|
manifest;
|
|
7
7
|
label() {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach } from "@stencil/vitest";
|
|
2
|
-
import
|
|
2
|
+
import IIIFManifestResource from "./iiif-manifest";
|
|
3
3
|
const MANIFEST_URL = 'http://example.com/manifest.json';
|
|
4
4
|
// A source always points at a manifest URL; the manifest itself is fetched lazily
|
|
5
|
-
const createSource = () => new
|
|
5
|
+
const createSource = () => new IIIFManifestResource('test-id', MANIFEST_URL);
|
|
6
6
|
// A minimal IIIF v2 manifest with a single image
|
|
7
7
|
const v2Manifest = {
|
|
8
8
|
'@context': 'http://iiif.io/api/presentation/2/context.json',
|
|
@@ -60,7 +60,7 @@ const v3Manifest = {
|
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
};
|
|
63
|
-
describe('
|
|
63
|
+
describe('IIIFManifestResource', () => {
|
|
64
64
|
afterEach(() => vi.restoreAllMocks());
|
|
65
65
|
describe('getIIIFImageUrls', () => {
|
|
66
66
|
it('should fetch and extract image URLs from a IIIF v2 manifest', async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Resource from "./resource";
|
|
2
2
|
// A source of mapped vector or raster data, accessed remotely
|
|
3
|
-
export default class
|
|
3
|
+
export default class MapResource extends Resource {
|
|
4
4
|
// URL used to generate a MapLibre source when adding to map
|
|
5
5
|
getMapLibreSourceUrl() {
|
|
6
6
|
return this.url;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class
|
|
1
|
+
import GeoJsonResource from "./geojson";
|
|
2
|
+
export default class OpenIndexMapResource extends GeoJsonResource {
|
|
3
3
|
// Distinguish the layer name from regular GeoJSON
|
|
4
4
|
async getVectorLayers() {
|
|
5
5
|
return ['indexmap'];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Adapted from https://github.com/protomaps/PMTiles/blob/main/app/src/tileset.ts
|
|
2
2
|
import { PMTiles, TileType } from "pmtiles";
|
|
3
|
-
import
|
|
3
|
+
import Resource from "./resource";
|
|
4
4
|
// Vector or raster tileset stored in a PMTiles archive at a URL
|
|
5
|
-
export default class
|
|
5
|
+
export default class PMTilesResource extends Resource {
|
|
6
6
|
// PMTiles object for reading metadata and tiles from the archive
|
|
7
7
|
archive;
|
|
8
8
|
// Memoized PMTiles metadata and header
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import MapResource from "./map";
|
|
2
2
|
// Tiled raster data, accessed remotely
|
|
3
|
-
export default class
|
|
3
|
+
export default class RasterResource extends MapResource {
|
|
4
4
|
async isVector() {
|
|
5
5
|
return false;
|
|
6
6
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// A source of previewable data at a URL
|
|
2
|
-
export default class
|
|
2
|
+
export default class Resource {
|
|
3
3
|
// URL to the remote data source
|
|
4
4
|
url;
|
|
5
5
|
// Explicitly provided bounds for the source, if any
|
|
6
6
|
bounds;
|
|
7
|
-
// Unique ID for this
|
|
7
|
+
// Unique ID for this resource
|
|
8
8
|
id;
|
|
9
9
|
// Store the source URL
|
|
10
10
|
constructor(id, url, bounds) {
|
|
@@ -12,7 +12,7 @@ export default class Source {
|
|
|
12
12
|
this.url = url;
|
|
13
13
|
this.bounds = bounds;
|
|
14
14
|
}
|
|
15
|
-
// Used to label the tabs for switching between
|
|
15
|
+
// Used to label the tabs for switching between previews, e.g.
|
|
16
16
|
label() {
|
|
17
17
|
return this.constructor.name;
|
|
18
18
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { fetchOrThrow } from "../errors";
|
|
2
|
+
import MapResource from "./map";
|
|
2
3
|
// Vector or raster tileset described in a TileJSON document at a URL
|
|
3
|
-
export default class
|
|
4
|
+
export default class TileJsonResource extends MapResource {
|
|
4
5
|
// Metadata parsed from TileJSON document
|
|
5
6
|
metadata;
|
|
6
7
|
label() {
|
|
7
8
|
return 'TileJSON';
|
|
8
9
|
}
|
|
10
|
+
getMapLibreSourceUrl() {
|
|
11
|
+
return this.url;
|
|
12
|
+
}
|
|
13
|
+
getScheme() {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
9
16
|
// Fetch and memoize metadata
|
|
10
17
|
async getMetadata() {
|
|
11
18
|
if (!this.metadata) {
|
|
12
|
-
const resp = await
|
|
19
|
+
const resp = await fetchOrThrow(this.url);
|
|
13
20
|
this.metadata = await resp.json();
|
|
14
21
|
}
|
|
15
22
|
return this.metadata;
|
|
@@ -21,16 +28,26 @@ export default class TileJSONSource extends Source {
|
|
|
21
28
|
const pathname = new URL(template).pathname;
|
|
22
29
|
return pathname.endsWith('.pbf') || pathname.endsWith('.mvt') || pathname.endsWith('.mlt');
|
|
23
30
|
}
|
|
24
|
-
// Used to zoom the map to the data once loaded
|
|
31
|
+
// Used to zoom the map to the data once loaded; bounds are optional in TileJSON, and
|
|
32
|
+
// without them we leave the camera wherever it already is
|
|
25
33
|
async getBounds() {
|
|
26
34
|
if (this.bounds)
|
|
27
35
|
return this.bounds;
|
|
28
36
|
const metadata = await this.getMetadata();
|
|
37
|
+
if (!metadata.bounds)
|
|
38
|
+
return undefined;
|
|
29
39
|
return [
|
|
30
40
|
[metadata.bounds[0], metadata.bounds[1]],
|
|
31
41
|
[metadata.bounds[2], metadata.bounds[3]],
|
|
32
42
|
];
|
|
33
43
|
}
|
|
44
|
+
// TileJSON has no tileSize of its own, though some services add one to the document. Raster
|
|
45
|
+
// tiles served from a {z}/{x}/{y} template are 256px by convention, so assume that otherwise;
|
|
46
|
+
// MapLibre would default to 512 and draw them upscaled and blurry.
|
|
47
|
+
async getTileSize() {
|
|
48
|
+
const metadata = await this.getMetadata();
|
|
49
|
+
return metadata.tileSize ?? 256;
|
|
50
|
+
}
|
|
34
51
|
async getVectorLayers() {
|
|
35
52
|
const metadata = await this.getMetadata();
|
|
36
53
|
return metadata.vector_layers.map((l) => l.id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import MapResource from "./map";
|
|
2
2
|
// A source of vector data, accessed remotely
|
|
3
|
-
export default class
|
|
3
|
+
export default class VectorResource extends MapResource {
|
|
4
4
|
async isVector() {
|
|
5
5
|
return true;
|
|
6
6
|
}
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import RasterResource from "./raster";
|
|
2
|
+
// Number of features to ask for when inspecting; the spec defaults this to 1, which
|
|
3
|
+
// hides everything under the cursor but the topmost feature
|
|
4
|
+
const DEFAULT_FEATURE_COUNT = 10;
|
|
5
|
+
const defaultWmsOptions = {
|
|
3
6
|
layerIds: [],
|
|
4
7
|
bbox: '{bbox-epsg-3857}',
|
|
5
|
-
|
|
8
|
+
crs: 'EPSG:3857',
|
|
6
9
|
tileSize: 256,
|
|
7
10
|
format: 'image/png',
|
|
8
11
|
transparent: true,
|
|
9
12
|
version: '1.3.0',
|
|
10
13
|
};
|
|
11
14
|
// Data accessed via WMS GetMap requests, which return raster tiles
|
|
12
|
-
export default class
|
|
15
|
+
export default class WmsResource extends RasterResource {
|
|
13
16
|
options;
|
|
14
17
|
// Memoized metadata via GetCapabilities request
|
|
15
18
|
metadata;
|
|
16
19
|
constructor(id, url, options, bounds) {
|
|
17
20
|
super(id, url, bounds);
|
|
18
|
-
this.options = { ...
|
|
21
|
+
this.options = { ...defaultWmsOptions, ...options };
|
|
19
22
|
// Assume we're using one layer with the given ID if no layer IDs are provided
|
|
20
23
|
if (!this.options.layerIds || this.options.layerIds.length === 0) {
|
|
21
24
|
this.options.layerIds = [id];
|
|
@@ -43,6 +46,9 @@ export default class WmsSource extends RasterSource {
|
|
|
43
46
|
getTileSize() {
|
|
44
47
|
return this.options.tileSize;
|
|
45
48
|
}
|
|
49
|
+
async inspect(options) {
|
|
50
|
+
return await fetch(this.inspectUrl(options));
|
|
51
|
+
}
|
|
46
52
|
// WMS GetMap URL that will fetch tiles for this source
|
|
47
53
|
get tilesUrl() {
|
|
48
54
|
const tilesUrl = new URL(this.url);
|
|
@@ -54,7 +60,7 @@ export default class WmsSource extends RasterSource {
|
|
|
54
60
|
tilesUrl.searchParams.set('width', String(this.options.tileSize));
|
|
55
61
|
tilesUrl.searchParams.set('height', String(this.options.tileSize));
|
|
56
62
|
tilesUrl.searchParams.set('transparent', String(this.options.transparent));
|
|
57
|
-
tilesUrl.searchParams.set('srs', this.options.
|
|
63
|
+
tilesUrl.searchParams.set(this.isVersion130 ? 'crs' : 'srs', this.options.crs);
|
|
58
64
|
tilesUrl.searchParams.set('format', this.options.format);
|
|
59
65
|
tilesUrl.searchParams.set('version', this.options.version);
|
|
60
66
|
// This param can't be encoded because MapLibre needs to template it
|
|
@@ -62,6 +68,40 @@ export default class WmsSource extends RasterSource {
|
|
|
62
68
|
tilesUrlString += `&bbox=${this.options.bbox}`;
|
|
63
69
|
return tilesUrlString;
|
|
64
70
|
}
|
|
71
|
+
// WMS GetFeatureInfo URL, used to fetch information about features at a specific point
|
|
72
|
+
inspectUrl(inspectOptions) {
|
|
73
|
+
const inspectUrl = new URL(this.url);
|
|
74
|
+
// Merge the provided options with the instance's options
|
|
75
|
+
const options = { ...this.options, ...inspectOptions };
|
|
76
|
+
inspectUrl.searchParams.set('service', 'WMS');
|
|
77
|
+
inspectUrl.searchParams.set('request', 'GetFeatureInfo');
|
|
78
|
+
inspectUrl.searchParams.set('info_format', 'application/json');
|
|
79
|
+
inspectUrl.searchParams.set('layers', options.layerIds.join(','));
|
|
80
|
+
inspectUrl.searchParams.set('query_layers', options.layerIds.join(','));
|
|
81
|
+
inspectUrl.searchParams.set('version', options.version);
|
|
82
|
+
inspectUrl.searchParams.set('bbox', options.bbox);
|
|
83
|
+
inspectUrl.searchParams.set('width', String(options.width));
|
|
84
|
+
inspectUrl.searchParams.set('height', String(options.height));
|
|
85
|
+
inspectUrl.searchParams.set('feature_count', String(DEFAULT_FEATURE_COUNT));
|
|
86
|
+
// 1.3.0 renamed SRS to CRS, and renamed the pixel coordinates X,Y to I,J. Servers
|
|
87
|
+
// do enforce the names for the version they were asked for, so pick them to match.
|
|
88
|
+
if (this.isVersion130) {
|
|
89
|
+
inspectUrl.searchParams.set('crs', options.crs);
|
|
90
|
+
inspectUrl.searchParams.set('i', String(Math.round(options.x)));
|
|
91
|
+
inspectUrl.searchParams.set('j', String(Math.round(options.y)));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
inspectUrl.searchParams.set('srs', options.crs);
|
|
95
|
+
inspectUrl.searchParams.set('x', String(Math.round(options.x)));
|
|
96
|
+
inspectUrl.searchParams.set('y', String(Math.round(options.y)));
|
|
97
|
+
}
|
|
98
|
+
return inspectUrl.toString();
|
|
99
|
+
}
|
|
100
|
+
// Whether this source speaks WMS 1.3.0 or later, which changed some param names
|
|
101
|
+
get isVersion130() {
|
|
102
|
+
const [major, minor] = this.options.version.split('.').map(Number);
|
|
103
|
+
return major > 1 || (major === 1 && minor >= 3);
|
|
104
|
+
}
|
|
65
105
|
// WMS GetCapabilities URL, used to fetch metadata about the layers
|
|
66
106
|
get capabilitiesUrl() {
|
|
67
107
|
const capabilitiesUrl = new URL(this.url);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from "@stencil/vitest";
|
|
2
|
+
import WmsResource from "./wms";
|
|
3
|
+
// The GetFeatureInfo URL is built by a protected method, so expose it for testing
|
|
4
|
+
class TestWmsSource extends WmsResource {
|
|
5
|
+
url_for(options) {
|
|
6
|
+
return new URL(this.inspectUrl(options));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const ENDPOINT = 'https://geoservices.lib.berkeley.edu/geoserver/wms';
|
|
10
|
+
// A click in the middle of a 51x51 pixel window over Calaveras County
|
|
11
|
+
const options = {
|
|
12
|
+
bbox: '-13416701.09,4600766.56,-13411296.18,4606171.48',
|
|
13
|
+
width: 51,
|
|
14
|
+
height: 51,
|
|
15
|
+
x: 25,
|
|
16
|
+
y: 25,
|
|
17
|
+
};
|
|
18
|
+
describe('WmsSource#inspectUrl', () => {
|
|
19
|
+
it('uses the 1.3.0 param names by default', () => {
|
|
20
|
+
const source = new TestWmsSource('s7st30', ENDPOINT, { layerIds: [] });
|
|
21
|
+
const params = source.url_for(options).searchParams;
|
|
22
|
+
expect(params.get('version')).toEqual('1.3.0');
|
|
23
|
+
// 1.3.0 spells these CRS and I,J rather than SRS and X,Y
|
|
24
|
+
expect(params.get('crs')).toEqual('EPSG:3857');
|
|
25
|
+
expect(params.get('i')).toEqual('25');
|
|
26
|
+
expect(params.get('j')).toEqual('25');
|
|
27
|
+
expect(params.get('srs')).toBeNull();
|
|
28
|
+
expect(params.get('x')).toBeNull();
|
|
29
|
+
expect(params.get('y')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
it('uses the older param names for 1.1.1', () => {
|
|
32
|
+
const source = new TestWmsSource('s7st30', ENDPOINT, { layerIds: [], version: '1.1.1' });
|
|
33
|
+
const params = source.url_for(options).searchParams;
|
|
34
|
+
expect(params.get('srs')).toEqual('EPSG:3857');
|
|
35
|
+
expect(params.get('x')).toEqual('25');
|
|
36
|
+
expect(params.get('y')).toEqual('25');
|
|
37
|
+
expect(params.get('crs')).toBeNull();
|
|
38
|
+
expect(params.get('i')).toBeNull();
|
|
39
|
+
expect(params.get('j')).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it('passes through the query window and asks about the layer', () => {
|
|
42
|
+
const source = new TestWmsSource('s7st30', ENDPOINT, { layerIds: [] });
|
|
43
|
+
const params = source.url_for(options).searchParams;
|
|
44
|
+
expect(params.get('request')).toEqual('GetFeatureInfo');
|
|
45
|
+
expect(params.get('bbox')).toEqual(options.bbox);
|
|
46
|
+
expect(params.get('width')).toEqual('51');
|
|
47
|
+
expect(params.get('height')).toEqual('51');
|
|
48
|
+
// Defaults the layer to the source ID, and asks about it rather than just drawing it
|
|
49
|
+
expect(params.get('layers')).toEqual('s7st30');
|
|
50
|
+
expect(params.get('query_layers')).toEqual('s7st30');
|
|
51
|
+
});
|
|
52
|
+
it('asks for more than the one feature the spec defaults to', () => {
|
|
53
|
+
const source = new TestWmsSource('s7st30', ENDPOINT, { layerIds: [] });
|
|
54
|
+
const params = source.url_for(options).searchParams;
|
|
55
|
+
expect(Number(params.get('feature_count'))).toBeGreaterThan(1);
|
|
56
|
+
});
|
|
57
|
+
it('rounds pixel coordinates, which must be integers', () => {
|
|
58
|
+
const source = new TestWmsSource('s7st30', ENDPOINT, { layerIds: [] });
|
|
59
|
+
const params = source.url_for({ ...options, x: 25.4, y: 25.6 }).searchParams;
|
|
60
|
+
expect(params.get('i')).toEqual('25');
|
|
61
|
+
expect(params.get('j')).toEqual('26');
|
|
62
|
+
});
|
|
63
|
+
});
|