Raster Reprojection

OpenLayers has an ability to display raster data from WMS, WMTS, static images and many other sources in a different coordinate system than delivered from the server. Transformation of the map projections of the image happens directly in a web browser. The view in any Proj4js supported coordinate reference system is possible and previously incompatible layers can now be combined and overlaid.

Usage

The API usage is very simple. Just specify proper projection (e.g. using EPSG code) on ol/View:

import Map from 'ol/Map.js';
import TileLayer from 'ol/layer/Tile.js';
import TileWMS from 'ol/source/TileWMS.js';
import View from 'ol/View.js';

const map = new Map({
  target: 'map',
  view: new View({
    projection: 'EPSG:3857', // here is the view projection
    center: [0, 0],
    zoom: 2,
  }),
  layers: [
    new TileLayer({
      source: new TileWMS({
        projection: 'EPSG:4326', // here is the source projection
        url: 'https://ahocevar.com/geoserver/wms',
        params: {
          'LAYERS': 'ne:NE1_HR_LC_SR_W_DR',
        },
      }),
    }),
  ],
});

If a source (based on ol/source/TileImage or ol/source/Image) has a projection different from the current ol/View’s projection then the reprojection happens automatically under the hood.

Examples

Custom projection

The easiest way to use a custom projection is to add the Proj4js library to your project and then define the projection using a proj4 definition string. It can be installed with

npm install proj4

Following example shows definition of a British National Grid:

import proj4 from 'proj4';
import {get as getProjection} from 'ol/proj.js';
import {register} from 'ol/proj/proj4.js';

proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
    '+x_0=400000 +y_0=-100000 +ellps=airy ' +
    '+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
    '+units=m +no_defs');
register(proj4);
const proj27700 = getProjection('EPSG:27700');
proj27700.setExtent([0, 0, 700000, 1300000]);

Change of the view projection

To switch the projection used to display the map you have to set a new ol/View with selected projection on the ol/Map:

map.setView(new View({
  projection: 'EPSG:27700',
  center: [400000, 650000],
  zoom: 4,
}));

TileGrid and Extents

When reprojection is needed, new tiles (in the target projection) are under the hood created from the original source tiles. The TileGrid of the reprojected tiles is by default internally constructed using ol/tilegrid~getForProjection(projection). The projection should have extent defined (see above) for this to work properly.

Alternatively, a custom target TileGrid can be constructed manually and set on the source instance using ol/source/TileImage~setTileGridForProjection(projection, tilegrid). This TileGrid will then be used when reprojecting to the specified projection instead of creating the default one. In certain cases, this can be used to optimize performance (by tweaking tile sizes) or visual quality (by specifying resolutions).

How it works

The reprojection process is based on triangles – the target raster is divided into a limited number of triangles with vertices transformed using ol/proj capabilities (proj4js is usually utilized to define custom transformations). The reprojection of pixels inside the triangle is approximated with an affine transformation (with rendering hardware-accelerated by the canvas 2d context):

How it works

This way we can support a wide range of projections from proj4js (or even custom transformation functions) on almost any hardware (with canvas 2d support) with a relatively small number of actual transformation calculations.

The precision of the reprojection is then limited by the number of triangles.

The reprojection process preserves transparency on the raster data supplied from the source (png or gif) and the gaps and no-data pixels generated by reprojection are automatically transparent.

Dynamic triangulation

The above image above shows a noticeable error (especially on the edges) when the original image (left; EPSG:27700) is transformed with only a limited number of triangles (right; EPSG:3857). The error can be minimized by increasing the number of triangles used.

Since some transformations require a more detail triangulation network, the dynamic triangulation process automatically measures reprojection error and iteratively subdivides to meet a specific error threshold:

Iterative triangulation

For debugging, rendering of the reprojection edges can be enabled by ol.source.TileImage#setRenderReprojectionEdges(true).

Advanced

Triangulation precision threshold

The default triangulation error threshold in pixels is given by ERROR_THRESHOLD (0.5 pixel). In case a different threshold needs to be defined for different sources, the reprojectionErrorThreshold option can be passed when constructing the tile image source.

Limiting visibility of reprojected map by extent

The reprojection algorithm uses inverse transformation (from view projection to data projection). For certain coordinate systems this can result in a "double occurrence" of the source data on a map. For example, when reprojecting a map of Switzerland from EPSG:21781 to EPSG:3857, it is displayed twice: once at the proper place in Europe, but also in the Pacific Ocean near New Zealand, on the opposite side of the globe.

Double occurrence of a reprojected map

Although this is mathematically correct behavior of the inverse transformation, visibility of the layer on multiple places is not expected by users. A possible general solution would be to calculate the forward transformation for every vertex as well - but this would significantly decrease performance (especially for computationally expensive transformations).

Therefore a recommended workaround is to define a proper visibility extent on the ol.layer.Tile in the view projection. Setting such a limit is demonstrated in the reprojection demo example.

Resolution calculation

When determining source tiles to load, the ideal source resolution needs to be calculated. The ol/reproj~calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution) function calculates the ideal value in order to achieve pixel mapping as close as possible to 1:1 during reprojection, which is then used to select proper zoom level from the source.

It is, however, generally not practical to use the same source zoom level for the whole target zoom level -- different projections can have significantly different resolutions in different parts of the world (e.g. polar regions in EPSG:3857 vs EPSG:4326) and enforcing a single resolution for the whole zoom level would result in some tiles being scaled up/down, possibly requiring a huge number of source tiles to be loaded. Therefore, the resolution mapping is calculated separately for each reprojected tile (in the middle of the tile extent).