maplibre-gl-raster-reprojection
v1.0.4
Published
Reproject maplibre raster tiles on the fly in the browser
Downloads
51
Maintainers
Readme
Maplibre Gl Raster Reprojection
Reproject maplibre raster map tiles in the browser.
Purpose
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
How it works
This library uses maplibre addProtocol
API to hook into the layer request/response lifecycle.
- Maplibre makes a request for a tile in EPSG:3857
maplibre-gl-raster-reprojection
converts that request into 1 or many tile server requestsmaplibre-gl-raster-reprojection
slices and reprojects the tile server responses into 1 in order to match the maplibre expected request- 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.
Install
npm install maplibre-gl-raster-reprojection
Usage
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 viaprotocol
option.bbox={bbox-epsg-3857}
: Pass destination (EPSG:3857)bbox
to the loader function.z={z}
: Pass the destination (EPSG:3857) tilez
to the loader function.x={x}
: Pass the destination (EPSG:3857) tilex
to the loader function.y={y}
: Pass the destination (EPSG:3857) tiley
to the loader function.
URL Source Params:
{sz}
: Pass the source tilez
to the tile server request{sx}
: Pass the source tilex
to the tile server request{sy}
: Pass the source tiley
to the tile server request{sbbox}
: Pass the source tilebbox
to the tile server request{sxmin}
: Pass the source tilexmin
to the tile server request{symin}
: Pass the source tileymin
to the tile server request{sxmax}
: Pass the source tilexmax
to the tile server request{symax}
: Pass the source tileymax
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
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' }
]
}
})
API
createProtocol: (options: CreateProtocolOptions) => CreateProtocolResult
Create and initialize input for maplibregl.addProtocol
.
CreateProtocolOptions
| field | description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| protocol
| string
Url prefix that identifying a custom protocol. (Default: 'reproject'
) |
| cacheSize
| number
Total images stored in the internal reprojection cache. (Default: 10
) |
| destinationTileSize
| number
The destination tile size. (Defaults to tileSize
) |
| destinationTileToSourceTiles
| DestinationTileToSourceTilesFn
See common type below |
| destinationToPixel
| DestinationToPixelFn
See common type below |
| destinationToSource
| DestinationToSourceFn
See common type below |
| interval
| [intervalX: number, intervalY: number]
The pixel sampling interval when reprojecting the source to destination. Max interval value is the destination tile size. (Default: [1, 1]
) |
| pixelToDestination
| PixelToDestinationFn
See common type below |
| sourceTileSize
| number
The source tile size. (Defaults to tileSize
) |
| sourceToPixel
| SourceToPixelFn
See common type below |
| tileSize
| number
Shorthand for setting sourceTileSize
and destinationTileSize
to the same value. (Default: 256
) |
| zoomOffset
| number
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 destinationTileToSourceTiles
. Must be an integer. (Default: 0
) |
CreateProtocolResult
| field | description |
| ---------- | ---------------------------------------------------------------- |
| protocol
| string
Url prefix that identifying a custom protocol. |
| loader
| maplibregl.AddProtocolAction
See maplibregl documentation |
epsg4326ToEpsg3857Presets: Partial<CreateProtocolOptions>
Preset options to convert EPSG:4326 to EPSG:3857.
Common
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].
Tradeoffs
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.
Visual effects
- 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.
Prior Art
- 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)
Tests
npm run lint
npm run test
npm run e2e
Development
- Update the
CHANGELOG.md
with new version and commit the change. - Run
npm version ...
or somethign similar or tag manually - Push tag to remote
git push --tags
- [Optional] Run the
publish
workflow with tag