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.
Files changed (117) hide show
  1. package/dist/cjs/{lerc-IU-tWkVO.js → lerc-DQmE0Pfk.js} +1 -1
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. 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
  4. package/dist/cjs/ogm-alerts_11.cjs.entry.js +1 -1
  5. package/dist/cjs/ogm-viewer.cjs.js +1 -1
  6. package/dist/collection/components/ogm-attributes/ogm-attributes.js +1 -1
  7. package/dist/collection/components/ogm-attributes/ogm-attributes.test.js +24 -0
  8. package/dist/collection/components/ogm-image/ogm-image.js +14 -14
  9. package/dist/collection/components/ogm-map/ogm-map.js +135 -53
  10. package/dist/collection/components/ogm-preview/ogm-preview.js +13 -13
  11. package/dist/collection/components/ogm-preview/ogm-preview.test.js +5 -5
  12. package/dist/collection/components/ogm-previews/ogm-previews.js +29 -26
  13. package/dist/collection/components/ogm-previews/ogm-previews.test.js +1 -1
  14. package/dist/collection/lib/geometry.js +35 -1
  15. package/dist/collection/lib/geometry.test.js +90 -1
  16. package/dist/collection/lib/previewers/cog-deck.js +4 -4
  17. package/dist/collection/lib/previewers/cog.js +15 -11
  18. package/dist/collection/lib/previewers/cog.test.js +61 -0
  19. package/dist/collection/lib/previewers/geojson.js +15 -7
  20. package/dist/collection/lib/previewers/geojson.test.js +81 -0
  21. package/dist/collection/lib/previewers/map.js +51 -0
  22. package/dist/collection/lib/previewers/openindexmap.js +2 -2
  23. package/dist/collection/lib/previewers/pmtiles-raster.js +13 -9
  24. package/dist/collection/lib/previewers/pmtiles-vector.js +12 -51
  25. package/dist/collection/lib/previewers/pmtiles.test.js +62 -0
  26. package/dist/collection/lib/previewers/previewer.js +4 -3
  27. package/dist/collection/lib/previewers/raster.js +15 -11
  28. package/dist/collection/lib/previewers/tiled-vector.js +47 -0
  29. package/dist/collection/lib/previewers/tilejson-raster.js +27 -0
  30. package/dist/collection/lib/previewers/tilejson-vector.js +18 -0
  31. package/dist/collection/lib/previewers/tilejson.test.js +140 -0
  32. package/dist/collection/lib/previewers/vector.js +7 -4
  33. package/dist/collection/lib/previewers/wms.js +86 -6
  34. package/dist/collection/lib/previewers/wms.test.js +133 -0
  35. package/dist/collection/lib/previewers/wmts.js +31 -0
  36. package/dist/collection/lib/previewers/wmts.test.js +108 -0
  37. package/dist/collection/lib/{sources → resources}/cog.js +2 -2
  38. package/dist/collection/lib/{sources → resources}/geojson.js +3 -3
  39. package/dist/collection/lib/{sources → resources}/iiif-manifest.js +2 -2
  40. package/dist/collection/lib/{sources → resources}/iiif-manifest.test.js +3 -3
  41. package/dist/collection/lib/{sources → resources}/iiif.js +2 -2
  42. package/dist/collection/lib/{sources/maplibre.js → resources/map.js} +2 -2
  43. package/dist/collection/lib/{sources → resources}/openindexmap.js +2 -2
  44. package/dist/collection/lib/{sources → resources}/pmtiles.js +2 -2
  45. package/dist/collection/lib/{sources → resources}/raster.js +2 -2
  46. package/dist/collection/lib/{sources/source.js → resources/resource.js} +3 -3
  47. package/dist/collection/lib/{sources → resources}/tilejson.js +21 -4
  48. package/dist/collection/lib/{sources → resources}/tms.js +2 -2
  49. package/dist/collection/lib/{sources → resources}/vector.js +2 -2
  50. package/dist/collection/lib/{sources → resources}/wms.js +46 -6
  51. package/dist/collection/lib/resources/wms.test.js +63 -0
  52. package/dist/collection/lib/resources/wmts.js +317 -0
  53. package/dist/collection/lib/resources/wmts.test.js +235 -0
  54. package/dist/collection/lib/{sources → resources}/xyz.js +2 -2
  55. package/dist/esm/{lerc-yC3LNu-h.js → lerc-BQHa9iiK.js} +1 -1
  56. package/dist/esm/loader.js +1 -1
  57. 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
  58. package/dist/esm/ogm-alerts_11.entry.js +1 -1
  59. package/dist/esm/ogm-viewer.js +1 -1
  60. package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
  61. package/dist/ogm-viewer/p-667JHO1P.js +2822 -0
  62. package/dist/ogm-viewer/{p-_eTjrSO2.js → p-9T5uP6M8.js} +1 -1
  63. package/dist/ogm-viewer/{p-11cf948e.entry.js → p-c95acce0.entry.js} +1 -1
  64. package/dist/types/components/ogm-image/ogm-image.d.ts +2 -2
  65. package/dist/types/components/ogm-map/ogm-map.d.ts +12 -9
  66. package/dist/types/components/ogm-preview/ogm-preview.d.ts +2 -2
  67. package/dist/types/components/ogm-previews/ogm-previews.d.ts +2 -2
  68. package/dist/types/components.d.ts +6 -6
  69. package/dist/types/lib/geometry.d.ts +5 -1
  70. package/dist/types/lib/previewers/cog-deck.d.ts +3 -3
  71. package/dist/types/lib/previewers/cog.d.ts +6 -5
  72. package/dist/types/lib/previewers/cog.test.d.ts +1 -0
  73. package/dist/types/lib/previewers/geojson.d.ts +8 -4
  74. package/dist/types/lib/previewers/geojson.test.d.ts +1 -0
  75. package/dist/types/lib/previewers/{maplibre.d.ts → map.d.ts} +10 -7
  76. package/dist/types/lib/previewers/openindexmap.d.ts +2 -2
  77. package/dist/types/lib/previewers/pmtiles-raster.d.ts +2 -2
  78. package/dist/types/lib/previewers/pmtiles-vector.d.ts +4 -11
  79. package/dist/types/lib/previewers/pmtiles.test.d.ts +1 -0
  80. package/dist/types/lib/previewers/previewer.d.ts +3 -3
  81. package/dist/types/lib/previewers/raster.d.ts +10 -7
  82. package/dist/types/lib/previewers/tiled-vector.d.ts +11 -0
  83. package/dist/types/lib/previewers/tilejson-raster.d.ts +8 -0
  84. package/dist/types/lib/previewers/tilejson-vector.d.ts +6 -0
  85. package/dist/types/lib/previewers/tilejson.test.d.ts +1 -0
  86. package/dist/types/lib/previewers/vector.d.ts +9 -5
  87. package/dist/types/lib/previewers/wms.d.ts +17 -5
  88. package/dist/types/lib/previewers/wms.test.d.ts +1 -0
  89. package/dist/types/lib/previewers/wmts.d.ts +11 -0
  90. package/dist/types/lib/previewers/wmts.test.d.ts +1 -0
  91. package/dist/types/lib/{sources → resources}/cog.d.ts +2 -2
  92. package/dist/types/lib/{sources → resources}/geojson.d.ts +2 -2
  93. package/dist/types/lib/{sources → resources}/iiif-manifest.d.ts +2 -2
  94. package/dist/types/lib/resources/iiif-manifest.test.d.ts +1 -0
  95. package/dist/types/lib/resources/iiif.d.ts +5 -0
  96. package/dist/types/lib/{sources/maplibre.d.ts → resources/map.d.ts} +2 -2
  97. package/dist/types/lib/resources/openindexmap.d.ts +5 -0
  98. package/dist/types/lib/{sources → resources}/pmtiles.d.ts +2 -2
  99. package/dist/types/lib/{sources → resources}/raster.d.ts +2 -2
  100. package/dist/types/lib/{sources/source.d.ts → resources/resource.d.ts} +1 -1
  101. package/dist/types/lib/{sources → resources}/tilejson.d.ts +5 -2
  102. package/dist/types/lib/resources/tms.d.ts +5 -0
  103. package/dist/types/lib/{sources → resources}/vector.d.ts +2 -2
  104. package/dist/types/lib/{sources → resources}/wms.d.ts +15 -5
  105. package/dist/types/lib/resources/wms.test.d.ts +1 -0
  106. package/dist/types/lib/resources/wmts.d.ts +59 -0
  107. package/dist/types/lib/resources/wmts.test.d.ts +1 -0
  108. package/dist/types/lib/resources/xyz.d.ts +5 -0
  109. package/package.json +1 -1
  110. package/readme.md +1 -1
  111. package/dist/collection/lib/previewers/maplibre.js +0 -56
  112. package/dist/ogm-viewer/p-DPOGJxir.js +0 -2822
  113. package/dist/types/lib/sources/iiif.d.ts +0 -5
  114. package/dist/types/lib/sources/openindexmap.d.ts +0 -5
  115. package/dist/types/lib/sources/tms.d.ts +0 -5
  116. package/dist/types/lib/sources/xyz.d.ts +0 -5
  117. /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,5 +1,5 @@
1
- import RasterSource from "./raster";
2
- export default class CogSource extends RasterSource {
1
+ import RasterResource from "./raster";
2
+ export default class CogResource extends RasterResource {
3
3
  label() {
4
4
  return 'Cloud Optimized GeoTIFF';
5
5
  }
@@ -1,7 +1,7 @@
1
1
  import geojsonExtent from "@mapbox/geojson-extent";
2
- import VectorSource from "./vector";
2
+ import VectorResource from "./vector";
3
3
  import { fetchOrThrow } from "../errors";
4
- export default class GeoJSONSource extends VectorSource {
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 source
18
+ // GeoJSON is always a vector resource
19
19
  async isVector() {
20
20
  return true;
21
21
  }
@@ -1,7 +1,7 @@
1
- import IIIFSource from "./iiif";
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 IIIFManifestSource extends IIIFSource {
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 IIIFManifestSource from "./iiif-manifest";
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 IIIFManifestSource('test-id', MANIFEST_URL);
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('IIIFManifestSource', () => {
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 Source from "./source";
1
+ import Resource from "./resource";
2
2
  // A source of IIIF image URL(s) to be previewed
3
- export default class IIIFSource extends Source {
3
+ export default class IIIFResource extends Resource {
4
4
  label() {
5
5
  return 'IIIF Image';
6
6
  }
@@ -1,6 +1,6 @@
1
- import Source from "./source";
1
+ import Resource from "./resource";
2
2
  // A source of mapped vector or raster data, accessed remotely
3
- export default class MapLibreSource extends Source {
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 GeoJSONSource from "./geojson";
2
- export default class OpenIndexMapSource extends GeoJSONSource {
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 Source from "./source";
3
+ import Resource from "./resource";
4
4
  // Vector or raster tileset stored in a PMTiles archive at a URL
5
- export default class PMTilesSource extends Source {
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 MapLibreSource from "./maplibre";
1
+ import MapResource from "./map";
2
2
  // Tiled raster data, accessed remotely
3
- export default class RasterSource extends MapLibreSource {
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 Source {
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 source
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 sources, e.g.
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 Source from "./source";
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 TileJSONSource extends Source {
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 fetch(this.url);
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,5 +1,5 @@
1
- import RasterSource from "./raster";
2
- export default class TmsSource extends RasterSource {
1
+ import RasterResource from "./raster";
2
+ export default class TmsResource extends RasterResource {
3
3
  label() {
4
4
  return 'Tiled Map Service (TMS)';
5
5
  }
@@ -1,6 +1,6 @@
1
- import MapLibreSource from "./maplibre";
1
+ import MapResource from "./map";
2
2
  // A source of vector data, accessed remotely
3
- export default class VectorSource extends MapLibreSource {
3
+ export default class VectorResource extends MapResource {
4
4
  async isVector() {
5
5
  return true;
6
6
  }
@@ -1,21 +1,24 @@
1
- import RasterSource from "./raster";
2
- const defaultOptions = {
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
- srs: 'EPSG:3857',
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 WmsSource extends RasterSource {
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 = { ...defaultOptions, ...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.srs);
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
+ });