ogm-viewer 0.5.0 → 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.
- package/dist/cjs/{lerc-Df0hDWkS.js → lerc-XQuzL9p2.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-DMEfXquS.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} +11313 -10658
- 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-image/ogm-image.js +14 -14
- package/dist/collection/components/ogm-map/ogm-map.js +144 -52
- package/dist/collection/components/ogm-menubar/ogm-menubar.js +23 -2
- package/dist/collection/components/ogm-menubar/ogm-menubar.test.js +34 -0
- 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 +30 -28
- package/dist/collection/components/ogm-previews/ogm-previews.test.js +3 -3
- package/dist/collection/components/ogm-viewer/ogm-viewer.css +6 -2
- package/dist/collection/components/ogm-viewer/ogm-viewer.js +29 -18
- 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 +11 -11
- package/dist/collection/lib/previewers/geojson.js +10 -7
- 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 +9 -9
- package/dist/collection/lib/previewers/pmtiles-vector.js +12 -51
- 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-CB_Jcu33.js → lerc-BxCv6y50.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-Dlc6USWB.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} +11313 -10658
- 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-7EhN80oi.js +2822 -0
- package/dist/ogm-viewer/{p-DqRDI3AC.js → p-BO_nFj5K.js} +1 -1
- package/dist/ogm-viewer/{p-7936ccc9.entry.js → p-c9af56a3.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 +13 -9
- package/dist/types/components/ogm-menubar/ogm-menubar.d.ts +2 -1
- 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/ogm-viewer/ogm-viewer.d.ts +1 -0
- package/dist/types/components.d.ts +24 -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 -6
- package/dist/types/lib/previewers/geojson.d.ts +7 -4
- 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 -3
- package/dist/types/lib/previewers/pmtiles-vector.d.ts +4 -11
- 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/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 -0
- package/dist/collection/lib/previewers/maplibre.js +0 -56
- package/dist/ogm-viewer/p-CWtUrqsM.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 → previewers/tilejson.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,36 +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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
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 })))))));
|
|
54
|
+
if (!this.record || !this.resources.length)
|
|
55
|
+
return;
|
|
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 })))))));
|
|
55
57
|
}
|
|
56
58
|
static get is() { return "ogm-previews"; }
|
|
57
59
|
static get encapsulation() { return "shadow"; }
|
|
@@ -150,7 +152,7 @@ export class OgmPreviews {
|
|
|
150
152
|
}
|
|
151
153
|
static get states() {
|
|
152
154
|
return {
|
|
153
|
-
"
|
|
155
|
+
"resources": {}
|
|
154
156
|
};
|
|
155
157
|
}
|
|
156
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);
|
|
@@ -65,10 +65,10 @@ describe('ogm-previews', () => {
|
|
|
65
65
|
expect(tabs).toHaveLength(1);
|
|
66
66
|
expect(tabs[0].textContent).toContain('WMS');
|
|
67
67
|
});
|
|
68
|
-
it('
|
|
68
|
+
it('renders nothing for a record with no previewable sources', async () => {
|
|
69
69
|
const shadowRoot = await renderPreviews(buildRecord(false));
|
|
70
70
|
expect(shadowRoot.querySelector('wa-tab-group')).toBeNull();
|
|
71
|
-
expect(shadowRoot.
|
|
71
|
+
expect(shadowRoot.querySelectorAll('wa-tab')).toHaveLength(0);
|
|
72
72
|
});
|
|
73
73
|
it('wraps a map source in an ogm-preview that renders the map', async () => {
|
|
74
74
|
const previewShadow = await renderPreviewChild(buildRecordWith({ 'http://geojson.org/geojson-spec.html': 'https://example.com/data.json' }));
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
:host {
|
|
2
2
|
--stanford-cardinal-red: #8f1414;
|
|
3
|
-
--accent-color: var(--wa-color-brand-50);
|
|
4
|
-
/* --accent-color: var(--stanford-cardinal-red); */
|
|
5
3
|
}
|
|
6
4
|
|
|
7
5
|
.container {
|
|
6
|
+
/* Defined here rather than on :host because it resolves a Web Awesome token, which is only
|
|
7
|
+
available on .container (where the palette/theme stylesheet is activated by the
|
|
8
|
+
wa-palette-default / wa-light|wa-dark classes) and its descendants. */
|
|
9
|
+
--accent-color: var(--wa-color-brand-50);
|
|
10
|
+
/* --accent-color: var(--stanford-cardinal-red); */
|
|
11
|
+
|
|
8
12
|
display: flex;
|
|
9
13
|
flex-direction: column;
|
|
10
14
|
position: relative;
|
|
@@ -8,23 +8,6 @@ setBasePath(getAssetPath(''));
|
|
|
8
8
|
registerIconLibrary('default', {
|
|
9
9
|
resolver: name => getBasePath(`assets/icons/${name}.svg`),
|
|
10
10
|
});
|
|
11
|
-
// Web Awesome's raw color palette is scoped to `:root`, which can only ever match the actual
|
|
12
|
-
// document root - never an element inside a shadow tree, so a component-scoped @import can't
|
|
13
|
-
// activate it. Loading it here, at the document level, lets it match `:root` for real, and lets
|
|
14
|
-
// its `.wa-light`/`.wa-dark` theme rules match Host element in each component because that element
|
|
15
|
-
// is part of the page light DOM and not the shadow DOM. Injecting it here means consumers
|
|
16
|
-
// don't have to remember to add it to their own page.
|
|
17
|
-
function loadWebAwesomeStylesheet() {
|
|
18
|
-
const id = 'ogm-viewer-webawesome-styles';
|
|
19
|
-
if (document.getElementById(id))
|
|
20
|
-
return;
|
|
21
|
-
const link = document.createElement('link');
|
|
22
|
-
link.id = id;
|
|
23
|
-
link.rel = 'stylesheet';
|
|
24
|
-
link.href = getBasePath('assets/webawesome/styles/webawesome.css');
|
|
25
|
-
document.head.appendChild(link);
|
|
26
|
-
}
|
|
27
|
-
loadWebAwesomeStylesheet();
|
|
28
11
|
// Import all required Web Awesome components
|
|
29
12
|
import "@awesome.me/webawesome/dist/components/button/button.js";
|
|
30
13
|
import "@awesome.me/webawesome/dist/components/callout/callout.js";
|
|
@@ -36,10 +19,16 @@ import "@awesome.me/webawesome/dist/components/tab-group/tab-group.js";
|
|
|
36
19
|
import "@awesome.me/webawesome/dist/components/tab-panel/tab-panel.js";
|
|
37
20
|
import "@awesome.me/webawesome/dist/components/tab/tab.js";
|
|
38
21
|
import "@awesome.me/webawesome/dist/components/tooltip/tooltip.js";
|
|
22
|
+
// Web Awesome activates its palette and semantic color variants on `:root` by default (with
|
|
23
|
+
// `.wa-*` classes only needed to override the defaults). `:root` never matches inside a shadow
|
|
24
|
+
// tree, so we opt into each default explicitly on our container to reproduce what a document-level
|
|
25
|
+
// load would give us: the default palette plus the default hue for every color variant.
|
|
26
|
+
const WA_SCOPE = 'wa-palette-default wa-brand-blue wa-neutral-gray wa-success-green wa-warning-yellow wa-danger-red';
|
|
39
27
|
export class OgmViewer {
|
|
40
28
|
el;
|
|
41
29
|
recordUrl;
|
|
42
30
|
theme = this.getThemePreference();
|
|
31
|
+
hideTitle = false;
|
|
43
32
|
record;
|
|
44
33
|
error;
|
|
45
34
|
previewOpacity = 100;
|
|
@@ -103,8 +92,10 @@ export class OgmViewer {
|
|
|
103
92
|
return undefined;
|
|
104
93
|
}
|
|
105
94
|
}
|
|
95
|
+
// Link Web Awesome's stylesheet inside the shadow tree so it doesn't
|
|
96
|
+
// leak into the host page when rendering.
|
|
106
97
|
render() {
|
|
107
|
-
return (h(Host, { key: '
|
|
98
|
+
return (h(Host, { key: '042eb882fe5979453b3ea32c11dd9a95dfff6cc1', class: `wa-${this.theme}` }, h("link", { key: '510b4ccabdff543fd3724d9f00fcce2fc0da59e4', rel: "stylesheet", href: getBasePath('assets/webawesome/styles/themes/default.css') }), h("div", { key: 'ca0916da0a4e14fc6dbda29bc0ed7fcbab4389fa', class: `container ${WA_SCOPE} wa-${this.theme}` }, h("ogm-menubar", { key: 'e153abb59c5aa5858e723f1496916517c69a6f79', theme: this.theme, record: this.record, loading: this.loading, hideTitle: this.hideTitle }), h("div", { key: 'e83ad788ed7a7da1cbe6771184ba821fedebf868', class: "main-container" }, h("ogm-sidebar", { key: '664f9ad8f2b64f998e6fe2a67116413a6d1ac377', theme: this.theme, record: this.record, open: this.sidebarOpen }), this.error ? (h("ogm-alerts", { theme: this.theme, error: this.error })) : (h("ogm-previews", { theme: this.theme, record: this.record, "preview-opacity": this.previewOpacity, "sidebar-padding": this.sidebarPadding }))))));
|
|
108
99
|
}
|
|
109
100
|
static get is() { return "ogm-viewer"; }
|
|
110
101
|
static get encapsulation() { return "shadow"; }
|
|
@@ -158,6 +149,26 @@ export class OgmViewer {
|
|
|
158
149
|
"reflect": false,
|
|
159
150
|
"attribute": "theme",
|
|
160
151
|
"defaultValue": "this.getThemePreference()"
|
|
152
|
+
},
|
|
153
|
+
"hideTitle": {
|
|
154
|
+
"type": "boolean",
|
|
155
|
+
"mutable": false,
|
|
156
|
+
"complexType": {
|
|
157
|
+
"original": "boolean",
|
|
158
|
+
"resolved": "boolean",
|
|
159
|
+
"references": {}
|
|
160
|
+
},
|
|
161
|
+
"required": false,
|
|
162
|
+
"optional": false,
|
|
163
|
+
"docs": {
|
|
164
|
+
"tags": [],
|
|
165
|
+
"text": ""
|
|
166
|
+
},
|
|
167
|
+
"getter": false,
|
|
168
|
+
"setter": false,
|
|
169
|
+
"reflect": false,
|
|
170
|
+
"attribute": "hide-title",
|
|
171
|
+
"defaultValue": "false"
|
|
161
172
|
}
|
|
162
173
|
};
|
|
163
174
|
}
|
|
@@ -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,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(
|
|
10
|
-
super(
|
|
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
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
3
|
-
async
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
2
|
-
export default class OpenIndexMapPreviewer extends
|
|
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
|
|
5
|
-
return
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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.
|
|
15
|
+
return await this.resource.getBounds();
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -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
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class RasterPreviewer extends
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
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
|
{
|