Reproject maplibre raster tiles on the fly in the browser
npm install maplibre-gl-raster-reprojection!build


Reproject maplibre raster map tiles in the browser.
This library is for when your maplibre map projection differs from your tile projection.
In a perfect world your map tiles would be in the same projection as your map. However, this is not always the case. Sometimes you may need to mix. For example,
- Your map and some of your tiles are in EPSG:3857 (web mercator), but you need to another tileset that is only served in EPSG:4326.
- Your map is in some exotic projection and there are only EPSG:3857 tile providers.
With maplibre-gl-raster-reprojection you can reproject those tiles on the fly so that you can use them.
This library is inteded to be a stopgap solution until non-mercator projections are supported in maplibre. See latest updates:
- Roadmap - Non-Mercator Projection
- Bounty Direction: Custom Coordinate System/EPSG/Non-Mercator Projection #272
This library uses maplibre addProtocol API to hook into the layer request/response lifecycle.
1. Maplibre makes a request for a tile in EPSG:3857
1. maplibre-gl-raster-reprojection converts that request into 1 or many tile server requests
1. maplibre-gl-raster-reprojection slices and reprojects the tile server responses into 1 in order to match the maplibre expected request
1. Maplibre renders the repojected tile
Key Terms
- _source_: Original tile from the tile server
- _destination_: Maplibre tile (EPSG:3857)
Maplibre does not directly pass the tile request bbox, x, y, and z params to the protocol loader function. You must add a url prefix and url source params to your tile url in order for maplibre-gl-raster-reprojection to receive those values and build requests.
``bash`
npm install maplibre-gl-raster-reprojection
You must add the following url prefix and source params to your maplibre raster source config in order for maplibre-gl-raster-reprojection to work:
- Add the url prefix to your tile url
- Use the url source params to your tile url
URL Prefix: reproject://bbox={bbox-epsg-3857}&z={z}&x={x}&y={y}://
- reproject: The protocol name. Can be changed via protocol option.bbox={bbox-epsg-3857}
- : Pass destination (EPSG:3857) bbox to the loader function.z={z}
- : Pass the destination (EPSG:3857) tile z to the loader function.x={x}
- : Pass the destination (EPSG:3857) tile x to the loader function.y={y}
- : Pass the destination (EPSG:3857) tile y to the loader function.
URL Source Params:
- {sz}: Pass the source tile z to the tile server request{sx}
- : Pass the source tile x to the tile server request{sy}
- : Pass the source tile y to the tile server request{sbbox}
- : Pass the source tile bbox to the tile server request{sxmin}
- : Pass the source tile xmin to the tile server request{symin}
- : Pass the source tile ymin to the tile server request{sxmax}
- : Pass the source tile xmax to the tile server request{symax}
- : Pass the source tile ymax to the tile server request
Example URL:
reproject://bbox={bbox-epsg-3857}&z={z}&x={x}&y={y}://https://api.tilehost.com/map/{sz}/{sx}/{sy}.png
`ts
import maplibregl from 'maplibre-gl';
import { createProtocol, epsg4326ToEpsg3857Presets } from 'maplibre-gl-raster-reprojection';
const { protocol, loader } = createProtocol({
// Converts EPSG:4326 tile endpoint to EPSG:3857
...epsg4326ToEpsg3857Presets,
// Draw EPSG:3857 tile in 256 pixel width by 1 pixel height intervals (more accurate latitude)
interval: [256, 1],
});
maplibregl.addProtocol(protocol, loader);
const map = new maplibregl.Map({
style: {
...,
sources: {
version: 8,
epsg4326Source: {
type: 'raster',
tiles: ['reproject://bbox={bbox-epsg-3857}&z={z}&x={x}&y={y}://https://api.tilehost.com/map/{sz}/{sx}/{sy}.png'],
tileSize: 256,
scheme: 'xyz'
}
},
layers: [
{ id: 'reprojectedLayer', source: 'epsg4326Source', type: 'raster' }
]
}
})
`
Create and initialize input for maplibregl.addProtocol.
#### CreateProtocolOptions
| field | description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| protocol | string 'reproject'
Url prefix that identifying a custom protocol. _(Default: )_ |cacheSize
| | number 10
Total images stored in the internal reprojection cache. _(Default: )_ |destinationTileSize
| | number tileSize
The destination tile size. _(Defaults to )_ |destinationTileToSourceTiles
| | DestinationTileToSourceTilesFn destinationToPixel
See common type below |
| | DestinationToPixelFn destinationToSource
See common type below |
| | DestinationToSourceFn interval
See common type below |
| | [intervalX: number, intervalY: number] [1, 1]
The pixel sampling interval when reprojecting the source to destination. Max interval value is the destination tile size. _(Default: )_ |pixelToDestination
| | PixelToDestinationFn sourceTileSize
See common type below |
| | number tileSize
The source tile size. _(Defaults to )_ |sourceToPixel
| | SourceToPixelFn tileSize
See common type below |
| | number sourceTileSize
Shorthand for setting and destinationTileSize to the same value. _(Default: 256)_ |zoomOffset
| | number destinationTileToSourceTiles
An offset zoom value applied to the reprojection which makes the tile text appear smaller or bigger. The offset is applied when determining which source tiles are needed to cover a destination tile in . Must be an integer. _(Default: 0)_ |
#### CreateProtocolResult
| field | description |
| ---------- | ---------------------------------------------------------------- |
| protocol | string loader
Url prefix that identifying a custom protocol. |
| | maplibregl.AddProtocolAction
See maplibregl documentation |
Preset options to convert EPSG:4326 to EPSG:3857.
#### Tile: number[] | [number, number, number]
A reference to a map tile. [x, y, z]
#### Bbox: number[] | [number, number, number, number]
A bounding box. [xmin, ymin, xmax, ymax]
#### DestinationTileToSourceTilesFn: (destinationRequest: { tile: Tile, bbox: Bbox }, zoomOffset?: number) => { tile: Tile, bbox: Bbox }[]
Calculate the source tile references needed to cover destination tile reference. A zoomOffset is used to apply any source-to-destination zoom adjustments.
#### DestinationToPixelFn: ([dx, dy]: number[], zoom: number, tileSize: number) => number[]
Transform a destination tile reference to pixel coordinate [x, y].
#### DestinationToSourceFn: ([dx, dy]: number[]) => number[]
Transform a destination coordinate [x, y] to a source coordinate [x, y].
#### PixelToDestinationFn: ([px, py]: number[], zoom: number, tileSize: number) => number[]
Transform a pixel coordinate [x, y] to destination coordinate [x, y].
#### SourceToPixelFn: ([sx, sy]: number[], zoom: number, tileSize: number) => number[]
Transform a source coordinate [x, y] to a pixel coordinate [x, y].
Map tiles are best used in their native projection. Reprojecting a tile almost always be suboptimal and most easily visualized in the following ways.
Use params like interval, zoomOffset, etc. to adjust the reprojection based on your needs.
- Text labels will likely be distorted when reprojecting raster images. Labels are placed and "burned" into tiles. So when tile reprojects those labels will transform with the terrain. Those labels may also be smaller or larger due to scale differences.
- Pixel precision will likely be blured or pixelated.
- OpenLayers Image Reprojection
- Tiles à la Google Maps and globalmaptiles.js for map tile conversion
- Raster Reprojection (Mike Bostock)
- Reprojected Raster Tiles (Jason Davies)
- A stackoverflow deep dive on reprojecting map tiles in d3 (Andrew Reid)
`bash`
npm run lint
npm run test
npm run e2e
1. Update the CHANGELOG.md with new version and commit the change.npm version ...
1. Run or somethign similar or tag manuallygit push --tags
1. Push tag to remote publish` workflow with tag
1. [Optional] Run the