blob: 425745c87e8b1f79641a1a6dbf3d713b47f57abd [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<title>CoordinateOperations</title>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="../../../content/book/book.css"/>
</head>
<body>
<!--
Content below this point is copied in "(…)/content/book/fr/developer-guide.html" file
by the 'org.apache.sis.internal.book.Assembler' class in 'sis-build-helper' module.
-->
<section>
<header>
<h2 id="CoordinateOperations">Opérations sur les coordonnées</h2>
</header>
<p>
Étant donné un système de référence des coordonnées (<abbr>CRS</abbr>) <em>source</em> selon lequel sont exprimés des coordonnées existantes
et un système de référence des coordonnées <em>destination</em> selon lequel les coordonnées sont désirées,
Apache <abbr>SIS</abbr> peut fournir une <em>opération sur les coordonnées</em> qui effectuera le travail de conversion ou de transformation.
La recherche d’une opération peut utiliser un troisième argument, optionnel mais recommandé: la région géographique des données à transformer.
Ce dernier argument est recommandé parce que les opérations sur les coordonnées sont souvent valides seulement dans une région géographique
(typiquement un pays ou une province particulière), et plusieurs transformations peuvent exister pour la même paire de <abbr>CRS</abbr>
source et destination mais avec des domaines de validité différents.
Il peut aussi y avoir des différentes transformations qui sont différents compromis entre la précision et le domaine de validité,
de sorte que spécifier à Apache <abbr>SIS</abbr> qu’on s’intéresse à une région plus petite
peut lui permettre de sélectionner une opération plus précise.
</p>
<div class="example"><p><b>Exemple:</b>
la base de données géodésiques <abbr>EPSG</abbr> (dans sa version 7.9) définit 77 opérations sur les coordonnées
allant du système géographique <cite>North American Datum 1927</cite> (EPSG:4267)
vers le système <cite>World Geodetic System 1984</cite> (EPSG:4326).
Il y a une opération valide seulement pour la transformation de coordonnées au Québec,
une autre opération valide pour la transformation de coordonnées au Texas mais à l’ouest de 100°W,
une autre opération pour le même état mais à l’est de 100°W, <i>etc</i>.
Si l’utilisateur ne spécifie pas la région géographique qui l’intéresse,
alors le comportement par défaut de Apache <abbr>SIS</abbr> est de sélectionner l’opération valide dans la plus grande région géographique.
Dans cet exemple, ce critère entraîne la sélection d’une opération valide pour le Canada, mais qui n’est pas valide pour les États-Unis.</p>
</div>
<h3 id="CRS.findOperation">Obtention d’une opération sur les coordonnées</h3>
<p>
La façon la plus facile d’obtenir une opération sur les coordonnées à partir des informations présentées ci-dessus
est d’utiliser la classe de commodité <code>org.apache.sis.referencing.CRS</code>:
</p>
<pre><code>CoordinateOperation cop = <code class="SIS">CRS.findOperation</code>(sourceCRS, targetCRS, areaOfInterest);</code></pre>
<p>
Parmi les information fournies par l’objet <code>CoordinateOperation</code> obtenu, on note en particulier:
</p>
<ul>
<li>Le <cite>domaine de validité</cite>, soit comme une description textuelle telle que « Canada – onshore and offshore »
ou comme les coordonnées géographiques d’une boîte englobante.</li>
<li>La <cite>précision</cite>, qui peut être n’importe quoi entre 1 centimètre et quelques kilomètres.</li>
<li>Le sous-type de l’opération sur les coordonnées. Parmi les sous-types possibles,
deux offrent les mêmes fonctionnalités mais avec une différence conceptuelle notable:
<ul class="verbose">
<li>
Les <strong>conversions</strong> de coordonnées sont entièrement définies par une formule mathématique.
Les conversions s’effectueraient avec une précision infinie s’il n’y avait pas les inévitables
erreurs d’arrondissements inhérents aux calculs sur des nombres réels.
Les projections cartographiques entrent dans cette catégorie.
</li><li>
Les <strong>transformations</strong> de coordonnées sont définies de manière empirique.
Elles ont souvent des erreurs de quelques mètres qui ne sont pas dues à des limites de la précision des ordinateurs.
Ces erreurs découlent du fait que la transformation utilisée n’est qu’une approximation d’une réalité plus complexe.
Les changements de référentiels tel que le passage de la
<cite>Nouvelle Triangulation Française</cite> (<abbr>NTF</abbr>) vers le
<cite>Réseau Géodésique Français 1993</cite> (<abbr>RGF93</abbr>) entrent dans cette catégorie.
</li>
</ul>
</li>
</ul>
<p>
Lorsque l’opération sur les coordonnées est une instance de <code>Transformation</code>,
il est possible que l’instance choisie par <abbr>SIS</abbr> ne soit qu’une parmi plusieurs possibilités en fonction de la région d’intérêt.
En outre, sa précision sera certainement moindre que la précision centimétrique que l’on peut attendre d’une <code>Conversion</code>.
Vérifier la zone de validité ainsi que la précision déclarées dans les méta-données de la transformation prend alors une importance particulière.
</p>
<h3 id="MathTransform">Exécution de opérations</h3>
<p>
L’objet <code>CoordinateOperation</code> introduit dans la section précédente fournit des informations de haut-niveau
(<abbr>CRS</abbr> source et destination, zone de validité, précision, paramètres de l’opération, <i>etc</i>).
Le travail mathématique réel est effectué par un objet séparé, obtenu par un appel à <code>CoordinateOperation.getMathTransform()</code>.
Contrairement aux instances de <code>CoordinateOperation</code>, les instances de <code>MathTransform</code> ne contiennent aucune méta-données.
Elles sont une sorte de boîte noire qui ignore tout des <abbr>CRS</abbr> source et destination
(en fait la même instance de <code>MathTransform</code> peut être utilisée pour différentes paires de <abbr>CRS</abbr> si le travail mathématique est le même).
En outre une instance de <code>MathTransform</code> peut être implémentée d’une manière très différente à ce que <code>CoordinateOperation</code> dit.
En particulier, plusieurs opérations conceptuellement différentes (par exemple rotations de la longitude,
changements d’unités de mesure, conversions entre deux projections de Mercator qui utilisent le même référentiel, <i>etc.</i>)
sont implémentées par <code>MathTransform</code> comme des <a href="#AffineTransform">transformations affines</a>
et concaténées pour des raisons d’efficacité, même si <code>CoordinateOperation</code> les affiche comme une chaîne d’opérations telles que la projection de Mercator.
La section « <a href="#CoordinateOperationSteps">chaîne d’opération conceptuelle versus réelle</a> » explique plus en détails les différences.
</p>
<p>
Le code Java suivant effectue une projection cartographique à partir de coordonnées géographiques selon le référentiel
<cite>World Geodetic System 1984</cite> (<abbr>WGS84</abbr>) vers des coordonnées selon le système <cite>WGS 84 / UTM zone 33N</cite>.
Afin de rendre l’exemple un peu plus simple, ce code utilise des constantes pré-définies dans la classe de commodité <code>CommonCRS</code>.
Mais des applications plus avancées voudront souvent utiliser des codes <abbr>EPSG</abbr> plutôt.
Notons que toutes les coordonnées géographiques dans ce code ont la latitude avant la longitude.
</p>
<pre><code>import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.geometry.DirectPosition2D;
public class MyApp {
public static void main(String[] args) throws FactoryException, TransformException {
CoordinateReferenceSystem sourceCRS = CommonCRS.WGS84.geographic();
CoordinateReferenceSystem targetCRS = CommonCRS.WGS84.universal(40, 14); // Obtient la zone valide pour 14°E.
CoordinateOperation operation = <code class="SIS">CRS.findOperation</code>(sourceCRS, targetCRS, null);
// Les lignes précédentes sont coûteuses et ne devraient être exécutées qu’une seule fois avant
// de transformer plusieurs points. Dans cet exemple, l’opération que nous obtenons est valide
// pour des coordonnées dans la région géographique allant de 12°E à 18°E (zone 33) et 0°N à 84°N.
DirectPosition ptSrc = new DirectPosition2D(40, 14); // 40°N 14°E
DirectPosition ptDst = operation.getMathTransform().transform(ptSrc, null);
System.out.println("Source: " + ptSrc);
System.out.println("Target: " + ptDst);
}
}</code></pre>
<h3 id="TransformDerivative">Dérivées partielles des opérations</h3>
<p>
La section précédente indiquait comment projeter les coordonnées d’un système de référence vers un autre.
Mais il existe une autre opération moins connue, qui consiste à calculer non pas la coordonnée projetée d’un point,
mais plutôt la dérivée de la fonction de projection cartographique en ce point.
Cette opération était définie dans une ancienne spécification du consortium Open Geospatial,
<a href="https://www.ogc.org/standards/ct">OGC 01-009</a>, aujourd’hui un peu oubliée mais pourtant encore utile.
Appelons <var>P</var> une projection cartographique qui convertit une latitude et longitude (<var>φ</var><var>λ</var>) en degrés
vers une coordonnée projetée (<var>x</var><var>y</var>) en mètres.
Dans l’expression ci-dessous, nous représentons le résultat de la projection cartographique
sous forme d’une matrice colonne (la raison sera plus claire bientôt):
</p>
<div class="row-of-boxes">
<div style="min-width:350px; padding-right:60px">
<div class="caption">Équation</div>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block" alttext="MathML capable browser required">
<mi>P</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo>
<mo>=</mo>
<mfenced open="[" close="]">
<mtable>
<mtr><mtd><mi>x</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mtd></mtr>
<mtr><mtd><mi>y</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mtd></mtr>
</mtable>
</mfenced>
</math>
</div>
<div style="min-width:500px; padding-left:60px">
<div class="caption">Code Java</div>
<pre style="margin:0"><code>DirectPosition geographic = new DirectPosition2D(<var>φ</var>, <var>λ</var>);
DirectPosition projected = <var><b>P</b></var>.transform(geographic, null);
double <var>x</var> = projected.getOrdinate(0);
double <var>y</var> = projected.getOrdinate(1);</code></pre>
</div>
</div>
<p>La dérivée de la projection cartographique en ce même point peut se représenter par une matrice Jacobienne:</p>
<div class="row-of-boxes">
<div style="min-width:350px; padding-right:60px">
<div class="caption">Équation</div>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block" alttext="MathML capable browser required">
<msup><mi>P</mi><mo></mo></msup><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo>
<mo>=</mo>
<msub><mi>JAC</mi><mrow><mi>P</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mrow></msub>
<mo>=</mo>
<mfenced open="[" close="]">
<mtable>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>x</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mrow><mrow><mo></mo><mi>φ</mi></mrow></mfrac></mtd>
<mtd><mfrac><mrow><mo></mo><mi>x</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mrow><mrow><mo></mo><mi>λ</mi></mrow></mfrac></mtd>
</mtr>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>y</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mrow><mrow><mo></mo><mi>φ</mi></mrow></mfrac></mtd>
<mtd><mfrac><mrow><mo></mo><mi>y</mi><mo>(</mo><mi>φ</mi><mo>,</mo><mi>λ</mi><mo>)</mo></mrow><mrow><mo></mo><mi>λ</mi></mrow></mfrac></mtd>
</mtr>
</mtable>
</mfenced>
</math>
</div>
<div style="min-width:500px; padding-left:60px">
<div class="caption">Code Java</div>
<pre style="margin:0"><code>DirectPosition geographic = new DirectPosition2D(<var>φ</var>, <var>λ</var>);
Matrix jacobian = <var><b>P</b></var>.derivative(geographic);
double dx_dλ = jacobian.getElement(0,1);
double dy_dφ = jacobian.getElement(1,0);</code></pre>
</div>
</div>
<p>
Les équations suivantes dans cette section abrégeront
<var>x</var>(<var>φ</var><var>λ</var>) par ∂<var>x</var> ainsi que
<var>y</var>(<var>φ</var><var>λ</var>) par ∂<var>y</var>,
mais il faut garder à l’esprit que chacune de ces valeurs dépendent de la coordonnée (<var>φ</var><var>λ</var>) donnée au moment du calcul de la matrice Jacobienne.
La première colonne de la matrice nous dit que si l’on effectue un petit déplacement de ∂<var>φ</var> degrés de latitude
à partir de la position (<var>φ</var><var>λ</var>)
— c’est-à-dire si on se déplace à la position geographique (<var>φ</var> + ∂<var>φ</var>, <var>λ</var>) —
alors la coordonnée projetée subira un déplacement de (∂<var>x</var>, ∂<var>λ</var>) metres
— c’est-à-dire qu’elle deviendra (<var>x</var> + ∂<var>x</var>, <var>y</var> + ∂<var>λ</var>).
De même la dernière colonne de la matrice nous indique quel sera le déplacement que subira la coordonnée projetée
si on effectue un petit déplacement de ∂<var>λ</var> degrés de longitude de la coordonnée géographique source.
On peut se représenter visuellement ces déplacements comme ci-dessous.
Cette figure représente la dérivée en deux points, <var>P</var><sub>1</sub> et <var>P</var><sub>2</sub>,
pour mieux illustrer le fait que le résultat varie en chaque point.
Dans cette figure, les vecteurs <var>U</var> et <var>V</var> désignent respectivement
la première et deuxième colonne des matrices Jacobiennes.
</p>
<div class="row-of-boxes">
<div>
<img style="border: solid 1px" src="../../../content/book/images/Derivatives.png" alt="Exemple de dérivées d’une projection cartographique"/>
</div><div>
<p>où les vecteurs sont reliés à la matrice par:</p>
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block" alttext="MathML capable browser required">
<mtable><mtr>
<mtd>
<mover><mi>U</mi><mo></mo></mover><mo>=</mo>
<mfenced open="[" close="]">
<mtable>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>x</mi></mrow><mrow><mo></mo><mi>φ</mi></mrow></mfrac></mtd>
</mtr>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>y</mi></mrow><mrow><mo></mo><mi>φ</mi></mrow></mfrac></mtd>
</mtr>
</mtable>
</mfenced>
</mtd>
<mtd><mtext>et</mtext></mtd>
<mtd>
<mover><mi>V</mi><mo></mo></mover><mo>=</mo>
<mfenced open="[" close="]">
<mtable>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>x</mi></mrow><mrow><mo></mo><mi>λ</mi></mrow></mfrac></mtd>
</mtr>
<mtr>
<mtd><mfrac><mrow><mo></mo><mi>y</mi></mrow><mrow><mo></mo><mi>λ</mi></mrow></mfrac></mtd>
</mtr>
</mtable>
</mfenced>
</mtd>
</mtr></mtable>
</math>
</div>
</div>
<p>
Cette figure nous montre déjà une utilisation possible des dérivées:
elles donnent la direction des parallèles et des méridiens à une position donnée dans une projection cartographique.
Par extension, on peut aussi s’en servir pour déterminer si des axes sont interchangés,
ou si la direction d’un axe est renversée. Mais l’intérêt des dérivées ne s’arrête pas là.
</p>
<h4 id="DerivativeAndEnvelope">Utilité des dérivées pour la reprojection d’enveloppes</h4>
<p>
Les systèmes d’information géographiques ont très fréquemment besoin de projeter une enveloppe.
Mais l’approche naïve, qui consisterait à projeter chacun des 4 coins du rectangle, ne suffit pas.
La figure ci-dessous montre une enveloppe avant le projection, et la forme géométrique que l’on obtiendrait
si on projetait finement l’enveloppe (pas seulement les 4 coins). Cette forme géométrique est plus complexe
qu’un simple rectangle à cause des courbures induites par la projection cartographique.
Construire une enveloppe rectangulaire qui engloberait les 4 coins de cette forme géométrique ne suffit pas,
car la surface en bas de la forme est plus basse que les 2 coins du bas.
Cette surface serait donc en dehors du rectangle.
</p>
<div class="row-of-boxes">
<div>
<div class="caption">Enveloppe avant la projection</div>
<img style="border: solid 1px; margin-right: 15px" src="../../../content/book/images/GeographicArea.png" alt="Enveloppe dans un CRS géographique"/>
</div><div>
<div class="caption">Forme géométrique après la projection</div>
<img style="border: solid 1px; margin-left: 15px" src="../../../content/book/images/ConicArea.png" alt="Forme géométrique dans un CRS projeté"/>
</div>
</div>
<p>
Une façon simple d’atténuer le problème est d’échantillonner un plus grand nombre de points sur chacun des
bords de la forme géométrique. On trouve ainsi des bibliothèques de <abbr>SIG</abbr> qui vont par exemple
échantillonner 40 points sur chaque bord, et construire un rectangle qui englobe tout ces points.
Mais même avec 40 points, les échantillons les plus proches peuvent encore être légèrement à côté du point le plus bas de la figure.
Une petite portion de la forme géométrique peut donc toujours se trouver en dehors du rectangle.
Il est tentant de considérer cette légère erreur comme négligeable, mais quelques pixels manquants
entraînent divers artefacts comme une apparence de quadrillage lors de l’affichage d’images tuilées,
ou une “pointe de tarte” manquante lors de la projection d’images sur un pôle.
Augmenter artificiellement d’un certain pourcentage la taille de l’enveloppe projetée peut éliminer ces artefacts dans certains cas.
Mais un pourcentage trop élevé fera traiter plus de données que nécessaire
(en particulier lorsque cela entraîne le chargement de nouvelles tuiles d’images),
alors qu’un pourcentage trop faible laissera quelques artefacts.
</p><p>
Les dérivées des projections cartographiques permettent de résoudre ce problème d’une manière plus efficace que la force brute.
La figure ci-dessous reprend la forme projetée en exagérant des déformations.
L’approche consiste à calculer la projection cartographiques des 4 coins comme dans l’approche naïve,
mais en récupérant aussi les dérivées de la projection de ces 4 coins.
Entre deux coins et avec leurs dérivées, on peut faire passer une et une seule courbe cubique
(de la forme <i>f(<var>x</var>)</i> = <var>C</var>₀ + <var>C</var><var>x</var> + <var>C</var><var>x</var>² + <var>C</var><var>x</var>³),
dont on peut calculer les coefficients <var>C</var>.
Cette approximation (représentée en rouge ci-dessous) ne correspond pas tout-à-fait à la courbe désirée (en bleue) mais s’en rapproche.
Ce qui nous intéresse n’est pas vraiment les valeurs de l’approximation, mais plutôt la position de son minimum,
en particulier la longitude λ où se trouve ce minimum dans notre exemple (ligne pointillée verte).
L’avantage est que la position du minimum d’une courbe cubique est facile à calculer lorsque l’on connaît les valeurs de <var>C</var>.
En supposant que la longitude du minimum de la courbe cubique est proche de la longitude du minimum de la courbe réelle,
il suffit de calculer la projection cartographique d’un point à cette longitude plutôt que d’échantillonner 40 points sur le bord de l’enveloppe.
</p>
<div class="row-of-boxes">
<div>
<img src="../../../content/book/images/Approximation.png" alt="Approximation cubique d’un bord d’une enveloppe"/>
</div><div>
Légende:
<ul>
<li><b>En bleue:</b> la forme géométrique correspondant à la projection de l’enveloppe.
C’est la forme dont on souhaite avoir le rectangle englobant.</li>
<li><b>En rouge</b> (sous les hachures): L’approximation
<var>y</var> = <var>C</var>₀ + <var>C</var>₁λ + <var>C</var>₂λ² + <var>C</var>₃λ³.</li>
<li><b>En vert</b> (pointillés): La position λ<sub>m</sub> du minimum de l’approximation, trouvée en résolvant
0 = <var>C</var>₁ + 2<var>C</var>₂λ<sub>m</sub> + 3<var>C</var>₃λ<sub>m</sub>².
Il peut y avoir jusqu’à deux minimums pour une même courbe cubique.</li>
</ul>
</div>
</div>
<p>
Dans la pratique Apache <abbr>SIS</abbr> utilise 8 points, soit les 4 coins plus un point au centre de chaque bord du rectangle à projeter,
afin de réduire le risque d’erreur qu’induirait une courbe trop tordue entre deux points.
Selon nos tests, l’utilisation de ces seuls 8 points avec leurs dérivées comme décrit ci-haut
donne un résultat plus précis que l’approche « force brute » utilisant un échantillonnage de 160 points sur les 4 bords du rectangle.
La précision de <abbr>SIS</abbr> pourrait être encore améliorée en répétant le processus à partir du minimum trouvée
(une ou deux itérations suffiraient peut-être).
</p><p>
Une économie de 150 points n’est pas énorme vu les performances des ordinateurs d’aujourd’hui.
Mais toute la discussion précédente utilisait une forme géométrique à deux dimensions en guise d’exemple,
alors que l’algorithme est applicable dans un espace à <var>n</var> dimensions.
Et de fait, l’implémentation de Apache SIS fonctionne pour un nombre arbitraire de dimensions.
Les économies apportées par cet algorithme par rapport à la force brute augmentent de manière exponentielle avec le nombre de dimensions.
</p><p>
L’approche décrite dans cette section est implémentée dans Apache <abbr>SIS</abbr>
par la méthode statique <code>Envelopes.transform(CoordinateOperation, Envelope)</code>.
Une méthode <code>Envelopes.transform(MathTransform, Envelope)</code> existe aussi comme alternative,
mais cette dernière ne devrait être utilisée que si on ne connaît pas l’objet <code>CoordinateOperation</code> utilisé.
La raison est que les objets de type <code>MathTransform</code> ne contiennent pas d’information sur le système de coordonnées sous-jasent,
ce qui empêche la méthode <code>Envelopes.transform(…)</code> de savoir comment gérer les points aux pôles.
</p>
<h4 id="DerivativeAndRaster">Utilité des dérivées pour la reprojection d’images</h4>
<p>
La projection cartographique d’une image s’effectue en préparant une image initialement vide qui contiendra le résultat de l’opération,
puis à remplir cette image en itérant sur tous les pixels. Pour chaque pixel de l’image <em>destination</em>, on obtient la coordonnées
du pixel correspondant dans l’image source en utilisant <em>l’inverse</em> de la projection cartographique que l’on souhaite appliquer.
La position obtenue ne sera pas nécessairement au centre du pixel de l’image source, ce qui implique qu’une interpolation de la valeur
(ou de la couleur dans l’image ci-dessous) peut être nécessaire.
</p>
<div style="display:flex; flex-direction:column; align-items:center">
<div style="display:flex; justify-content:space-between; width:564px">
<div class="caption">Image source</div>
<div class="caption">Image destination</div>
</div>
<img src="../../../content/book/images/RasterProjection.png" alt="Reprojection d’images"/>
</div>
<p>
Toutefois, calculer la projection inverse pour chacun des pixels peut être relativement lent.
Afin d’accélérer les calculs, on utilise parfois une <cite>grille d’interpolation</cite>
dans laquelle on a pré-calculé les <em>coordonnées</em> de la projection inverse de seulement quelques points.
Les coordonnées des autres points se calculent alors par des interpolations bilinéaires entre les points pré-calculés,
calculs qui pourraient éventuellement tirer parti d’accélérations matérielles sous forme de <cite>transformations affines</cite>.
Cette approche est implémentée par exemple dans la bibliothèque <cite>Java Advanced Imaging</cite> avec l’objet <code>WarpGrid</code>.
Elle offre en outre l’avantage de permettre de réutiliser la grille autant de fois que l’on veut si on a plusieurs images de même
taille à projeter aux mêmes coordonnées géographiques.
</p><p>
Mais une difficulté de cette approche est de déterminer combien de points il faut pré-calculer pour que l’erreur
(la différence entre une position interpolée et la position réelle) ne dépasse pas un certain seuil (par exemple ¼ de pixel).
On peut procéder en commençant par une grille de taille 3×3, puis en augmentant le nombre de points de manière itérative:
</p>
<div class="row-of-boxes">
<div>
<img src="../../../content/book/images/WarpGrid.png" alt="Warp grid"/>
</div><div>
Légende:
<ul>
<li><b>Points bleus:</b> première itération (9 points).</li>
<li><b>Points verts:</b> seconde itération (25 points, dont 16 nouveaux).</li>
<li><b>Points rouges:</b> troisième itération (81 points, dont 56 nouveaux).</li>
</ul>
Si l’on continue…
<ul>
<li>Quatrième itération: 289 points, dont 208 nouveaux.</li>
<li>Cinquième itération: 1089 points, dont 800 nouveaux.</li>
<li>Sixième itération: 4225 points, dont 3136 nouveaux.</li>
<li></li>
</ul>
</div>
</div>
<p>
L’itération s’arrête lorsque, après avoir calculé de nouveaux points, on a vérifié que la différence entre les
coordonnées projetées et les coordonnées interpolées de ces nouveaux points est inférieure au seuil qu’on s’est fixé.
Malheureusement cette approche nous permet seulement de déterminer <strong>après</strong> avoir calculé de nouveaux points…
que ce n’était pas la peine de les calculer. C’est un peu dommage vu que le nombre de nouveaux points requis par chaque itération
est environ 3 fois la <em>somme</em> du nombre de nouveaux points de <em>toutes</em> les itérations précédentes.
</p><p>
Les dérivées des projections cartographiques nous permettent d’améliorer cette situation en estimant
si c’est la peine d’effectuer une nouvelle itération <strong>avant</strong> de la faire.
L’idée de base est de vérifier si les dérivées de deux points voisins sont presque pareilles,
auquel cas on présumera que la transformation entre ces deux points est pratiquement linéaire.
Pour quantifier « presque pareil », on procède en calculant l’intersection entre les tangentes aux deux points
(une information fournie par les dérivées), et en calculant la distance entre cette intersection et la droite
qui relie les deux points (la ligne pointillée dans la figure ci-dessous).
</p>
<p style="text-align:center"><img style="border: solid 1px" src="../../../content/book/images/IntersectionOfDerivatives.png" alt="Intersection of derivatives"/></p>
<p>
Dans l’approche sans dérivées, l’itération s’arrête lorsque la distance entre la ligne pointillée (positions interpolées)
et la ligne rouge (positions projetées) est inférieure au seuil de tolérance, ce qui implique de calculer la position projetée.
Dans l’approche avec dérivées, on remplace la position projetée par l’intersection des deux tangentes (carré bleu foncé).
Si la courbe n’est pas trop tordue – ce qui ne devrait pas être le cas entre deux points suffisamment proches –
la courbe réelle passera à quelque part entre la droite pointillée et l’intersection.
On s’évite ainsi des projections cartographiques, en apparence une seule dans cette illustration,
mais en fait beaucoup plus dans une grille de transformation d’image (3× la somme des itérations précédentes).
</p>
<h4 id="GetDerivative">Obtention de la dérivée en un point</h4>
<p>
Cette discussion n’aurait pas un grand intérêt si le coût du calcul des dérivées des projections cartographiques
était élevé par rapport aux coût de la projection des points. Mais lorsque l’on dérive analytiquement les équations
des projections, on constate que les calculs des positions et de leurs dérivées ont souvent plusieurs termes en commun.
En outre le calcul des dérivées est simplifié lorsque le code Java effectuant les projections ne se concentre que sur le « noyau » non-linéaire,
après s’être déchargé des parties linéaires en les déléguant aux transformations affines comme le fait <abbr>SIS</abbr>.
Les implémentations des projections cartographiques dans Apache <abbr>SIS</abbr> tirent parti de ces propriétés
en ne calculant les dérivées que si elles sont demandées,
et en offrant une méthode qui permet de projeter un point et obtenir sa dérivée en une seule opération
afin de permettre à <abbr>SIS</abbr> de réutiliser un maximum de termes communs.
Exemple:</p>
<pre><code>AbstractMathTransform projection = ...; // Une projection cartographique de Apache SIS.
double[] sourcePoint = {longitude, latitude}; // La coordonnée géographique que l’on veut projeter.
double[] targetPoint = new double[2]; // Là où on mémorisera le résultat de la projection.
Matrix derivative = projection.<code class="SIS">transform</code>(sourcePoint, 0, targetPoint, 0, true);</code></pre>
<p>
Si seule la matrice Jacobienne est désirée (sans la projection du point), alors la méthode
<code>MathTransform.derivative(DirectPosition)</code> offre une alternative plus lisible.
</p><p>
Apache <abbr>SIS</abbr> est capable combiner les dérivées des projections cartographiques de la même façon que pour les projections de coordonnées:
concaténation d’une chaîne de transformations, inversion, opérer sur un sous-ensemble des dimensions, <i>etc.</i>
Les opérations inverses (des systèmes projetés vers géographiques)
sont souvent beaucoup plus compliquées à implémenter que les opérations originales (des systèmes géographiques vers projetés),
mais par chance la matrice Jacobienne d’une fonction inverse est simplement l’inverse de la matrice Jacobienne de la fonction originale.
Une fonction inverse peut donc implémenter le calcul de sa dérivée comme suit:
</p>
<pre><code>@Override
public Matrix derivative(DirectPosition p) throws TransformException {
Matrix jac = inverse().derivative(transform(p));
return Matrices.inverse(jac);
}</code></pre>
</section>
</body>
</html>