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: ` +