Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide OS API keys as properties, rather than env vars #29

Merged
merged 5 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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=👻
60 changes: 46 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,38 @@ 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
<my-map osVectorTilesApiKey="secret" />
```

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 = "";
```

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.

#### Example: load static geojson

```html
<body>
<my-map geojsonBuffer="10" />
<script>
const map = document.querySelector("my-map");
map.geojsonData = { ... }
</script>
</body>
```

Available properties & their default values:
```js
@property({ type: Object })
geojsonData = {
Expand All @@ -29,39 +58,42 @@ 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
<my-map showFeaturesAtPoint osFeaturesApiKey="secret" latitude="51.4858363" longitude="-0.0761246" featureColor="#8a2be2" />
```

Relevant configurable properties & their default values:
Requires access to the Ordnance Survey Features API. Sign up for a key here https://osdatahub.os.uk/plans.

Available properties & their default values:
```js
@property({ type: Boolean })
showFeaturesAtPoint = false;

@property({ type: String })
osFeaturesApiKey = "";

@property({ type: Number })
latitude = 51.507351;

@property({ type: Number })
longitude = -0.127758;

@property({ type: Boolean })
showFeaturesAtPoint = false;

@property({ type: String })
featureColor = "#0000ff";

@property({ type: Number })
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.

`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
<my-map latitude="51.4858363" longitude="-0.0761246" showFeaturesAtPoint featureColor="#8a2be2" />
```

## 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
32 changes: 23 additions & 9 deletions src/my-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, getFeatures } from "./os-features";
import { osVectorTileBaseMap, rasterBaseMap } from "./os-layers";
import { makeFeatureLayer, featureSource, getFeaturesAtPoint } from "./os-features";
import { makeOsVectorTileBaseMap, makeRasterBaseMap } from "./os-layers";
import { formatArea } from "./utils";

@customElement("my-map")
Expand Down Expand Up @@ -84,17 +85,27 @@ export class MyMap extends LitElement {
@property({ type: Number })
geojsonBuffer = 12;

private useVectorTiles =
Boolean(import.meta.env.VITE_APP_ORDNANCE_SURVEY_KEY) &&
osVectorTileBaseMap;
Copy link
Member Author

@jessicamcinchak jessicamcinchak Aug 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a bit confusing: I originally thought useVectorTiles could use lit's @state() decorator and be an internal reactive property, but since it now references other configurable property values, it has to be defined within firstUpdated() to evaluate correctly!

@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 || "";

// 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(
Expand Down Expand Up @@ -179,7 +190,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) {
Expand Down Expand Up @@ -213,9 +224,12 @@ 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);
const featureLayer = makeFeatureLayer(this.featureColor);
map.addLayer(featureLayer);

// ensure getFeatures has fetched successfully
Expand Down
8 changes: 5 additions & 3 deletions src/os-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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<number>) {
export function getFeaturesAtPoint(coord: Array<number>, apiKey: any) {
const xml = `
<ogc:Filter>
<ogc:Contains>
Expand All @@ -38,9 +39,10 @@ export function getFeatures(coord: Array<number>) {
</ogc:Contains>
</ogc:Filter>
`;

// 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",
Expand Down
79 changes: 37 additions & 42 deletions src/os-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,47 @@ 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 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
}`;
const vectorTileStyleUrl = `https://api.os.uk/maps/vector/v1/vts/resources/styles?srs=3857&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=`;
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 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 const osVectorTileBaseMap = new VectorTileLayer({
declutter: true,
source: new VectorTileSource({
format: new MVT(),
url: vectorTileServiceUrl,
attributions: [COPYRIGHT],
attributionsCollapsible: false,
}),
});
export function makeRasterBaseMap(apiKey?: string) {
return new TileLayer({
source: apiKey
? new XYZ({
url: tileServiceUrl + apiKey,
attributions: [COPYRIGHT],
attributionsCollapsible: false,
maxZoom: 20,
})
: // no OS API key found, sign up here https://osdatahub.os.uk/plans
new OSM({
attributions: [ATTRIBUTION],
}),
});
}

// 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;
}
4 changes: 2 additions & 2 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// <reference types="vite/client" />

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";