|
|
<!DOCTYPE html>
|
|
|
<html>
|
|
|
<head>
|
|
|
<title>OpenLayers - Raster Reprojection</title>
|
|
|
<script>
|
|
|
var gaProperty = 'UA-2577926-1';
|
|
|
// Disable tracking if the opt-out cookie exists.
|
|
|
var disableStr = 'ga-disable-' + gaProperty;
|
|
|
if (document.cookie.indexOf(disableStr + '=true') > -1) {
|
|
|
window[disableStr] = true;
|
|
|
}
|
|
|
function gaOptout() {
|
|
|
document.cookie = disableStr + '=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/';
|
|
|
window[disableStr] = true;
|
|
|
}
|
|
|
function gaOptoutRevoke() {
|
|
|
document.cookie = disableStr + '=false; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/';
|
|
|
window[disableStr] = false;
|
|
|
}
|
|
|
</script>
|
|
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-2577926-1"></script>
|
|
|
<script>
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
|
function gtag(){dataLayer.push(arguments);}
|
|
|
gtag('js', new Date());
|
|
|
gtag('config', 'UA-2577926-1', { 'anonymize_ip': true });
|
|
|
</script>
|
|
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.css" />
|
|
|
<script src="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.0/cookieconsent.min.js"></script>
|
|
|
<script>
|
|
|
window.addEventListener("load", function() {
|
|
|
window.cookieconsent.initialise({
|
|
|
'palette': {
|
|
|
'popup': {
|
|
|
'background': '#eaf7f7',
|
|
|
'text': '#5c7291'
|
|
|
},
|
|
|
'button': {
|
|
|
'background': '#56cbdb',
|
|
|
'text': '#ffffff'
|
|
|
}
|
|
|
},
|
|
|
'theme': 'edgeless',
|
|
|
'type': 'opt-out',
|
|
|
'onInitialise': function (status) {
|
|
|
if (!this.hasConsented()) {
|
|
|
gaOptout()
|
|
|
}
|
|
|
},
|
|
|
'onStatusChange': function(status, chosenBefore) {
|
|
|
if (!this.hasConsented()) {
|
|
|
gaOptout()
|
|
|
}
|
|
|
},
|
|
|
'onRevokeChoice': function() {
|
|
|
gaOptoutRevoke()
|
|
|
}
|
|
|
})
|
|
|
});
|
|
|
</script>
|
|
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
|
<link href='https://fonts.googleapis.com/css?family=Quattrocento+Sans:400,400italic,700' rel='stylesheet' type='text/css'>
|
|
|
<script src="//code.jquery.com/jquery-3.5.1.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/components/prism-core.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/plugins/toolbar/prism-toolbar.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
|
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
|
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
|
<link href='../../../../assets/theme/site.css' rel='stylesheet' type='text/css'>
|
|
|
<link rel="icon" type="image/x-icon" href="../../../../assets/theme/img/favicon.ico" />
|
|
|
|
|
|
</head>
|
|
|
<body>
|
|
|
<header class="navbar navbar-expand-md navbar-dark mb-3 py-0 fixed-top" role="navigation">
|
|
|
<a href='/' class='navbar-brand'><img src='../../../../assets/theme/img/logo70.png'> OpenLayers</a>
|
|
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-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 ml-auto'>
|
|
|
<li class="nav-item dropdown">
|
|
|
<a class="nav-link dropdown-toggle active" href="#" id="docdropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Docs</a>
|
|
|
<div class="dropdown-menu dropdown-menu-right mb-3" aria-labelledby="docdropdown">
|
|
|
<a class="dropdown-item" href="/en/latest/doc/">Docs</a>
|
|
|
<div class="dropdown-divider"></div>
|
|
|
<a class="dropdown-item" href="/en/latest/doc/quickstart.html"><i class="fa fa-check fa-fw mr-2 fa-lg"></i>Quick Start</a>
|
|
|
<a class="dropdown-item" href="/en/latest/doc/faq.html"><i class="fa fa-question fa-fw mr-2 fa-lg"></i>FAQ</a>
|
|
|
<a class="dropdown-item" href="/en/latest/doc/tutorials/"><i class="fa fa-book fa-fw mr-2 fa-lg"></i>Tutorials</a>
|
|
|
<a class="dropdown-item" href="/workshop/"><i class="fa fa-graduation-cap fa-fw mr-2 fa-lg"></i>Workshop</a>
|
|
|
<div class="dropdown-divider"></div>
|
|
|
<a class="dropdown-item" href="https://stackoverflow.com/questions/tagged/openlayers"><i class="fa fa-stack-overflow fa-fw mr-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"><a class="nav-link" href="/en/latest/apidoc/"><i class="fa fa-sitemap mr-1"></i>API</a></li>
|
|
|
<li class="nav-item dropdown">
|
|
|
<a class="nav-link dropdown-toggle" href="#" id="codedropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Code</a>
|
|
|
<div class="dropdown-menu dropdown-menu-right mb-3" aria-labelledby="codedropdown">
|
|
|
<a class="dropdown-item" href="https://github.com/openlayers/openlayers"><i class="fa fa-github fa-fw mr-2 fa-lg"></i>Repository</a>
|
|
|
<a class="dropdown-item" href="/download/"><i class="fa fa-download fa-fw mr-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, View} from <span class="string">'ol'</span>;
|
|
|
import TileLayer from <span class="string">'ol/layer/Tile'</span>;
|
|
|
import TileWMS from <span class="string">'ol/source/TileWMS'</span>;
|
|
|
|
|
|
<span class="keyword">var</span> map = <span class="keyword">new</span> Map({
|
|
|
target: <span class="string">'map'</span>,
|
|
|
view: <span class="keyword">new</span> View({
|
|
|
projection: <span class="string">'EPSG:3857'</span>, <span class="comment">//HERE IS THE VIEW PROJECTION</span>
|
|
|
center: [<span class="number">0</span>, <span class="number">0</span>],
|
|
|
zoom: <span class="number">2</span>
|
|
|
}),
|
|
|
layers: [
|
|
|
<span class="keyword">new</span> TileLayer({
|
|
|
source: <span class="keyword">new</span> TileWMS({
|
|
|
projection: <span class="string">'EPSG:4326'</span>, <span class="comment">//HERE IS THE DATA SOURCE PROJECTION</span>
|
|
|
url: <span class="string">'http://demo.boundlessgeo.com/geoserver/wms'</span>,
|
|
|
params: {
|
|
|
<span class="string">'LAYERS'</span>: <span class="string">'ne:NE1_HR_LC_SR_W_DR'</span>
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
]
|
|
|
});</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="https://openlayers.org/en/latest/examples/reprojection.html">Raster reprojection demo</a></li>
|
|
|
<li><a href="https://openlayers.org/en/latest/examples/reprojection-wgs84.html">OpenStreetMap to WGS84 reprojection</a></li>
|
|
|
<li><a href="https://openlayers.org/en/latest/examples/reprojection-by-code.html">Reprojection with EPSG.io database search</a></li>
|
|
|
<li><a href="https://openlayers.org/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 <span class="string">'proj4'</span>;
|
|
|
import {get as getProjection, register} from <span class="string">'ol/proj'</span>;
|
|
|
|
|
|
proj4.defs(<span class="string">'EPSG:27700'</span>, <span class="string">'+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 '</span> +
|
|
|
<span class="string">'+x_0=400000 +y_0=-100000 +ellps=airy '</span> +
|
|
|
<span class="string">'+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 '</span> +
|
|
|
<span class="string">'+units=m +no_defs'</span>);
|
|
|
register(proj4);
|
|
|
<span class="keyword">var</span> proj27700 = getProjection(<span class="string">'EPSG:27700'</span>);
|
|
|
proj27700.setExtent([<span class="number">0</span>, <span class="number">0</span>, <span class="number">700000</span>, <span class="number">1300000</span>]);</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-javascript">map.setView(<span class="keyword">new</span> View({
|
|
|
projection: <span class="string">'EPSG:27700'</span>,
|
|
|
center: [<span class="number">400000</span>, <span class="number">650000</span>],
|
|
|
zoom: <span class="number">4</span>
|
|
|
}));</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>
|
|
|
<p><img src="raster-reprojection-resources/how-it-works.jpg" alt="How it works" width="600" /></p>
|
|
|
<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>
|
|
|
<p><img src="raster-reprojection-resources/iterative-triangulation.png" alt="Iterative triangulation" width="600" /></p>
|
|
|
<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 "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.</p>
|
|
|
<p><img src="raster-reprojection-resources/double-occurrence.jpg" alt="Double occurrence of a reprojected map" width="600" /></p>
|
|
|
<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='/sponsors.html'>sponsors</a>.
|
|
|
<br>
|
|
|
<a href="https://www.netlify.com">
|
|
|
This site is powered by Netlify.
|
|
|
</a>
|
|
|
</footer>
|
|
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>
|
|
|
|
|
|
</body>
|
|
|
</html>
|