Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak committed Aug 19, 2024
1 parent deb19ca commit de021c3
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 153 deletions.
3 changes: 1 addition & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<h1 style="color:red;font-size:16px;">*** This is a testing sandbox - these components are unaware of each other!
***</h1>
<div style="margin-bottom:1em">
<my-map zoom="20" maxZoom="23" id="example-map" drawMode drawMany drawType="Polygon" applySatelliteStyle useScaleBarStyle showScale showNorthArrow showPrint
<my-map zoom="20" maxZoom="23" id="example-map" drawMode drawMany drawType="Polygon" useScaleBarStyle showScale showNorthArrow showPrint
osProxyEndpoint="https://api.editor.planx.dev/proxy/ordnance-survey" ariaLabelOlFixedOverlay="Interactive example map" />
</div>
<div style="margin-bottom:1em">
Expand All @@ -67,7 +67,6 @@ <h1 style="color:red;font-size:16px;">*** This is a testing sandbox - these comp
<script>
// --- MAP --- //
const map = document.querySelector("my-map");
map.geojsonDataCopyright = `<a href="https://www.planning.data.gov.uk/dataset/title-boundary" target="_blank">Title boundary</a> subject to Crown copyright and database rights ${new Date().getFullYear()} OS (0)100026316`;
map.clipGeojsonData = {
"type": "Feature",
"geometry": {
Expand Down
89 changes: 57 additions & 32 deletions src/components/my-map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import { Point } from "ol/geom";
import { Feature } from "ol/index";
import { defaults as defaultInteractions } from "ol/interaction";
import { Vector as VectorLayer } from "ol/layer";
import BaseLayer from "ol/layer/Base";
import TileLayer from "ol/layer/Tile";
import VectorTileLayer from "ol/layer/VectorTile";
import Map from "ol/Map";
import { ProjectionLike, transform, transformExtent } from "ol/proj";
import { Vector as VectorSource } from "ol/source";
import { Vector as VectorSource, XYZ } from "ol/source";
import { Circle, Fill, Icon, Stroke, Style } from "ol/style";
import View from "ol/View";
import {
Expand All @@ -29,12 +32,18 @@ import {
snap,
} from "./drawing";
import pinIcon from "./icons/poi-alt.svg";
import {
BasemapEnum,
makeDefaultTileLayer,
makeMapboxSatelliteBasemap,
makeOSRasterBasemap,
makeOsVectorTileBasemap,
} from "./layers";
import {
getFeaturesAtPoint,
makeFeatureLayer,
outlineSource,
} from "./os-features";
import { makeOsVectorTileBaseMap, makeRasterBaseMap } from "./os-layers";
import { proj27700, ProjectionEnum } from "./projections";
import {
getSnapPointsFromVectorTiles,
Expand Down Expand Up @@ -68,6 +77,9 @@ export class MyMap extends LitElement {
@property({ type: Number })
longitude = -0.127758;

@property({ type: String })
basemap: BasemapEnum = "OSVectorTile";

@property({ type: String })
projection: ProjectionEnum = "EPSG:4326";

Expand Down Expand Up @@ -176,6 +188,9 @@ export class MyMap extends LitElement {
@property({ type: Number })
geojsonBuffer = 12;

/**
* @deprecated - please specify `basemap="OSRaster"` directly instead
*/
@property({ type: Boolean })
disableVectorTiles = false;

Expand All @@ -192,6 +207,9 @@ export class MyMap extends LitElement {
@property({ type: String })
osProxyEndpoint = "";

/**
* @deprecated - please specify `basemap="MapboxSatellite"` + `mapboxAccessToken` instead
*/
@property({ type: Boolean })
applySatelliteStyle = false;

Expand Down Expand Up @@ -248,21 +266,35 @@ export class MyMap extends LitElement {
firstUpdated() {
const target = this.renderRoot.querySelector(`#${this.id}`) as HTMLElement;

const rasterBaseMap = makeRasterBaseMap(
this.osVectorTilesApiKey,
this.osProxyEndpoint,
this.osCopyright,
this.collapseAttributions,
);
const osVectorTileBaseMap = makeOsVectorTileBaseMap(
this.osVectorTilesApiKey,
this.osProxyEndpoint,
this.osCopyright,
this.collapseAttributions,
);
const basemapLayers: BaseLayer[] = [];
let osVectorTileBasemap: VectorTileLayer | undefined,
osRasterBasemap: TileLayer<XYZ> | undefined,
mapboxSatelliteBasemap: VectorLayer<VectorSource> | undefined;

const useVectorTiles =
!this.disableVectorTiles && Boolean(osVectorTileBaseMap);
if (this.basemap === "OSVectorTile") {
osVectorTileBasemap = makeOsVectorTileBasemap(
this.osVectorTilesApiKey,
this.osProxyEndpoint,
this.osCopyright,
);
if (osVectorTileBasemap) basemapLayers.push(osVectorTileBasemap);
} else if (this.basemap === "OSRaster") {
osRasterBasemap = makeOSRasterBasemap(
this.osVectorTilesApiKey,
this.osProxyEndpoint,
this.osCopyright,
);
if (osRasterBasemap) basemapLayers.push(osRasterBasemap);
} else if (this.basemap === "MapboxSatellite") {
mapboxSatelliteBasemap = makeMapboxSatelliteBasemap(
this.mapboxAccessToken,
);
if (mapboxSatelliteBasemap) basemapLayers.push(mapboxSatelliteBasemap);
} else if (this.basemap === "OSM" || basemapLayers.length === 0) {
// Fallback to OpenStreetMap if we've failed to make any of the above layers, or if it's set directly
const osmBasemap = makeDefaultTileLayer();
basemapLayers.push(osmBasemap);
}

// @ts-ignore
const projection: ProjectionLike =
Expand All @@ -284,7 +316,7 @@ export class MyMap extends LitElement {

const map = new Map({
target,
layers: [useVectorTiles ? osVectorTileBaseMap! : rasterBaseMap],
layers: basemapLayers,
view: new View({
projection: "EPSG:3857",
extent: clipExtent
Expand All @@ -303,6 +335,9 @@ export class MyMap extends LitElement {
}),
controls: defaultControls({
attribution: true,
attributionOptions: {
collapsed: this.collapseAttributions,
},
zoom: !this.staticMode,
rotate: false, // alternatively uses custom prop `showNorthArrow`
}),
Expand All @@ -315,21 +350,11 @@ export class MyMap extends LitElement {

this.map = map;

if (this.mapboxAccessToken && this.applySatelliteStyle) {
if (osVectorTileBaseMap) map.removeLayer(osVectorTileBaseMap);
if (rasterBaseMap) map.removeLayer(rasterBaseMap);

if (this.basemap === "MapboxSatellite" && mapboxSatelliteBasemap) {
apply(
map,
`https://api.mapbox.com/styles/v1/mapbox/satellite-v9?access_token=${this.mapboxAccessToken}`,
);

const satelliteAttribution =
'<a href="https://www.mapbox.com/about/maps/" target="_blank" rel="noopener noreferrer">© Mapbox</a> <a href="http://www.openstreetmap.org/about/" target="_blank" rel="noopener noreferrer">© OpenStreetMap</a> <a href="https://labs.mapbox.com/contribute/#/-74@site/src/10" target="_blank" rel="noopener noreferrer"><strong>Improve this map</strong></a>';
const satelliteLayer = new VectorLayer({
source: new VectorSource({ attributions: satelliteAttribution }), // empty besides attribution
});
map.addLayer(satelliteLayer);
}

// Append to global window for reference in tests
Expand Down Expand Up @@ -543,8 +568,8 @@ export class MyMap extends LitElement {
if (
this.drawMode &&
this.drawType === "Polygon" &&
useVectorTiles &&
osVectorTileBaseMap
this.basemap === "OSVectorTile" &&
osVectorTileBasemap
) {
// define zoom threshold for showing snaps (not @property yet because computationally expensive!)
const snapsZoom: number = 20;
Expand All @@ -559,7 +584,7 @@ export class MyMap extends LitElement {
const currentZoom: number | undefined = map.getView().getZoom();
if (currentZoom && currentZoom >= snapsZoom) {
const extent = map.getView().calculateExtent(map.getSize());
getSnapPointsFromVectorTiles(osVectorTileBaseMap, extent);
getSnapPointsFromVectorTiles(osVectorTileBasemap, extent);
}
};

Expand All @@ -570,7 +595,7 @@ export class MyMap extends LitElement {
// Timeout minimizes updates mid-pan/drag
map.on("moveend", () => {
const isSourceLoaded =
osVectorTileBaseMap.getSource()?.getState() === "ready";
osVectorTileBasemap.getSource()?.getState() === "ready";
if (isSourceLoaded) setTimeout(addSnapPoints, 200);
});
}
Expand Down
122 changes: 122 additions & 0 deletions src/components/my-map/layers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { setupMap } from "../../test-utils";
import "./index";

import type { IWindow } from "happy-dom";
import { OSM, XYZ } from "ol/source";
import VectorTileSource from "ol/source/VectorTile";

declare global {
interface Window extends IWindow {}
}

declare global {
interface Window extends IWindow {}
}

describe("Basemap layer loading", () => {
afterEach(() => {
vi.resetAllMocks();
});

it("loads OSVectorTile basemap by default and requests layers directly from OS when an API key is provided", async () => {
const apiKey = process.env.VITE_APP_OS_VECTOR_TILES_API_KEY;
await setupMap(`
<my-map
id="map-vitest"
osVectorTileApiKey=${apiKey}
/>`);
const vectorBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "osVectorTileBasemap");
expect(vectorBasemap).toBeDefined();
const source = vectorBasemap?.getSource() as VectorTileSource;
expect(source.getUrls()).toHaveLength(1);
expect(source.getUrls()?.[0]).toEqual(
expect.stringMatching(/^https:\/\/api.os.uk/),
);
expect(source.getUrls()?.[0]).toEqual(
expect.stringContaining(`key=${apiKey}`),
);
});

it("loads OSVectorTile basemap by default and requests layers via proxy when an API key is not provided", async () => {
const fetchSpy = vi.spyOn(window, "fetch");

const osProxyEndpoint = "https://www.my-site.com/api/v1/os";
await setupMap(`
<my-map
id="map-vitest"
osProxyEndpoint=${osProxyEndpoint}
/>`);
const vectorBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "osVectorTileBasemap");
expect(vectorBasemap).toBeDefined();
const source = vectorBasemap?.getSource() as VectorTileSource;

// Tiles are being requested via proxy
expect(source.getUrls()).toHaveLength(1);
expect(source.getUrls()?.[0]).toEqual(
expect.stringContaining(osProxyEndpoint),
);
// Style is being fetched via proxy
expect(fetchSpy).toHaveBeenCalledWith(
"https://www.my-site.com/api/v1/os/maps/vector/v1/vts/resources/styles?srs=3857",
);
});

it("loads OSRaster basemap when an OS API key is provided", async () => {
const apiKey = process.env.VITE_APP_OS_VECTOR_TILES_API_KEY;
await setupMap(`
<my-map
id="map-vitest"
basemap="OSRaster"
osVectorTileApiKey=${apiKey}
/>`);
const rasterBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "osRasterBasemap");
expect(rasterBasemap).toBeDefined();
const source = rasterBasemap?.getSource() as XYZ;
expect(source.getUrls()?.length).toBeGreaterThan(0);
source.getUrls()?.forEach((url) => expect(url).toMatch(/api.os.uk/));
});

it.skip("loads MapboxSatellite basemap when a Mapbox access token is provided", async () => {
const accessToken = process.env.VITE_APP_MAPBOX_ACCESS_TOKEN;
await setupMap(
`<my-map id="map-vitest" basemap="MapboxSatellite" mapboxAccessToken=${accessToken}`,
);
const satelliteBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "mapboxSatelliteBasemap"); // 'name' not getting set?
expect(satelliteBasemap).toBeDefined();
});

it("loads OSM basemap when specified", async () => {
await setupMap(`<my-map id="map-vitest" basemap="OSM" />`);
const osmBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "osmBasemap");
expect(osmBasemap).toBeDefined();
const source = osmBasemap?.getSource() as OSM;
expect(source.getUrls()?.length).toBeGreaterThan(0);
source
.getUrls()
?.forEach((url) => expect(url).toMatch(/openstreetmap\.org/));
});

it("fallsback to an OSM basemap when an OS basemap is specified without an OS API key or proxy endpoint", async () => {
await setupMap(`
<my-map id="map-vitest" basemap="OSVectorTile" osVectorTilesApiKey=${undefined} />`);
const osmBasemap = window.olMap
?.getAllLayers()
.find((layer) => layer.get("name") === "osmBasemap");
expect(osmBasemap).toBeDefined();
const source = osmBasemap?.getSource() as OSM;
expect(source.getUrls()?.length).toBeGreaterThan(0);
source
.getUrls()
?.forEach((url) => expect(url).toMatch(/openstreetmap\.org/));
});
});
Loading

0 comments on commit de021c3

Please sign in to comment.