npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

maplibre-gl-raster-reprojection

v1.0.4

Published

Reproject maplibre raster tiles on the fly in the browser

Downloads

51

Readme

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

| 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

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