ogm-viewer 0.5.1 → 0.6.0

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 (107) hide show
  1. package/dist/cjs/{lerc-IU-tWkVO.js → lerc-XQuzL9p2.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-B4jkHqvr.js} +11302 -10648
  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-image/ogm-image.js +14 -14
  7. package/dist/collection/components/ogm-map/ogm-map.js +135 -53
  8. package/dist/collection/components/ogm-preview/ogm-preview.js +13 -13
  9. package/dist/collection/components/ogm-preview/ogm-preview.test.js +5 -5
  10. package/dist/collection/components/ogm-previews/ogm-previews.js +29 -26
  11. package/dist/collection/components/ogm-previews/ogm-previews.test.js +1 -1
  12. package/dist/collection/lib/geometry.js +35 -1
  13. package/dist/collection/lib/geometry.test.js +90 -1
  14. package/dist/collection/lib/previewers/cog-deck.js +4 -4
  15. package/dist/collection/lib/previewers/cog.js +11 -11
  16. package/dist/collection/lib/previewers/geojson.js +10 -7
  17. package/dist/collection/lib/previewers/map.js +51 -0
  18. package/dist/collection/lib/previewers/openindexmap.js +2 -2
  19. package/dist/collection/lib/previewers/pmtiles-raster.js +9 -9
  20. package/dist/collection/lib/previewers/pmtiles-vector.js +12 -51
  21. package/dist/collection/lib/previewers/previewer.js +4 -3
  22. package/dist/collection/lib/previewers/raster.js +15 -11
  23. package/dist/collection/lib/previewers/tiled-vector.js +47 -0
  24. package/dist/collection/lib/previewers/tilejson-raster.js +27 -0
  25. package/dist/collection/lib/previewers/tilejson-vector.js +18 -0
  26. package/dist/collection/lib/previewers/tilejson.test.js +140 -0
  27. package/dist/collection/lib/previewers/vector.js +7 -4
  28. package/dist/collection/lib/previewers/wms.js +86 -6
  29. package/dist/collection/lib/previewers/wms.test.js +133 -0
  30. package/dist/collection/lib/previewers/wmts.js +31 -0
  31. package/dist/collection/lib/previewers/wmts.test.js +108 -0
  32. package/dist/collection/lib/{sources → resources}/cog.js +2 -2
  33. package/dist/collection/lib/{sources → resources}/geojson.js +3 -3
  34. package/dist/collection/lib/{sources → resources}/iiif-manifest.js +2 -2
  35. package/dist/collection/lib/{sources → resources}/iiif-manifest.test.js +3 -3
  36. package/dist/collection/lib/{sources → resources}/iiif.js +2 -2
  37. package/dist/collection/lib/{sources/maplibre.js → resources/map.js} +2 -2
  38. package/dist/collection/lib/{sources → resources}/openindexmap.js +2 -2
  39. package/dist/collection/lib/{sources → resources}/pmtiles.js +2 -2
  40. package/dist/collection/lib/{sources → resources}/raster.js +2 -2
  41. package/dist/collection/lib/{sources/source.js → resources/resource.js} +3 -3
  42. package/dist/collection/lib/{sources → resources}/tilejson.js +21 -4
  43. package/dist/collection/lib/{sources → resources}/tms.js +2 -2
  44. package/dist/collection/lib/{sources → resources}/vector.js +2 -2
  45. package/dist/collection/lib/{sources → resources}/wms.js +46 -6
  46. package/dist/collection/lib/resources/wms.test.js +63 -0
  47. package/dist/collection/lib/resources/wmts.js +317 -0
  48. package/dist/collection/lib/resources/wmts.test.js +235 -0
  49. package/dist/collection/lib/{sources → resources}/xyz.js +2 -2
  50. package/dist/esm/{lerc-yC3LNu-h.js → lerc-BxCv6y50.js} +1 -1
  51. package/dist/esm/loader.js +1 -1
  52. 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-DtEO48bS.js} +11302 -10648
  53. package/dist/esm/ogm-alerts_11.entry.js +1 -1
  54. package/dist/esm/ogm-viewer.js +1 -1
  55. package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
  56. package/dist/ogm-viewer/p-7EhN80oi.js +2822 -0
  57. package/dist/ogm-viewer/{p-_eTjrSO2.js → p-BO_nFj5K.js} +1 -1
  58. package/dist/ogm-viewer/{p-11cf948e.entry.js → p-c9af56a3.entry.js} +1 -1
  59. package/dist/types/components/ogm-image/ogm-image.d.ts +2 -2
  60. package/dist/types/components/ogm-map/ogm-map.d.ts +12 -9
  61. package/dist/types/components/ogm-preview/ogm-preview.d.ts +2 -2
  62. package/dist/types/components/ogm-previews/ogm-previews.d.ts +2 -2
  63. package/dist/types/components.d.ts +6 -6
  64. package/dist/types/lib/geometry.d.ts +5 -1
  65. package/dist/types/lib/previewers/cog-deck.d.ts +3 -3
  66. package/dist/types/lib/previewers/cog.d.ts +6 -6
  67. package/dist/types/lib/previewers/geojson.d.ts +7 -4
  68. package/dist/types/lib/previewers/{maplibre.d.ts → map.d.ts} +10 -7
  69. package/dist/types/lib/previewers/openindexmap.d.ts +2 -2
  70. package/dist/types/lib/previewers/pmtiles-raster.d.ts +2 -3
  71. package/dist/types/lib/previewers/pmtiles-vector.d.ts +4 -11
  72. package/dist/types/lib/previewers/previewer.d.ts +3 -3
  73. package/dist/types/lib/previewers/raster.d.ts +10 -7
  74. package/dist/types/lib/previewers/tiled-vector.d.ts +11 -0
  75. package/dist/types/lib/previewers/tilejson-raster.d.ts +8 -0
  76. package/dist/types/lib/previewers/tilejson-vector.d.ts +6 -0
  77. package/dist/types/lib/previewers/vector.d.ts +9 -5
  78. package/dist/types/lib/previewers/wms.d.ts +17 -5
  79. package/dist/types/lib/previewers/wms.test.d.ts +1 -0
  80. package/dist/types/lib/previewers/wmts.d.ts +11 -0
  81. package/dist/types/lib/previewers/wmts.test.d.ts +1 -0
  82. package/dist/types/lib/{sources → resources}/cog.d.ts +2 -2
  83. package/dist/types/lib/{sources → resources}/geojson.d.ts +2 -2
  84. package/dist/types/lib/{sources → resources}/iiif-manifest.d.ts +2 -2
  85. package/dist/types/lib/resources/iiif-manifest.test.d.ts +1 -0
  86. package/dist/types/lib/resources/iiif.d.ts +5 -0
  87. package/dist/types/lib/{sources/maplibre.d.ts → resources/map.d.ts} +2 -2
  88. package/dist/types/lib/resources/openindexmap.d.ts +5 -0
  89. package/dist/types/lib/{sources → resources}/pmtiles.d.ts +2 -2
  90. package/dist/types/lib/{sources → resources}/raster.d.ts +2 -2
  91. package/dist/types/lib/{sources/source.d.ts → resources/resource.d.ts} +1 -1
  92. package/dist/types/lib/{sources → resources}/tilejson.d.ts +5 -2
  93. package/dist/types/lib/resources/tms.d.ts +5 -0
  94. package/dist/types/lib/{sources → resources}/vector.d.ts +2 -2
  95. package/dist/types/lib/{sources → resources}/wms.d.ts +15 -5
  96. package/dist/types/lib/resources/wms.test.d.ts +1 -0
  97. package/dist/types/lib/resources/wmts.d.ts +59 -0
  98. package/dist/types/lib/resources/wmts.test.d.ts +1 -0
  99. package/dist/types/lib/resources/xyz.d.ts +5 -0
  100. package/package.json +1 -1
  101. package/dist/collection/lib/previewers/maplibre.js +0 -56
  102. package/dist/ogm-viewer/p-DPOGJxir.js +0 -2822
  103. package/dist/types/lib/sources/iiif.d.ts +0 -5
  104. package/dist/types/lib/sources/openindexmap.d.ts +0 -5
  105. package/dist/types/lib/sources/tms.d.ts +0 -5
  106. package/dist/types/lib/sources/xyz.d.ts +0 -5
  107. /package/dist/types/lib/{sources/iiif-manifest.test.d.ts → previewers/tilejson.test.d.ts} +0 -0
@@ -1,20 +1,21 @@
1
1
  import { h, Host } from "@stencil/core";
2
- import CogSource from "../../lib/sources/cog";
3
- import GeoJSONSource from "../../lib/sources/geojson";
4
- import OpenIndexMapSource from "../../lib/sources/openindexmap";
5
- import PMTilesSource from "../../lib/sources/pmtiles";
6
- import TileJSONSource from "../../lib/sources/tilejson";
7
- import TMSSource from "../../lib/sources/tms";
8
- import WmsSource from "../../lib/sources/wms";
9
- import XYZSource from "../../lib/sources/xyz";
10
- import IIIFSource from "../../lib/sources/iiif";
11
- import IIIFManifestSource from "../../lib/sources/iiif-manifest";
2
+ import CogResource from "../../lib/resources/cog";
3
+ import GeoJsonResource from "../../lib/resources/geojson";
4
+ import OpenIndexMapResource from "../../lib/resources/openindexmap";
5
+ import PMTilesResource from "../../lib/resources/pmtiles";
6
+ import TileJsonResource from "../../lib/resources/tilejson";
7
+ import TmsResource from "../../lib/resources/tms";
8
+ import WmsResource from "../../lib/resources/wms";
9
+ import WmtsResource from "../../lib/resources/wmts";
10
+ import XyzResource from "../../lib/resources/xyz";
11
+ import IIIFResource from "../../lib/resources/iiif";
12
+ import IIIFManifestResource from "../../lib/resources/iiif-manifest";
12
13
  export class OgmPreviews {
13
14
  theme;
14
15
  record;
15
16
  previewOpacity;
16
17
  sidebarPadding;
17
- sources = [];
18
+ resources = [];
18
19
  // @Watch only fires on changes; handle the initial load here
19
20
  componentWillLoad() {
20
21
  if (this.record)
@@ -22,35 +23,37 @@ export class OgmPreviews {
22
23
  }
23
24
  // Given a record, get all of the valid sources that can be used to preview it on a map
24
25
  getSources(record) {
25
- while (this.sources.length)
26
- this.sources.pop();
26
+ while (this.resources.length)
27
+ this.resources.pop();
27
28
  const recordBounds = record.getBounds();
28
29
  if (record.references.iiifImageUrl)
29
- this.sources.push(new IIIFSource(record.id, record.references.iiifImageUrl, recordBounds));
30
+ this.resources.push(new IIIFResource(record.id, record.references.iiifImageUrl, recordBounds));
30
31
  if (record.references.iiifManifestUrl)
31
- this.sources.push(new IIIFManifestSource(record.id, record.references.iiifManifestUrl, recordBounds));
32
+ this.resources.push(new IIIFManifestResource(record.id, record.references.iiifManifestUrl, recordBounds));
32
33
  if (record.references.pmtilesUrl)
33
- this.sources.push(new PMTilesSource(record.id, record.references.pmtilesUrl, recordBounds));
34
+ this.resources.push(new PMTilesResource(record.id, record.references.pmtilesUrl, recordBounds));
34
35
  if (record.references.tilejsonUrl)
35
- this.sources.push(new TileJSONSource(record.id, record.references.tilejsonUrl, recordBounds));
36
+ this.resources.push(new TileJsonResource(record.id, record.references.tilejsonUrl, recordBounds));
36
37
  if (record.references.indexMapUrl)
37
- this.sources.push(new OpenIndexMapSource(record.id, record.references.indexMapUrl, recordBounds));
38
+ this.resources.push(new OpenIndexMapResource(record.id, record.references.indexMapUrl, recordBounds));
38
39
  if (record.references.geojsonUrl)
39
- this.sources.push(new GeoJSONSource(record.id, record.references.geojsonUrl, recordBounds));
40
+ this.resources.push(new GeoJsonResource(record.id, record.references.geojsonUrl, recordBounds));
40
41
  if (record.references.cogUrl)
41
- this.sources.push(new CogSource(record.id, record.references.cogUrl, recordBounds));
42
+ this.resources.push(new CogResource(record.id, record.references.cogUrl, recordBounds));
42
43
  if (record.references.tmsUrl)
43
- this.sources.push(new TMSSource(record.id, record.references.tmsUrl, recordBounds));
44
+ this.resources.push(new TmsResource(record.id, record.references.tmsUrl, recordBounds));
44
45
  if (record.references.xyzUrl)
45
- this.sources.push(new XYZSource(record.id, record.references.xyzUrl, recordBounds));
46
+ this.resources.push(new XyzResource(record.id, record.references.xyzUrl, recordBounds));
47
+ if (record.references.wmtsUrl && record.wxsIdentifier)
48
+ this.resources.push(new WmtsResource(record.id, record.references.wmtsUrl, { layerIds: [record.wxsIdentifier] }, recordBounds));
46
49
  if (record.references.wmsUrl && record.wxsIdentifier)
47
- this.sources.push(new WmsSource(record.id, record.references.wmsUrl, { layerIds: [record.wxsIdentifier] }, recordBounds));
50
+ this.resources.push(new WmsResource(record.id, record.references.wmsUrl, { layerIds: [record.wxsIdentifier] }, recordBounds));
48
51
  }
49
52
  // Render as tabs for switching between sources
50
53
  render() {
51
- if (!this.record || !this.sources.length)
54
+ if (!this.record || !this.resources.length)
52
55
  return;
53
- return (h(Host, { class: this.theme && `wa-${this.theme}` }, h("wa-tab-group", null, this.sources.map((source, idx) => (h("wa-tab", { key: idx, panel: `${source.constructor.name}-${source.id}-${idx}` }, source.label()))), this.sources.map((source, idx) => (h("wa-tab-panel", { key: idx, name: `${source.constructor.name}-${source.id}-${idx}`, active: idx === 0 }, h("ogm-preview", { theme: this.theme, source: source, "preview-opacity": this.previewOpacity, "sidebar-padding": this.sidebarPadding })))))));
56
+ return (h(Host, { class: this.theme && `wa-${this.theme}` }, h("wa-tab-group", null, this.resources.map((resource, idx) => (h("wa-tab", { key: idx, panel: `${resource.constructor.name}-${resource.id}-${idx}` }, resource.label()))), this.resources.map((resource, idx) => (h("wa-tab-panel", { key: idx, name: `${resource.constructor.name}-${resource.id}-${idx}`, active: idx === 0 }, h("ogm-preview", { theme: this.theme, previewResource: resource, "preview-opacity": this.previewOpacity, "sidebar-padding": this.sidebarPadding })))))));
54
57
  }
55
58
  static get is() { return "ogm-previews"; }
56
59
  static get encapsulation() { return "shadow"; }
@@ -149,7 +152,7 @@ export class OgmPreviews {
149
152
  }
150
153
  static get states() {
151
154
  return {
152
- "sources": {}
155
+ "resources": {}
153
156
  };
154
157
  }
155
158
  static get watchers() {
@@ -34,7 +34,7 @@ const renderPreviews = async (record) => {
34
34
  const flush = () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
35
35
  // Render previews and drill into the single <ogm-preview> to see which preview (map or image) it
36
36
  // chose. Going through <ogm-previews> means the source is constructed in the built component's module
37
- // realm, so <ogm-preview>'s `instanceof IIIFSource` check holds (unlike a source built in the test).
37
+ // realm, so <ogm-preview>'s `instanceof IIIFResource` check holds (unlike a source built in the test).
38
38
  const renderPreviewChild = async (record) => {
39
39
  const container = document.createElement('div');
40
40
  document.body.appendChild(container);
@@ -1,7 +1,9 @@
1
- import { LngLatBounds } from "maplibre-gl";
1
+ import { LngLatBounds, MercatorCoordinate } from "maplibre-gl";
2
2
  import { wktToGeoJSON } from "@terraformer/wkt";
3
3
  // Regular expression to match ENVELOPE syntax in bbox strings
4
4
  export const ENVELOPE_REGEX = /^ENVELOPE\((?<west>[^,]+),(?<east>[^,]+),(?<north>[^,]+),(?<south>[^,]+)\)$/;
5
+ // EPSG:3857 spans this many meters from the origin on both axes
6
+ const MERCATOR_EXTENT = 20037508.342789244;
5
7
  // Convert LngLatBounds to GeoJSON Polygon
6
8
  export const boundsToGeoJSON = (bounds) => {
7
9
  return {
@@ -27,6 +29,38 @@ export const bboxToBounds = (bbox) => {
27
29
  const { west, east, north, south } = coords.groups;
28
30
  return new LngLatBounds([parseFloat(west), parseFloat(south)], [parseFloat(east), parseFloat(north)]);
29
31
  };
32
+ // Convert a geographic coordinate to EPSG:3857 (Web Mercator) meters
33
+ export const lngLatToMercator = (lngLat) => {
34
+ // MercatorCoordinate is normalized to 0..1, with y increasing southward
35
+ const { x, y } = MercatorCoordinate.fromLngLat(lngLat);
36
+ return [(x * 2 - 1) * MERCATOR_EXTENT, (1 - y * 2) * MERCATOR_EXTENT];
37
+ };
38
+ // Axis-aligned envelope around the given coordinates, as the minx,miny,maxx,maxy
39
+ // string in EPSG:3857 meters that OGC services expect for a BBOX parameter
40
+ export const mercatorBbox = (coords) => {
41
+ const points = coords.map(lngLatToMercator);
42
+ const xs = points.map(([x]) => x);
43
+ const ys = points.map(([, y]) => y);
44
+ return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)].join(',');
45
+ };
46
+ // Convert an EPSG:3857 (Web Mercator) coordinate in meters back to a geographic coordinate
47
+ export const mercatorToLngLat = ([x, y]) => {
48
+ const { lng, lat } = new MercatorCoordinate((x / MERCATOR_EXTENT + 1) / 2, (1 - y / MERCATOR_EXTENT) / 2).toLngLat();
49
+ return [lng, lat];
50
+ };
51
+ const mercatorPositionsToLngLat = (coordinates) => {
52
+ if (typeof coordinates[0] === 'number')
53
+ return mercatorToLngLat(coordinates);
54
+ return coordinates.map(mercatorPositionsToLngLat);
55
+ };
56
+ // Reproject a GeoJSON geometry from EPSG:3857 meters to geographic coordinates. OGC services
57
+ // answer in the CRS the request asked for, but MapLibre sources are always in degrees.
58
+ export const mercatorGeomToLngLat = (geometry) => {
59
+ if (geometry.type === 'GeometryCollection') {
60
+ return { ...geometry, geometries: geometry.geometries.map(mercatorGeomToLngLat) };
61
+ }
62
+ return { ...geometry, coordinates: mercatorPositionsToLngLat(geometry.coordinates) };
63
+ };
30
64
  // Convert either WKT or ENVELOPE format geometry to GeoJSON
31
65
  // If WKT parsing fails, try ENVELOPE instead
32
66
  export const geomToGeoJSON = (geometry) => {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from "@stencil/vitest";
2
2
  import { LngLatBounds } from "maplibre-gl";
3
- import { bboxToBounds, boundsToGeoJSON, geomToGeoJSON } from "./geometry";
3
+ import { bboxToBounds, boundsToGeoJSON, geomToGeoJSON, lngLatToMercator, mercatorBbox, mercatorGeomToLngLat, mercatorToLngLat } from "./geometry";
4
4
  describe('geomToGeoJSON', () => {
5
5
  it('should convert WKT to GeoJSON', () => {
6
6
  const wkt = 'POINT (-122.6764 45.5165)';
@@ -70,3 +70,92 @@ describe('boundsToGeoJSON', () => {
70
70
  });
71
71
  });
72
72
  });
73
+ describe('lngLatToMercator', () => {
74
+ it('should put the null island at the origin', () => {
75
+ expect(lngLatToMercator([0, 0])).toEqual([0, 0]);
76
+ });
77
+ it('should convert degrees to EPSG:3857 meters', () => {
78
+ const [x, y] = lngLatToMercator([-120.99553605196368, 37.83228807647538]);
79
+ expect(x).toBeCloseTo(-13469161.46, 1);
80
+ expect(y).toBeCloseTo(4555760.76, 1);
81
+ });
82
+ it('should count north and east as positive', () => {
83
+ const [x, y] = lngLatToMercator([120, 60]);
84
+ expect(x).toBeGreaterThan(0);
85
+ expect(y).toBeGreaterThan(0);
86
+ });
87
+ });
88
+ describe('mercatorBbox', () => {
89
+ it('should envelope the coordinates as minx,miny,maxx,maxy', () => {
90
+ // Corners of a query window, in the arbitrary order they were unprojected in
91
+ const bbox = mercatorBbox([
92
+ [-120.9, 38.5],
93
+ [-120.0, 38.5],
94
+ [-120.0, 37.8],
95
+ [-120.9, 37.8],
96
+ ]);
97
+ const [minX, minY, maxX, maxY] = bbox.split(',').map(Number);
98
+ expect(minX).toBeLessThan(maxX);
99
+ expect(minY).toBeLessThan(maxY);
100
+ expect([minX, minY]).toEqual(lngLatToMercator([-120.9, 37.8]));
101
+ expect([maxX, maxY]).toEqual(lngLatToMercator([-120.0, 38.5]));
102
+ });
103
+ });
104
+ describe('mercatorToLngLat', () => {
105
+ it('should place the origin at null island', () => {
106
+ expect(mercatorToLngLat([0, 0])).toEqual([0, 0]);
107
+ });
108
+ it('should invert lngLatToMercator', () => {
109
+ const [lng, lat] = mercatorToLngLat(lngLatToMercator([-120.99553605196368, 37.83228807647538]));
110
+ expect(lng).toBeCloseTo(-120.99553605196368, 9);
111
+ expect(lat).toBeCloseTo(37.83228807647538, 9);
112
+ });
113
+ it('should convert a coordinate a WMS returned in EPSG:3857 meters', () => {
114
+ // A vertex of a Calaveras County tract, as GeoServer reported it
115
+ const [lng, lat] = mercatorToLngLat([-13402032.01418895, 4622299.14920388]);
116
+ expect(lng).toBeCloseTo(-120.39250196605, 6);
117
+ expect(lat).toBeCloseTo(38.30286413751, 6);
118
+ });
119
+ });
120
+ describe('mercatorGeomToLngLat', () => {
121
+ it('should reproject a point', () => {
122
+ const geometry = mercatorGeomToLngLat({ type: 'Point', coordinates: lngLatToMercator([-120.5, 38.2]) });
123
+ expect(geometry.type).toEqual('Point');
124
+ expect(geometry.coordinates[0]).toBeCloseTo(-120.5, 9);
125
+ expect(geometry.coordinates[1]).toBeCloseTo(38.2, 9);
126
+ });
127
+ it('should reproject every ring of a nested geometry', () => {
128
+ const ring = [
129
+ [-120.9, 38.5],
130
+ [-120.0, 38.5],
131
+ [-120.0, 37.8],
132
+ [-120.9, 38.5],
133
+ ];
134
+ const geometry = mercatorGeomToLngLat({
135
+ type: 'MultiPolygon',
136
+ coordinates: [[ring.map(lngLatToMercator)]],
137
+ });
138
+ expect(geometry.type).toEqual('MultiPolygon');
139
+ geometry.coordinates[0][0].forEach(([lng, lat], index) => {
140
+ expect(lng).toBeCloseTo(ring[index][0], 9);
141
+ expect(lat).toBeCloseTo(ring[index][1], 9);
142
+ });
143
+ });
144
+ it('should reproject the members of a geometry collection', () => {
145
+ const geometry = mercatorGeomToLngLat({
146
+ type: 'GeometryCollection',
147
+ geometries: [
148
+ { type: 'Point', coordinates: [0, 0] },
149
+ { type: 'LineString', coordinates: [lngLatToMercator([-120.5, 38.2]), lngLatToMercator([-120.4, 38.3])] },
150
+ ],
151
+ });
152
+ expect(geometry.geometries).toHaveLength(2);
153
+ expect(geometry.geometries[0].coordinates).toEqual([0, 0]);
154
+ expect(geometry.geometries[1].coordinates[1][0]).toBeCloseTo(-120.4, 9);
155
+ });
156
+ it('should leave the original geometry untouched', () => {
157
+ const original = { type: 'Point', coordinates: [-13402032.01418895, 4622299.14920388] };
158
+ mercatorGeomToLngLat(original);
159
+ expect(original.coordinates).toEqual([-13402032.01418895, 4622299.14920388]);
160
+ });
161
+ });
@@ -18,8 +18,8 @@ export default class DeckCogPreviewer extends Previewer {
18
18
  // Current opacity state
19
19
  opacity;
20
20
  // Initialize with opacity at the theme's opacity value
21
- constructor(source, map, style) {
22
- super(source);
21
+ constructor(resource, map, style) {
22
+ super(resource);
23
23
  this.map = map;
24
24
  this.style = style;
25
25
  this.opacity = this.style.opacity;
@@ -39,12 +39,12 @@ export default class DeckCogPreviewer extends Previewer {
39
39
  }
40
40
  }
41
41
  getSourceId() {
42
- return `${this.source.id}-cog`;
42
+ return `${this.resource.id}-cog`;
43
43
  }
44
44
  createLayer() {
45
45
  return new COGLayer({
46
46
  id: this.getSourceId(),
47
- geotiff: this.source.getMapLibreSourceUrl(),
47
+ geotiff: this.resource.getMapLibreSourceUrl(),
48
48
  onGeoTIFFLoad: (_data, options) => {
49
49
  const { west, south, east, north } = options.geographicBounds;
50
50
  this.bounds = [
@@ -6,19 +6,19 @@ import RasterPreviewer from "./raster";
6
6
  // See: https://github.com/geomatico/maplibre-cog-protocol
7
7
  export default class CogPreviewer extends RasterPreviewer {
8
8
  // Register the 'cog://' protocol handler with MapLibre when the previewer is created
9
- constructor(source, map, style) {
10
- super(source, map, style);
9
+ constructor(resource, map, style) {
10
+ super(resource, map, style);
11
11
  maplibregl.addProtocol('cog', cogProtocol);
12
12
  }
13
13
  // COG sources use 'url' instead of 'tiles' and have no scheme
14
- async createSource() {
15
- return {
16
- type: 'raster',
17
- url: await this.source.getMapLibreSourceUrl(),
18
- tileSize: this.source.getTileSize(),
19
- };
20
- }
21
- getSourceId() {
22
- return `${this.source.id}-cog`;
14
+ async createSources() {
15
+ return [
16
+ {
17
+ id: `${this.resource.id}-cog`,
18
+ type: 'raster',
19
+ url: await this.resource.getMapLibreSourceUrl(),
20
+ tileSize: this.resource.getTileSize(),
21
+ },
22
+ ];
23
23
  }
24
24
  }
@@ -1,10 +1,13 @@
1
1
  import VectorPreviewer from "./vector";
2
- export default class GeoJSONPreviewer extends VectorPreviewer {
3
- async createSource() {
4
- return {
5
- type: await this.source.getMapLibreSourceType(),
6
- data: await this.source.getMapLibreSourceUrl(),
7
- generateId: true,
8
- };
2
+ export default class GeoJsonPreviewer extends VectorPreviewer {
3
+ async createSources() {
4
+ return [
5
+ {
6
+ id: `${this.resource.id}-geojson`,
7
+ type: await this.resource.getMapLibreSourceType(),
8
+ data: await this.resource.getMapLibreSourceUrl(),
9
+ generateId: true, // autogenerate feature IDs for labeling
10
+ },
11
+ ];
9
12
  }
10
13
  }
@@ -0,0 +1,51 @@
1
+ import Previewer from "./previewer";
2
+ export default class MapPreviewer extends Previewer {
3
+ // Store reference to the map and styles
4
+ style;
5
+ map;
6
+ // Stored state for added MapLibre sources and layers to allow for cleanup
7
+ sourceIds = [];
8
+ layerIds = [];
9
+ // Current opacity state
10
+ opacity;
11
+ // Initialize with opacity at the theme's opacity value
12
+ constructor(resource, map, style) {
13
+ super(resource);
14
+ this.map = map;
15
+ this.style = style;
16
+ this.opacity = this.style.opacity;
17
+ }
18
+ // Add source and preview layers if they don't already exist
19
+ async preview() {
20
+ const sources = await this.createSources();
21
+ sources.forEach(source => {
22
+ const { id, ...sourceSpec } = source;
23
+ if (this.map.getSource(id))
24
+ return;
25
+ this.map.addSource(id, sourceSpec);
26
+ this.sourceIds.push(id);
27
+ });
28
+ const layers = await this.createLayers();
29
+ layers.forEach(layer => {
30
+ if (this.map.getLayer(layer.id))
31
+ return;
32
+ this.map.addLayer(layer);
33
+ this.layerIds.push(layer.id);
34
+ });
35
+ }
36
+ // Remove preview layers and sources
37
+ async clearPreview() {
38
+ this.layerIds.forEach(layerId => {
39
+ if (this.map.getLayer(layerId)) {
40
+ this.map.removeLayer(layerId);
41
+ }
42
+ });
43
+ this.layerIds = [];
44
+ this.sourceIds.forEach(sourceId => {
45
+ if (this.map.getSource(sourceId)) {
46
+ this.map.removeSource(sourceId);
47
+ }
48
+ });
49
+ this.sourceIds = [];
50
+ }
51
+ }
@@ -1,3 +1,3 @@
1
- import GeoJSONPreviewer from "./geojson";
2
- export default class OpenIndexMapPreviewer extends GeoJSONPreviewer {
1
+ import GeoJsonPreviewer from "./geojson";
2
+ export default class OpenIndexMapPreviewer extends GeoJsonPreviewer {
3
3
  }
@@ -1,17 +1,17 @@
1
1
  import RasterPreviewer from "./raster";
2
2
  export default class PMTilesRasterPreviewer extends RasterPreviewer {
3
3
  // PMTiles sources use 'url' instead of 'tiles' and have no scheme or tileSize
4
- async createSource() {
5
- return {
6
- type: 'raster',
7
- url: await this.source.getMapLibreSourceUrl(),
8
- };
9
- }
10
- getSourceId() {
11
- return `${this.source.id}-pmtiles`;
4
+ async createSources() {
5
+ return [
6
+ {
7
+ id: `${this.resource.id}-pmtiles`,
8
+ type: 'raster',
9
+ url: await this.resource.getMapLibreSourceUrl(),
10
+ },
11
+ ];
12
12
  }
13
13
  // Raster PMTiles have bounds info in the header
14
14
  async getBounds() {
15
- return await this.source.getBounds();
15
+ return await this.resource.getBounds();
16
16
  }
17
17
  }
@@ -1,53 +1,14 @@
1
- import VectorPreviewer from "./vector";
2
- export default class PMTilesVectorPreviewer extends VectorPreviewer {
3
- async createSource() {
4
- return {
5
- type: 'vector',
6
- url: this.source.getMapLibreSourceUrl(),
7
- encoding: await this.source.getVectorEncoding(),
8
- };
9
- }
10
- /* Add 'source-layer' property because PMTiles can contain multiple layers */
11
- createPolygonLayer(layerId) {
12
- return {
13
- ...super.createPolygonLayer(layerId),
14
- 'source-layer': layerId,
15
- };
16
- }
17
- createPolygonOutlineLayer(layerId) {
18
- return {
19
- ...super.createPolygonOutlineLayer(layerId),
20
- 'source-layer': layerId,
21
- };
22
- }
23
- createLineLayer(layerId) {
24
- return {
25
- ...super.createLineLayer(layerId),
26
- 'source-layer': layerId,
27
- };
28
- }
29
- createPointLayer(layerId) {
30
- return {
31
- ...super.createPointLayer(layerId),
32
- 'source-layer': layerId,
33
- };
34
- }
35
- createPolygonLabelLayer(layerId) {
36
- return {
37
- ...super.createPolygonLabelLayer(layerId),
38
- 'source-layer': layerId,
39
- };
40
- }
41
- createLineLabelLayer(layerId) {
42
- return {
43
- ...super.createLineLabelLayer(layerId),
44
- 'source-layer': layerId,
45
- };
46
- }
47
- createPointLabelLayer(layerId) {
48
- return {
49
- ...super.createPointLabelLayer(layerId),
50
- 'source-layer': layerId,
51
- };
1
+ import TiledVectorPreviewer from "./tiled-vector";
2
+ export default class PMTilesVectorPreviewer extends TiledVectorPreviewer {
3
+ // Only one source for PMTiles
4
+ async createSources() {
5
+ return [
6
+ {
7
+ id: this.resource.id,
8
+ type: 'vector',
9
+ url: this.resource.getMapLibreSourceUrl(),
10
+ encoding: await this.resource.getVectorEncoding(),
11
+ },
12
+ ];
52
13
  }
53
14
  }
@@ -1,6 +1,7 @@
1
+ // Base class for rendering a resource into a preview
1
2
  export default class Previewer {
2
- source;
3
- constructor(source) {
4
- this.source = source;
3
+ resource;
4
+ constructor(resource) {
5
+ this.resource = resource;
5
6
  }
6
7
  }
@@ -1,17 +1,21 @@
1
- import MapLibrePreviewer from "./maplibre";
2
- export default class RasterPreviewer extends MapLibrePreviewer {
3
- async createSource() {
4
- return {
5
- type: 'raster',
6
- tiles: [await this.source.getMapLibreSourceUrl()],
7
- scheme: this.source.getScheme(),
8
- tileSize: this.source.getTileSize(),
9
- };
1
+ import MapPreviewer from "./map";
2
+ export default class RasterPreviewer extends MapPreviewer {
3
+ // By default, rasters only have one source
4
+ async createSources() {
5
+ return [
6
+ {
7
+ id: this.getSourceId(),
8
+ type: 'raster',
9
+ tiles: [await this.resource.getMapLibreSourceUrl()],
10
+ scheme: this.resource.getScheme(),
11
+ tileSize: this.resource.getTileSize(),
12
+ },
13
+ ];
10
14
  }
11
15
  getSourceId() {
12
- return `${this.source.id}-${this.source.getScheme()}`;
16
+ return `${this.resource.id}-${this.resource.getScheme()}`;
13
17
  }
14
- // Rasters only have one layer
18
+ // Rasters only have one layer of their own; subclasses may add companion layers after it
15
19
  async createLayers() {
16
20
  return [
17
21
  {
@@ -0,0 +1,47 @@
1
+ import VectorPreviewer from "./vector";
2
+ // A tileset (a PMTiles archive, a TileJSON tileset) can hold several named layers, so every style
3
+ // layer drawn from one has to name the layer it reads. Subclasses supply the source itself.
4
+ export default class TiledVectorPreviewer extends VectorPreviewer {
5
+ createPolygonLayer(layerId) {
6
+ return {
7
+ ...super.createPolygonLayer(layerId),
8
+ 'source-layer': layerId,
9
+ };
10
+ }
11
+ createPolygonOutlineLayer(layerId) {
12
+ return {
13
+ ...super.createPolygonOutlineLayer(layerId),
14
+ 'source-layer': layerId,
15
+ };
16
+ }
17
+ createLineLayer(layerId) {
18
+ return {
19
+ ...super.createLineLayer(layerId),
20
+ 'source-layer': layerId,
21
+ };
22
+ }
23
+ createPointLayer(layerId) {
24
+ return {
25
+ ...super.createPointLayer(layerId),
26
+ 'source-layer': layerId,
27
+ };
28
+ }
29
+ createPolygonLabelLayer(layerId) {
30
+ return {
31
+ ...super.createPolygonLabelLayer(layerId),
32
+ 'source-layer': layerId,
33
+ };
34
+ }
35
+ createLineLabelLayer(layerId) {
36
+ return {
37
+ ...super.createLineLabelLayer(layerId),
38
+ 'source-layer': layerId,
39
+ };
40
+ }
41
+ createPointLabelLayer(layerId) {
42
+ return {
43
+ ...super.createPointLabelLayer(layerId),
44
+ 'source-layer': layerId,
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ import RasterPreviewer from "./raster";
2
+ export default class TileJsonRasterPreviewer extends RasterPreviewer {
3
+ // Handed the URL of the document, MapLibre reads it itself and takes the tile templates, zoom
4
+ // range and scheme from there; only the tile size has to come from us
5
+ async createSources() {
6
+ return [
7
+ {
8
+ id: this.getSourceId(),
9
+ type: 'raster',
10
+ url: await this.resource.getMapLibreSourceUrl(),
11
+ tileSize: await this.tilejson.getTileSize(),
12
+ },
13
+ ];
14
+ }
15
+ // A record can reference the same tileset both ways, so keep the two sources distinct
16
+ getSourceId() {
17
+ return `${this.resource.id}-tilejson`;
18
+ }
19
+ // Raster tilesets have their bounds in the TileJSON document
20
+ async getBounds() {
21
+ return await this.tilejson.getBounds();
22
+ }
23
+ // A TileJSON document can describe either kind of tileset, so its source isn't a RasterSource
24
+ get tilejson() {
25
+ return this.resource;
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ import TiledVectorPreviewer from "./tiled-vector";
2
+ export default class TileJsonVectorPreviewer extends TiledVectorPreviewer {
3
+ getSourceId() {
4
+ return `${this.resource.id}-tilejson`;
5
+ }
6
+ // Handed the URL of the document, MapLibre reads it itself and takes the tile templates and
7
+ // zoom range from there, so 'url' is all the source needs
8
+ async createSources() {
9
+ return [
10
+ {
11
+ id: this.getSourceId(),
12
+ type: 'vector',
13
+ url: this.resource.getMapLibreSourceUrl(),
14
+ encoding: await this.resource.getVectorEncoding(),
15
+ },
16
+ ];
17
+ }
18
+ }