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
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { h, Host } from "@stencil/core";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
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
|
-
|
|
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.
|
|
26
|
-
this.
|
|
26
|
+
while (this.resources.length)
|
|
27
|
+
this.resources.pop();
|
|
27
28
|
const recordBounds = record.getBounds();
|
|
28
29
|
if (record.references.iiifImageUrl)
|
|
29
|
-
this.
|
|
30
|
+
this.resources.push(new IIIFResource(record.id, record.references.iiifImageUrl, recordBounds));
|
|
30
31
|
if (record.references.iiifManifestUrl)
|
|
31
|
-
this.
|
|
32
|
+
this.resources.push(new IIIFManifestResource(record.id, record.references.iiifManifestUrl, recordBounds));
|
|
32
33
|
if (record.references.pmtilesUrl)
|
|
33
|
-
this.
|
|
34
|
+
this.resources.push(new PMTilesResource(record.id, record.references.pmtilesUrl, recordBounds));
|
|
34
35
|
if (record.references.tilejsonUrl)
|
|
35
|
-
this.
|
|
36
|
+
this.resources.push(new TileJsonResource(record.id, record.references.tilejsonUrl, recordBounds));
|
|
36
37
|
if (record.references.indexMapUrl)
|
|
37
|
-
this.
|
|
38
|
+
this.resources.push(new OpenIndexMapResource(record.id, record.references.indexMapUrl, recordBounds));
|
|
38
39
|
if (record.references.geojsonUrl)
|
|
39
|
-
this.
|
|
40
|
+
this.resources.push(new GeoJsonResource(record.id, record.references.geojsonUrl, recordBounds));
|
|
40
41
|
if (record.references.cogUrl)
|
|
41
|
-
this.
|
|
42
|
+
this.resources.push(new CogResource(record.id, record.references.cogUrl, recordBounds));
|
|
42
43
|
if (record.references.tmsUrl)
|
|
43
|
-
this.
|
|
44
|
+
this.resources.push(new TmsResource(record.id, record.references.tmsUrl, recordBounds));
|
|
44
45
|
if (record.references.xyzUrl)
|
|
45
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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(
|
|
22
|
-
super(
|
|
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.
|
|
42
|
+
return `${this.resource.id}-cog`;
|
|
43
43
|
}
|
|
44
44
|
createLayer() {
|
|
45
45
|
return new COGLayer({
|
|
46
46
|
id: this.getSourceId(),
|
|
47
|
-
geotiff: this.
|
|
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,23 @@ 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(
|
|
10
|
-
super(
|
|
9
|
+
constructor(resource, map, style) {
|
|
10
|
+
super(resource, map, style);
|
|
11
11
|
maplibregl.addProtocol('cog', cogProtocol);
|
|
12
12
|
}
|
|
13
|
-
// COG
|
|
14
|
-
async createSource() {
|
|
15
|
-
return {
|
|
16
|
-
type: 'raster',
|
|
17
|
-
url: await this.source.getMapLibreSourceUrl(),
|
|
18
|
-
tileSize: this.source.getTileSize(),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
13
|
+
// A COG has no scheme to name it by, and the raster layer draws from this too
|
|
21
14
|
getSourceId() {
|
|
22
|
-
return `${this.
|
|
15
|
+
return `${this.resource.id}-cog`;
|
|
16
|
+
}
|
|
17
|
+
// COG sources use 'url' instead of 'tiles' and have no scheme
|
|
18
|
+
async createSources() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
id: this.getSourceId(),
|
|
22
|
+
type: 'raster',
|
|
23
|
+
url: await this.resource.getMapLibreSourceUrl(),
|
|
24
|
+
tileSize: this.resource.getTileSize(),
|
|
25
|
+
},
|
|
26
|
+
];
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from "@stencil/vitest";
|
|
2
|
+
import CogPreviewer from "./cog";
|
|
3
|
+
import CogResource from "../resources/cog";
|
|
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
|
+
// The previewer only reads the opacity
|
|
31
|
+
const style = { opacity: 0.8 };
|
|
32
|
+
const COG_URL = 'https://example.com/scan.tif';
|
|
33
|
+
// Nothing here reads the GeoTIFF: the source is built from the URL alone
|
|
34
|
+
const preview = async () => {
|
|
35
|
+
const map = new FakeMap();
|
|
36
|
+
const previewer = new CogPreviewer(new CogResource('princeton-fk4544658v', COG_URL), map, style);
|
|
37
|
+
await previewer.preview();
|
|
38
|
+
return { map, previewer };
|
|
39
|
+
};
|
|
40
|
+
describe('CogPreviewer#preview', () => {
|
|
41
|
+
it('hands MapLibre the URL behind the cog:// protocol', async () => {
|
|
42
|
+
const { map, previewer } = await preview();
|
|
43
|
+
const source = map.sources.get('princeton-fk4544658v-cog');
|
|
44
|
+
expect(source.type).toEqual('raster');
|
|
45
|
+
expect(source.url).toEqual(`cog://${COG_URL}`);
|
|
46
|
+
expect(previewer.sourceIds).toEqual(['princeton-fk4544658v-cog']);
|
|
47
|
+
});
|
|
48
|
+
it('draws its raster layer from the source it added', async () => {
|
|
49
|
+
const { map, previewer } = await preview();
|
|
50
|
+
// A layer pointing at any other ID would be dropped by MapLibre, drawing nothing
|
|
51
|
+
expect([...map.layers.values()].every(layer => map.sources.has(layer.source))).toBe(true);
|
|
52
|
+
expect([...map.layers.keys()]).toEqual(['princeton-fk4544658v-cog']);
|
|
53
|
+
expect(previewer.layerIds).toEqual(['princeton-fk4544658v-cog']);
|
|
54
|
+
});
|
|
55
|
+
it('removes what it added when cleared', async () => {
|
|
56
|
+
const { map, previewer } = await preview();
|
|
57
|
+
await previewer.clearPreview();
|
|
58
|
+
expect(map.sources.size).toEqual(0);
|
|
59
|
+
expect(map.layers.size).toEqual(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import VectorPreviewer from "./vector";
|
|
2
|
-
export default class
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
export default class GeoJsonPreviewer extends VectorPreviewer {
|
|
3
|
+
// A record can reference the same data more than one way, so keep the sources distinct. The
|
|
4
|
+
// style layers draw from this too, so both have to come from here.
|
|
5
|
+
getSourceId() {
|
|
6
|
+
return `${this.resource.id}-geojson`;
|
|
7
|
+
}
|
|
8
|
+
async createSources() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
id: this.getSourceId(),
|
|
12
|
+
type: await this.resource.getMapLibreSourceType(),
|
|
13
|
+
data: await this.resource.getMapLibreSourceUrl(),
|
|
14
|
+
generateId: true, // autogenerate feature IDs for labeling
|
|
15
|
+
},
|
|
16
|
+
];
|
|
9
17
|
}
|
|
10
18
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from "@stencil/vitest";
|
|
2
|
+
import GeoJsonPreviewer from "./geojson";
|
|
3
|
+
import OpenIndexMapPreviewer from "./openindexmap";
|
|
4
|
+
import GeoJsonResource from "../resources/geojson";
|
|
5
|
+
import OpenIndexMapResource from "../resources/openindexmap";
|
|
6
|
+
// Just enough of a MapLibre map to record what the previewer adds
|
|
7
|
+
class FakeMap {
|
|
8
|
+
sources = new Map();
|
|
9
|
+
layers = new Map();
|
|
10
|
+
getSource(id) {
|
|
11
|
+
return this.sources.get(id);
|
|
12
|
+
}
|
|
13
|
+
addSource(id, spec) {
|
|
14
|
+
this.sources.set(id, spec);
|
|
15
|
+
}
|
|
16
|
+
removeSource(id) {
|
|
17
|
+
this.sources.delete(id);
|
|
18
|
+
}
|
|
19
|
+
getLayer(id) {
|
|
20
|
+
return this.layers.get(id);
|
|
21
|
+
}
|
|
22
|
+
addLayer(layer) {
|
|
23
|
+
// MapLibre refuses a layer whose source hasn't been added yet
|
|
24
|
+
if (!this.sources.has(layer.source))
|
|
25
|
+
throw new Error(`No source ${layer.source} for layer ${layer.id}`);
|
|
26
|
+
this.layers.set(layer.id, layer);
|
|
27
|
+
}
|
|
28
|
+
removeLayer(id) {
|
|
29
|
+
this.layers.delete(id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// The previewers only read the colors and label styles, none of which these tests assert on
|
|
33
|
+
const style = { opacity: 0.8, fillColor: '#00f', strokeColor: '#009', textColor: '#000', textFont: 'Noto Sans Regular', textSize: 12 };
|
|
34
|
+
const GEOJSON_URL = 'https://example.com/index-map.json';
|
|
35
|
+
// Nothing here fetches: the source URL and the layer names are known without reading the document
|
|
36
|
+
const previewGeoJson = async () => {
|
|
37
|
+
const map = new FakeMap();
|
|
38
|
+
const previewer = new GeoJsonPreviewer(new GeoJsonResource('princeton-fk4544658v', GEOJSON_URL), map, style);
|
|
39
|
+
await previewer.preview();
|
|
40
|
+
return { map, previewer };
|
|
41
|
+
};
|
|
42
|
+
const previewIndexMap = async () => {
|
|
43
|
+
const map = new FakeMap();
|
|
44
|
+
const previewer = new OpenIndexMapPreviewer(new OpenIndexMapResource('princeton-fk4544658v', GEOJSON_URL), map, style);
|
|
45
|
+
await previewer.preview();
|
|
46
|
+
return { map, previewer };
|
|
47
|
+
};
|
|
48
|
+
const SUFFIXES = ['polygons', 'polygon-outlines', 'lines', 'points', 'polygon-labels', 'line-labels', 'point-labels'];
|
|
49
|
+
describe('GeoJsonPreviewer#preview', () => {
|
|
50
|
+
it('hands MapLibre the document URL as a geojson source', async () => {
|
|
51
|
+
const { map, previewer } = await previewGeoJson();
|
|
52
|
+
const source = map.sources.get('princeton-fk4544658v-geojson');
|
|
53
|
+
expect(source.type).toEqual('geojson');
|
|
54
|
+
expect(source.data).toEqual(GEOJSON_URL);
|
|
55
|
+
expect(previewer.sourceIds).toEqual(['princeton-fk4544658v-geojson']);
|
|
56
|
+
});
|
|
57
|
+
it('draws its style layers from the source it added', async () => {
|
|
58
|
+
const { map } = await previewGeoJson();
|
|
59
|
+
// A layer pointing at any other ID would be dropped by MapLibre, drawing nothing
|
|
60
|
+
expect([...map.layers.values()].every(layer => map.sources.has(layer.source))).toBe(true);
|
|
61
|
+
expect([...map.layers.keys()]).toEqual(SUFFIXES.map(suffix => `princeton-fk4544658v-geojson-geojson-${suffix}`));
|
|
62
|
+
});
|
|
63
|
+
it('removes what it added when cleared', async () => {
|
|
64
|
+
const { map, previewer } = await previewGeoJson();
|
|
65
|
+
await previewer.clearPreview();
|
|
66
|
+
expect(map.sources.size).toEqual(0);
|
|
67
|
+
expect(map.layers.size).toEqual(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('OpenIndexMapPreviewer#preview', () => {
|
|
71
|
+
it('draws the index map polygons from the source it added', async () => {
|
|
72
|
+
const { map } = await previewIndexMap();
|
|
73
|
+
const polygons = map.layers.get('princeton-fk4544658v-geojson-indexmap-polygons');
|
|
74
|
+
expect(polygons.type).toEqual('fill');
|
|
75
|
+
expect(map.sources.has(polygons.source)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it('styles the one layer an index map has', async () => {
|
|
78
|
+
const { map } = await previewIndexMap();
|
|
79
|
+
expect([...map.layers.keys()]).toEqual(SUFFIXES.map(suffix => `princeton-fk4544658v-geojson-indexmap-${suffix}`));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -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
|
|
2
|
-
export default class OpenIndexMapPreviewer extends
|
|
1
|
+
import GeoJsonPreviewer from "./geojson";
|
|
2
|
+
export default class OpenIndexMapPreviewer extends GeoJsonPreviewer {
|
|
3
3
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import RasterPreviewer from "./raster";
|
|
2
2
|
export default class PMTilesRasterPreviewer extends RasterPreviewer {
|
|
3
|
-
//
|
|
4
|
-
async createSource() {
|
|
5
|
-
return {
|
|
6
|
-
type: 'raster',
|
|
7
|
-
url: await this.source.getMapLibreSourceUrl(),
|
|
8
|
-
};
|
|
9
|
-
}
|
|
3
|
+
// An archive has no scheme to name it by, and the raster layer draws from this too
|
|
10
4
|
getSourceId() {
|
|
11
|
-
return `${this.
|
|
5
|
+
return `${this.resource.id}-pmtiles`;
|
|
6
|
+
}
|
|
7
|
+
// PMTiles sources use 'url' instead of 'tiles' and have no scheme or tileSize
|
|
8
|
+
async createSources() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
id: this.getSourceId(),
|
|
12
|
+
type: 'raster',
|
|
13
|
+
url: await this.resource.getMapLibreSourceUrl(),
|
|
14
|
+
},
|
|
15
|
+
];
|
|
12
16
|
}
|
|
13
17
|
// Raster PMTiles have bounds info in the header
|
|
14
18
|
async getBounds() {
|
|
15
|
-
return await this.
|
|
19
|
+
return await this.resource.getBounds();
|
|
16
20
|
}
|
|
17
21
|
}
|
|
@@ -1,53 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class PMTilesVectorPreviewer extends
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
}
|