diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 174640928..6d19a4205 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -46,6 +46,8 @@ jobs:
- name: Build Docusaurus + LikeC4
run: npm run docs:build:all
+ env:
+ OS_API_KEY: ${{ secrets.OS_API_KEY }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
diff --git a/demo/DemoMapBasic.js b/demo/DemoMapBasic.js
index 613888a05..beadb9ec6 100644
--- a/demo/DemoMapBasic.js
+++ b/demo/DemoMapBasic.js
@@ -1,14 +1,16 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
function MapInner () {
const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
useEffect(() => {
if (initialised.current) {
@@ -28,6 +30,7 @@ function MapInner () {
behaviour: 'inline',
mapProvider: maplibreProvider(),
mapStyle: MAP_STYLE,
+ transformRequest,
center: [-2.9631008,54.432306],
zoom: 15,
containerHeight: '516px'
diff --git a/demo/DemoMapButton.js b/demo/DemoMapButton.js
index e0e82f739..920d78c67 100644
--- a/demo/DemoMapButton.js
+++ b/demo/DemoMapButton.js
@@ -1,16 +1,18 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const CENTER = [-2.9631008, 54.432306]
const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
function MapInner () {
const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
useEffect(() => {
if (!initialised.current) {
@@ -28,6 +30,7 @@ function MapInner () {
behaviour: 'buttonFirst',
mapProvider: maplibreProvider(),
mapStyle: MAP_STYLE,
+ transformRequest,
center: CENTER,
zoom: 15,
hasExitButton: true
diff --git a/demo/DemoMapDrawTools.js b/demo/DemoMapDrawTools.js
index 98f19ca46..0e140c869 100644
--- a/demo/DemoMapDrawTools.js
+++ b/demo/DemoMapDrawTools.js
@@ -1,10 +1,11 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const MAP_STYLE = {
id: 'outdoor',
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
@@ -16,6 +17,7 @@ const ICON_DELETE = ' {
if (initialised.current) {
@@ -116,7 +119,8 @@ function MapInner () {
behaviour: 'inline',
mapProvider: maplibreProvider(),
mapStyle: MAP_STYLE,
- center: [-2.464, 54.558],
+ transformRequest,
+ center: [-2.464,54.5585475],
zoom: 14,
containerHeight: '516px',
plugins: [datasetsPlugin]
diff --git a/demo/DemoMapSearch.js b/demo/DemoMapSearch.js
new file mode 100644
index 000000000..3779b86ff
--- /dev/null
+++ b/demo/DemoMapSearch.js
@@ -0,0 +1,61 @@
+import { useEffect, useRef } from 'react'
+import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, OS_NAMES_URL, useOsTransformRequest, useOsGeocodeTransformRequest } from './osMapStyle.js'
+
+const MAP_STYLE = {
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
+ backgroundColor: '#f5f5f0'
+}
+
+function MapInner () {
+ const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
+ const geocodeTransformRequest = useOsGeocodeTransformRequest()
+
+ useEffect(() => {
+ if (initialised.current) {
+ return
+ }
+ initialised.current = true
+
+ Promise.all([
+ import('../src/index.js'),
+ import('../providers/maplibre/src/index.js'),
+ import('../plugins/search/src/index.js')
+ ]).then(([
+ { default: InteractiveMap },
+ { default: maplibreProvider },
+ { default: createSearchPlugin }
+ ]) => {
+ const searchPlugin = createSearchPlugin({
+ osNamesURL: OS_NAMES_URL,
+ transformRequest: geocodeTransformRequest,
+ width: '300px'
+ })
+
+ new InteractiveMap('demo-map-search', {
+ behaviour: 'inline',
+ mapProvider: maplibreProvider(),
+ mapStyle: MAP_STYLE,
+ transformRequest,
+ center: [-2.9631008, 54.432306],
+ zoom: 6,
+ containerHeight: '516px',
+ plugins: [searchPlugin]
+ })
+ })
+ }, [])
+
+ return
+}
+
+export default function DemoMapSearch () {
+ return (
+ The map requires JavaScript to be enabled.}
+ >
+ {() => }
+
+ )
+}
diff --git a/demo/DemoMapSearchNominatim.js b/demo/DemoMapSearchNominatim.js
deleted file mode 100644
index 0c404ac9f..000000000
--- a/demo/DemoMapSearchNominatim.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { useEffect, useRef } from 'react'
-import BrowserOnly from '@docusaurus/BrowserOnly'
-
-const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
- backgroundColor: '#f5f5f0'
-}
-
-const markQuery = (text, query) => {
- const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- return text.replace(new RegExp(`(${escaped})`, 'i'), '$1')
-}
-
-const SETTLEMENT_TYPES = new Set(['city', 'town', 'village', 'hamlet', 'suburb', 'quarter', 'neighbourhood', 'municipality', 'borough', 'district', 'county'])
-
-const nominatimDataset = {
- name: 'nominatim',
- buildRequest: (query) => {
- const params = new URLSearchParams({
- q: query,
- format: 'json',
- limit: '8',
- countrycodes: 'gb'
- })
- return new Request(`https://nominatim.openstreetmap.org/search?${params}`, {
- headers: { 'Accept-Language': 'en' }
- })
- },
- parseResults: (json, query) => {
- if (!Array.isArray(json)) {
- return []
- }
- const results = json.filter(item => SETTLEMENT_TYPES.has(item.addresstype)).map(item => {
- // Nominatim boundingbox: [min_lat, max_lat, min_lon, max_lon]
- const [minLat, maxLat, minLon, maxLon] = item.boundingbox.map(Number)
- return {
- id: String(item.place_id),
- text: item.display_name,
- marked: markQuery(item.display_name, query),
- point: [Number(item.lon), Number(item.lat)],
- bounds: [minLon, minLat, maxLon, maxLat]
- }
- })
- return Array.from(new Map(results.map(r => [r.text, r])).values())
- }
-}
-
-function MapInner () {
- const initialised = useRef(false)
-
- useEffect(() => {
- if (initialised.current) {
- return
- }
- initialised.current = true
-
- Promise.all([
- import('../src/index.js'),
- import('../providers/maplibre/src/index.js'),
- import('../plugins/search/src/index.js')
- ]).then(([
- { default: InteractiveMap },
- { default: maplibreProvider },
- { default: createSearchPlugin }
- ]) => {
- const searchPlugin = createSearchPlugin({
- customDatasets: [nominatimDataset],
- width: '300px'
- })
-
- new InteractiveMap('demo-map-search-control', {
- behaviour: 'inline',
- mapProvider: maplibreProvider(),
- mapStyle: MAP_STYLE,
- center: [-1.6, 53.1],
- zoom: 6,
- containerHeight: '516px',
- plugins: [searchPlugin]
- })
- })
- }, [])
-
- return
-}
-
-export default function DemoMapSearchNominatim () {
- return (
- The map requires JavaScript to be enabled.}
- >
- {() => }
-
- )
-}
diff --git a/demo/DemoMapSelectBuilding.js b/demo/DemoMapSelectBuilding.js
deleted file mode 100644
index cd3b58cda..000000000
--- a/demo/DemoMapSelectBuilding.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useEffect, useRef } from 'react'
-import BrowserOnly from '@docusaurus/BrowserOnly'
-
-const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
- backgroundColor: '#f5f5f0'
-}
-
-const CENTER = [-1.5491, 53.8008]
-const PANEL_ID = 'building-info'
-
-function MapInner () {
- const initialised = useRef(false)
-
- useEffect(() => {
- if (initialised.current) {
- return
- }
- initialised.current = true
-
- Promise.all([
- import('../src/index.js'),
- import('../providers/maplibre/src/index.js'),
- import('../plugins/interact/src/index.js')
- ]).then(([
- { default: InteractiveMap },
- { default: maplibreProvider },
- { default: createInteractPlugin }
- ]) => {
- const interactPlugin = createInteractPlugin({
- interactionModes: ['selectFeature'],
- deselectOnClickOutside: true,
- debug: true,
- layers: [
- { layerId: 'buildings 3D', idProperty: 'uuid' }
- ]
- })
-
- const map = new InteractiveMap('demo-map-select-building', {
- behaviour: 'inline',
- mapProvider: maplibreProvider(),
- mapStyle: MAP_STYLE,
- center: CENTER,
- zoom: 14.5,
- maxZoom: 14.5,
- containerHeight: '516px',
- plugins: [interactPlugin]
- })
-
- map.on('map:ready', () => {
- interactPlugin.enable()
-
- map.addPanel(PANEL_ID, {
- focus: false,
- label: 'Selected building',
- html: '',
- mobile: { slot: 'drawer', dismissible: true, open: false },
- tablet: { slot: 'left-top', dismissible: true, width: '300px', open: false },
- desktop: { slot: 'left-top', dismissible: true, width: '300px', open: false }
- })
- })
-
- map.on('interact:selectionchange', ({ selectedFeatures }) => {
- if (selectedFeatures.length > 0) {
- let html = ''
- for (const [k, v] of Object.entries(selectedFeatures[0].properties)) {
- html += `${k}: ${v}
`
- }
- document.getElementById('building-info-content').innerHTML = html
- map.showPanel(PANEL_ID)
- } else {
- map.hidePanel(PANEL_ID)
- }
- })
-
- map.on('app:panelclosed', ({ panelId }) => {
- if (panelId === PANEL_ID) {
- interactPlugin.clear()
- }
- })
- })
- }, [])
-
- return
-}
-
-export default function DemoMapSelectBuilding () {
- return (
- The map requires JavaScript to be enabled.}
- >
- {() => }
-
- )
-}
diff --git a/demo/DemoMapSelectFeature.js b/demo/DemoMapSelectFeature.js
new file mode 100644
index 000000000..8aaa00f6e
--- /dev/null
+++ b/demo/DemoMapSelectFeature.js
@@ -0,0 +1,119 @@
+import { useEffect, useRef } from 'react'
+import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
+import { geojson } from './DemoMapPolygons.js'
+
+const MAP_STYLE = {
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
+ backgroundColor: '#f5f5f0'
+}
+
+// Same field parcels as the Add polygons demo — a plain, single-style dataset
+// (no sublayers, no key/menu entry) purely so there's something with real ids and
+// properties to click on. The Open VTS style's own layers have neither.
+const parcelsDataset = {
+ id: 'field-parcels',
+ geojson,
+ minZoom: 10,
+ maxZoom: 24,
+ style: {
+ fill: 'rgba(29, 112, 184, 0.1)',
+ stroke: '#1d70b8',
+ strokeWidth: 2
+ }
+}
+
+// This dataset's geometry sits near Ambleside's field parcels, not the wider area
+// used by the other demos, so it needs its own center to actually be on screen
+const CENTER = [-2.464,54.5585475]
+const PANEL_ID = 'parcel-info'
+
+function MapInner () {
+ const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
+
+ useEffect(() => {
+ if (initialised.current) {
+ return
+ }
+ initialised.current = true
+
+ Promise.all([
+ import('../src/index.js'),
+ import('../providers/maplibre/src/index.js'),
+ import('../plugins/interact/src/index.js'),
+ import('../plugins/beta/datasets/src/index.js')
+ ]).then(([
+ { default: InteractiveMap },
+ { default: maplibreProvider },
+ { default: createInteractPlugin },
+ { default: createDatasetsPlugin }
+ ]) => {
+ const datasetsPlugin = createDatasetsPlugin({
+ datasets: [parcelsDataset]
+ })
+
+ const interactPlugin = createInteractPlugin({
+ interactionModes: ['selectFeature'],
+ deselectOnClickOutside: true,
+ layers: [
+ { layerId: 'field-parcels', idProperty: 'name' }
+ ]
+ })
+
+ const map = new InteractiveMap('demo-map-select-feature', {
+ behaviour: 'inline',
+ mapProvider: maplibreProvider(),
+ mapStyle: MAP_STYLE,
+ transformRequest,
+ center: CENTER,
+ zoom: 14,
+ containerHeight: '516px',
+ plugins: [datasetsPlugin, interactPlugin]
+ })
+
+ map.on('map:ready', () => {
+ interactPlugin.enable()
+
+ map.addPanel(PANEL_ID, {
+ focus: false,
+ label: 'Selected parcel',
+ html: '',
+ mobile: { slot: 'drawer', dismissible: true, open: false },
+ tablet: { slot: 'left-top', dismissible: true, width: '300px', open: false },
+ desktop: { slot: 'left-top', dismissible: true, width: '300px', open: false }
+ })
+ })
+
+ map.on('interact:selectionchange', ({ selectedFeatures }) => {
+ if (selectedFeatures.length > 0) {
+ const { name } = selectedFeatures[0].properties
+ document.getElementById('parcel-info-content').innerHTML =
+ `${name}
`
+ map.showPanel(PANEL_ID)
+ } else {
+ map.hidePanel(PANEL_ID)
+ }
+ })
+
+ map.on('app:panelclosed', ({ panelId }) => {
+ if (panelId === PANEL_ID) {
+ interactPlugin.clear()
+ }
+ })
+ })
+ }, [])
+
+ return
+}
+
+export default function DemoMapSelectFeature () {
+ return (
+ The map requires JavaScript to be enabled.}
+ >
+ {() => }
+
+ )
+}
diff --git a/demo/DemoMapStyleSwitcher.js b/demo/DemoMapStyleSwitcher.js
index 38e200f90..7b2e02fff 100644
--- a/demo/DemoMapStyleSwitcher.js
+++ b/demo/DemoMapStyleSwitcher.js
@@ -1,53 +1,44 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
-import outdoorThumb from '../docs/assets/images/outdoor-ozs-map-thumb.jpg'
-import nightThumb from '../docs/assets/images/night-ozs-map-thumb.jpg'
-import deuteranopiaThumb from '../docs/assets/images/deuteranopia-ozs-map-thumb.jpg'
-import tritanopiaThumb from '../docs/assets/images/tritanopia-ozs-map-thumb.jpg'
-
-const ATTRIBUTION = `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`
+import outdoorThumb from '../docs/assets/images/outdoor-map-thumb.jpg'
+import darkThumb from '../docs/assets/images/dark-map-thumb.jpg'
+import blackWhiteThumb from '../docs/assets/images/black-and-white-map-thumb.jpg'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const MAP_STYLES = [
{
id: 'outdoor',
label: 'Outdoor',
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
+ url: OS_VTS_STYLE_URLS.outdoor,
thumbnail: outdoorThumb,
- attribution: ATTRIBUTION,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
},
{
- id: 'night',
- label: 'Night',
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-night/style.json',
- thumbnail: nightThumb,
- attribution: ATTRIBUTION,
+ id: 'dark',
+ label: 'Dark',
+ url: OS_VTS_STYLE_URLS.dark,
+ thumbnail: darkThumb,
+ attribution: OS_ATTRIBUTION,
mapColorScheme: 'dark',
appColorScheme: 'dark'
},
{
- id: 'deuteranopia',
- label: 'Deuteranopia',
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-deuteranopia/style.json',
- thumbnail: deuteranopiaThumb,
- attribution: ATTRIBUTION,
- backgroundColor: '#f5f5f0'
- },
- {
- id: 'tritanopia',
- label: 'Tritanopia',
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-tritanopia/style.json',
- thumbnail: tritanopiaThumb,
- attribution: ATTRIBUTION,
+ id: 'blackWhite',
+ label: 'Black/White',
+ url: OS_VTS_STYLE_URLS.blackWhite,
+ thumbnail: blackWhiteThumb,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
]
function MapInner () {
const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
useEffect(() => {
- if (initialised.current) return
+ if (initialised.current) { return }
initialised.current = true
Promise.all([
@@ -69,9 +60,9 @@ function MapInner () {
new InteractiveMap('demo-map-style-switcher', {
behaviour: 'inline',
mapProvider: maplibreProvider(),
- mapStyle: MAP_STYLES[0],
- center: [-0.1276, 51.5074],
- zoom: 12,
+ transformRequest,
+ center: [-2.9631008, 54.432306],
+ zoom: 15,
containerHeight: '516px',
plugins: [mapStylesPlugin]
})
diff --git a/demo/DemoMapSymbols.js b/demo/DemoMapSymbols.js
index 73347b058..4d0443663 100644
--- a/demo/DemoMapSymbols.js
+++ b/demo/DemoMapSymbols.js
@@ -1,9 +1,10 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
@@ -67,6 +68,7 @@ const historicMonumentsDataset = {
function MapInner () {
const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
useEffect(() => {
if (initialised.current) {
@@ -91,6 +93,7 @@ function MapInner () {
behaviour: 'inline',
mapProvider: maplibreProvider(),
mapStyle: MAP_STYLE,
+ transformRequest,
center: [-2.4608,54.5574],
zoom: 14,
containerHeight: '516px',
diff --git a/demo/DemoMapToggleMarkerLabel.js b/demo/DemoMapToggleMarkerLabel.js
index c56a7638c..829be1e01 100644
--- a/demo/DemoMapToggleMarkerLabel.js
+++ b/demo/DemoMapToggleMarkerLabel.js
@@ -1,9 +1,10 @@
import { useEffect, useRef } from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
+import { OS_VTS_STYLE_URLS, OS_ATTRIBUTION, useOsTransformRequest } from './osMapStyle.js'
const MAP_STYLE = {
- url: 'https://labs.os.uk/tiles/styles/open-zoomstack-outdoor/style.json',
- attribution: `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`,
+ url: OS_VTS_STYLE_URLS.outdoor,
+ attribution: OS_ATTRIBUTION,
backgroundColor: '#f5f5f0'
}
@@ -12,6 +13,7 @@ const MARKER_ID = 'my-marker'
function MapInner () {
const initialised = useRef(false)
+ const transformRequest = useOsTransformRequest()
useEffect(() => {
if (initialised.current) {
@@ -36,6 +38,7 @@ function MapInner () {
behaviour: 'inline',
mapProvider: maplibreProvider(),
mapStyle: MAP_STYLE,
+ transformRequest,
center: MARKER_COORDS,
zoom: 15,
containerHeight: '516px',
diff --git a/demo/osMapStyle.js b/demo/osMapStyle.js
new file mode 100644
index 000000000..d5e2235e8
--- /dev/null
+++ b/demo/osMapStyle.js
@@ -0,0 +1,58 @@
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
+
+const WEB_MERCATOR_SRS = 3857
+
+export const OS_ATTRIBUTION = `Contains OS data © Crown copyright and database rights ${new Date().getFullYear()}`
+
+export const OS_VTS_STYLE_URLS = {
+ outdoor: 'https://raw.githubusercontent.com/OrdnanceSurvey/OS-Vector-Tile-API-Stylesheets/main/OS_VTS_3857_Open_Outdoor.json',
+ dark: 'https://raw.githubusercontent.com/OrdnanceSurvey/OS-Vector-Tile-API-Stylesheets/main/OS_VTS_3857_Open_Dark.json',
+ blackWhite: 'https://raw.githubusercontent.com/OrdnanceSurvey/OS-Vector-Tile-API-Stylesheets/main/OS_VTS_3857_Open_Black_and_White.json'
+}
+
+// The style JSON itself needs no key — only the tile/font requests it triggers
+// (all served from api.os.uk) do, so append it there instead. srs=3857 is required
+// too: without it the API serves tiles on its default British National Grid (27700)
+// tiling scheme, so Web Mercator x/y/z coords fetch the wrong (near-empty) tiles
+const createTransformRequest = (osApiKey) => (url) => {
+ if (!url.startsWith('https://api.os.uk')) {
+ return { url }
+ }
+ const withKey = new URL(url)
+ if (!withKey.searchParams.has('key')) {
+ withKey.searchParams.append('key', osApiKey)
+ }
+ if (!withKey.searchParams.has('srs')) {
+ withKey.searchParams.append('srs', WEB_MERCATOR_SRS)
+ }
+ return { url: withKey.toString() }
+}
+
+// Hook so demos don't each need their own useDocusaurusContext() + wiring
+export const useOsTransformRequest = () => {
+ const { siteConfig } = useDocusaurusContext()
+ return createTransformRequest(siteConfig.customFields?.osApiKey)
+}
+
+export const OS_NAMES_URL = 'https://api.os.uk/search/names/v1/find?query={query}&fq=local_type:postcode%20local_type:hamlet%20local_type:village%20local_type:town%20local_type:city%20local_type:other_settlement&maxresults=8'
+
+// The search plugin's transformRequest works on a Request object (not a url string,
+// unlike the map's transformRequest above) — same key requirement, different shape
+const createGeocodeTransformRequest = (osApiKey) => (request) => {
+ if (!request.url.startsWith('https://api.os.uk')) {
+ return request
+ }
+ const withKey = new URL(request.url)
+ if (!withKey.searchParams.has('key')) {
+ withKey.searchParams.append('key', osApiKey)
+ }
+ return new Request(withKey.toString(), {
+ method: request.method,
+ headers: request.headers
+ })
+}
+
+export const useOsGeocodeTransformRequest = () => {
+ const { siteConfig } = useDocusaurusContext()
+ return createGeocodeTransformRequest(siteConfig.customFields?.osApiKey)
+}
diff --git a/docs/assets/images/black-and-white-map-thumb.jpg b/docs/assets/images/black-and-white-map-thumb.jpg
new file mode 100644
index 000000000..8d19119af
Binary files /dev/null and b/docs/assets/images/black-and-white-map-thumb.jpg differ
diff --git a/docs/assets/images/dark-map-thumb.jpg b/docs/assets/images/dark-map-thumb.jpg
new file mode 100644
index 000000000..fdc3aad84
Binary files /dev/null and b/docs/assets/images/dark-map-thumb.jpg differ
diff --git a/docs/assets/images/deuteranopia-ozs-map-thumb.jpg b/docs/assets/images/deuteranopia-ozs-map-thumb.jpg
deleted file mode 100644
index 1113f1d1b..000000000
Binary files a/docs/assets/images/deuteranopia-ozs-map-thumb.jpg and /dev/null differ
diff --git a/docs/assets/images/night-ozs-map-thumb.jpg b/docs/assets/images/night-ozs-map-thumb.jpg
deleted file mode 100644
index 9316d535e..000000000
Binary files a/docs/assets/images/night-ozs-map-thumb.jpg and /dev/null differ
diff --git a/docs/assets/images/outdoor-map-thumb.jpg b/docs/assets/images/outdoor-map-thumb.jpg
new file mode 100644
index 000000000..05c0bc1c1
Binary files /dev/null and b/docs/assets/images/outdoor-map-thumb.jpg differ
diff --git a/docs/assets/images/outdoor-ozs-map-thumb.jpg b/docs/assets/images/outdoor-ozs-map-thumb.jpg
deleted file mode 100644
index 1ede6915e..000000000
Binary files a/docs/assets/images/outdoor-ozs-map-thumb.jpg and /dev/null differ
diff --git a/docs/assets/images/tritanopia-ozs-map-thumb.jpg b/docs/assets/images/tritanopia-ozs-map-thumb.jpg
deleted file mode 100644
index 928cae02d..000000000
Binary files a/docs/assets/images/tritanopia-ozs-map-thumb.jpg and /dev/null differ
diff --git a/docs/examples/add-polygons.mdx b/docs/examples/add-polygons.mdx
index e06e2399a..729f7bf1d 100644
--- a/docs/examples/add-polygons.mdx
+++ b/docs/examples/add-polygons.mdx
@@ -22,37 +22,37 @@ Use the datasets plugin to overlay GeoJSON polygon data on your map. This exampl
features: [{
type: 'Feature',
id: 1,
- properties: { name: 'Feature 1', land_use: 'Permanent grassland' },
+ properties: { name: 'Large meadow', land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4691585,54.5552164],[-2.4692202,54.5553906],[-2.4690646,54.5554155],[-2.4690002,54.5555088],[-2.4690297,54.5556690],[-2.4690995,54.5557919],[-2.4669269,54.5566971],[-2.4662375,54.5562819],[-2.4637780,54.5574391],[-2.4628580,54.5568169],[-2.4607612,54.5560985],[-2.4608248,54.5560719],[-2.4611789,54.5558914],[-2.4613774,54.5558168],[-2.4615973,54.5557577],[-2.4617475,54.5557297],[-2.4618816,54.5556519],[-2.4620104,54.5556115],[-2.4624771,54.5555275],[-2.4627131,54.5555150],[-2.4628741,54.5554839],[-2.4630082,54.5554404],[-2.4632335,54.5553346],[-2.4633944,54.5552164],[-2.4634749,54.5551106],[-2.4635178,54.5550235],[-2.4635285,54.5548213],[-2.4635768,54.5546346],[-2.4635875,54.5544884],[-2.4636090,54.5544075],[-2.4636465,54.5543515],[-2.4636573,54.5542769],[-2.4636358,54.5541804],[-2.4637377,54.5540155],[-2.4639094,54.5538724],[-2.4643493,54.5535831],[-2.4645370,54.5535084],[-2.4647141,54.5534804],[-2.4648052,54.5534836],[-2.4648213,54.5535116],[-2.4648750,54.5535396],[-2.4650842,54.5535893],[-2.4652451,54.5536516],[-2.4652719,54.5536796],[-2.4653900,54.5537355],[-2.4655455,54.5537853],[-2.4658459,54.5539502],[-2.4663556,54.5542738],[-2.4668008,54.5545164],[-2.4668330,54.5545506],[-2.4668437,54.5546782],[-2.4668598,54.5547031],[-2.4670798,54.5548306],[-2.4671817,54.5549053],[-2.4674177,54.5549364],[-2.4677664,54.5550484],[-2.4681956,54.5551448],[-2.4684101,54.5551759],[-2.4691129,54.5552195],[-2.4691585,54.5552164]]] }
}, {
type: 'Feature',
id: 2,
- properties: { name: 'Feature 2', land_use: 'Arable' },
+ properties: { name: 'Lamb field', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4611682,54.5562375],[-2.4628580,54.5568161],[-2.4636841,54.5573753],[-2.4637793,54.5574398],[-2.4641401,54.5576751],[-2.4642567,54.5577999],[-2.4641307,54.5578823],[-2.4636841,54.5582502],[-2.4624221,54.5592884],[-2.4623376,54.5592534],[-2.4615370,54.5586429],[-2.4611869,54.5582852],[-2.4605258,54.5581071],[-2.4598472,54.5578186],[-2.4611682,54.5562375]]] }
}, {
type: 'Feature',
id: 3,
- properties: { name: 'Feature 3', land_use: 'Arable' },
+ properties: { name: 'Small hay field', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4598472,54.5578186],[-2.4605258,54.5581067],[-2.4611869,54.5582852],[-2.4615376,54.5586433],[-2.4623108,54.5592332],[-2.4623376,54.5592534],[-2.4624221,54.5592884],[-2.4623108,54.5594120],[-2.4616731,54.5601189],[-2.4586053,54.5601189],[-2.4584410,54.5600299],[-2.4580038,54.5596574],[-2.4581815,54.5593953],[-2.4585778,54.5590702],[-2.4587287,54.5589353],[-2.4589198,54.5586736],[-2.4591116,54.5582136],[-2.4598472,54.5578186]]] }
}, {
type: 'Feature',
id: 4,
- properties: { name: 'Feature 4', land_use: 'Permanent grassland' },
+ properties: { name: 'The pasture', land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4633756,54.5612877],[-2.4613908,54.5604315],[-2.4624221,54.5592884],[-2.4641307,54.5578823],[-2.4642567,54.5577999],[-2.4653631,54.5591733],[-2.4659613,54.5597013],[-2.4633756,54.5612877]]] }
}, {
type: 'Feature',
id: 5,
- properties: { name: 'Feature 5', land_use: 'Arable' },
+ properties: { name: 'Herdwick Croft', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4678040,54.5590679],[-2.4661879,54.5595952],[-2.4659613,54.5597013],[-2.4653638,54.5591737],[-2.4642567,54.5577999],[-2.4641401,54.5576751],[-2.4637793,54.5574398],[-2.4662369,54.5562822],[-2.4669282,54.5566975],[-2.4678040,54.5563320],[-2.4678040,54.5590679]]] }
}, {
type: 'Feature',
id: 6,
- properties: { name: 'Feature 6', land_use: 'Permanent grassland' },
+ properties: { name: "Wainwright's Rest", land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4669316,54.5603767],[-2.4676430,54.5613102],[-2.4673292,54.5614556],[-2.4671616,54.5617468],[-2.4658446,54.5625528],[-2.4651546,54.5621528],[-2.4639215,54.5633040],[-2.4633146,54.5633040],[-2.4628372,54.5630921],[-2.4631195,54.5628195],[-2.4643741,54.5618374],[-2.4665071,54.5604793],[-2.4667056,54.5604187],[-2.4669316,54.5603767]]] }
}, {
type: 'Feature',
id: 7,
- properties: { name: 'Feature 7', land_use: 'Woodland' },
+ properties: { name: 'Badger Sett Copse', land_use: 'Woodland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4599129,54.5564203],[-2.4599773,54.5565478],[-2.4599773,54.5565852],[-2.4599344,54.5566256],[-2.4596876,54.5566069],[-2.4594945,54.5566132],[-2.4593657,54.5566505],[-2.4592584,54.5567158],[-2.4592048,54.5567780],[-2.4591780,54.5568496],[-2.4591887,54.5569616],[-2.4591297,54.5570829],[-2.4590009,54.5572726],[-2.4589419,54.5573162],[-2.4588561,54.5574686],[-2.4587435,54.5575588],[-2.4586684,54.5575837],[-2.4585879,54.5575962],[-2.4584001,54.5575837],[-2.4583304,54.5575899],[-2.4580836,54.5576397],[-2.4579817,54.5576895],[-2.4579227,54.5576988],[-2.4578154,54.5576988],[-2.4576384,54.5576739],[-2.4574614,54.5576739],[-2.4573044,54.5577050],[-2.4563093,54.5573092],[-2.4563871,54.5569849],[-2.4564891,54.5560664],[-2.4565601,54.5559039],[-2.4567747,54.5559412],[-2.4571985,54.5559817],[-2.4573219,54.5560097],[-2.4576116,54.5560936],[-2.4582446,54.5562181],[-2.4584913,54.5562959],[-2.4590331,54.5563705],[-2.4599129,54.5564203]]] }
}]
}
@@ -129,37 +129,37 @@ Use the datasets plugin to overlay GeoJSON polygon data on your map. This exampl
features: [{
type: 'Feature',
id: 1,
- properties: { name: 'Feature 1', land_use: 'Permanent grassland' },
+ properties: { name: 'Large meadow', land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4691585,54.5552164],[-2.4692202,54.5553906],[-2.4690646,54.5554155],[-2.4690002,54.5555088],[-2.4690297,54.5556690],[-2.4690995,54.5557919],[-2.4669269,54.5566971],[-2.4662375,54.5562819],[-2.4637780,54.5574391],[-2.4628580,54.5568169],[-2.4607612,54.5560985],[-2.4608248,54.5560719],[-2.4611789,54.5558914],[-2.4613774,54.5558168],[-2.4615973,54.5557577],[-2.4617475,54.5557297],[-2.4618816,54.5556519],[-2.4620104,54.5556115],[-2.4624771,54.5555275],[-2.4627131,54.5555150],[-2.4628741,54.5554839],[-2.4630082,54.5554404],[-2.4632335,54.5553346],[-2.4633944,54.5552164],[-2.4634749,54.5551106],[-2.4635178,54.5550235],[-2.4635285,54.5548213],[-2.4635768,54.5546346],[-2.4635875,54.5544884],[-2.4636090,54.5544075],[-2.4636465,54.5543515],[-2.4636573,54.5542769],[-2.4636358,54.5541804],[-2.4637377,54.5540155],[-2.4639094,54.5538724],[-2.4643493,54.5535831],[-2.4645370,54.5535084],[-2.4647141,54.5534804],[-2.4648052,54.5534836],[-2.4648213,54.5535116],[-2.4648750,54.5535396],[-2.4650842,54.5535893],[-2.4652451,54.5536516],[-2.4652719,54.5536796],[-2.4653900,54.5537355],[-2.4655455,54.5537853],[-2.4658459,54.5539502],[-2.4663556,54.5542738],[-2.4668008,54.5545164],[-2.4668330,54.5545506],[-2.4668437,54.5546782],[-2.4668598,54.5547031],[-2.4670798,54.5548306],[-2.4671817,54.5549053],[-2.4674177,54.5549364],[-2.4677664,54.5550484],[-2.4681956,54.5551448],[-2.4684101,54.5551759],[-2.4691129,54.5552195],[-2.4691585,54.5552164]]] }
}, {
type: 'Feature',
id: 2,
- properties: { name: 'Feature 2', land_use: 'Arable' },
+ properties: { name: 'Lamb field', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4611682,54.5562375],[-2.4628580,54.5568161],[-2.4636841,54.5573753],[-2.4637793,54.5574398],[-2.4641401,54.5576751],[-2.4642567,54.5577999],[-2.4641307,54.5578823],[-2.4636841,54.5582502],[-2.4624221,54.5592884],[-2.4623376,54.5592534],[-2.4615370,54.5586429],[-2.4611869,54.5582852],[-2.4605258,54.5581071],[-2.4598472,54.5578186],[-2.4611682,54.5562375]]] }
}, {
type: 'Feature',
id: 3,
- properties: { name: 'Feature 3', land_use: 'Arable' },
+ properties: { name: 'Small hay field', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4598472,54.5578186],[-2.4605258,54.5581067],[-2.4611869,54.5582852],[-2.4615376,54.5586433],[-2.4623108,54.5592332],[-2.4623376,54.5592534],[-2.4624221,54.5592884],[-2.4623108,54.5594120],[-2.4616731,54.5601189],[-2.4586053,54.5601189],[-2.4584410,54.5600299],[-2.4580038,54.5596574],[-2.4581815,54.5593953],[-2.4585778,54.5590702],[-2.4587287,54.5589353],[-2.4589198,54.5586736],[-2.4591116,54.5582136],[-2.4598472,54.5578186]]] }
}, {
type: 'Feature',
id: 4,
- properties: { name: 'Feature 4', land_use: 'Permanent grassland' },
+ properties: { name: 'The pasture', land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4633756,54.5612877],[-2.4613908,54.5604315],[-2.4624221,54.5592884],[-2.4641307,54.5578823],[-2.4642567,54.5577999],[-2.4653631,54.5591733],[-2.4659613,54.5597013],[-2.4633756,54.5612877]]] }
}, {
type: 'Feature',
id: 5,
- properties: { name: 'Feature 5', land_use: 'Arable' },
+ properties: { name: 'Herdwick Croft', land_use: 'Arable' },
geometry: { type: 'Polygon', coordinates: [[[-2.4678040,54.5590679],[-2.4661879,54.5595952],[-2.4659613,54.5597013],[-2.4653638,54.5591737],[-2.4642567,54.5577999],[-2.4641401,54.5576751],[-2.4637793,54.5574398],[-2.4662369,54.5562822],[-2.4669282,54.5566975],[-2.4678040,54.5563320],[-2.4678040,54.5590679]]] }
}, {
type: 'Feature',
id: 6,
- properties: { name: 'Feature 6', land_use: 'Permanent grassland' },
+ properties: { name: "Wainwright's Rest", land_use: 'Permanent grassland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4669316,54.5603767],[-2.4676430,54.5613102],[-2.4673292,54.5614556],[-2.4671616,54.5617468],[-2.4658446,54.5625528],[-2.4651546,54.5621528],[-2.4639215,54.5633040],[-2.4633146,54.5633040],[-2.4628372,54.5630921],[-2.4631195,54.5628195],[-2.4643741,54.5618374],[-2.4665071,54.5604793],[-2.4667056,54.5604187],[-2.4669316,54.5603767]]] }
}, {
type: 'Feature',
id: 7,
- properties: { name: 'Feature 7', land_use: 'Woodland' },
+ properties: { name: 'Badger Sett Copse', land_use: 'Woodland' },
geometry: { type: 'Polygon', coordinates: [[[-2.4599129,54.5564203],[-2.4599773,54.5565478],[-2.4599773,54.5565852],[-2.4599344,54.5566256],[-2.4596876,54.5566069],[-2.4594945,54.5566132],[-2.4593657,54.5566505],[-2.4592584,54.5567158],[-2.4592048,54.5567780],[-2.4591780,54.5568496],[-2.4591887,54.5569616],[-2.4591297,54.5570829],[-2.4590009,54.5572726],[-2.4589419,54.5573162],[-2.4588561,54.5574686],[-2.4587435,54.5575588],[-2.4586684,54.5575837],[-2.4585879,54.5575962],[-2.4584001,54.5575837],[-2.4583304,54.5575899],[-2.4580836,54.5576397],[-2.4579817,54.5576895],[-2.4579227,54.5576988],[-2.4578154,54.5576988],[-2.4576384,54.5576739],[-2.4574614,54.5576739],[-2.4573044,54.5577050],[-2.4563093,54.5573092],[-2.4563871,54.5569849],[-2.4564891,54.5560664],[-2.4565601,54.5559039],[-2.4567747,54.5559412],[-2.4571985,54.5559817],[-2.4573219,54.5560097],[-2.4576116,54.5560936],[-2.4582446,54.5562181],[-2.4584913,54.5562959],[-2.4590331,54.5563705],[-2.4599129,54.5564203]]] }
}]
}
diff --git a/docs/examples/draw-tools.mdx b/docs/examples/draw-tools.mdx
index 0463f04a1..eb62dda14 100644
--- a/docs/examples/draw-tools.mdx
+++ b/docs/examples/draw-tools.mdx
@@ -9,6 +9,10 @@ Draw, edit, select and delete polygons and lines using the draw and interact plu
The draw plugin (`draw-ml`) is currently in beta.
:::
+:::note
+This example configures snapping (`snapLayers`) against the base map style's woodland layer, so new vertices placed near a wooded area snap to its boundary. Swap in whichever layer ID suits your own style.
+:::
+
+
+
{
@@ -49,8 +105,8 @@ The layer ID used here (`buildings 3D`) is specific to the OS Open Zoomstack out
map.addPanel(PANEL_ID, {
focus: false,
- label: 'Selected building',
- html: '',
+ label: 'Selected parcel',
+ html: '',
mobile: { slot: 'drawer', dismissible: true },
tablet: { slot: 'left-top', dismissible: true, width: '300px' },
desktop: { slot: 'left-top', dismissible: true, width: '300px' }
@@ -59,11 +115,9 @@ The layer ID used here (`buildings 3D`) is specific to the OS Open Zoomstack out
map.on('interact:selectionchange', ({ selectedFeatures }) => {
if (selectedFeatures.length > 0) {
- let html = ''
- for (const [k, v] of Object.entries(selectedFeatures[0].properties)) {
- html += '' + k + ': ' + v + '
'
- }
- document.getElementById('building-info-content').innerHTML = html
+ const name = selectedFeatures[0].properties.name
+ document.getElementById('parcel-info-content').innerHTML =
+ '' + name + '
'
map.showPanel(PANEL_ID)
} else {
map.hidePanel(PANEL_ID)
@@ -83,16 +137,73 @@ The layer ID used here (`buildings 3D`) is specific to the OS Open Zoomstack out
code: `
+