From 2e6d002735706ed8596f68ff6e32b97ed2264671 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 10 Aug 2021 17:06:02 +0200 Subject: [PATCH 1/4] wip, todo vector tile style function --- .env.example | 4 ++-- src/my-map.ts | 22 ++++++++++++++++------ src/os-features.ts | 6 ++++-- src/os-layers.ts | 36 ++++++++++++++++++------------------ 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index 317a9ad..e54cd54 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ -VITE_APP_ORDNANCE_SURVEY_KEY=👻 -VITE_APP_OS_WFS_KEY=👻 +VITE_APP_OS_VECTOR_TILES_API_KEY=👻 +VITE_APP_OS_FEATURES_API_KEY=👻 diff --git a/src/my-map.ts b/src/my-map.ts index e7ef0ad..98de518 100644 --- a/src/my-map.ts +++ b/src/my-map.ts @@ -11,8 +11,8 @@ import View from "ol/View"; import { last } from "rambda"; import { draw, drawingLayer, drawingSource, modify, snap } from "./draw"; -import { createFeatureLayer, featureSource, getFeatures } from "./os-features"; -import { osVectorTileBaseMap, rasterBaseMap } from "./os-layers"; +import { createFeatureLayer, featureSource, getFeaturesAtPoint } from "./os-features"; +import { osVectorTileBaseMap, makeRasterBaseMap } from "./os-layers"; import { formatArea } from "./utils"; @customElement("my-map") @@ -84,14 +84,21 @@ export class MyMap extends LitElement { @property({ type: Number }) geojsonBuffer = 12; + @property({ type: String }) + osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY; + + @property({ type: String }) + osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; + private useVectorTiles = - Boolean(import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY) && - osVectorTileBaseMap; + Boolean(this.osVectorTilesApiKey) && osVectorTileBaseMap; // runs after the initial render firstUpdated() { const target = this.shadowRoot?.querySelector("#map") as HTMLElement; + const rasterBaseMap = makeRasterBaseMap(this.osVectorTilesApiKey); + const map = new Map({ target, layers: [this.useVectorTiles ? osVectorTileBaseMap : rasterBaseMap], @@ -179,7 +186,7 @@ export class MyMap extends LitElement { // log total area of feature (assumes geojson is a single polygon) const data = outlineSource.getFeatures()[0].getGeometry(); - console.log('geojsonData total area:', formatArea(data)); + console.log("geojsonData total area:", formatArea(data)); } if (this.drawMode) { @@ -213,7 +220,10 @@ export class MyMap extends LitElement { } if (this.showFeaturesAtPoint) { - getFeatures(fromLonLat([this.longitude, this.latitude])); + getFeaturesAtPoint( + fromLonLat([this.longitude, this.latitude]), + this.osFeaturesApiKey + ); const featureLayer = createFeatureLayer(this.featureColor); map.addLayer(featureLayer); diff --git a/src/os-features.ts b/src/os-features.ts index 31d2a41..87938cb 100644 --- a/src/os-features.ts +++ b/src/os-features.ts @@ -24,8 +24,9 @@ export function createFeatureLayer(color: string) { * Create an OGC XML filter parameter value which will select the TopographicArea * features containing the coordinates of the provided point * @param coord - xy coordinate + * @param apiKey - Ordnance Survey Features API key, sign up here: https://osdatahub.os.uk/plans */ -export function getFeatures(coord: Array) { +export function getFeaturesAtPoint(coord: Array, apiKey: any) { const xml = ` @@ -38,9 +39,10 @@ export function getFeatures(coord: Array) { `; + // Define (WFS) parameters object const wfsParams = { - key: import.meta.env.VITE_APP_OS_WFS_KEY, + key: apiKey, service: "WFS", request: "GetFeature", version: "2.0.0", diff --git a/src/os-layers.ts b/src/os-layers.ts index 7bd80e1..c7506fa 100644 --- a/src/os-layers.ts +++ b/src/os-layers.ts @@ -7,32 +7,32 @@ import { ATTRIBUTION } from "ol/source/OSM"; import VectorTileSource from "ol/source/VectorTile"; // Ordnance Survey sources -const tileServiceUrl = `https://api.os.uk/maps/raster/v1/zxy/Light_3857/{z}/{x}/{y}.png?key=${ - import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY -}`; +const tileServiceUrl = `https://api.os.uk/maps/raster/v1/zxy/Light_3857/{z}/{x}/{y}.png?key=`; const vectorTileServiceUrl = `https://api.os.uk/maps/vector/v1/vts/tile/{z}/{y}/{x}.pbf?srs=3857&key=${ - import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY + import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY }`; const vectorTileStyleUrl = `https://api.os.uk/maps/vector/v1/vts/resources/styles?srs=3857&key=${ - import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY + import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY }`; // currently based on Southwark's OS license const COPYRIGHT = "© Crown copyright and database rights 2021 OS (0)100019252"; -export const rasterBaseMap = new TileLayer({ - source: import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY - ? new XYZ({ - url: tileServiceUrl, - attributions: [COPYRIGHT], - attributionsCollapsible: false, - maxZoom: 20, - }) - : // No OrdnanceSurvey key found, sign up for free here https://osdatahub.os.uk/plans - new OSM({ - attributions: [ATTRIBUTION], - }), -}); +export function makeRasterBaseMap(apiKey?: any) { + return new TileLayer({ + source: apiKey + ? new XYZ({ + url: tileServiceUrl + apiKey, + attributions: [COPYRIGHT], + attributionsCollapsible: false, + maxZoom: 20, + }) + : // No OrdnanceSurvey key found, sign up for free here https://osdatahub.os.uk/plans + new OSM({ + attributions: [ATTRIBUTION], + }), + }); +} export const osVectorTileBaseMap = new VectorTileLayer({ declutter: true, From ddf3f70ab4a54e58021572dd830bd681a009545a Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 10 Aug 2021 21:00:54 +0200 Subject: [PATCH 2/4] refactor os-layers to accept apiKeys as props --- src/my-map.ts | 18 ++++++++++------ src/os-features.ts | 2 +- src/os-layers.ts | 51 +++++++++++++++++++++------------------------- src/vite-env.d.ts | 4 ++-- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/my-map.ts b/src/my-map.ts index 37877c7..d73e253 100644 --- a/src/my-map.ts +++ b/src/my-map.ts @@ -1,5 +1,5 @@ import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { Control } from "ol/control"; import { buffer } from "ol/extent"; import { GeoJSON } from "ol/format"; @@ -10,9 +10,10 @@ import { Vector as VectorSource } from "ol/source"; import { Stroke, Style } from "ol/style"; import View from "ol/View"; import { last } from "rambda"; + import { draw, drawingLayer, drawingSource, modify, snap } from "./draw"; -import { createFeatureLayer, featureSource, getFeaturesAtPoint } from "./os-features"; -import { osVectorTileBaseMap, makeRasterBaseMap } from "./os-layers"; +import { makeFeatureLayer, featureSource, getFeaturesAtPoint } from "./os-features"; +import { makeOsVectorTileBaseMap, makeRasterBaseMap } from "./os-layers"; import { formatArea } from "./utils"; @customElement("my-map") @@ -84,20 +85,25 @@ export class MyMap extends LitElement { @property({ type: Number }) geojsonBuffer = 12; + @property({ type: Boolean }) + renderVectorTiles = true; + @property({ type: String }) osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY; @property({ type: String }) osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; - private useVectorTiles = - Boolean(this.osVectorTilesApiKey) && osVectorTileBaseMap; + // internal component properties + @state() + useVectorTiles = this.renderVectorTiles && Boolean(this.osVectorTilesApiKey); // runs after the initial render firstUpdated() { const target = this.shadowRoot?.querySelector("#map") as HTMLElement; const rasterBaseMap = makeRasterBaseMap(this.osVectorTilesApiKey); + const osVectorTileBaseMap = makeOsVectorTileBaseMap(this.osVectorTilesApiKey); const map = new Map({ target, @@ -225,7 +231,7 @@ export class MyMap extends LitElement { this.osFeaturesApiKey ); - const featureLayer = createFeatureLayer(this.featureColor); + const featureLayer = makeFeatureLayer(this.featureColor); map.addLayer(featureLayer); // ensure getFeatures has fetched successfully diff --git a/src/os-features.ts b/src/os-features.ts index 87938cb..2f67194 100644 --- a/src/os-features.ts +++ b/src/os-features.ts @@ -8,7 +8,7 @@ const featureServiceUrl = "https://api.os.uk/features/v1/wfs"; export const featureSource = new VectorSource(); -export function createFeatureLayer(color: string) { +export function makeFeatureLayer(color: string) { return new VectorLayer({ source: featureSource, style: new Style({ diff --git a/src/os-layers.ts b/src/os-layers.ts index c7506fa..48eaea7 100644 --- a/src/os-layers.ts +++ b/src/os-layers.ts @@ -8,17 +8,13 @@ import VectorTileSource from "ol/source/VectorTile"; // Ordnance Survey sources const tileServiceUrl = `https://api.os.uk/maps/raster/v1/zxy/Light_3857/{z}/{x}/{y}.png?key=`; -const vectorTileServiceUrl = `https://api.os.uk/maps/vector/v1/vts/tile/{z}/{y}/{x}.pbf?srs=3857&key=${ - import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY -}`; -const vectorTileStyleUrl = `https://api.os.uk/maps/vector/v1/vts/resources/styles?srs=3857&key=${ - import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY -}`; +const vectorTileServiceUrl = `https://api.os.uk/maps/vector/v1/vts/tile/{z}/{y}/{x}.pbf?srs=3857&key=`; +const vectorTileStyleUrl = `https://api.os.uk/maps/vector/v1/vts/resources/styles?srs=3857&key=`; // currently based on Southwark's OS license const COPYRIGHT = "© Crown copyright and database rights 2021 OS (0)100019252"; -export function makeRasterBaseMap(apiKey?: any) { +export function makeRasterBaseMap(apiKey?: string) { return new TileLayer({ source: apiKey ? new XYZ({ @@ -27,32 +23,31 @@ export function makeRasterBaseMap(apiKey?: any) { attributionsCollapsible: false, maxZoom: 20, }) - : // No OrdnanceSurvey key found, sign up for free here https://osdatahub.os.uk/plans + : // no OS API key found, sign up here https://osdatahub.os.uk/plans new OSM({ attributions: [ATTRIBUTION], }), }); } -export const osVectorTileBaseMap = new VectorTileLayer({ - declutter: true, - source: new VectorTileSource({ - format: new MVT(), - url: vectorTileServiceUrl, - attributions: [COPYRIGHT], - attributionsCollapsible: false, - }), -}); - -// ref https://github.com/openlayers/ol-mapbox-style#usage-example -async function applyVectorTileStyle() { - try { - const response = await fetch(vectorTileStyleUrl); - const glStyle = await response.json(); - stylefunction(osVectorTileBaseMap, glStyle, "esri"); - } catch (error) { - console.log(error); +export function makeOsVectorTileBaseMap(apiKey: string) { + let osVectorTileLayer = new VectorTileLayer({ + declutter: true, + source: new VectorTileSource({ + format: new MVT(), + url: vectorTileServiceUrl + apiKey, + attributions: [COPYRIGHT], + attributionsCollapsible: false, + }), + }); + + if (apiKey) { + // ref https://github.com/openlayers/ol-mapbox-style#usage-example + fetch(vectorTileStyleUrl + apiKey) + .then(response => response.json()) + .then(glStyle => stylefunction(osVectorTileLayer, glStyle, "esri")) + .catch(error => console.log(error)); } -} -applyVectorTileStyle(); + return osVectorTileLayer; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index a3de1d1..015b907 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,8 +1,8 @@ /// interface ImportMetaEnv { - VITE_APP_ORDNANCE_SURVEY_KEY: string; - VITE_APP_OS_WFS_KEY: string; + VITE_APP_OS_VECTOR_TILES_API_KEY: string; + VITE_APP_OS_FEATURES_API_KEY: string; } declare module "ol-mapbox-style/dist/stylefunction"; From 6bdecb2421c6880012a081a4dc7860c69763dd62 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 10 Aug 2021 21:44:15 +0200 Subject: [PATCH 3/4] define useVectorTiles in firstUpdated(), add API props to README examples --- README.md | 50 +++++++++++++++++++++++++++++++++++++------------- src/my-map.ts | 8 +++----- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 75052cf..ca138a9 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,30 @@ An OpenLayers-powered Web Component map for tasks related to planning permission [CodeSandbox](https://codesandbox.io/s/confident-benz-rr0s9?file=/index.html) -### Example: load static geojson +#### Example: render the Ordnance Survey vector tile basemap -Relevant configurable properties & their default values: +```html + +``` + +Requires access to the Ordnance Survey Vector Tiles API. Sign up for a key here https://osdatahub.os.uk/plans. + +Available properties & their default values: +```js +@property({ type: Boolean }) +renderVectorTiles = true; + +@property({ type: String }) +osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY; +``` + +We want to use the most detailed base map possible, so `renderVectorTiles` is true by default. If it's true and you've provided an API key, we'll render the Ordnance Survey vector tiles. If you configure it to false, but still provide an API key, we'll render the OS raster base map. If there is no API key, regardless of the value of `renderVectorTiles`, we'll fallback to the OpenStreetMap tile server. + +If you're developing locally, you can set your API key as an environment variable, otherwise pass it directly as an html property. + +#### Example: load static geojson + +Available properties & their default values: ```js @property({ type: Object }) geojsonData = { @@ -29,21 +50,28 @@ geojsonBuffer = 15; `geojsonColor` & `geojsonBuffer` are optional style properties. Color sets the stroke of the displayed data and buffer is used to fit the map view to the extent of the geojson features. `geojsonBuffer` corresponds to "value" param in OL documentation [here](https://openlayers.org/en/latest/apidoc/module-ol_extent.html). -### Example: highlight features that intersect with a given coordinate +#### Example: highlight features that intersect with a given coordinate -Requires access to the Ordnance Survey Features API. Sign up for a key here: https://osdatahub.os.uk/plans. +```html + +``` + +Requires access to the Ordnance Survey Features API. Sign up for a key here https://osdatahub.os.uk/plans. -Relevant configurable properties & their default values: +Available properties & their default values: ```js +@property({ type: Boolean }) +showFeaturesAtPoint = false; + +@property({ type: String }) +osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; + @property({ type: Number }) latitude = 51.507351; @property({ type: Number }) longitude = -0.127758; -@property({ type: Boolean }) -showFeaturesAtPoint = false; - @property({ type: String }) featureColor = "#0000ff"; @@ -51,14 +79,10 @@ featureColor = "#0000ff"; featureBuffer = 40; ``` -Set `showFeaturesAtPoint` to true. `latitude` and `longitude` are required and used to query the OS Features API for features that contain this point. +Set `showFeaturesAtPoint` to true. `osFeaturesApiKey`, `latitude`, and `longitude` are each required to query the OS Features API for features that contain this point. If you're developing locally, you can set your API key as an environment variable, otherwise pass it directly as an html property. `featureColor` & `featureBuffer` are optional style properties. Color sets the stroke of the displayed data and buffer is used to fit the map view to the extent of the features. `featureBuffer` corresponds to "value" param in OL documentation [here](https://openlayers.org/en/latest/apidoc/module-ol_extent.html). -```html - -``` - ## Running Locally - Rename `.env.example` to `.env.local` and replace the values diff --git a/src/my-map.ts b/src/my-map.ts index d73e253..c0a1b25 100644 --- a/src/my-map.ts +++ b/src/my-map.ts @@ -94,20 +94,18 @@ export class MyMap extends LitElement { @property({ type: String }) osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; - // internal component properties - @state() - useVectorTiles = this.renderVectorTiles && Boolean(this.osVectorTilesApiKey); - // runs after the initial render firstUpdated() { const target = this.shadowRoot?.querySelector("#map") as HTMLElement; + const useVectorTiles = this.renderVectorTiles && Boolean(this.osVectorTilesApiKey); + const rasterBaseMap = makeRasterBaseMap(this.osVectorTilesApiKey); const osVectorTileBaseMap = makeOsVectorTileBaseMap(this.osVectorTilesApiKey); const map = new Map({ target, - layers: [this.useVectorTiles ? osVectorTileBaseMap : rasterBaseMap], + layers: [useVectorTiles ? osVectorTileBaseMap : rasterBaseMap], view: new View({ projection: "EPSG:3857", extent: transformExtent( From 8e556543b338780ae2ea16532be060df6a3eb8c8 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Wed, 11 Aug 2021 13:12:21 +0200 Subject: [PATCH 4/4] .env.development.local instead so we don't bundle our personal keys in the prod build --- README.md | 20 ++++++++++++++------ src/my-map.ts | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ca138a9..7e7789e 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,23 @@ Available properties & their default values: renderVectorTiles = true; @property({ type: String }) -osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY; +osVectorTilesApiKey = ""; ``` We want to use the most detailed base map possible, so `renderVectorTiles` is true by default. If it's true and you've provided an API key, we'll render the Ordnance Survey vector tiles. If you configure it to false, but still provide an API key, we'll render the OS raster base map. If there is no API key, regardless of the value of `renderVectorTiles`, we'll fallback to the OpenStreetMap tile server. -If you're developing locally, you can set your API key as an environment variable, otherwise pass it directly as an html property. - #### Example: load static geojson +```html + + + + +``` + Available properties & their default values: ```js @property({ type: Object }) @@ -64,7 +72,7 @@ Available properties & their default values: showFeaturesAtPoint = false; @property({ type: String }) -osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; +osFeaturesApiKey = ""; @property({ type: Number }) latitude = 51.507351; @@ -79,13 +87,13 @@ featureColor = "#0000ff"; featureBuffer = 40; ``` -Set `showFeaturesAtPoint` to true. `osFeaturesApiKey`, `latitude`, and `longitude` are each required to query the OS Features API for features that contain this point. If you're developing locally, you can set your API key as an environment variable, otherwise pass it directly as an html property. +Set `showFeaturesAtPoint` to true. `osFeaturesApiKey`, `latitude`, and `longitude` are each required to query the OS Features API for features that contain this point. `featureColor` & `featureBuffer` are optional style properties. Color sets the stroke of the displayed data and buffer is used to fit the map view to the extent of the features. `featureBuffer` corresponds to "value" param in OL documentation [here](https://openlayers.org/en/latest/apidoc/module-ol_extent.html). ## Running Locally -- Rename `.env.example` to `.env.local` and replace the values +- Rename `.env.example` to `.env.development.local` and replace the values - or simply provide your API keys as props - Install dependencies `pnpm i` - Start dev server `pnpm dev` - Open http://localhost:3000 diff --git a/src/my-map.ts b/src/my-map.ts index c0a1b25..e55d45a 100644 --- a/src/my-map.ts +++ b/src/my-map.ts @@ -1,5 +1,5 @@ import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import { Control } from "ol/control"; import { buffer } from "ol/extent"; import { GeoJSON } from "ol/format"; @@ -89,10 +89,10 @@ export class MyMap extends LitElement { renderVectorTiles = true; @property({ type: String }) - osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY; + osVectorTilesApiKey = import.meta.env.VITE_APP_OS_VECTOR_TILES_API_KEY || ""; @property({ type: String }) - osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY; + osFeaturesApiKey = import.meta.env.VITE_APP_OS_FEATURES_API_KEY || ""; // runs after the initial render firstUpdated() {