Skip to content
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
12 changes: 6 additions & 6 deletions demo/js/gep.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ interactiveMap.on('app:ready', function () {
label: 'Layers',
panelId: 'layers',
iconSvgContent: '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/>',
mobile: { slot: 'top-left', order: 1, showLabel: false },
tablet: { slot: 'top-left', order: 2 },
desktop: { slot: 'top-left', order: 2 }
mobile: { slot: 'top-left', order: 3, showLabel: false },
tablet: { slot: 'top-left', order: 3 },
desktop: { slot: 'top-left', order: 3 }
})
interactiveMap.addButton('key', {
label: 'Key',
panelId: 'key',
iconSvgContent: '<path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/>',
mobile: { slot: 'top-left', order: 2, showLabel: false },
tablet: { slot: 'top-left', order: 3 },
desktop: { slot: 'top-left', order: 3 }
mobile: { slot: 'top-left', order: 4, showLabel: false },
tablet: { slot: 'top-left', order: 4 },
desktop: { slot: 'top-left', order: 4 }
})
interactiveMap.addPanel('layers', {
label: 'Layers',
Expand Down
14 changes: 13 additions & 1 deletion demo/js/mapStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ const AERIAL_THUMBNAIL = '/assets/images/aerial-map-thumb.jpg'
const BW_ID = 'black-and-white'
const BW_LABEL = 'Black/White'

// Real OS National Grid coverage extent [minX, minY, maxX, maxY], EPSG:27700 — captured from
// OS's own VTS capabilities response (matches the TILE_GRID_ORIGIN corner already baked into
// providers/beta/openlayers/src/defaults.js). Used to stop the raster styles below requesting
// tiles for areas OS has no coverage for.
const OS_NATIONAL_GRID_EXTENT_27700 = [-238375, 0, 700000, 1300000]

const openMapStyles = [{
id: 'outdoor',
label: 'Outdoor',
Expand Down Expand Up @@ -120,7 +126,7 @@ const vtsMapStyles27700 = [{

const apgbAerialStyle = {
id: 'apgb-aerial-125mm',
label: 'Aerial 12.5cm',
label: 'Aerial',
type: 'wms',
url: process.env.APGB_WMS_URL,
params: { LAYERS: 'APGB_Latest_UK_125mm', BGCOLOR: '0x1E3448', TRANSPARENT: false },
Expand Down Expand Up @@ -160,7 +166,9 @@ const ngdMapStyles27700 = [{
const mapsRasterStyles27700 = [{
id: 'outdoor',
label: 'Outdoor',
type: 'raster',
url: `${process.env.MAPS_OUTDOOR_URL}?key=${process.env.OS_CLIENT_ID}`,
extent: OS_NATIONAL_GRID_EXTENT_27700,
thumbnail: '/assets/images/outdoor-raster-thumb.jpg',
logo: OS_LOGO,
logoAltText: OS_LOGO_ALT,
Expand All @@ -171,7 +179,9 @@ const mapsRasterStyles27700 = [{
}, {
id: 'road',
label: 'Road',
type: 'raster',
url: `${process.env.MAPS_ROAD_URL}?key=${process.env.OS_CLIENT_ID}`,
extent: OS_NATIONAL_GRID_EXTENT_27700,
thumbnail: '/assets/images/road-raster-thumb.jpg',
logo: OS_LOGO,
logoAltText: OS_LOGO_ALT,
Expand All @@ -182,7 +192,9 @@ const mapsRasterStyles27700 = [{
}, {
id: 'light',
label: 'Light',
type: 'raster',
url: `${process.env.MAPS_LIGHT_URL}?key=${process.env.OS_CLIENT_ID}`,
extent: OS_NATIONAL_GRID_EXTENT_27700,
thumbnail: '/assets/images/light-raster-thumb.jpg',
logo: OS_LOGO,
logoAltText: OS_LOGO_ALT,
Expand Down
23 changes: 22 additions & 1 deletion docs/api/map-style-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ URL that returns a Mapbox GL style document (Mapbox Style Specification).
### `params`
**Type:** `Object`

WMS request parameters. Passed directly to the OpenLayers `TileWMS` source when `type` is `'wms'`. Most WMS GetMap requests should include `LAYERS`.
> [!NOTE]
> This property is only relevant when using the **OpenLayers provider** with `type: 'wms'`. It is ignored by other providers and by other `type` values.

WMS request parameters. Passed directly to the OpenLayers `TileWMS` source. Most WMS GetMap requests should include `LAYERS`.

```js
{
Expand All @@ -112,6 +115,24 @@ WMS request parameters. Passed directly to the OpenLayers `TileWMS` source when

---

### `extent`
**Type:** `[number, number, number, number]`

> [!NOTE]
> This property is only relevant when using the **OpenLayers provider** with `type: 'raster'`. A bare XYZ tile URL template has no capabilities document to determine real coverage from, so the consumer configuring the style must supply it directly. It is ignored by other providers and by other `type` values.

Bounding box `[minX, minY, maxX, maxY]` in EPSG:27700 — the units the OpenLayers provider's tile grid is built in. When set, no tiles outside this area are requested, avoiding failed tile requests where the basemap has no coverage. Omit to request tiles across the whole tile grid regardless of real coverage (the default). Only limits which tiles are *requested* — panning and zooming outside the extent is unaffected.

```js
{
type: 'raster',
url: 'https://api.os.uk/maps/raster/v1/zxy/Outdoor_27700/{z}/{x}/{y}.png?key=YOUR_API_KEY',
extent: [-238375, 0, 700000, 1300000] // OS National Grid coverage
}
```

---

### `label`
**Type:** `string`

Expand Down
19 changes: 12 additions & 7 deletions providers/beta/openlayers/src/utils/tileLayers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { TILE_GRID_RESOLUTIONS, TILE_GRID_ORIGIN, TILE_SIZE } from '../defaults.
recordStyleLayer(true)

const CRS = 'EPSG:27700'
const SUPPORTED_MAP_STYLE_TYPES = ['vector', 'raster', 'wms', 'ogc-vt']
const SUPPORTED_MAP_STYLE_TYPES = new Set(['vector', 'raster', 'wms', 'ogc-vt'])

export function fetchWithTransform (url, resourceType, transformRequest) {
const result = transformRequest ? (transformRequest(url, resourceType) || {}) : {}
Expand All @@ -32,11 +32,12 @@ const createTileLoadFunction = (transformRequest) => (tile, src) => {
.catch(() => tile.setState(TileState.ERROR))
}

function createTileGrid () {
function createTileGrid (extent) {
return new TileGrid({
resolutions: TILE_GRID_RESOLUTIONS,
origin: TILE_GRID_ORIGIN,
tileSize: TILE_SIZE
tileSize: TILE_SIZE,
...(extent && { extent })
})
}

Expand All @@ -52,8 +53,12 @@ export function createWMSTileSource (url, params, transformRequest) {
})
}

export function createTileSource (url, transformRequest) {
const tileGrid = createTileGrid()
// `extent` has no discovery protocol to fetch it from for a bare XYZ template — unlike the
// other map style types, only the consumer configuring this particular tile service can supply
// it, via `mapStyle.extent` ([minX, minY, maxX, maxY] in EPSG:27700). Omitted → no extent, same
// as before: OL requests tiles for the whole tile grid regardless of real coverage.
export function createTileSource (url, transformRequest, extent) {
const tileGrid = createTileGrid(extent)

const tileUrlFunction = ([z, x, y]) => url
.replace('{z}', z)
Expand All @@ -69,7 +74,7 @@ export function createTileSource (url, transformRequest) {
}

export async function createMapStyleLayer (mapStyle, transformRequest) {
if (mapStyle.type && !SUPPORTED_MAP_STYLE_TYPES.includes(mapStyle.type)) {
if (mapStyle.type && !SUPPORTED_MAP_STYLE_TYPES.has(mapStyle.type)) {
throw new Error(`Unsupported map style type: '${mapStyle.type}'`)
}

Expand All @@ -79,7 +84,7 @@ export async function createMapStyleLayer (mapStyle, transformRequest) {
}

if (mapStyle.type === 'raster') {
const source = createTileSource(mapStyle.url, transformRequest)
const source = createTileSource(mapStyle.url, transformRequest, mapStyle.extent)
return { layer: new TileLayer({ source }), source }
}

Expand Down
26 changes: 26 additions & 0 deletions providers/beta/openlayers/src/utils/tileLayers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ describe('createTileSource', () => {
const { tileLoadFunction } = XYZ.mock.calls[0][0]
expect(typeof tileLoadFunction).toBe('function')
})

it('does not set a TileGrid extent when none is given, so tiles across the whole grid can be requested', () => {
createTileSource('https://tiles.example.com/{z}/{x}/{y}', null)
expect(TileGrid).toHaveBeenCalledWith({
resolutions: TILE_GRID_RESOLUTIONS,
origin: TILE_GRID_ORIGIN,
tileSize: TILE_SIZE
})
})

it('passes a given extent through to the TileGrid, so out-of-coverage tiles are never requested', () => {
const extent = [0, 0, 700000, 1300000]
createTileSource('https://tiles.example.com/{z}/{x}/{y}', null, extent)
expect(TileGrid).toHaveBeenCalledWith({
resolutions: TILE_GRID_RESOLUTIONS,
origin: TILE_GRID_ORIGIN,
tileSize: TILE_SIZE,
extent
})
})
})

describe('createWMSTileSource', () => {
Expand Down Expand Up @@ -244,6 +264,12 @@ describe('createMapStyleLayer', () => {
expect(result).toEqual({ layer: mockTileLayerInstance, source: mockSourceInstance })
})

it('passes mapStyle.extent through to the raster TileGrid', async () => {
const extent = [-233752.41, -4325.11, 609472.1, 1278448.84]
await createMapStyleLayer({ url: 'https://tiles.example.com/{z}/{x}/{y}', type: 'raster', extent }, null)
expect(TileGrid).toHaveBeenCalledWith(expect.objectContaining({ extent }))
})

it('creates an OGC vector tile layer and source when mapStyle.type is ogc-vt', async () => {
global.fetch = makeOGCFetchMock()
const result = await createMapStyleLayer({ url: 'https://example.com/ogc-styles', type: 'ogc-vt' }, null)
Expand Down
9 changes: 9 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@
* @property {Object} [params]
* WMS request parameters. Passed directly to the OpenLayers `TileWMS` source when `type` is `'wms'`.
* Most WMS GetMap requests should include `LAYERS`. Example: `{ LAYERS: 'MyLayer', FORMAT: 'image/jpeg' }`.
*
* @property {[number, number, number, number]} [extent]
* Bounding box [minX, minY, maxX, maxY] in EPSG:27700, the units the OpenLayers provider's
* tile grid is built in. When set, no tiles outside this area are requested — a plain XYZ tile
* URL template has no capabilities document to determine real coverage from, so the consumer
* configuring the style must supply it directly. **Currently only supported by the OpenLayers
* provider's `'raster'` type.** Omit to request tiles across the whole tile grid regardless of
* real coverage. Panning/zooming outside the extent is unaffected;
* only tile requests are limited.
*/

/**
Expand Down
Loading