1.0.4 • Published 9 months ago

maplibre-gl-raster-reprojection v1.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

Maplibre Gl Raster Reprojection

build codecov npm

Reproject maplibre raster map tiles in the browser.

DEMO

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:

How it works

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
  2. maplibre-gl-raster-reprojection converts that request into 1 or many tile server requests
  3. maplibre-gl-raster-reprojection slices and reprojects the tile server responses into 1 in order to match the maplibre expected request
  4. 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 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

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

fielddescription
protocolstring Url prefix that identifying a custom protocol. (Default: 'reproject')
cacheSizenumber Total images stored in the internal reprojection cache. (Default: 10)
destinationTileSizenumber The destination tile size. (Defaults to tileSize)
destinationTileToSourceTilesDestinationTileToSourceTilesFn See common type below
destinationToPixelDestinationToPixelFn See common type below
destinationToSourceDestinationToSourceFn 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])
pixelToDestinationPixelToDestinationFn See common type below
sourceTileSizenumber The source tile size. (Defaults to tileSize)
sourceToPixelSourceToPixelFn See common type below
tileSizenumber Shorthand for setting sourceTileSize and destinationTileSize to the same value. (Default: 256)
zoomOffsetnumber 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

fielddescription
protocolstring Url prefix that identifying a custom protocol.
loadermaplibregl.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

Tests

npm run lint
npm run test
npm run e2e

Development

  1. Update the CHANGELOG.md with new version and commit the change.
  2. Run npm version ... or somethign similar or tag manually
  3. Push tag to remote git push --tags
  4. Optional Run the publish workflow with tag
1.0.4

9 months ago

1.0.3

9 months ago

1.0.2

9 months ago