ogm-viewer 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/{lerc-IU-tWkVO.js → lerc-DQmE0Pfk.js} +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BAGJpqMB.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-B_nGMI1e.js} +12000 -11333
- package/dist/cjs/ogm-alerts_11.cjs.entry.js +1 -1
- package/dist/cjs/ogm-viewer.cjs.js +1 -1
- package/dist/collection/components/ogm-attributes/ogm-attributes.js +1 -1
- package/dist/collection/components/ogm-attributes/ogm-attributes.test.js +24 -0
- package/dist/collection/components/ogm-image/ogm-image.js +14 -14
- package/dist/collection/components/ogm-map/ogm-map.js +135 -53
- package/dist/collection/components/ogm-preview/ogm-preview.js +13 -13
- package/dist/collection/components/ogm-preview/ogm-preview.test.js +5 -5
- package/dist/collection/components/ogm-previews/ogm-previews.js +29 -26
- package/dist/collection/components/ogm-previews/ogm-previews.test.js +1 -1
- package/dist/collection/lib/geometry.js +35 -1
- package/dist/collection/lib/geometry.test.js +90 -1
- package/dist/collection/lib/previewers/cog-deck.js +4 -4
- package/dist/collection/lib/previewers/cog.js +15 -11
- package/dist/collection/lib/previewers/cog.test.js +61 -0
- package/dist/collection/lib/previewers/geojson.js +15 -7
- package/dist/collection/lib/previewers/geojson.test.js +81 -0
- package/dist/collection/lib/previewers/map.js +51 -0
- package/dist/collection/lib/previewers/openindexmap.js +2 -2
- package/dist/collection/lib/previewers/pmtiles-raster.js +13 -9
- package/dist/collection/lib/previewers/pmtiles-vector.js +12 -51
- package/dist/collection/lib/previewers/pmtiles.test.js +62 -0
- package/dist/collection/lib/previewers/previewer.js +4 -3
- package/dist/collection/lib/previewers/raster.js +15 -11
- package/dist/collection/lib/previewers/tiled-vector.js +47 -0
- package/dist/collection/lib/previewers/tilejson-raster.js +27 -0
- package/dist/collection/lib/previewers/tilejson-vector.js +18 -0
- package/dist/collection/lib/previewers/tilejson.test.js +140 -0
- package/dist/collection/lib/previewers/vector.js +7 -4
- package/dist/collection/lib/previewers/wms.js +86 -6
- package/dist/collection/lib/previewers/wms.test.js +133 -0
- package/dist/collection/lib/previewers/wmts.js +31 -0
- package/dist/collection/lib/previewers/wmts.test.js +108 -0
- package/dist/collection/lib/{sources → resources}/cog.js +2 -2
- package/dist/collection/lib/{sources → resources}/geojson.js +3 -3
- package/dist/collection/lib/{sources → resources}/iiif-manifest.js +2 -2
- package/dist/collection/lib/{sources → resources}/iiif-manifest.test.js +3 -3
- package/dist/collection/lib/{sources → resources}/iiif.js +2 -2
- package/dist/collection/lib/{sources/maplibre.js → resources/map.js} +2 -2
- package/dist/collection/lib/{sources → resources}/openindexmap.js +2 -2
- package/dist/collection/lib/{sources → resources}/pmtiles.js +2 -2
- package/dist/collection/lib/{sources → resources}/raster.js +2 -2
- package/dist/collection/lib/{sources/source.js → resources/resource.js} +3 -3
- package/dist/collection/lib/{sources → resources}/tilejson.js +21 -4
- package/dist/collection/lib/{sources → resources}/tms.js +2 -2
- package/dist/collection/lib/{sources → resources}/vector.js +2 -2
- package/dist/collection/lib/{sources → resources}/wms.js +46 -6
- package/dist/collection/lib/resources/wms.test.js +63 -0
- package/dist/collection/lib/resources/wmts.js +317 -0
- package/dist/collection/lib/resources/wmts.test.js +235 -0
- package/dist/collection/lib/{sources → resources}/xyz.js +2 -2
- package/dist/esm/{lerc-yC3LNu-h.js → lerc-BQHa9iiK.js} +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BpVupgSB.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-DSguoh85.js} +12000 -11333
- package/dist/esm/ogm-alerts_11.entry.js +1 -1
- package/dist/esm/ogm-viewer.js +1 -1
- package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
- package/dist/ogm-viewer/p-667JHO1P.js +2822 -0
- package/dist/ogm-viewer/{p-_eTjrSO2.js → p-9T5uP6M8.js} +1 -1
- package/dist/ogm-viewer/{p-11cf948e.entry.js → p-c95acce0.entry.js} +1 -1
- package/dist/types/components/ogm-image/ogm-image.d.ts +2 -2
- package/dist/types/components/ogm-map/ogm-map.d.ts +12 -9
- package/dist/types/components/ogm-preview/ogm-preview.d.ts +2 -2
- package/dist/types/components/ogm-previews/ogm-previews.d.ts +2 -2
- package/dist/types/components.d.ts +6 -6
- package/dist/types/lib/geometry.d.ts +5 -1
- package/dist/types/lib/previewers/cog-deck.d.ts +3 -3
- package/dist/types/lib/previewers/cog.d.ts +6 -5
- package/dist/types/lib/previewers/cog.test.d.ts +1 -0
- package/dist/types/lib/previewers/geojson.d.ts +8 -4
- package/dist/types/lib/previewers/geojson.test.d.ts +1 -0
- package/dist/types/lib/previewers/{maplibre.d.ts → map.d.ts} +10 -7
- package/dist/types/lib/previewers/openindexmap.d.ts +2 -2
- package/dist/types/lib/previewers/pmtiles-raster.d.ts +2 -2
- package/dist/types/lib/previewers/pmtiles-vector.d.ts +4 -11
- package/dist/types/lib/previewers/pmtiles.test.d.ts +1 -0
- package/dist/types/lib/previewers/previewer.d.ts +3 -3
- package/dist/types/lib/previewers/raster.d.ts +10 -7
- package/dist/types/lib/previewers/tiled-vector.d.ts +11 -0
- package/dist/types/lib/previewers/tilejson-raster.d.ts +8 -0
- package/dist/types/lib/previewers/tilejson-vector.d.ts +6 -0
- package/dist/types/lib/previewers/tilejson.test.d.ts +1 -0
- package/dist/types/lib/previewers/vector.d.ts +9 -5
- package/dist/types/lib/previewers/wms.d.ts +17 -5
- package/dist/types/lib/previewers/wms.test.d.ts +1 -0
- package/dist/types/lib/previewers/wmts.d.ts +11 -0
- package/dist/types/lib/previewers/wmts.test.d.ts +1 -0
- package/dist/types/lib/{sources → resources}/cog.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/geojson.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/iiif-manifest.d.ts +2 -2
- package/dist/types/lib/resources/iiif-manifest.test.d.ts +1 -0
- package/dist/types/lib/resources/iiif.d.ts +5 -0
- package/dist/types/lib/{sources/maplibre.d.ts → resources/map.d.ts} +2 -2
- package/dist/types/lib/resources/openindexmap.d.ts +5 -0
- package/dist/types/lib/{sources → resources}/pmtiles.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/raster.d.ts +2 -2
- package/dist/types/lib/{sources/source.d.ts → resources/resource.d.ts} +1 -1
- package/dist/types/lib/{sources → resources}/tilejson.d.ts +5 -2
- package/dist/types/lib/resources/tms.d.ts +5 -0
- package/dist/types/lib/{sources → resources}/vector.d.ts +2 -2
- package/dist/types/lib/{sources → resources}/wms.d.ts +15 -5
- package/dist/types/lib/resources/wms.test.d.ts +1 -0
- package/dist/types/lib/resources/wmts.d.ts +59 -0
- package/dist/types/lib/resources/wmts.test.d.ts +1 -0
- package/dist/types/lib/resources/xyz.d.ts +5 -0
- package/package.json +1 -1
- package/readme.md +1 -1
- package/dist/collection/lib/previewers/maplibre.js +0 -56
- package/dist/ogm-viewer/p-DPOGJxir.js +0 -2822
- package/dist/types/lib/sources/iiif.d.ts +0 -5
- package/dist/types/lib/sources/openindexmap.d.ts +0 -5
- package/dist/types/lib/sources/tms.d.ts +0 -5
- package/dist/types/lib/sources/xyz.d.ts +0 -5
- /package/dist/types/{lib/sources/iiif-manifest.test.d.ts → components/ogm-attributes/ogm-attributes.test.d.ts} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "@stencil/vitest";
|
|
2
|
+
import PMTilesRasterPreviewer from "./pmtiles-raster";
|
|
3
|
+
import PMTilesResource from "../resources/pmtiles";
|
|
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 PMTILES_URL = 'https://example.com/tiles.pmtiles';
|
|
33
|
+
// Nothing here reads the archive: the source is built from the URL alone, and the header is only
|
|
34
|
+
// touched for bounds
|
|
35
|
+
const preview = async () => {
|
|
36
|
+
const map = new FakeMap();
|
|
37
|
+
const previewer = new PMTilesRasterPreviewer(new PMTilesResource('princeton-fk4544658v', PMTILES_URL), map, style);
|
|
38
|
+
await previewer.preview();
|
|
39
|
+
return { map, previewer };
|
|
40
|
+
};
|
|
41
|
+
describe('PMTilesRasterPreviewer#preview', () => {
|
|
42
|
+
it('hands MapLibre the URL behind the pmtiles:// protocol', async () => {
|
|
43
|
+
const { map, previewer } = await preview();
|
|
44
|
+
const source = map.sources.get('princeton-fk4544658v-pmtiles');
|
|
45
|
+
expect(source.type).toEqual('raster');
|
|
46
|
+
expect(source.url).toEqual(`pmtiles://${PMTILES_URL}`);
|
|
47
|
+
expect(previewer.sourceIds).toEqual(['princeton-fk4544658v-pmtiles']);
|
|
48
|
+
});
|
|
49
|
+
it('draws its raster layer from the source it added', async () => {
|
|
50
|
+
const { map, previewer } = await preview();
|
|
51
|
+
// A layer pointing at any other ID would be dropped by MapLibre, drawing nothing
|
|
52
|
+
expect([...map.layers.values()].every(layer => map.sources.has(layer.source))).toBe(true);
|
|
53
|
+
expect([...map.layers.keys()]).toEqual(['princeton-fk4544658v-pmtiles']);
|
|
54
|
+
expect(previewer.layerIds).toEqual(['princeton-fk4544658v-pmtiles']);
|
|
55
|
+
});
|
|
56
|
+
it('removes what it added when cleared', async () => {
|
|
57
|
+
const { map, previewer } = await preview();
|
|
58
|
+
await previewer.clearPreview();
|
|
59
|
+
expect(map.sources.size).toEqual(0);
|
|
60
|
+
expect(map.layers.size).toEqual(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -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
|
{
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "@stencil/vitest";
|
|
2
|
+
import TileJsonRasterPreviewer from "./tilejson-raster";
|
|
3
|
+
import TileJsonVectorPreviewer from "./tilejson-vector";
|
|
4
|
+
import TileJsonResource from "../resources/tilejson";
|
|
5
|
+
// Just enough of a MapLibre map to record what the previewer adds
|
|
6
|
+
class FakeMap {
|
|
7
|
+
sources = new Map();
|
|
8
|
+
layers = new Map();
|
|
9
|
+
getSource(id) {
|
|
10
|
+
return this.sources.get(id);
|
|
11
|
+
}
|
|
12
|
+
addSource(id, spec) {
|
|
13
|
+
this.sources.set(id, spec);
|
|
14
|
+
}
|
|
15
|
+
removeSource(id) {
|
|
16
|
+
this.sources.delete(id);
|
|
17
|
+
}
|
|
18
|
+
getLayer(id) {
|
|
19
|
+
return this.layers.get(id);
|
|
20
|
+
}
|
|
21
|
+
addLayer(layer) {
|
|
22
|
+
// MapLibre refuses a layer whose source hasn't been added yet
|
|
23
|
+
if (!this.sources.has(layer.source))
|
|
24
|
+
throw new Error(`No source ${layer.source} for layer ${layer.id}`);
|
|
25
|
+
this.layers.set(layer.id, layer);
|
|
26
|
+
}
|
|
27
|
+
removeLayer(id) {
|
|
28
|
+
this.layers.delete(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// The previewers only read the colors and label styles, none of which these tests assert on
|
|
32
|
+
const style = { opacity: 0.8, fillColor: '#00f', strokeColor: '#009', textColor: '#000', textFont: 'Noto Sans Regular', textSize: 12 };
|
|
33
|
+
const TILEJSON_URL = 'https://example.com/tilejson.json';
|
|
34
|
+
const BALKANS_BOUNDS = [19.333333, 39.75, 29.333333, 43.083333];
|
|
35
|
+
// A raster tileset, as a service like titiler describes one
|
|
36
|
+
const rasterDoc = {
|
|
37
|
+
tilejson: '3.0.0',
|
|
38
|
+
tiles: ['https://example.com/tiles/{z}/{x}/{y}@1x.png'],
|
|
39
|
+
minzoom: 0,
|
|
40
|
+
maxzoom: 16,
|
|
41
|
+
bounds: BALKANS_BOUNDS,
|
|
42
|
+
};
|
|
43
|
+
// A vector tileset with more than one layer in its tiles
|
|
44
|
+
const vectorDoc = {
|
|
45
|
+
tilejson: '3.0.0',
|
|
46
|
+
tiles: ['https://example.com/tiles/{z}/{x}/{y}.pbf'],
|
|
47
|
+
vector_layers: [{ id: 'districts' }, { id: 'places' }],
|
|
48
|
+
bounds: BALKANS_BOUNDS,
|
|
49
|
+
};
|
|
50
|
+
// Serve a TileJSON document at the source URL. Every source fetches it lazily, so nothing has been
|
|
51
|
+
// requested until the previewer asks.
|
|
52
|
+
const serve = (doc) => {
|
|
53
|
+
vi.spyOn(global, 'fetch').mockResolvedValue(new Response(JSON.stringify(doc)));
|
|
54
|
+
return new TileJsonResource('princeton-fk4544658v', TILEJSON_URL);
|
|
55
|
+
};
|
|
56
|
+
afterEach(() => vi.restoreAllMocks());
|
|
57
|
+
describe('TileJsonRasterPreviewer#preview', () => {
|
|
58
|
+
const preview = async (doc) => {
|
|
59
|
+
const map = new FakeMap();
|
|
60
|
+
const previewer = new TileJsonRasterPreviewer(serve(doc), map, style);
|
|
61
|
+
await previewer.preview();
|
|
62
|
+
return { map, previewer };
|
|
63
|
+
};
|
|
64
|
+
it('hands MapLibre the document URL rather than the tile template', async () => {
|
|
65
|
+
const { map } = await preview(rasterDoc);
|
|
66
|
+
const source = map.sources.get('princeton-fk4544658v-tilejson');
|
|
67
|
+
expect(source.type).toEqual('raster');
|
|
68
|
+
expect(source.url).toEqual(TILEJSON_URL);
|
|
69
|
+
expect(source.tiles).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it('draws the tiles at 256px, the size a template implies', async () => {
|
|
72
|
+
const { map } = await preview(rasterDoc);
|
|
73
|
+
expect(map.sources.get('princeton-fk4544658v-tilejson').tileSize).toEqual(256);
|
|
74
|
+
});
|
|
75
|
+
it('defers to a tile size the document declares', async () => {
|
|
76
|
+
const { map } = await preview({ ...rasterDoc, tileSize: 512 });
|
|
77
|
+
expect(map.sources.get('princeton-fk4544658v-tilejson').tileSize).toEqual(512);
|
|
78
|
+
});
|
|
79
|
+
it('adds a single raster layer over the source', async () => {
|
|
80
|
+
const { map, previewer } = await preview(rasterDoc);
|
|
81
|
+
expect([...map.layers.keys()]).toEqual(['princeton-fk4544658v-tilejson']);
|
|
82
|
+
expect(map.layers.get('princeton-fk4544658v-tilejson').type).toEqual('raster');
|
|
83
|
+
expect(previewer.layerIds).toEqual(['princeton-fk4544658v-tilejson']);
|
|
84
|
+
});
|
|
85
|
+
it('takes its bounds from the document', async () => {
|
|
86
|
+
const { previewer } = await preview(rasterDoc);
|
|
87
|
+
expect(await previewer.getBounds()).toEqual([
|
|
88
|
+
[19.333333, 39.75],
|
|
89
|
+
[29.333333, 43.083333],
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
it('has no bounds when the document leaves them out', async () => {
|
|
93
|
+
const { bounds: _bounds, ...doc } = rasterDoc;
|
|
94
|
+
const { previewer } = await preview(doc);
|
|
95
|
+
expect(await previewer.getBounds()).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
it('removes what it added when cleared', async () => {
|
|
98
|
+
const { map, previewer } = await preview(rasterDoc);
|
|
99
|
+
await previewer.clearPreview();
|
|
100
|
+
expect(map.sources.size).toEqual(0);
|
|
101
|
+
expect(map.layers.size).toEqual(0);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('TileJsonVectorPreviewer#preview', () => {
|
|
105
|
+
const preview = async (doc) => {
|
|
106
|
+
const map = new FakeMap();
|
|
107
|
+
const previewer = new TileJsonVectorPreviewer(serve(doc), map, style);
|
|
108
|
+
await previewer.preview();
|
|
109
|
+
return { map, previewer };
|
|
110
|
+
};
|
|
111
|
+
it('hands MapLibre the document URL and the tile encoding', async () => {
|
|
112
|
+
const { map } = await preview(vectorDoc);
|
|
113
|
+
const source = map.sources.get('princeton-fk4544658v-tilejson');
|
|
114
|
+
expect(source.type).toEqual('vector');
|
|
115
|
+
expect(source.url).toEqual(TILEJSON_URL);
|
|
116
|
+
expect(source.encoding).toEqual('mvt');
|
|
117
|
+
});
|
|
118
|
+
it('asks for MapLibre Tile decoding when the tiles are .mlt', async () => {
|
|
119
|
+
const { map } = await preview({ ...vectorDoc, tiles: ['https://example.com/tiles/{z}/{x}/{y}.mlt'] });
|
|
120
|
+
expect(map.sources.get('princeton-fk4544658v-tilejson').encoding).toEqual('mlt');
|
|
121
|
+
});
|
|
122
|
+
it('styles every layer the document lists', async () => {
|
|
123
|
+
const { map } = await preview(vectorDoc);
|
|
124
|
+
const suffixes = ['polygons', 'polygon-outlines', 'lines', 'points', 'polygon-labels', 'line-labels', 'point-labels'];
|
|
125
|
+
expect([...map.layers.keys()]).toEqual(['districts', 'places'].flatMap(layer => suffixes.map(suffix => `princeton-fk4544658v-tilejson-${layer}-${suffix}`)));
|
|
126
|
+
});
|
|
127
|
+
it('names the layer within the tiles that each style layer reads', async () => {
|
|
128
|
+
const { map } = await preview(vectorDoc);
|
|
129
|
+
// Without this a layer would draw from whichever layer of the tiles came first
|
|
130
|
+
expect(map.layers.get('princeton-fk4544658v-tilejson-places-points')['source-layer']).toEqual('places');
|
|
131
|
+
expect([...map.layers.values()].every(layer => layer['source-layer'])).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
it('takes its bounds from the document', async () => {
|
|
134
|
+
const { previewer } = await preview(vectorDoc);
|
|
135
|
+
expect(await previewer.getBounds()).toEqual([
|
|
136
|
+
[19.333333, 39.75],
|
|
137
|
+
[29.333333, 43.083333],
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class VectorPreviewer extends
|
|
1
|
+
import MapPreviewer from "./map";
|
|
2
|
+
export default class VectorPreviewer extends MapPreviewer {
|
|
3
|
+
getSourceId() {
|
|
4
|
+
return this.resource.id;
|
|
5
|
+
}
|
|
3
6
|
// Set opacity of all layers in this previewer
|
|
4
7
|
async setOpacity(opacity) {
|
|
5
8
|
this.opacity = opacity;
|
|
@@ -8,10 +11,10 @@ export default class VectorPreviewer extends MapLibrePreviewer {
|
|
|
8
11
|
}
|
|
9
12
|
}
|
|
10
13
|
async getBounds() {
|
|
11
|
-
return await this.
|
|
14
|
+
return await this.resource.getBounds();
|
|
12
15
|
}
|
|
13
16
|
async createLayers() {
|
|
14
|
-
const layerIds = await this.
|
|
17
|
+
const layerIds = await this.resource.getVectorLayers();
|
|
15
18
|
return layerIds.flatMap(layerId => {
|
|
16
19
|
return [
|
|
17
20
|
this.createPolygonLayer(layerId),
|
|
@@ -1,14 +1,94 @@
|
|
|
1
1
|
import RasterPreviewer from "./raster";
|
|
2
|
+
// Contents of the highlight source when nothing is selected
|
|
3
|
+
const NO_FEATURES = { type: 'FeatureCollection', features: [] };
|
|
2
4
|
export default class WmsPreviewer extends RasterPreviewer {
|
|
3
5
|
// WMS sources have no scheme
|
|
4
|
-
async
|
|
6
|
+
async createSources() {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
id: this.getSourceId(),
|
|
10
|
+
type: 'raster',
|
|
11
|
+
tiles: [await this.resource.getMapLibreSourceUrl()],
|
|
12
|
+
tileSize: this.resource.getTileSize(),
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
getSourceId() {
|
|
17
|
+
return `${this.resource.id}-wms`;
|
|
18
|
+
}
|
|
19
|
+
// The server draws the tiles, so unlike a vector preview there are no client-side features to
|
|
20
|
+
// restyle when one is selected. GetFeatureInfo does hand back the geometry it matched, so we
|
|
21
|
+
// keep a GeoJSON source alongside the tiles and draw the selection into it.
|
|
22
|
+
get highlightSourceId() {
|
|
23
|
+
return `${this.getSourceId()}-highlight`;
|
|
24
|
+
}
|
|
25
|
+
// Add the highlight source ahead of the layers that draw from it
|
|
26
|
+
async preview() {
|
|
27
|
+
if (!this.map.getSource(this.highlightSourceId)) {
|
|
28
|
+
this.map.addSource(this.highlightSourceId, { type: 'geojson', data: NO_FEATURES });
|
|
29
|
+
}
|
|
30
|
+
await super.preview();
|
|
31
|
+
}
|
|
32
|
+
// The highlight layers go with the rest of the preview, since they're tracked in layerIds,
|
|
33
|
+
// but the extra source is ours to clean up
|
|
34
|
+
async clearPreview() {
|
|
35
|
+
await super.clearPreview();
|
|
36
|
+
if (this.map.getSource(this.highlightSourceId)) {
|
|
37
|
+
this.map.removeSource(this.highlightSourceId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Outline the given features on top of the tiles, replacing any previous highlight
|
|
41
|
+
highlightFeatures(features) {
|
|
42
|
+
const data = {
|
|
43
|
+
type: 'FeatureCollection',
|
|
44
|
+
// A server can answer with attributes and no geometry, which we have nothing to draw for
|
|
45
|
+
features: features.filter(feature => feature.geometry).map(({ id, geometry }) => ({ type: 'Feature', id, geometry, properties: {} })),
|
|
46
|
+
};
|
|
47
|
+
this.highlightSource?.setData(data);
|
|
48
|
+
}
|
|
49
|
+
// Drop the highlight, leaving the tiles alone
|
|
50
|
+
clearHighlight() {
|
|
51
|
+
this.highlightSource?.setData(NO_FEATURES);
|
|
52
|
+
}
|
|
53
|
+
// Delegate inspection to the source, which makes the GetFeatureInfo request
|
|
54
|
+
async inspect(options) {
|
|
55
|
+
return await this.resource.inspect(options);
|
|
56
|
+
}
|
|
57
|
+
// Tiles first, so the highlight draws over them
|
|
58
|
+
async createLayers() {
|
|
59
|
+
return [...(await super.createLayers()), this.createHighlightOutlineLayer(), this.createHighlightPointLayer()];
|
|
60
|
+
}
|
|
61
|
+
// Outline for selected polygons and lines
|
|
62
|
+
createHighlightOutlineLayer() {
|
|
5
63
|
return {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
64
|
+
id: `${this.highlightSourceId}-outlines`,
|
|
65
|
+
type: 'line',
|
|
66
|
+
source: this.highlightSourceId,
|
|
67
|
+
paint: {
|
|
68
|
+
'line-color': this.style.strokeSelectedColor,
|
|
69
|
+
'line-width': 2,
|
|
70
|
+
},
|
|
71
|
+
filter: ['!=', ['geometry-type'], 'Point'],
|
|
9
72
|
};
|
|
10
73
|
}
|
|
11
|
-
|
|
12
|
-
|
|
74
|
+
// Outline for selected points
|
|
75
|
+
createHighlightPointLayer() {
|
|
76
|
+
return {
|
|
77
|
+
id: `${this.highlightSourceId}-points`,
|
|
78
|
+
type: 'circle',
|
|
79
|
+
source: this.highlightSourceId,
|
|
80
|
+
paint: {
|
|
81
|
+
'circle-color': this.style.fillSelectedColor,
|
|
82
|
+
'circle-opacity': this.style.fillHighlightOpacity,
|
|
83
|
+
'circle-stroke-color': this.style.strokeSelectedColor,
|
|
84
|
+
'circle-stroke-width': 2,
|
|
85
|
+
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 2, 12, 4],
|
|
86
|
+
},
|
|
87
|
+
filter: ['==', ['geometry-type'], 'Point'],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// The MapLibre source holding the highlighted geometry
|
|
91
|
+
get highlightSource() {
|
|
92
|
+
return this.map.getSource(this.highlightSourceId);
|
|
13
93
|
}
|
|
14
94
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@stencil/vitest";
|
|
2
|
+
import WmsPreviewer from "./wms";
|
|
3
|
+
import WmsResource from "../resources/wms";
|
|
4
|
+
// Just enough of a MapLibre map to record what the previewer adds, removes and draws
|
|
5
|
+
class FakeMap {
|
|
6
|
+
sources = new Map();
|
|
7
|
+
layers = new Map();
|
|
8
|
+
getSource(id) {
|
|
9
|
+
const source = this.sources.get(id);
|
|
10
|
+
if (!source)
|
|
11
|
+
return undefined;
|
|
12
|
+
return { ...source, setData: (data) => (source.data = data) };
|
|
13
|
+
}
|
|
14
|
+
addSource(id, spec) {
|
|
15
|
+
this.sources.set(id, { ...spec });
|
|
16
|
+
}
|
|
17
|
+
removeSource(id) {
|
|
18
|
+
this.sources.delete(id);
|
|
19
|
+
}
|
|
20
|
+
getLayer(id) {
|
|
21
|
+
return this.layers.get(id);
|
|
22
|
+
}
|
|
23
|
+
addLayer(layer) {
|
|
24
|
+
// MapLibre refuses a layer whose source hasn't been added yet
|
|
25
|
+
if (!this.sources.has(layer.source))
|
|
26
|
+
throw new Error(`No source ${layer.source} for layer ${layer.id}`);
|
|
27
|
+
this.layers.set(layer.id, layer);
|
|
28
|
+
}
|
|
29
|
+
removeLayer(id) {
|
|
30
|
+
this.layers.delete(id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// The previewer only reads the colors used by the highlight
|
|
34
|
+
const style = { opacity: 0.8, strokeSelectedColor: '#0a0', fillSelectedColor: '#0f0', fillHighlightOpacity: 0.8 };
|
|
35
|
+
const TRACT_GEOMETRY = {
|
|
36
|
+
type: 'MultiPolygon',
|
|
37
|
+
coordinates: [
|
|
38
|
+
[
|
|
39
|
+
[
|
|
40
|
+
[-120.4, 38.3],
|
|
41
|
+
[-120.3, 38.3],
|
|
42
|
+
[-120.3, 38.4],
|
|
43
|
+
[-120.4, 38.3],
|
|
44
|
+
],
|
|
45
|
+
],
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
// A tract as GetFeatureInfo returns it, already reprojected to degrees by the caller. These are
|
|
49
|
+
// plain objects rather than real MapGeoJSONFeatures, which is also what the caller hands over.
|
|
50
|
+
const tract = (id, geometry = TRACT_GEOMETRY) => ({ type: 'Feature', id, geometry, properties: { TRACT: '0301' } });
|
|
51
|
+
const HIGHLIGHT_SOURCE = 's7st30-wms-highlight';
|
|
52
|
+
let map;
|
|
53
|
+
let previewer;
|
|
54
|
+
beforeEach(async () => {
|
|
55
|
+
map = new FakeMap();
|
|
56
|
+
const source = new WmsResource('s7st30', 'https://geoservices.lib.berkeley.edu/geoserver/wms', { layerIds: [] });
|
|
57
|
+
previewer = new WmsPreviewer(source, map, style);
|
|
58
|
+
await previewer.preview();
|
|
59
|
+
});
|
|
60
|
+
describe('WmsPreviewer#preview', () => {
|
|
61
|
+
it('adds a highlight source alongside the tiles', () => {
|
|
62
|
+
expect([...map.sources.keys()]).toEqual(['s7st30-wms-highlight', 's7st30-wms']);
|
|
63
|
+
expect(map.sources.get(HIGHLIGHT_SOURCE)?.type).toEqual('geojson');
|
|
64
|
+
});
|
|
65
|
+
it('draws the highlight over the tiles', () => {
|
|
66
|
+
// Order is paint order: the raster layer first, then the layers that highlight over it
|
|
67
|
+
expect([...map.layers.keys()]).toEqual(['s7st30-wms', 's7st30-wms-highlight-outlines', 's7st30-wms-highlight-points']);
|
|
68
|
+
expect(previewer.layerIds).toEqual([...map.layers.keys()]);
|
|
69
|
+
});
|
|
70
|
+
it('outlines polygons and lines but circles only points', () => {
|
|
71
|
+
const outlines = map.layers.get('s7st30-wms-highlight-outlines');
|
|
72
|
+
const points = map.layers.get('s7st30-wms-highlight-points');
|
|
73
|
+
expect(outlines?.type).toEqual('line');
|
|
74
|
+
expect(points?.type).toEqual('circle');
|
|
75
|
+
// Both draw from the highlight source, not from the tiles
|
|
76
|
+
expect(outlines?.source).toEqual(HIGHLIGHT_SOURCE);
|
|
77
|
+
expect(points?.source).toEqual(HIGHLIGHT_SOURCE);
|
|
78
|
+
});
|
|
79
|
+
it('starts with nothing highlighted', () => {
|
|
80
|
+
expect(map.sources.get(HIGHLIGHT_SOURCE)?.data).toEqual({ type: 'FeatureCollection', features: [] });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('WmsPreviewer#highlightFeatures', () => {
|
|
84
|
+
it('draws the geometry of the given features', () => {
|
|
85
|
+
previewer.highlightFeatures([tract('s7st30.18')]);
|
|
86
|
+
const data = map.sources.get(HIGHLIGHT_SOURCE)?.data;
|
|
87
|
+
expect(data.features).toHaveLength(1);
|
|
88
|
+
expect(data.features[0].id).toEqual('s7st30.18');
|
|
89
|
+
expect(data.features[0].geometry).toEqual(TRACT_GEOMETRY);
|
|
90
|
+
});
|
|
91
|
+
it('replaces the previous highlight rather than adding to it', () => {
|
|
92
|
+
previewer.highlightFeatures([tract('s7st30.18')]);
|
|
93
|
+
previewer.highlightFeatures([tract('s7st30.19')]);
|
|
94
|
+
const data = map.sources.get(HIGHLIGHT_SOURCE)?.data;
|
|
95
|
+
expect(data.features).toHaveLength(1);
|
|
96
|
+
expect(data.features[0].id).toEqual('s7st30.19');
|
|
97
|
+
});
|
|
98
|
+
it('draws every feature when several are selected', () => {
|
|
99
|
+
previewer.highlightFeatures([tract('s7st30.18'), tract('s7st30.19')]);
|
|
100
|
+
const data = map.sources.get(HIGHLIGHT_SOURCE)?.data;
|
|
101
|
+
expect(data.features.map(feature => feature.id)).toEqual(['s7st30.18', 's7st30.19']);
|
|
102
|
+
});
|
|
103
|
+
it('skips features a server returned without geometry', () => {
|
|
104
|
+
// The spec allows a null geometry, and a server can answer with attributes alone
|
|
105
|
+
previewer.highlightFeatures([tract('s7st30.18'), tract('s7st30.19', null)]);
|
|
106
|
+
const data = map.sources.get(HIGHLIGHT_SOURCE)?.data;
|
|
107
|
+
expect(data.features).toHaveLength(1);
|
|
108
|
+
expect(data.features[0].id).toEqual('s7st30.18');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('WmsPreviewer#clearHighlight', () => {
|
|
112
|
+
it('empties the highlight source but leaves the layers in place', () => {
|
|
113
|
+
previewer.highlightFeatures([tract('s7st30.18')]);
|
|
114
|
+
previewer.clearHighlight();
|
|
115
|
+
expect(map.sources.get(HIGHLIGHT_SOURCE)?.data).toEqual({ type: 'FeatureCollection', features: [] });
|
|
116
|
+
expect(map.layers.size).toEqual(3);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe('WmsPreviewer#clearPreview', () => {
|
|
120
|
+
it('removes the highlight along with the tiles', async () => {
|
|
121
|
+
previewer.highlightFeatures([tract('s7st30.18')]);
|
|
122
|
+
await previewer.clearPreview();
|
|
123
|
+
expect(map.sources.size).toEqual(0);
|
|
124
|
+
expect(map.layers.size).toEqual(0);
|
|
125
|
+
expect(previewer.layerIds).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
it('can be previewed again afterwards', async () => {
|
|
128
|
+
await previewer.clearPreview();
|
|
129
|
+
await previewer.preview();
|
|
130
|
+
expect(map.sources.has(HIGHLIGHT_SOURCE)).toBe(true);
|
|
131
|
+
expect(map.layers.size).toEqual(3);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import RasterPreviewer from "./raster";
|
|
2
|
+
export default class WmtsPreviewer extends RasterPreviewer {
|
|
3
|
+
layers;
|
|
4
|
+
// WMTS sources have multiple XYZ tile URLs for fallbacks, and each layer
|
|
5
|
+
// can have different tile size, zoom range, bounds, etc.
|
|
6
|
+
async createSources() {
|
|
7
|
+
const layers = await this.resource.getLayers();
|
|
8
|
+
return layers.map(layer => ({
|
|
9
|
+
id: `${this.resource.id}-${layer.id}`,
|
|
10
|
+
type: 'raster',
|
|
11
|
+
tiles: layer.tileUrls,
|
|
12
|
+
scheme: this.resource.getScheme(),
|
|
13
|
+
tileSize: layer.tileSize,
|
|
14
|
+
minzoom: layer.minzoom,
|
|
15
|
+
maxzoom: layer.maxzoom,
|
|
16
|
+
...(layer.bounds && { bounds: layer.bounds }),
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
// One layer per source, with the same ID as the source
|
|
20
|
+
async createLayers() {
|
|
21
|
+
const layers = await this.resource.getLayers();
|
|
22
|
+
return layers.map(layer => ({
|
|
23
|
+
id: `${this.resource.id}-${layer.id}`,
|
|
24
|
+
type: 'raster',
|
|
25
|
+
source: `${this.resource.id}-${layer.id}`,
|
|
26
|
+
paint: {
|
|
27
|
+
'raster-opacity': this.opacity,
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|