expoweb/map/test/doc/tutorials/raster-reprojection.html

188 lines
15 KiB
HTML
Raw Normal View History

2024-05-16 19:50:28 +01:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>OpenLayers - Raster Reprojection</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/toolbar/prism-toolbar.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Quattrocento+Sans:400,400italic,700" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/fontawesome.min.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/solid.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.2/css/brands.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/theme/ol.css">
<link rel="stylesheet" type="text/css" href="/theme/site.css">
<link rel="icon" type="image/svg+xml" href="/theme/img/logo-light.svg" media="(prefers-color-scheme: light)" />
<link rel="icon" type="image/svg+xml" href="/theme/img/logo-dark.svg" media="(prefers-color-scheme: dark)" />
</head>
<body>
<header class="navbar navbar-expand-md navbar-dark mb-3 px-3 py-0 fixed-top" role="navigation">
<a class="navbar-brand" href="/"><img src="/theme/img/logo-dark.svg" width="70" height="70" alt="">&nbsp;OpenLayers</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#olmenu" aria-controls="olmenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- menu items that get hidden below 768px width -->
<nav class="collapse navbar-collapse" id="olmenu">
<ul class="nav navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="docdropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Docs</a>
<div class="dropdown-menu dropdown-menu-end mb-3" aria-labelledby="docdropdown">
<a class="dropdown-item" href="/doc/">Docs</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/doc/quickstart.html"><i class="fa fa-check fa-fw me-2 fa-lg"></i>Quick Start</a>
<a class="dropdown-item" href="/doc/faq.html"><i class="fa fa-question fa-fw me-2 fa-lg"></i>FAQ</a>
<a class="dropdown-item" href="/doc/tutorials/"><i class="fa fa-book fa-fw me-2 fa-lg"></i>Tutorials</a>
<a class="dropdown-item" href="/workshop/"><i class="fa fa-graduation-cap fa-fw me-2 fa-lg"></i>Workshop</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="https://stackoverflow.com/questions/tagged/openlayers"><i class="fab fa-stack-overflow fa-fw me-2"></i>Ask a Question</a>
</div>
</li>
<li class="nav-item"><a class="nav-link" href="/en/latest/examples/">Examples</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="apidropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-sitemap me-1"></i>API
</a>
<div class="dropdown-menu dropdown-menu-end mb-3" aria-labelledby="apidropdown">
<a class="dropdown-item" href="/en/latest/apidoc/"><i class="fa fa-sitemap fa-fw me-2 fa-lg"></i>v9.1.0 (latest)</a>
<a class="dropdown-item" href="/en/v8.1.0/apidoc/"><i class="fa fa-sitemap a-fw me-2 fa-lg"></i>v8.1.0</a>
<a class="dropdown-item" href="/en/v7.5.2/apidoc/"><i class="fa fa-sitemap a-fw me-2 fa-lg"></i>v7.5.2</a>
<a class="dropdown-item" href="/en/v6.15.1/apidoc/"><i class="fa fa-sitemap a-fw me-2 fa-lg"></i>v6.15.1</a>
<a class="dropdown-item" href="/en/v5.3.0/apidoc/"><i class="fa fa-sitemap fa-fw me-2 fa-lg"></i>v5.3.0</a>
<a class="dropdown-item" href="/en/v4.6.5/apidoc/"><i class="fa fa-sitemap fa-fw me-2 fa-lg"></i>v4.6.5</a>
<a class="dropdown-item" href="/en/v3.20.1/apidoc/"><i class="fa fa-sitemap fa-fw me-2 fa-lg"></i>v3.20.1</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="codedropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Code</a>
<div class="dropdown-menu dropdown-menu-end mb-3" aria-labelledby="codedropdown">
<a class="dropdown-item" href="https://github.com/openlayers/openlayers"><i class="fab fa-github fa-fw me-2 fa-lg"></i>Repository</a>
<a class="dropdown-item" href="/download/"><i class="fa fa-download fa-fw me-2 fa-lg"></i>Download</a>
</div>
</li>
</ul>
</nav>
</header>
<div class="container">
<h1 id="raster-reprojection">Raster Reprojection</h1>
<p>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.</p>
<h1 id="usage">Usage</h1>
<p>The API usage is very simple. Just specify proper projection (e.g. using <a href="https://epsg.io">EPSG</a> code) on <code>ol/View</code>:</p>
<pre><code class="language-js">import Map from &#39;ol/Map.js&#39;;
import TileLayer from &#39;ol/layer/Tile.js&#39;;
import TileWMS from &#39;ol/source/TileWMS.js&#39;;
import View from &#39;ol/View.js&#39;;
const map = new Map({
target: &#39;map&#39;,
view: new View({
projection: &#39;EPSG:3857&#39;, // here is the view projection
center: [0, 0],
zoom: 2,
}),
layers: [
new TileLayer({
source: new TileWMS({
projection: &#39;EPSG:4326&#39;, // here is the source projection
url: &#39;https://ahocevar.com/geoserver/wms&#39;,
params: {
&#39;LAYERS&#39;: &#39;ne:NE1_HR_LC_SR_W_DR&#39;,
},
}),
}),
],
});
</code></pre>
<p>If a source (based on <code>ol/source/TileImage</code> or <code>ol/source/Image</code>) has a projection different from the current <code>ol/View</code>s projection then the reprojection happens automatically under the hood.</p>
<h3 id="examples">Examples</h3>
<ul>
<li><a href="/en/latest/examples/reprojection.html">Raster reprojection demo</a></li>
<li><a href="/en/latest/examples/reprojection-wgs84.html">OpenStreetMap to WGS84 reprojection</a></li>
<li><a href="/en/latest/examples/reprojection-by-code.html">Reprojection with EPSG.io database search</a></li>
<li><a href="/en/latest/examples/reprojection-image.html">Image reprojection</a></li>
</ul>
<h3 id="custom-projection">Custom projection</h3>
<p>The easiest way to use a custom projection is to add the <a href="http://proj4js.org/">Proj4js</a> library to your project and then define the projection using a proj4 definition string. It can be installed with</p>
<pre><code>npm install proj4
</code></pre>
<p>Following example shows definition of a <a href="https://epsg.io/27700">British National Grid</a>:</p>
<pre><code class="language-js">import proj4 from &#39;proj4&#39;;
import {get as getProjection} from &#39;ol/proj.js&#39;;
import {register} from &#39;ol/proj/proj4.js&#39;;
proj4.defs(&#39;EPSG:27700&#39;, &#39;+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 &#39; +
&#39;+x_0=400000 +y_0=-100000 +ellps=airy &#39; +
&#39;+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 &#39; +
&#39;+units=m +no_defs&#39;);
register(proj4);
const proj27700 = getProjection(&#39;EPSG:27700&#39;);
proj27700.setExtent([0, 0, 700000, 1300000]);
</code></pre>
<h3 id="change-of-the-view-projection">Change of the view projection</h3>
<p>To switch the projection used to display the map you have to set a new <code>ol/View</code> with selected projection on the <code>ol/Map</code>:</p>
<pre><code class="language-js">map.setView(new View({
projection: &#39;EPSG:27700&#39;,
center: [400000, 650000],
zoom: 4,
}));
</code></pre>
<h2 id="tilegrid-and-extents">TileGrid and Extents</h2>
<p>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 <code>ol/tilegrid~getForProjection(projection)</code>.
The projection should have extent defined (see above) for this to work properly.</p>
<p>Alternatively, a custom target TileGrid can be constructed manually and set on the source instance using <code>ol/source/TileImage~setTileGridForProjection(projection, tilegrid)</code>.
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).</p>
<h1 id="how-it-works">How it works</h1>
<p>The reprojection process is based on triangles the target raster is divided into a limited number of triangles with vertices transformed using <code>ol/proj</code> capabilities (<a href="http://proj4js.org/">proj4js</a> 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):</p>
<img src="raster-reprojection-resources/how-it-works.jpg" alt="How it works" width="600" />
<p>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.</p>
<p>The precision of the reprojection is then limited by the number of triangles.</p>
<p>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.</p>
<h3 id="dynamic-triangulation">Dynamic triangulation</h3>
<p>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.</p>
<p>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:</p>
<img src="raster-reprojection-resources/iterative-triangulation.png" alt="Iterative triangulation" width="600" />
<p>For debugging, rendering of the reprojection edges can be enabled by <code>ol.source.TileImage#setRenderReprojectionEdges(true)</code>.</p>
<h1 id="advanced">Advanced</h1>
<h3 id="triangulation-precision-threshold">Triangulation precision threshold</h3>
<p>The default <a href="#dynamic-triangulation">triangulation error threshold</a> in pixels is given by <code>ERROR_THRESHOLD</code> (0.5 pixel).
In case a different threshold needs to be defined for different sources, the <code>reprojectionErrorThreshold</code> option can be passed when constructing the tile image source.</p>
<h3 id="limiting-visibility-of-reprojected-map-by-extent">Limiting visibility of reprojected map by extent</h3>
<p>The reprojection algorithm uses inverse transformation (from <em>view projection</em> to <em>data projection</em>).
For certain coordinate systems this can result in a &quot;double occurrence&quot; 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.</p>
<img src="raster-reprojection-resources/double-occurrence.jpg" alt="Double occurrence of a reprojected map" width="600" />
<p>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).</p>
<p>Therefore a recommended workaround is to define a proper visibility extent on the <code>ol.layer.Tile</code> in the view projection.
Setting such a limit is demonstrated in the <a href="https://openlayers.org/en/latest/examples/reprojection.html">reprojection demo example</a>.</p>
<h3 id="resolution-calculation">Resolution calculation</h3>
<p>When determining source tiles to load, the ideal source resolution needs to be calculated.
The <code>ol/reproj~calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution)</code> 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.</p>
<p>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).</p>
</div>
<footer>
Code licensed under the <a href='http://www.tldrlegal.com/license/bsd-2-clause-license-(freebsd)'>2-Clause BSD</a>.
All documentation <a href='http://creativecommons.org/licenses/by/3.0/'>CC BY 3.0</a>.
Thanks to our <a href='https://opencollective.com/openlayers'>sponsors</a>.
<br>
<a href="https://www.netlify.com">This site is powered by Netlify.</a>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>