Introduction à Apache SIS®

(English | Français)

Martin Desruisseaux
Christina Hough (pour la traduction anglaise)

Ce document est distribué sous licence Apache 2.


Table des matières

1. Standards et normes

Une communauté d’informations géospatiales est un ensemble de systèmes ou d’individus capables d’échanger leurs données géospatiales grâce à des définitions et des standards communs ainsi qu’une reconnaissance réciproque. Comme il existe une multitude de façons de représenter des informations géospatiales, chaque communauté est amenée à les structurer en fonction de ses centres d’intérêts. Cette diversité complique la tâche des utilisateurs de systèmes d’information géographiques (SIG) en les plaçant devant une variété apparemment chaotique de formats et de structures de données. Les caractéristiques de ces structures varient en fonction des phénomènes observés et des méthodes de mesure, ainsi que des habitudes des organisations produisant les données. Une telle variété agit comme un frein aux études qui requièrent des combinaisons de données hétérogènes, surtout lorsqu’elles proviennent de communautés traditionnellement distinctes. Par exemple, un chercheur étudiant le choléra peut s’intéresser aux populations de crevettes comme vecteur de propagation de la maladie. Mais les médecins et les océanographes n’ayant pas forcement l’habitude de partager leurs travaux, les participants à une telle étude peuvent être limités par les efforts qu’ils sont disposés à fournir pour convertir les données.

Nous ne pouvons pas imposer un format uniforme à l’ensemble des données, car la diversité des formats tient à des facteurs tels que les contraintes des appareils de mesure et la distribution statistique des valeurs. Une solution plus flexible consiste à assurer l’interopérabilité des données à travers une interface de programmation (API) commune. Cette API n’est pas forcement définie dans un langage de programmation; la tendance actuelle est plutôt de définir des conventions utilisant les protocoles web existants, que l’on peut transposer dans des langages de programmation. Mais pour que cette démarche puisse être pérennisée, l’API doit être largement accepté par des développeurs indépendants. Autrement dit, l’API doit s’approcher autant que possible des standards industriels.

Les accès aux bases de données relationnelles sont un exemple de tâche ayant bénéficié d’une standardisation relativement bien réussie. L’industrie a établie un langage commun — le standard SQL — que les concepteurs du Java ont enrobé dans des interfaces de programmation formant le standard JDBC. Ces interfaces sont aujourd’hui implementées par de nombreux logiciels libres et commerciaux. Comme pour les bases de données, des méthodes d’accès aux informations géographiques ont été standardisées. Mais les efforts en ce sens sont plus récents et leurs intégrations dans les logiciels, surtout les plus anciens, sont incomplètes et pas toujours cohérentes. Au moment d’écrire ces lignes, aucun produit de notre connaissance n’implémente la totalité des spécifications. Mais on trouve de nombreuses implémentations couvrant un spectre plus ou moins large. La bibliothèque Apache SIS® décrite dans ce document en est une.

Apache SIS se caractérise par un effort soutenu de respect des standards. De manière générale, le respect des standards exige un effort plus grand que ce qu’aurait requis un développement isolé, mais se rentabilise par un double avantage: en plus d’accroître l’interopérabilité des données avec celles des projets externes, il nous indique aussi une voie robuste à suivre pour l’élaboration du modèle conceptuel qui sera reflété par l’API. En effet, les groupes d’experts qui conçoivent les standards anticipent des difficultés qui échappent parfois à l’ingénieur en début de projet, mais qui risquent de le rattraper avant la fin.

1.1. Sources des modèles conceptuels de Apache SIS

La majorité des standards utilisés par Apache SIS ont été élaborés par le consortium Open Geospatial (OGC), parfois en collaboration avec l’organisation internationale de normalisation (ISO). Certains standards de l’ISO deviennent eux-mêmes des standards Européens via la directive INSPIRE, ou des standards français via l’AFNOR. Ces standards offrent deux technologies clés:

Ces standards sont fournis gratuitement à la communauté internationale sous la forme de spécifications (fichiers PDF) ou de schémas (fichiers XSD). Les organismes de normalisation ne fabriquent pas de logiciel; pour obtenir une implémentation de ces spécifications, les utilisateurs doivent choisir un des produits conformes disponibles sur le marché ou développer leur propres solutions. C’est le respect volontaire de ces spécifications qui permet à des communautés à priori indépendantes d’échanger plus facilement des informations géographiques.

Pour en savoir plus sur le processus de standardisation

Processus de standardisation à l’OGC

Les travaux de l’OGC se font par courriers électroniques, par conférences téléphoniques et par réunions réelles. L’OGC organise quatre réunions par années, chacune d’une durée de cinq jours, hébergées par des organisations membres sponsorisant l’événement (compagnies, universités, centres de recherches, etc.). Le continent hôte alterne entre l’Europe et l’Amérique du Nord, avec une présence croissante en Asie depuis 2011. Ces réunions reçoivent habituellement entre 50 et 100 participants parmi les centaines de membres de l’OGC. Certains participants sont présents à quasiment toutes les réunions et constituent des piliers de l’organisation.

Les réunions de l’OGC offrent des opportunités d’échanges avec des membres d’horizons diverses. La création d’un standard OGC commence par le regroupement d’organisations ou d’individus constatant un intérêt commun pour une problématique. Un groupe de travail est proposé sous l’appellation de Domain Working Group (DWG) ou Standard Working Group (SWG). Les DWG sont ouverts à tout membre de l’OGC, tandis que les SWG nécessitent de la part des participants un engagement à ne pas entraver la diffusion du standard par des réclamations de propriétés intellectuelles.

Fonctionnement des groupes de travail (SWG)

Pour être accepté, un projet de standardisation doit être supporté par un nombre minimal de membres appartement à des organisations distinctes. Ces membres fondateurs rédigent une charte définissant les objectifs du SWG, qui doit être approuvée par le comité technique de l’OGC. Chaque membre fondateur est doté d’un droit de vote, dans les limites d’un membre votant par organisation. Tout nouveau membre qui souhaite joindre le SWG après sa création se verra attribué un rôle d’observateur, avec attribution sur demande d’un droit de vote après quelques mois d’observation.

Un SWG peut contenir plusieurs dizaines de membres, mais les volontaires effectuant l’essentiel du travail sont habituellement moins nombreux. Leurs propositions sont soumises à l’ensemble des membres du groupe, qui peuvent les accepter par consentement unanime. Les objections, s’il y en a, doivent être argumentées et une alternative proposée. Les SWG essaient généralement de débattre d’un problème jusqu’à ce qu’un consensus se forme plutôt que d’avancer malgré des votes négatifs, même s’ils sont minoritaires. Les décisions du groupes sont alors intégrées dans la spécification par un membre assumant le rôle d’éditeur.

Le groupe de travail doit autant que possible structurer la spécification sous forme d’un noyau autour duquel gravite diverses extensions. Une suite de tests doit accompagner le standard, et permettre de classer les implémentations en fonction du niveau des tests passés. Au moins une implémentation de référence passant les tests doit exister pour démontrer que le standard est utilisable.

Lorsque le standard est jugé prêt, le SWG vote une motion proposant de le soumettre au vote des instances supérieures de l’OGC. Cette procédure nécessite plusieurs mois. Il existe une procédure plus rapide pour entériner des standards de fait, mais elle n’est appliquée qu’avec parcimonie.

Le conseil d’architecture (OAB) et le comité technique (TC)

Toute proposition de standard est d’abord examinée par le conseil d’architecture (OGC Architecture BoardOAB). Ce conseil vérifie que le standard répond aux exigences de l’OGC sur la forme, sur la modularisation, et en termes d’intégration avec les autres standards. Si l’OAB donne son aval, le standard est alors soumis au vote des membres du comité technique (TC). Ce comité regroupe les principaux membres de l’OGC qui sont seuls habilités à donner le vote final. En cas d’approbation, le standard est diffusé publiquement pour commentaires pendant une période de quelques mois. Au terme de cette période, le SWG doit examiner et répondre à chacun des commentaires. Les éventuelles modifications au standard sous soumises à l’OAB, puis le standard est définitivement publié. Cette diffusion est alors annoncée par un communiqué de presse de l’OGC.

Certains membres de l’OGC et du TC assurent aussi la liaison avec l’organisation internationale de normalisation (ISO). La coopération entre les deux organismes va dans les deux sens: l’OGC adopte les standards ISO comme base sur laquelle développer de nouveaux standards, et certains de ces nouveaux standards OGC deviennent des standards ISO.

Procédure de soumission de propositions de modifications

Tout utilisateur, qu’il soit membre ou non du consortium Open Geospatial, peut proposer des modifications à des standards OGC. Une liste des propositions actuelles de changements, ainsi qu’un formulaire permettant d’en soumettre de nouvelles, sont disponibles en ligne. Chaque proposition est revue par le SWG.

Certains groupes de travail utilisent d’autres systèmes de soumission en parallèle, par exemple GitHub, hébergés en dehors des structures de l’OGC.

Outre ces organisations formelles de normalisation, il existe aussi des organisations qui ne sont pas officiellement dédiées à l’élaboration de normes mais dont les travaux ont été largement adoptés comme standards de fait. En particulier, la base de données EPSG fournit des codes numériques permettant d’identifier facilement un système de référence des coordonnées parmi plusieurs milliers. Cette base de données est offerte par des compagnies pétrolières qui ont vu leur intérêt à ce que leurs prospections se fassent bien à l’endroit voulu, sachant qu’elles ne contrôlent pas toujours la production des cartes sur lesquelles elles se positionnent. D’autres exemples de standards de fait sont les formats GeoTIFF pour les données réparties sur une grille (les images), et Shapefile pour les données vectorielles (les géométries).

Les standards OGC sont spécifiés dans plusieurs dizaines de documents. Chaque document élabore un service, par exemple les transformations de coordonnées. Le fonctionnement de chaque service est décrit par un ensemble de classes d’objets et leurs interactions. Ces éléments sont illustrés par des diagrammes UML (Unified Modeling Language) dans des spécifications dites « abstraites ». Les spécifications abstraites ne font référence à aucun langage informatique concret. Leurs concepts peuvent se concrétiser dans un langage de programmation, une base de données ou un schéma XML de manière plus ou moins directe. Il existe toutefois une part d’arbitraire dans la façon de concrétiser une spécification abstraite, étant donné que des ajustements sont souvent nécessaires pour tenir compte des contraintes ou des conventions du langage ciblé. Certaines structures de données n’existent que dans quelques langages, par exemple les unions qui existent en C/C++ mais pas en Java.

Pour en savoir plus sur les « spécifications d’implémentation »

Note historique

Au tournant du millénaire, les spécifications abstraites étaient explicitement concrétisées dans des spécifications d’implémentations. Le terme « implémentation » était ici à prendre au sens de tout type d’interfaces (Java ou autres) dérivées des diagrammes UML — et non pas d’implémentations au sens du Java. Des telles spécifications existaient pour les langages SQL, CORBA, COM et Java. Ces langages étant capables d’exécuter des procédures, les spécifications de cette époque définissaient non seulement des structures de données, mais aussi des opérations s’appliquant sur ces structures.

Par la suite, l’engouement pour le « web 2.0 » a fait grimper l’intérêt pour le XML au détriment des autres langages. Les anciennes spécifications d’implémentations ont été dépréciées, et les schémas XSD sont devenus la principale concrétisation des spécifications abstraites. Même la façon de concevoir les spécifications abstraites a évoluée: les opérations y sont plus rarement définies, par conséquence ce qui reste ressemble davantage à des descriptions de schémas de base de données. Certaines opérations qui étaient définies dans les anciennes normes apparaissent maintenant, sous une autre forme, dans les spécifications des services web. Enfin le terme « spécification d’implémentation » a été abandonné, pour être englobé dans « standard OGC ». Mais malgré leur dépréciation, les anciennes spécifications d’implémentation restent utiles aux programmes en langage Java car:

  • Leurs modèles plus simples, appliqués aux mêmes concepts, aident à comprendre les nouvelles spécifications.
  • Ils définissent parfois des façons simples d’effectuer des tâches courantes là où les nouvelles spécifications se limitent au cas général.
  • Les opérations étant plus souvent omises dans les nouvelles spécifications, les anciennes spécifications restent un complément utile pour définir des API.

Le projet Apache SIS se base sur les spécifications les plus récentes, tout en puisant dans les archives de l’OGC pour compléter certains standards abstraits ou les rendre un peu plus facile d’utilisation. Certaines anciennes définitions sont conservées comme « méthodes de commodités », n’apportant pas toujours de nouvelles fonctionnalités mais facilitant l’usage pratique d’une bibliothèque.

Le tableau suivant liste les principales normes utilisées par le projet. Plusieurs normes sont publiées à la fois comme standard ISO et comme standard OGC, d’où la disposition côte-à-côte des deux premières colonnes. La section des « spécifications d’implémentation » liste des spécifications qui apportent peu de concepts nouveaux comparativement aux spécifications abstraites, mais précisent comment les représenter dans des contextes précis tels qu’un document XML. Les normes dépréciées mais malgré tout partiellement utilisées apparaissent barrées. Enfin, les paquets GeoAPI seront introduits dans le chapitre suivant.

Principaux standards en relation avec le projet Apache SIS
Norme ISO Norme OGC Titre Paquet de GeoAPI Paquet de Apache SIS
Spécifications abstraites
ISO 19103 Conceptual schema language org.opengis.util org.apache.sis.util.iso
ISO 19115-1 Topic 11 Metadata org.opengis.metadata org.apache.sis.metadata.iso
ISO 19115-2 Metadata — extensions for imagery and gridded data org.opengis.metadata org.apache.sis.metadata.iso
ISO 19111 Topic 2 Spatial referencing by coordinates org.opengis.referencing org.apache.sis.referencing
ISO 19111-2 Referencing — extension for parametric values org.opengis.referencing org.apache.sis.referencing
ISO 19108 Temporal Schema org.opengis.temporal
ISO 19107 Topic 1 Feature geometry org.opengis.geometry org.apache.sis.geometry
ISO 19101 Topic 5 Features org.opengis.feature org.apache.sis.feature
ISO 19123 Topic 6 Schema for coverage geometry and functions org.opengis.coverage org.apache.sis.coverage
ISO 19156 Topic 20 Observations and measurements org.opengis.observation
Spécifications d’implémentation
ISO 19139 Metadata XML schema implementation org.apache.sis.xml
ISO 19136 OGC 07-036 Geography Markup Language (GML) Encoding Standard org.apache.sis.xml
ISO 19162 OGC 12-063 Well-known text representation of coordinate reference systems org.apache.sis.io.wkt
ISO 13249 SQL spatial
OGC 01-009 Coordinate Transformation Services org.opengis.referencing org.apache.sis.referencing
OGC 01-004 Grid Coverage org.opengis.coverage org.apache.sis.coverage
SLD Styled Layer Descriptor org.opengis.style
Services web
ISO 19128 WMS Web Map Service
WMTS Web Map Tile Service
ISO 19142 WFS Web Feature Service
WCS Web Coverage Service
WPS Web Processing Service
OpenLS Location Services
SWE Sensor Web Enablement
SOS Sensor Observation Service

1.2. Des modèles conceptuels vers des interfaces Java: GeoAPI

Le projet GeoAPI offre un ensemble d’interfaces Java pour les applications géo-spatiales. Dans une séries de paquets org.opengis.*, GeoAPI définit des structures représentant des méta-données, des systèmes de référence des coordonnées, ainsi que des opérations effectuant des projections cartographiques. Dans une partie qui n’est pas encore standardisée — dénommée pending — GeoAPI définit des structures représentant des images géo-référencées, des géométries, des filtres pouvant s’appliquer à des requêtes, et d’autres fonctionnalités. Ces interfaces suivent de très près les spécifications de l’OGC, tout en les interprétant et en les adaptant de manière à répondre aux attentes des développeurs Java — par exemple en se conformant aux conventions de nommage. Ces interfaces bénéficient à la fois aux applications clientes et aux bibliothèques:

Pour en savoir plus sur les origines du projet GeoAPI

Historique du projet GeoAPI

En 2001, le consortium Open GIS (l’ancien nom du consortium Open Geospatial) publia la spécification d’implémentation OGC 01-009: Coordinate Transformation Services. Cette spécification, élaborée par Computer Aided Development Corporation (Cadcorp), était accompagnée d’interfaces COM, CORBA et Java. À cette époque, la vague des services web n’avait pas encore éclipsé les interfaces de programmation classiques. Les interfaces de l’OGC anticipaient tout de même un monde connecté en réseau, mais misaient plutôt — dans le cas du Java — sur la technologie RMI (Remote Method Invocation). Bien que le projet GeoAPI n’existait pas encore, nous désignons rétrospectivement ces interfaces historiques sous le nom de « GeoAPI 0.1 ». Ces interfaces utilisaient déjà le nom de paquet org.opengis, qui sera adopté par GeoAPI.

En 2002, des développeurs de projets libres ont lancé un appel à la création d’un API géo-spatial. La proposition initiale suscita l’intérêt d’au moins cinq projets libres. Le projet fut créé sur SourceForge, qui héberge depuis lors le code source dans un dépôt Subversion. Le projet pris le nom de « GeoAPI » à ce moment là, et utilisa les interfaces de la spécification OGC 01-009 comme point de départ.

Quelques mois plus tard, l’OGC lança le projet GO-1: Geographic Objects, qui poursuivait des buts similaires à ceux de GeoAPI. Entre-temps, l’OGC avait abandonné certaines de leur spécifications en faveur des normes ISO. GeoAPI et GO-1 ont joint leurs efforts pour une refonte des interfaces de GeoAPI en les basant sur ces nouvelles normes ISO. La première mouture, GeoAPI 1.0, a servit de point de départ aux premières ébauches de la spécification OGC 03-064 du groupe de travail GO-1. La version finale de cette spécification est devenue un standard OGC en 2005, et GeoAPI 2.0 a été publiée à cette occasion.

Le projet GO-1 était porté essentiellement par une compagnie nommée Polexis. Son rachat par Sys Technology et le changement de priorité des nouveaux propriétaires ont causé l’arrêt des travaux de GO-1, et par ricochet un ralentissement des développements de GeoAPI. Afin de reprendre les développements, un nouveau groupe de travail « GeoAPI 3.0 » a été créé à l’OGC. Ce groupe a réduit les ambitions par rapport à GeoAPI 2.0 en se concentrant sur les interfaces les plus stables, et en plaçant les autres — notamment les géométries — dans un module nommé « pending », pour considérations futures. GeoAPI 3.0 est devenu un standard OGC en 2011. Cette version a été la première à être déployée dans le dépôt central de Maven.

Les interfaces Java du projet GeoAPI sont parfois générées à partir d’autres fichiers fournis par l’OGC, tels que les fichiers XSD. Mais il y a toujours une révision manuelle, et très souvent des modifications par rapport aux fichiers générés par des processus automatiques. Les écarts par rapport aux normes sont documentés dans chaque classe et chaque méthode concernées. Chaque mention d’un écart est aussi recopiée dans une page unique, pour donner une vue d’ensemble. Étant donné que ces écarts brouillent les liens qui existent entre les standards et certaines interfaces Java, la correspondance entre ces langages est explicitée par des annotations @UML et des fichiers de propriétés, décrits dans la section suivante.

Pour en savoir plus sur les raisons d’une définition manuelle des interfaces Java

Des spécifications de l’OGC aux interfaces Java

Il est possible de générer automatiquement des interfaces Java à partir des standards de l’OGC à l’aide d’outils existants. Une des approches les plus utilisées est de transformer les schémas XSD en interfaces Java à l’aide de l’utilitaire en ligne de commande xjc. Cet utilitaire étant fournit avec la plupart des distributions du Java (il fait partie des outils de JAXB), cette approche est choisie par plusieurs projets que l’on trouve sur internet. D’autres approches utilisent des outils intégrés à l’environnement de développement Eclipse, ou prennent comme point de départ les schémas UML plutôt que XSD.

Une approche similaire avait été tentée dans les débuts du projet GeoAPI, mais a été rapidement abandonnée. Nous avons privilégié une approche manuelle pour les raisons suivantes:

  • Certains schémas XSD sont beaucoup plus verbeux que les schémas UML d’origines. Une conversion à partir des schémas XSD introduit, au moins dans le cas des méta-données, près du double du nombre d’interfaces réellement définies par le standard, sans que cela n’apporte de nouvelles fonctionnalités. Les schémas XSD définissent aussi des attributs propres aux documents XML (id, uuid, xlink:href, etc.), qui n’existent pas dans les diagrammes UML originaux et que l’on ne souhaite pas forcément exposer dans un API Java. Une conversion à partir des schémas UML évite ce problème, mais les outils capable d’effectuer cette opération sont plus rares.

    Exemple: Les schémas XSD des méta-données insèrent un élément <gmd:CI_Citation> à l’intérieur de <gmd:citation>, un élément <gmd:CI_OnlineResource> à l’intérieur de <gmd:onlineResource>, et ainsi de suite pour la centaine de classes définies dans le standard ISO 19115. Cette redondance n’est absolument pas nécessaire à un programme Java.

  • Les standards de l’OGC utilisent des conventions de nommage qui sont différentes de celles du Java. En particulier les noms de presque toutes les classes de l’OGC commencent par un préfixe de deux lettres, comme dans MD_Identifier. Ces préfixes jouent le même rôle que les noms de paquets en Java. GeoAPI adapte cette pratique en utilisant des noms d’interfaces sans préfixes, et en plaçant ces interfaces dans des paquets correspondants aux préfixes mais avec des noms plus descriptifs. Occasionnellement nous changeons aussi les noms, par exemple pour éviter des acronymes ou pour se conformer à une convention établie telle que Java beans.

    Exemple: la classe MD_Identifier de l’OGC devient l’interface Identifier dans le paquet org.opengis.metadata. La classe SC_CRS de l’OGC devient l’interface CoordinateReferenceSystem, et l’association usesDatum devient une méthode getDatum() — et non pas « getUsesDatum() » comme aurait fait un outil de conversion automatique. Nous ne laissons pas des programmes appliquer aveuglement des règles qui ignorent les conventions de la communauté dont on traduit les schémas.

  • Les standards contiennent parfois des structures qui n’ont pas d’équivalent direct en Java, notamment les unions telles qu’on peut trouver en C/C++. La stratégie employée pour obtenir une fonctionnalité équivalente en Java dépend du contexte: multi-héritage des interfaces, modification de la hiérarchie ou simplement omettre l’union. Les décisions se font au cas-par-cas en fonction de l’analyse des besoins.

    Exemple: Le standard ISO 19111 définit différents types de systèmes de coordonnées, tels que sphérique, cylindrique, polaire ou cartésien. Puis, il définit différents sous-ensembles de ces types de systèmes de coordonnées. Ces sous-ensembles, représentés par des unions, servent à spécifier qu’une classe peut être associée à seulement tel ou tel type de système de coordonnées. Par exemple l’union des types pouvant être associés à une image, nommée CS_ImageCS, ne contient que CS_CartesianCS et CS_AffineCS. Dans ce cas particulier, nous obtenons en Java l’effet souhaité par une modification de la hiérarchie des classes: nous définissons l’interface CartesianCS comme une spécialisation de AffineCS, ce qui est sémantiquement correct. Mais il n’est pas possible d’appliquer une stratégie similaire pour les autres unions sans violer la sémantique.

  • Plusieurs spécifications se chevauchent. GeoAPI effectue un travail d’intégration en remplaçant certaines structures qui font doublons par des références vers les structures équivalentes du standard qui les définies le mieux.

    Exemple: Le standard ISO 19115, qui définit des structures de méta-données, s’aventure aussi à décrire quelques structures représentant les systèmes de références des coordonnées (CRS). Or ces derniers sont le sujet à part entière d’un autre standard: ISO 19111. D’ailleurs le standard ISO 19111:2007 stipule, dans sa section 3, qu’il réutilise tous les éléments de ISO 19115 à l’exclusion de MD_CRS et de ses composantes. Les interfaces de GeoAPI réduisent la redondance en appliquant à l’ensemble du projet l’exclusion recommandée par ISO 19111.

  • Certains standards ont vu leur complexité s’accroître pour des raisons historiques plutôt que techniques, liées au processus de standardisation. GeoAPI réduit la dette technique en concevant les interfaces comme si chaque élément avait pu être intégré à sa place, sans les contraintes liées à l’ordre chronologique dans lequel les standards ont été publiés.

    Exemple: Le standard ISO 19115-2 est une extension du standard ISO 19115-1 ajoutant des structures de méta-données d’images. Ces méta-données ont été définies dans un standard séparé parce qu’elles n’ont pas été prêtes à temps pour la publication de la première partie du standard. Comme il n’était pas possible, pour des raisons administratives, d’ajouter des attributs dans les classes déjà publiées, les nouveaux attributs ont été ajoutées dans une sous-classe portant quasiment le même nom. Ainsi, le standard ISO 19115-2 définit une classe MI_Band qui étend la classe MD_Band du standard ISO 19115-1 en y ajoutant les attributs qui auraient dû apparaître directement dans la classe parente s’ils avaient été prêts à temps. Dans GeoAPI, nous avons choisis de « réparer » ces anomalies en fusionnant ces deux classes en une seule interface.

GeoAPI est constitué de plusieurs modules. Les modules geoapi et geoapi-pending fournissent les interfaces dérivées des schémas UML des standards internationaux. Le modèle conceptuel sera expliqué en détails dans les chapitres décrivant l’implémentation Apache SIS. On peut toutefois avoir un aperçu de son contenu en consultant la page listant les types et méthodes de GeoAPI et les standards d’où ils proviennent.

Pour en savoir plus sur les modules de GeoAPI

Les modules de GeoAPI

Le projet GeoAPI est composé d’une partie standardisée (geoapi) et d’une partie expérimentale (geoapi-pending). Ces deux parties étant mutuellement exclusives, les utilisateurs doivent veiller à ne pas les mélanger dans un même projet. Cette séparation est garantie pour tous les projets qui ne dépendent que du dépôt central de Maven (incluant les versions finales de Apache SIS), car le module geoapi-pending n’est jamais déployé sur ce dépôt central. En revanche certaines branches de développement de SIS peuvent dépendre de geoapi-pending.

Les modules de GeoAPI sont:

  • geoapi — contient les interfaces couvertes par le standard GeoAPI de l’OGC. Les versions finales de Apache SIS dépendent de ce module.

  • geoapi-pending — contient une copie de toutes les interfaces du module geoapi (non pas une dépendance) avec des ajouts qui n’ont pas encore été approuvés comme un standard OGC. Certains ajouts apparaissent dans des interfaces normalement définies par le module geoapi, d’où la nécessité de les copier. Les branches de développement jdk6, jdk7 et jdk8 de Apache SIS dépendent de ce module, mais cette dépendance est transformée en une dépendance vers le module geoapi standard au moment de fusionner les branches avec le tronc.

  • geoapi-conformance — contient une suite de tests JUnit que les développeurs peuvent utiliser pour tester leurs implémentations.

  • geoapi-examples — contient des exemples d’implémentations relativement simples. Ces exemples sont placés dans le domaine public afin d’encourager les utilisateurs à les copier et les adapter à leurs besoins si les services de Apache SIS ne conviennent pas.

  • geoapi-proj4 — contient une implémentation partielle des paquets org.opengis.referencing sous forme d’adaptateurs basés sur la bibliothèque C/C++ Proj.4. Ce module peut être utilisé comme alternative au module sis-referencing pour certaines fonctions.

  • geoapi-netcdf — contient une implémentation partielle des paquets org.opengis.referencing et org.opengis.coverage sous forme d’adaptateurs basés sur la bibliothèque NetCDF de l’UCAR. La suite de tests de ce module a été conçue de manière à être réutilisable par d’autres projets. Apache SIS l’utilise pour tester son propre module sis-netcdf.

  • geoapi-openoffice — contient un add-in pour les suites bureautiques Libre/OpenOffice.org.

1.2.1. Correspondances explicites entre GeoAPI et les spécifications abstraites

Pour chaque classe, méthode et constante définie à partir d’un standard OGC ou ISO, GeoAPI indique sa provenance à l’aide d’annotations définies dans le paquet org.opengis.annotation. En particulier l’annotation @UML indique le standard, le nom de l’élément dans ce standard ainsi que son niveau d’obligation. Par exemple dans l’extrait de code suivant, la première annotation @UML indique que l’interface Java qui la suit (ProjectedCRS) est définie à partir du type SC_ProjectedCRS du standard ISO 19111. La seconde annotation @UML, appliquée cette fois à la méthode getCoordinateSystem(), indique que la méthode est définie à partir de l’association coordinateSystem du standard ISO 19111, et que cette association est obligatoire — ce qui, traduit en Java, signifie que la méthode n’est pas autorisée à retourner la valeur null.

package org.opengis.referencing.crs;

/**
 * A 2D coordinate reference system used to approximate the shape of the earth on a planar surface.
 */
@UML(specification=ISO_19111, identifier="SC_ProjectedCRS")
public interface ProjectedCRS extends GeneralDerivedCRS {
    /**
     * Returns the coordinate system, which must be Cartesian.
     */
    @UML(obligation=MANDATORY, specification=ISO_19111, identifier="coordinateSystem")
    CartesianCS getCoordinateSystem();
}

Les méthodes d’introspections du Java permettent d’accéder à ces informations pendant l’exécution d’une application. C’est utile pour obtenir les noms à afficher à des utilisateurs familiers avec les normes de l’OGC, ou pour écrire des éléments dans un document XML. La classe org.apache.sis.util.iso.Types fournit des méthodes de commodité telles que getStandardName(Class) pour effectuer cette opération. Par exemple le code suivant affichera « Le nom standard du type org.opengis.referencing.crs.ProjectedCRS est SC_ProjectedCRS »:

Class<?> type = ProjectedCRS.class;
System.out.println("Le nom standard du type " + type.getName() + " est " + Types.getStandardName(type));

La méthode de commodité Types​.forStandardName(String) effectue l’opération inverse. Les applications qui souhaiteraient effectuer ces opérations sans utiliser les méthodes de commodités de Apache SIS trouveront des indications dans un chapitre séparé.

1.2.2. Correspondances implicites avec le JDK standard

Certaines classes et méthodes n’ont ni annotation @UML, ni entrée dans le fichier class-index.properties. Il s’agit soit d’extensions de GeoAPI, ou soit de types définis dans d’autres bibliothèques, notamment le JDK standard. Pour ce dernier cas, la correspondance avec les standards ISO est implicite. Le tableau suivant décrit cette correspondance pour les types de la norme ISO 19103. Les types primitifs du Java standard sont préférés lorsqu’ils sont applicables, mais parfois leurs équivalents sous forme d’objets sont employés lorsqu’il est nécessaire d’autoriser des valeurs nulles.

Correspondances entre ISO 19103 et JDK
Type ISO Type JDK Remarques
Nombres
Integer int Parfois java.lang.Integer pour les attributs optionnels.
Integer (certains cas) long Parfois java.lang.Long pour les attributs optionnels.
Real double Parfois java.lang.Double pour les attributs optionnels.
Decimal java.math.BigDecimal
Number java.lang.Number
Textes
FreeText (pas d’équivalent) Voir org.opengis.util.InternationalString ci-dessous.
CharacterString java.lang.String Souvent org.opengis.util.InternationalString (voir ci-dessous).
LocalisedCharacterString java.lang.String
Sequence<Character> java.lang.CharSequence
Character char
Dates et heures
Date java.util.Date
Time java.util.Date
DateTime java.util.Date
Collections
Collection java.util.Collection
Bag java.util.Collection Un Bag est similaire à un Set sans la restriction d’unicité.
Set java.util.Set
Sequence java.util.List
Dictionary java.util.Map
KeyValuePair java.util.Map.Entry
Énumérations
Enumeration java.lang.Enum
CodeList (pas d’équivalent) Voir org.opengis.util.CodeList ci-dessous.
Divers
Boolean boolean Parfois java.lang.Boolean pour les attributs optionnels.
Any java.lang.Object

L’équivalent le plus direct de CharacterString est la classe String, mais GeoAPI emploie souvent l’interface InternationalString pour permettre au client de choisir la langue. C’est utile par exemple sur un serveur fournissant simultanément des pages dans plusieurs langues. En reportant les traductions à l’utilisation des objets plutôt qu’au moment de leur création, on permet à la bibliothèque SIS de fournir les mêmes instances de Metadata ou Coverage (par exemple) pour les mêmes données peu importe la langue du client. Les traductions peuvent être faites à la volée à l’aide d’un ResourceBundle de l’application, ou être fournies directement avec les données (cas des Metadata notamment).

Les Enumeration correspondent aux Enum du Java. Ils ont en commun de définir toutes les valeurs autorisées, sans permettre à l’utilisateur d’en ajouter. Les CodeList sont similaires à ces énumérations, excepté que les utilisateurs peuvent y ajouter leurs propres éléments. Le JDK standard n’offrant pas cette possibilité, GeoAPI définit une classe abstraite CodeList reproduisant certaines fonctionnalités de Enum tout en étant extensible. Les extensions s’obtiennent par les méthodes statiques valueOf(String) qui, contrairement à celle de Enum, créeront de nouvelles instances si le nom donné ne correspond pas au nom d’une instance existante.

MediumName cdRom  = MediumName.CD_ROM;
MediumName usbKey = MediumName.valueOf("USB_KEY"); // Aucune constante n’existe pour cette valeur.
assert MediumName.valueOf("CD_ROM")  == cdRom  : "valueOf doit retourner les constantes existantes.";
assert MediumName.valueOf("USB_KEY") == usbKey : "valueOf doit cacher les valeurs précédemment demandées.";

1.2.3. Implémentations fournies par Apache SIS

Apache SIS implémente la plupart des interfaces de GeoAPI avec une classe du même nom que l’interface, mais préfixée de « Abstract », « Default » ou « General ». Les classes de Apache SIS qui sont préfixées par « Default » peuvent être instanciées directement par une instruction new DefaultXXX(…) ou par la méthode createXXX(…) correspondante d’une fabrique.

Example: pour représenter un système de référence de coordonnées projetées (Mercator, Lambert, etc):
  • org.opengis.referencing.crs.ProjectedCRS est l’interface définie par GeoAPI sur la base du standard ISO 19111, et
  • org.apache.sis.referencing.crs.DefaultProjectedCRS est l’implémentation fournie par Apache SIS.
Une instance peut être créée par:
  • ProjectedCRS crs = new DefaultProjectedCRS(…), ou
  • ProjectedCRS crs = CRSFactory​.createProjectedCRS(…).
Les deux approches attendent les mêmes arguments (omis dans cet exemple).

Dans la configuration par défaut de Apache SIS, utiliser CRSFactory​.createXXX(…) ou new DefaultXXX(…) revient presque au même excepté que les Factory peuvent retourner des instances existantes plutôt que de créer systématiquement de nouvelles instances, et que les exceptions en cas d’arguments invalides sont de types différents. Dans des configurations plus avancées, l’usage des Factory permet de réduire la dépendance directe d’une application envers SIS et de permettre une inversion de contrôle.

Le préfix « General » est parfois utilisé à la place de « Default » afin de signaler que des implémentations alternatives existent pour des cas spécifiques. Par exemple l’interface Envelope est implémentée par au moins deux classes de Apache SIS: GeneralEnvelope et Envelope2D. La première implémentation peut représenter des enveloppes de n’importe quelle dimension alors que la seconde implémentation est spécialisée pour les enveloppes à deux dimensions.

Les classes de Apache SIS qui sont préfixées par « Abstract » ne doivent pas – en principe – être instanciées. Il faut plutôt instancier une sous-classe non-abstraites. Toutefois plusieurs classes de SIS ne sont abstraites que conceptuellement, sans que la définition de la classe ne contienne le mot-clé abstract du Java. Ces classes peuvent être instanciées par l’instruction new AbstractXXX(…) – mais pas par les Factory – malgré qu’elles soient conceptuellement abstraites. Mais ces instanciations ne devraient être faites qu’en dernier recours, lorsqu’il n’est vraiment pas possible de déterminer le sous-type exact.

1.3. Conventions utilisées dans ce guide

Les standards privilégient parfois l’application de certains termes génériques à des contextes particuliers, qui peuvent différer du contexte dans lequel d’autres communautés emploient ces termes. Par exemple les termes domain et range peuvent s’appliquer à des fonctions arbitraires pour désigner l’ensemble des valeurs possibles en entrés et en sorties respectivement. Mais les fonctions auxquelles certains standards ISO les appliquent ne sont pas les mêmes que les fonctions auxquelles d’autres bibliothèques les appliquent. Par exemple ISO 19123 applique ces termes aux objets CV_Coverage, vus comme des fonctions dont le domaine est l’ensemble des coordonnées spatio-temporelles de la couverture de données et le range l’ensemble des valeurs de la couverture. Mais la bibliothèque NetCDF de l’UCAR applique plutôt ces termes à la fonction convertissant les indices de pixels (son domaine) vers les coordonnées spatio-temporelles (son range). Ainsi, un range de la bibliothèque de l’UCAR peut être le domaine de ISO 19123.

La bibliothèque Apache SIS privilégie autant que possible l’utilisation des termes dans le sens des normes OGC et ISO. Mais un soin particulier doit être apporté aux interfaces entre SIS et certaines bibliothèques externes, afin de réduire les risques de confusions.

1.3.1. Code de couleurs

Les éléments définis dans un langage informatique, tels que les classes ou méthodes en Java ainsi que les éléments dans un fichier XML, apparaissent avec une police de caractères mono-espacée. Afin de faciliter la compréhension des liens qui existent entre Apache SIS et les standards, ces éléments sont en outre représentés en utilisant les codes de couleurs suivants:

Des compléments d’information apparaissent dans des boîtes grises. Le lecteur peut ignorer ces boîtes grises sans que cela ne nuise à la compréhension du texte.

2. Systèmes de références spatiales

Pour donner une position sur la Terre, on peut utiliser des noms tels que celui d’une ville ou une adresse postale — on parle alors de références spatiales par identifiants — ou on peut donner des valeurs numériques valides dans un système de coordonnées donné telles que les latitudes et longitudes — on parle alors de références spatiales par coordonnées. Chaque système de référence implique des approximations telles que le choix de la forme géométrique (géoïde, ellipsoïde, etc.) utilisée comme approximation de la forme de la Terre, le choix des propriétés géométriques (angles, distances, etc.) à préserver lors de la représentation d’une carte sur une surface plane, et les pertes de précision lorsque l’on doit transformer des coordonnées vers des systèmes utilisant un référentiel différent.

Une fausse croyance très répandue est que l’on peut éviter cette complexité en choisissant un seul système de référence des coordonnées (typiquement WGS84) comme système universel pour toutes les données. Les chapitres suivants expliqueront pourquoi la réalité n’est pas si simple. Qu’un système universel réponde ou non aux besoins d’une application dépend de la précision désirée, ainsi que du type de calculs que l’on souhaite effectuer avec les coordonnées. Sauf indication contraire, Apache SIS tente d’assurer une précision de 1 centimètre pour les coordonnées sur la Terre. Mais la maîtrise de cette précision nécessite le respect de certaines conditions:

Bibliothèques de type « early binding » versus « late binding »

Le caractère universel du système WGS84 rend tentante l’idée de l’utiliser comme système pivot, afin de simplifier l’implémentation d’une bibliothèque de transformation de coordonnées. La transformation d’une coordonnée d’un référentiel A vers un référentiel B pourrait se faire en transformant d’abord de A vers WGS84, puis de WGS84 vers B. Il suffirait ainsi de stocker dans chaque objet GeodeticDatum les informations nécessaires à la transformation vers WGS84. Cette approche était encouragée dans la version 1 du format WKT, qui définissait un élément TOWGS84[…] remplissant ce rôle. Cette approche est désignée par EPSG sous le nom de « early binding » car elle associe des informations sur la transformations de coordonnées très tôt dans la définition des objets géodésiques, souvent directement au moment de la construction d’un object GeographicCRS. Bien que EPSG reconnaisse que cette approche soit couramment employée, elle n’est pas recommandée pour plusieurs raisons:

  • Il existe parfois plusieurs transformations allant d’un référentiel A vers B, chacune étant plus précise pour une région géographique donnée.
  • Certaines opérations sont conçues spécifiquement pour transformer de A vers B et n’ont pas la même précision qu’aurait une autre transformation faisant un détour par WGS84.
  • WGS84 lui-même subit parfois des révisions, ce qui en fait une cible mouvante (bien que très lentement) pour les bibliothèques de transformations de coordonnées.
  • Il existe d’autres systèmes globaux qui pourraient servir de pivot, par exemple le Galileo Reference Frame (GTRF) mis en place par le concurrent européen du GPS.

Exemple: la base de données géodésiques EPSG définie une cinquantaine de transformations de NAD27 vers NAD83. Dans une approche de type « early binding », le même système de référence « NAD27 » représenté dans le format WKT 1 aurait besoin d’être défini avec un élément TOWGS84[-8, 160, 176] pour des coordonnées aux États-Unis, ou avec un élément TOWGS84[-10, 158, 187] pour coordonnées aux Canada. Différents paramètres existent aussi pour d’autres régions telles que Cuba. Il n’est donc pas possible de représenter une telle diversité en associant un seul élément TOWGS84[…] à un système de référence des coordonnées. Même en restreignant l’usage d’un référenciel au domaine de validité de son élément TOWGS84[…], ces transformations resteraient approximatives avec une précision de 10 mètres dans le cas des États-Unis. Des transformations plus précises existent sous la forme des grilles de changements de référentiel NADCON, mais ces grilles sont pour des transformations de NAD27 vers NAD83 (qui se déplacent ensemble sur la même plaque continentale) et non vers WGS84 (qui se déplace indépendamment). Cette différence était souvent ignorée lorsque l’on considérait que NAD83 et WGS84 étaient pratiquement équivalents, mais cette supposition est aujourd’hui à prendre avec plus de précautions.

EPSG recommande plutôt d’utiliser une approche dite « late binding », selon laquelle les méthodes et paramètres nécessaires aux transformations de coordonnées sont définis pour des paires de référentiels « A vers B » (éventuellement complétées par leurs domaines de validité) plutôt qu’associés à des référentiels pris isolément. Apache SIS est une implémentation de type « late binding », bien qu’une réminiscence de l’approche « early binding » existe toujours sous la forme de la propriété DefaultGeodeticDatum​.getBursaWolfParameters(). SIS n’utilise cette dernière que comme solution de dernier recours s’il ne peut pas appliquer l’approche « late binding » avec les systèmes de références donnés.

Le module sis-referencing de Apache SIS fournit un ensemble de classes implémentant les différentes spécialisations de l’interface ReferenceSystem ainsi que leurs composantes. Ces implémentations permettent de stocker une description des systèmes de références spatiales ainsi que leurs méta-données telles que la zone de validité. Toutefois ces objets n’effectuent aucune opération sur les coordonnées. Les conversions ainsi que les transformations de coordonnées sont le travail d’une autre famille de classes, dont la racine est l’interface CoordinateOperation. Ces classes seront discutées dans une autre section.

2.1. Composantes d’un système de références par coordonnées

Les systèmes de références spatiales par coordonnées fournissent les informations nécessaires pour faire correspondre des coordonnées numériques à des positions dans le monde réel. Dans Apache SIS, ils sont pratiquement tous représentés par des classes dont le nom se termine en CRS (l’abréviation de Coordinate Reference System en anglais). Ces objets contiennent:

Ces systèmes sont décrits par la norme ISO 19111 (Referencing by Coordinates), qui remplace en grande partie une norme plus ancienne mais encore utilisée pour certains aspects, OGC 01-009 (Coordinate Transformation Services). Ces normes sont complétées par deux autres standards définissant des formats d’échanges: ISO 19136 et 19162 pour respectivement le Geographic Markup Language (GML) — un format XML précis mais verbeux — et le Well-Known Text (WKT) — un format texte plus facile à lire par les humains.

2.1.1. Géoïde et ellipsoïde

La surface topographique réelle étant difficile à représenter mathématiquement, elle n’est pas utilisée directement en cartographie. Une autre surface un peu plus facilement utilisable est le géoïde, une surface sur laquelle la force gravitationnelle a partout la même valeur (surface équipotentielle du champ de gravité terrestre). Cette surface est en tout point perpendiculaire à la direction indiquée par un fil à plomb (verticale du lieu). Le géoïde coïnciderait avec le niveau moyen des mers s’il n’y avait ni vent ni courants marins permanents comme le Gulf Stream.

Tout en étant nettement plus lisse que la surface topographique, le géoïde présente des creux et des bosses liés à l’inégale distribution des masses de la Terre. Pour une utilisation mathématiquement plus aisée, le géoïde est donc approximé par un ellipsoïde. Cette « figure de la Terre » est représentée dans GeoAPI par l’interface Ellipsoid, qui constitue un élément fondamental des systèmes de références de type GeographicCRS et ProjectedCRS. Plusieurs dizaines d’ellipsoïdes sont couramment employés pour la définition de référentiels. Certains offrent une excellente approximation pour une région précise au détriment des régions pour lesquelles le référentiel n’a pas été conçu, et d’autres offrant un compromis pour l’ensemble de la planète.

Exemple: la base de données géodétiques EPSG définit entre autres les ellipsoïdes « WGS 84 », « Clarke 1866 », « Clarke 1880 », « GRS 1980 » and « GRS 1980 Authalic Sphere » (une sphère de même surface que l’ellipsoïde GRS 1980). Un ellipsoïde peut être utilisé en divers endroits de la planète ou peut être très spécifique à une région précise. Par exemple au début du XXe siècle aux États-Unis, l’état du Michigan utilisait pour ses cartes un ellipsoïde basé sur l’ellipsoïde « Clarke 1866 » mais auquel la longueur des axes a été allongée de 800 pieds. Cette modification visait à tenir compte du niveau moyen de l’état au dessus du niveau de la mer.

2.1.2. Référentiel géodésique

Pour définir un système géodésique dans un pays, l’état met en place un ellipsoïde de référence qui épouse au mieux sur l’ensemble du pays la forme locale du géoïde. L’écart entre cet ellipsoïde de référence et les creux et les bosses du géoïde reste généralement inférieur à 100 mètres. Les paramètres qui permettent de lier un Ellipsoid à la surface de la Terre (par exemple la position de son centre) sont représentées par un objet de type GeodeticDatum, que l’on traduit en français par « référentiel géodésique ». Plusieurs GeodeticDatum peuvent utiliser le même Ellipsoid, mais centré ou orienté différemment.

Avant l’avènement des satellites, les mesures géodésiques se déroulaient exclusivement à la surface de la terre. En conséquence, deux îles ou continents qui ne sont pas à portée visuelle l’un de l’autre n’étaient pas rattachés géodésiquement entre eux. Ainsi les référentiels North American Datum 1983 (NAD83) et European Datum 1950 (ED50) sont indépendants l’un de l’autre: leurs ellipsoïdes de référence ont des centres distincts et des dimensions différentes. Une même coordonnée géographique correspondra à des positions différentes dans le monde réel selon que la coordonnée se réfère à l’un ou l’autre de ces référentiels.

L’invention du GPS a précipité la création d’un système géodésique mondial, nommé WGS84. L’ellipsoïde de référence est alors unique et centré au centre de gravité de la terre. Les GPS donnent à tout moment la position absolue du récepteur rapportée à ce système géodésique. Mais WGS84 étant un système mondial, il peut diverger significativement des systèmes locaux. Par exemple l’écart entre WGS84 et le système européen ED50 est de l’ordre de 150 mètres, et l’écart moyen par rapport au système de l’île de la Réunion 1947 est de 1,5 kilomètres. Il ne faut donc pas rapporter aveuglement des positions GPS sur une carte. Des correspondances avec les systèmes régionaux peuvent être nécessaires et sont représentées dans GeoAPI sous forme d’objets de type Transformation.

Les généralisation de l’usage du système WGS84 tend à réduire le besoin d’utiliser les objets Transformation pour les données récentes, mais ne l’élimine pas complètement. La Terre bouge sous l’effet de la tectonique des plaques et de nouveaux systèmes sont définis chaque année pour en tenir compte. Par exemple bien que le référentiel NAD83 a été défini à l’origine comme pratiquement équivalent à WGS84, il existe maintenant (en 2016) un écart d’environ 1.5 mètres entre ces deux systèmes. Le référentiel Japanese Geodetic Datum 2000 était aussi défini comme pratiquement équivalent à WGS84, mais le nouveau référentiel Japanese Geodetic Datum 2011 s’en écarte. Le référentiel WGS84 lui-même, sensé correspondre à une définition à un instant donné, a subit des révisions dues notamment à l’amélioration de la précision des instruments. Ainsi il existe aujourd’hui au moins six versions de WGS84. En outre beaucoups de bordures ont été définies légalement dans des référentiels plus anciens, par exemple NAD27 aux États-Unis. Mettre à jour dans un nouveau référentiel peut obliger à transformer des lignes droites ou des formes géométriques simples en des formes plus irrégulières si on ne veut pas que des parcelles de terrain changent de propriétaire.

2.1.3. Systèmes de coordonnées

TODO

2.1.3.1. Ordre des axes

L’ordre des axes est spécifié par l’autorité (typiquement une agence nationale) qui définit le système de référence des coordonnées (CRS). L’ordre dépend du type de CRS ainsi que du pays qui l’a définit. Dans le cas des CRS de type géographique, l’ordre (latitude, longitude) est utilisé par les géographes et les pilotes depuis des siècles. Toutefois des développeurs de logiciels tendent à préférer l’ordre (x, y) pour tous systèmes de coordonnées. Ces différentes pratiques entraînent des définitions contradictoires de l’ordre des axes pour pratiquement tous les CRS de type GeographicCRS, pour certains ProjectedCRS dans l’hémisphère sud (Afrique du Sud, Australie, etc.) et pour certaines projections polaires entre autres.

Les standards OGC récents demandent d’ordonner les axes tel que spécifié par l’autorité qui a définit le CRS. Mais des standards OGC plus anciens utilisaient l’ordre (x, y) inconditionnellement, en ignorant les spécifications des autorités sur ce point. Beaucoup de logiciels continuent d’utiliser cet ordre (x, y), peut-être parce qu’une telle uniformisation rend l’implémentation et l’utilisation des CRS en apparence plus simple. Apache SIS supporte les deux conventions avec l’approche suivante: par défaut, SIS construit les CRS avec les axes dans l’ordre définit par l’autorité. Ces CRS sont construits par des appels à la méthode CRS.forCode(String), et l’ordre des axes effectif peut être vérifié après la création du CRS par un appel à System.out​.println(crs). Mais si l’ordre (x, y) est désiré pour des raisons de compatibilité avec d’anciens standards OGC ou avec d’autres logiciels, alors les CRS peuvent être modifiés de manière à avoir la longitude en premier avec un appel à la méthode suivante:

CoordinateReferenceSystem crs = …;  // CRS obtenu de n’importe quelle façon.
crs = AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED)

Parmi les anciens standards de l’OGC qui utilisaient un ordre des axes non-conforme, un standard influent était la version 1 du format Well Known Text (WKT). D’après ce format largement utilisé, les définitions WKT 1 sans éléments AXIS[…] explicites doivent être interprétés comme ayant ses axes dans l’ordre (longitude, latitude) ou (x, y). Dans la version 2 du format WKT, les éléments AXIS[…] ne sont plus optionnel et devrait contenir explicitement un sous-élément ORDER[…] pour rendre l’ordre voulu encore plus évident. Mais si les éléments AXIS[…] sont malgré tout omis dans une définition WKT 2, alors Apache SIS utilise l’ordre (latitude, longitude) par défaut. Pour résumer:

Pour éviter des ambiguïtés, les utilisateurs sont encouragés à toujours fournir explicitement les éléments AXIS[…] dans leurs WKT. Le format WKT sera présenté plus en détails dans les sections suivantes.

2.1.4. Systèmes géographiques

TODO

2.1.4.1. Format Well-Known Text

TODO

2.1.5. Projections cartographiques

Les projections cartographiques représentent une surface courbe (la Terre) sur une surface plane (une carte ou un écran d’ordinateur) en contrôlant les déformations: on peut préserver les angles ou les surfaces, mais pas les deux à la fois. Les propriétés géométriques à conserver dépendent de l’objet d’étude et du travail à effectuer. Par exemple les pays plutôt allongés dans le sens Est-Ouest utilisent souvent une projection de Lambert, alors que les pays plutôt allongés dans le sens Nord-Sud préfèrent une projection de Mercator Transverse.

TODO

2.1.5.1. Format Well-Known Text

TODO

2.1.6. Dimensions verticales et temporelles

TODO

2.1.6.1. Format Well-Known Text

TODO

2.2. Obtention d’un système de référence spatial

TODO

2.2.1. Systèmes prédéfinis par des autorités

TODO

2.2.2. Lecture d’une définition au format GML ou WKT

TODO

2.2.3. Construction programmatique explicite

TODO

2.2.4. Ajout de définitions

TODO

2.3. Opérations sur les coordonnées

Étant donné un système de référence des coordonnées (CRS) source selon lequel sont exprimés des coordonnées existantes et un système de référence des coordonnées destination selon lequel les coordonnées sont désirées, Apache SIS peut fournir une opération sur les coordonnées 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 CRS 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 SIS qu’on s’intéresse à une région plus petite peut lui permettre de sélectionner une opération plus précise.

Exemple: la base de données géodésiques EPSG (dans sa version 7.9) définit 77 opérations sur les coordonnées allant du système géographique North American Datum 1927 (EPSG:4267) vers le système World Geodetic System 1984 (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, etc. Si l’utilisateur ne spécifie pas la région géographique qui l’intéresse, alors le comportement par défaut de Apache SIS 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.

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é org.apache.sis.referencing.CRS:

CoordinateOperation cop = CRS.findOperation(sourceCRS, targetCRS, areaOfInterest);

Parmi les information fournies par l’objet CoordinateOperation obtenu, on note en particulier:

Lorsque l’opération sur les coordonnées est une instance de Transformation, il est possible que l’instance choisie par SIS 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 Conversion. 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.

2.3.1. Exécution de opérations

L’objet CoordinateOperation introduit dans la section précédente fournit des informations de haut-niveau (CRS source et destination, zone de validité, précision, paramètres de l’opération, etc). Le travail mathématique réel est effectué par un objet séparé, obtenu par un appel à CoordinateOperation​.getMathTransform(). Contrairement aux instances de CoordinateOperation, les instances de MathTransform ne contiennent aucune méta-données. Elles sont une sorte de boîte noire qui ignore tout des CRS source et destination (en fait la même instance de MathTransform peut être utilisée pour différentes paires de CRS si le travail mathématique est le même). En outre une instance de MathTransform peut être implémentée d’une manière très différente à ce que CoordinateOperation 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, etc.) sont implémentées par MathTransform comme des transformations affines et concaténées pour des raisons d’efficacité, même si CoordinateOperation les affiche comme une chaîne d’opérations telles que la projection de Mercator. La section « chaîne d’opération conceptuelle versus réelle » explique plus en détails les différences.

Le code Java suivant effectue une projection cartographique à partir de coordonnées géographiques selon le référentiel World Geodetic System 1984 (WGS84) vers des coordonnées selon le système WGS 84 / UTM zone 33N. Afin de rendre l’exemple un peu plus simple, ce code utilise des constantes pré-définies dans la classe de commodité CommonCRS. Mais des applications plus avancées voudront souvent utiliser des codes EPSG plutôt. Notons que toutes les coordonnées géographiques dans ce code ont la latitude avant la longitude.

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.UTM(40, 14);  // Obtient la zone valide pour 14°E.
        CoordinateOperation operation = CRS.findOperation(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);
    }
}

2.3.2. Dérivées partielles des opérations

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, OGC 01-009, aujourd’hui un peu oubliée mais pourtant encore utile. Appelons P une projection cartographique qui convertit une latitude et longitude (φλ) en degrés vers une coordonnée projetée (xy) 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):

La dérivée de la projection cartographique en ce même point peut se représenter par une matrice Jacobienne:

Les équations suivantes dans cette section abrégeront ∂x(φλ) par ∂x ainsi que ∂y(φλ) par ∂y, mais il faut garder à l’esprit que chacune de ces valeurs dépendent de la coordonnée (φλ) 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 ∂φ degrés de latitude à partir de la position (φλ) — c’est-à-dire si on se déplace à la position geographique (φ + ∂φ, λ) — alors la coordonnée projetée subira un déplacement de (∂x, ∂λ) metres — c’est-à-dire qu’elle deviendra (x + ∂x, y + ∂λ). 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 ∂λ 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, P1 et P2, pour mieux illustrer le fait que le résultat varie en chaque point. Dans cette figure, les vecteurs U et V désignent respectivement la première et deuxième colonne des matrices Jacobiennes.

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à.

2.3.2.1. Utilité des dérivées pour la reprojection d’enveloppes

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.

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 SIG 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.

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 f(x) = C₀ + Cx + Cx² + Cx³), dont on peut calculer les coefficients C. 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 C. 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.

Dans la pratique Apache SIS 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 SIS 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).

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 à n 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.

L’approche décrite dans cette section est implémentée dans Apache SIS par la méthode statique Envelopes​.transform(CoordinateOperation, Envelope). Une méthode Envelopes​.transform(MathTransform, Envelope) existe aussi comme alternative, mais cette dernière ne devrait être utilisée que si on ne connaît pas l’objet CoordinateOperation utilisé. La raison est que les objets de type MathTransform ne contiennent pas d’information sur le système de coordonnées sous-jasent, ce qui empêche la méthode Envelopes​.transform(…) de savoir comment gérer les points aux pôles.

2.3.2.2. Utilité des dérivées pour la reprojection d’images

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 destination, on obtient la coordonnées du pixel correspondant dans l’image source en utilisant l’inverse 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.

Toutefois, calculer la projection inverse pour chacun des pixels peut être relativement lent. Afin d’accélérer les calculs, on utilise parfois une grille d’interpolation dans laquelle on a pré-calculé les coordonnées 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 transformations affines. Cette approche est implémentée par exemple dans la bibliothèque Java Advanced Imaging avec l’objet WarpGrid. 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.

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:

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 après 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 somme du nombre de nouveaux points de toutes les itérations précédentes.

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 avant 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).

Intersection of derivatives

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).

2.3.2.3. Obtention de la dérivée en un point

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 SIS. Les implémentations des projections cartographiques dans Apache SIS 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 à SIS de réutiliser un maximum de termes communs. Exemple:

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.transform(sourcePoint, 0, targetPoint, 0, true);

Si seule la matrice Jacobienne est désirée (sans la projection du point), alors la méthode MathTransform​.derivative(DirectPosition) offre une alternative plus lisible.

Apache SIS 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, etc. 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:

@Override
public Matrix derivative(DirectPosition p) throws TransformException {
    Matrix jac = inverse().derivative(transform(p));
    return Matrices.inverse(jac);
}

3. Géométries

Ce chapitre introduit quelques aspects de la norme ISO 19107 (Spatial schema) et les classes de Apache SIS qui les implémentent.

3.1. Classes de base

Chaque objet géométrique est considéré comme un ensemble infini de points. En tant qu’ensemble, leurs opérations les plus fondamentales sont de même nature que les opérations standards des collections du Java. On pourrait donc voir une géométrie comme une sorte de java.util.Set dont les éléments seraient des points, à ceci près que le nombre d’éléments contenus dans cet ensemble est infini (à l’exception des géométries représentant un simple point). Pour mieux représenter ce concept, la norme ISO et GeoAPI définissent une interface TransfiniteSet que l’on peut voir comme un Set de taille infini. Bien qu’un lien de parenté existe conceptuellement entre ces interfaces, GeoAPI ne définit pas TransfiniteSet comme une sous-interface de java.util.Set car la définition de certaines méthodes telles que size() et iterator() serait problématique. On y retrouve toutefois des méthodes très similaires telles que contains(…) et intersects(…).

Toutes les géométries sont des spécialisations de TransfiniteSet. La classe parente de toutes ces géométries est appelée GM_Object dans la norme ISO 19107. Les interfaces de GeoAPI utilisent plutôt le nom Geometry, car l’omission du préfixe GM_ (comme le veut la convention dans GeoAPI) aurait laissé un nom trop proche de la classe Object du Java.

3.1.1. Points et positions directes

ISO 19107 définit deux types de structures pour représenter un point: GM_Point et DirectPosition. Le premier type est une véritable géométrie et peut donc être relativement lourd, selon les implémentations. Le second type n’est pas considéré formellement comme une géométrie; il n’étend ni GM_Object ni TransfiniteSet. Il ne définit pratiquement pas d’opérations autres que le stockage d’une séquence de nombres représentant une coordonnée. Il peut donc être un objet plus léger.

Afin de permettre à l’API de travailler indifféremment avec ces deux types de positions, ISO 19107 définit Position comme une union de DirectPosition et GM_Point. Il s’agit d’une union au sens du C/C++. Pour le langage Java, GeoAPI obtient le même effet en définissant Position comme l’interface parente de DirectPosition et Point. Dans la pratique, la grande majorité des API de Apache SIS travaillent sur des DirectPosition, ou occasionnellement des Position quand il semble utile d’autoriser aussi des points géométriques.

3.1.2. Enveloppes

Les enveloppes stockent les valeurs minimales et maximales des coordonnées d’une géométrie. Les enveloppes ne sont pas elles-mêmes des géométries; ce ne sont pas des ensembles infinis de points (TransfiniteSet). Il n’y a aucune garantie que toutes les positions contenues dans les limites d’une enveloppe soient géographiquement valides. Il faut voir les enveloppes comme une information sur les valeurs extrêmes que peuvent prendre les coordonnées d’une géométrie en faisant comme si chaque dimension était indépendante des autres, rien de plus. Nous assimilons néanmoins les enveloppes à des rectangles, cubes ou hyper-cubes (selon le nombre de dimensions) afin de faciliter la discussion, mais en gardant à l’esprit leur nature non-géométrique.

Exemple: Nous pouvons tester si une position est à l’intérieur des limites de l’enveloppe. Un résultat positif ne garantie pas que la position est à l’intérieur de la géométrie délimitée par l’enveloppe, mais un résultat négatif garantie qu’elle est à l’extérieur. De même on peut effectuer des tests d’intersections. En revanche appliquer une rotation n’a pas beaucoup de sens pour une enveloppe, car le résultat peut être très différent de celui que nous aurions obtenu en effectuant une rotation de la géométrie originale, puis en recalculant son enveloppe.

Une enveloppe peut être représentée par deux positions correspondant à deux coins opposés d’un rectangle, cube ou hyper-cube. On prend souvent comme premier coin celui dont toutes les ordonnées ont la valeur minimale (lowerCorner), et comme second coin celui dont toutes les ordonnées ont la valeur maximale (upperCorner). Lors d’un affichage utilisant un système de coordonnées classique (valeurs de l’axe des y augmentant vers le haut), ces deux positions apparaissent respectivement dans le coin inférieur gauche et dans le coin supérieur droit d’un rectangle. Attention toutefois aux différents systèmes de coordonnées, qui peuvent faire varier les positions de ces coins à l’écran. Les expressions lower corner et upper corner doivent être comprises au sens mathématique plutôt que visuel.

3.1.2.1. Enveloppes traversant l’antiméridien

Les minimums et maximums sont les valeurs les plus souvent assignées aux lowerCorner et upperCorner. Mais les choses se compliquent dans le cas d’une enveloppe traversant l’antiméridien (-180° ou 180° de longitude). Par exemple, une enveloppe de 10° de largeur peut commencer à 175° de longitude et se terminer à -175°. Dans ce cas, la valeur de longitude assignée au lowerCorner est supérieure à celle qui est assignée à l’upperCorner. Apache SIS emploie donc une définition légèrement différente de ces deux coins:

Lorsque l’enveloppe ne traverse par l’antiméridien, ces deux définitions sont équivalentes à la sélection des valeurs minimales et maximales respectivement. C’est le cas du rectangle vert dans la figure ci-dessous. Lorsque l’enveloppe traverse l’antiméridien, les coins lowerCorner et upperCorner apparaissent encore en bas et en haut du rectangle (en supposant un système de coordonnées classique), donc leurs noms restent appropriés d’un point de vue visuel. Mais les positions gauche et droite sont interchangées. Ce cas est représenté par le rectangle rouge dans la figure ci-dessous.

Exemples d’enveloppes avec et sans croisement de l’antiméridien.

Les notions d’inclusion et d’intersection s’interprètent toutefois de manière légèrement différente dans ces deux cas. Dans le cas habituel où l’on ne traverse pas l’antiméridien, le rectangle vert délimite bien une région d’inclusion. Les régions exclues de ce rectangle se propagent à l’infini dans toutes les directions. En d’autres mots, la région d’inclusion n’est pas répétée tous les 360°. Mais dans le cas du rectangle rouge, l’information fournie par l’enveloppe délimite plutôt la région d’exclusion qui se trouve entre les deux bords du rectangle. La région d’inclusion se propage à l’infini des côtés gauche et droit. Nous pourrions stipuler que toute longitude inférieure à -180° ou supérieure à 180° est considérée exclue, mais ça serait une décision arbitraire qui ne serait pas un reflet symétrique du cas habituel (rectangle vert). Un développeur pourrait vouloir utiliser ces valeurs, par exemple dans une mosaïque où la carte du monde est répétée plusieurs fois horizontalement tout en considérant chaque répétition comme distincte des autres. Si un développeur souhaite effectuer des opérations comme si les régions d’inclusions ou d’exclusions étaient répétées tous les 360°, alors il doit lui-même ramener ses valeurs de longitudes entre -180° et 180° au préalable. Toutes les fonctions add(…), contains(…), intersect(…), etc. de toutes les enveloppes définies dans le paquet org.apache.sis.geometry effectuent leurs calculs selon cette convention.

Pour que les fonctions telles que add(…) fonctionnent correctement, tous les objets impliqués doivent utiliser le même système de référence des coordonnées, y compris la même plage de valeurs. Ainsi, une enveloppe exprimant les longitudes dans la plage [-180 … +180]° n’est pas compatible avec une enveloppe exprimant les longitudes dans la plage [0 … 360]°. Les conversions, si nécessaires, sont à la charge de l’utilisateur (la classe Envelopes fournit des méthodes de commodités pour ce faire). En outre, les coordonnées de l’enveloppe doivent être comprises dans les limites du système de coordonnées, sauf si le développeur souhaite volontairement considérer (par exemple) 300° de longitude comme un position distincte de -60°. La classe GeneralEnvelope fournit une méthode normalize() pour ramener les coordonnées dans les limites attendues, au prix parfois de valeurs lower supérieures à la valeur upper.

4. Couvertures de données (Coverages)

Les images, souvent nommées rasters en anglais, sont des cas particuliers d’une structure de données appelée coverages. On pourrait traduire ce terme anglais par « couverture de données ». Le titre du standard les décrivant, « Coverage geometry and functions » (ISO 19123), résume bien les deux éléments essentiels des couvertures de données:

Les caractéristiques du domaine spatial sont définies par le standard ISO 19123, alors que les caractéristiques du range ne font pas parties du standard. Le standard mentionne simplement que les ranges peuvent être finis ou infinis, et ne sont pas nécessairement numériques. Par exemple les valeurs retournées par une couverture peuvent provenir d’une énumération (« ceci est une forêt », « ceci est un lac », etc.). Toutefois, le standard définit deux grands types de couvertures qui ont un impact sur les types de ranges autorisés: les couvertures discrètes et les couvertures continues. Présentées simplement, les couvertures continues sont des fonctions pouvant utiliser des méthodes d’interpolations. Or, les interpolations n’étant possibles qu’avec des valeurs numériques, les ranges de valeurs non-numériques ne peuvent être utilisés qu’avec des couvertures de type CV_DiscreteCoverage. En revanche, les ranges de valeurs numériques peuvent être utilisés aussi avec des couvertures de type CV_ContinuousCoverage.

5. Classes et méthodes utilitaires

Ce chapitre décrit des aspects de Apache SIS qui s’appliquent à l’ensemble de la bibliothèque. La plupart de ces utilitaires ne sont pas spécifiques aux systèmes d’information spatiales.

5.1. Modes de comparaisons des objets

Il existe différentes opinions sur la façon d’implémenter la méthode Object​.equals(Object) du Java standard. Selon certains, il doit être possible de comparer différentes implémentations d’une même interface ou classe de base. Mais cette politique nécessite que chaque interface ou classe de base définisse entièrement dans sa Javadoc les critères ou calculs que doivent employer les méthodes equals(Object) et hashCode() dans toutes les implémentations. Cette approche est choisie notamment par java.util.Collection et ses interfaces filles. La transposition de cette approche aux centaines d’interfaces de GeoAPI serait toutefois une entreprise ardue, qui risquerait d’être assez peu suivie par les diverses implémentations. En outre, elle se fait au détriment de la possibilité de prendre en compte des attributs supplémentaires dans les interfaces filles si cette possibilité n’a pas été spécifiée dans l’interface parente. Cette contrainte découle des points suivants du contrat des méthodes equals(Object) et hashCode():

Par exemple ces trois contraintes sont violées si A (et éventuellement C) peuvent contenir des attributs que B ignore. Pour contourner cette difficulté, une approche alternative consiste à exiger que les objets comparés par la méthode Object​.equals(Object) soient exactement de la même classe, c’est-à-dire que A.getClass() == B.getClass(). Cette approche est parfois considérée contraire aux principes de la programmation orientée objets. Dans la pratique, pour des applications relativement complexes, l’importance accordée à ces principes dépend du contexte dans lequel les objets sont comparés: si les objets sont ajoutés à un HashSet ou utilisés comme clés dans un HashMap, alors nous avons besoin d’un strict respect du contrat de equals(Object) et hashCode(). Mais si le développeur compare les objets lui-même, par exemple pour vérifier si des informations qui l’intéresse ont changées, alors les contraintes de symétrie, transitivité ou de cohérence avec les valeurs de hachages peuvent ne pas être pertinentes pour lui. Des comparaisons plus permissives peuvent être souhaitables, allant parfois jusqu’à tolérer de légers écarts dans les valeurs numériques.

Afin de donner une certaine flexibilité aux développeurs, un grand nombre de classes de la bibliothèque SIS implémentent l’interface org.apache.sis.util.LenientComparable, qui défini une méthode equals(Object, ComparisonMode). Les principaux modes de comparaisons sont:

Le mode par défaut, utilisé par les toutes les méthodes equals(Object) de SIS, est STRICT. Ce mode est choisi pour une utilisation sécuritaire — notamment avec HashMap — sans nécessiter de définitions rigoureuses des méthodes equals(Object) et hashCode() dans toutes les interfaces. Avec ce mode, l’ordre des objets (A.equals(B) ou B.equals(A)) n’a pas d’importance. C’est toutefois le seul mode à offrir cette garantie. Dans l’expression A.equals(B), le mode BY_CONTRACT (et donc par extension tous les autres modes qui en dépendent) ne comparera que les propriétés connues de A, sans se soucier de savoir si B en connaît davantage.

5.2. Object converters

Il est parfois nécessaire de convertir une instance d’un type source <S> vers un type destination <T> alors que ces types ne sont pas connus au moment de la compilation. Divers projets (Apache Common Convert, Spring, etc.) ont créé leur propres interfaces pour effectuer des conversions d’objets entre des types connus seulement au moment de l’exécution. Les détails varient, mais ces interfaces ressemblent typiquement à l’interface suivante:

interface ObjectConverter<S,T> {   // Certains projets utilisent seulement "Converter" comme nom d’interface.
    T apply(S object);             // Un autre nom de méthode souvent utilisé par les autres projets est "convert".
}

Comme d’autres projets, Apache SIS définit sa propre interface ObjectConverter. La principale différence entre l’interface de SIS est celle que l’on retrouve dans d’autres projets est que SIS fournit des informations à propos de certaines propriétés mathématiques des convertisseurs. Un convertisseur de Apache SIS peut avoir aucune, une ou plusieurs des propriétés suivantes:

Injective
Une fonction est injective si aucune paire de valeurs de S ne peut produire la même valeur de T.

Exemple: la conversion IntegerString effectuée par Integer​.toString() est une fonction injective car si deux valeurs de type Integer ne sont pas égales, alors il est garanti que leurs conversions produiront différentes valeurs de String. En revanche la conversion StringInteger effectuée par Integer​.valueOf(String) n’est pas une fonction injective parce que plusieurs valeurs distinctes de type String peuvent être converties vers la même valeur de type Integer. Par exemple les conversions des chaînes de caractères "42", "+42" et "0042" donnent toutes la même valeur entière 42.

Surjective
Une fonction est surjective si chaque valeur de T peut être produite à partir d’au moins une valeur de S.

Exemple: la conversion StringInteger effectuée par Integer​.valueOf(String) est une fonction surjective car chaque valeur de type Integer peut être produite à partir d’un moins une valeur de String. En revanche la conversion IntegerString effectuée par Integer​.toString() n’est pas une fonction surjective parce qu’elle ne peut pas produire toutes les valeurs possibles de type String. Par exemple il n’y a aucun moyen de produire la valeur "ABC" avec la méthode Integer​.toString().

Bijective
Une fonction est bijective s’il y a une relation de un-à-un entre les valeurs de S et de T.

Note: la propriété bijective est définie ici pour des raisons de clarté, mais en fait n’a pas d’item explicite dans l’énumération FunctionProperty de Apache SIS. Ce n’est pas nécessaire puisqu’une fonction qui est à la fois injective et surjective est nécessairement bijective.

Préservant l’ordre
Une fonction préserve l’ordre si toute séquence de valeurs S croissantes correspond à une séquence de valeurs T croissantes.

Exemple: la conversion du type Integer vers Long préserve l’ordre naturel des éléments. Toutefois la conversion du type Integer vers String ne préserve pas l’ordre naturel, parce que des séquences des nombres entiers croissants ont un ordre différents lorsque les chaînes de caractères sont classées par ordre lexicographique. Par exemple 1, 2, 10 devient "1", "10", "2".

Renversant l’ordre
Une fonction renverse l’ordre si toute séquence de valeurs S croissantes correspond à une séquence de valeurs T décroissantes.

Exemple: une conversion qui inverse le signe des nombres.

Ces informations peuvent sembler inutiles lorsque l’on convertit des valeurs sans tenir compte du contexte où elles apparaissent. Mais lorsque la valeur à convertir fait parti d’un objet plus gros, alors ces informations peuvent impacter la façon dont la valeur convertie sera utilisée. Par exemple la conversion d’une plage de valeurs représentée par [minmax] est directe lorsque la fonction de conversion préserve l’ordre. Mais si la fonction de conversion renverse l’ordre, alors les valeurs minimale et maximale doivent être interchangées. Par exemple si la fonction de conversion inverse le signe des valeurs, alors la plage convertie sera [-max … -min]. Si la fonction de conversion ne préserve ni ne renverse l’ordre, alors la plage de valeurs ne peut pas être convertie du tout (parce qu’elle ne contiendrait plus le même ensemble de valeurs) même si les valeurs individuelles auraient pu être converties.

5.3. Internationalisation

Dans une architecture où un programme exécuté sur un serveur fournit ses données à plusieurs clients, les conventions locales du serveur ne sont pas nécessairement les mêmes que celles des clients. Les conventions peuvent différer par la langue, mais aussi par la façon d’écrire les valeurs numériques (même entre deux pays parlant la même langue) ainsi que par le fuseau horaire. Pour produire des messages conformes aux conventions du client, SIS emploie deux approches qui diffèrent par leur niveau de granularité: au niveau des messages eux-mêmes, ou au niveau des objets produisant les messages. L’approche utilisée détermine aussi s’il est possible de partager une même instance d’un objet pour toutes les langues.

5.3.1. Chaînes de caractères distinctes pour chaque conventions locales

Certaines classes ne sont conçues que pour fonctionner selon une convention locale à la fois. C’est évidemment le cas des implémentations standards de java.text.Format, puisqu’elles sont entièrement dédiées au travail d’internationalisation. Mais c’est aussi le cas de d’autres classes moins évidentes comme javax.imageio.ImageReader/ImageWriter ainsi que les sous-classes de Exception. Lorsque une de ces classes est implémentée par SIS, nous l’identifions en implémentant l’interface org.apache.sis.util.Localized. La méthode getLocale() de cette interface permet alors de déterminer selon quelles conventions locales l’instance produira ses messages.

Certaines sous-classes de Exception définies par SIS implémentent aussi l’interface Localized. Pour ces exceptions, le message d’erreur peut être produit selon deux conventions locales selon qu’il s’adresse à l’administrateur du système ou au client: getMessage() retourne le message de l’exception selon les conventions par défaut du système, alors que getLocalizedMessage() retourne le message de l’exception selon les conventions locales spécifiées par getLocale(). Ce Locale sera lui-même déterminé par l’objet Localized qui a lancé l’exception.

Exemple: Supposons que dans un environnement où la langue par défaut serait l’anglais, un objet AngleFormat est construit pour lire des angles selon les conventions françaises. Si une ParseException est lancée lors de l’utilisation de ce formateur, alors getMessage() retournera le message d’erreur en anglais tandis que getLocalizedMessage() retournera le message d’erreur en français.

Les exceptions définies par SIS n’implémentent pas toutes l’interface Localized. Seules celles dont le message est le plus susceptible d’être montré à l’utilisateur sont ainsi localisées. Les ParseException sont de bonnes candidates puisqu’elles surviennent souvent suite à une saisie incorrecte du client. En revanche les NullPointerException sont généralement la conséquence d’une erreur de programmation; elles peuvent être localisées dans la langue par défaut du système, mais ça sera généralement tout.

La classe utilitaire org.apache.sis.util.Exceptions fournit des méthodes de commodité pour obtenir des messages selon des conventions locales spécifiées lorsque cette information est disponible.

5.3.2. Instance unique pour toutes les conventions locales

Les API définit par SIS ou hérités de GeoAPI privilégient plutôt l’utilisation du type InternationalString là où une valeur de type String serait susceptible d’être localisée. Cette approche permet de différer le processus d’internationalisation au moment d’obtenir une chaîne de caractères plutôt qu’au moment de construire l’objet qui les contient. C’est particulièrement utile pour les classes immutables qui serviront à créer des instances uniques indépendamment des conventions locales.

Exemple: Il existe dans SIS une seule instance de type OperationMethod représentant la projection de Mercator, quelle que soit la langue du client. Mais sa méthode getName() fournit (indirectement) une instance de InternationalString telle que toString(Locale.ENGLISH) retourne Mercator Projection alors que toString(Locale.FRENCH) retourne Projection de Mercator.

En définissant des objets spatiaux indépendemment des conventions locales, on réduit les risques de sur-coûts de calculs. Par exemple il est plus facile de détecter que deux cartes emploient la même projection cartographique si cette dernière est représentée par la même instance de CoordinateOperation, même si la projection porte différents noms selon les pays. En outre, certain types de CoordinateOperation peuvent nécessiter des grilles de transformation de coordonnées, ce qui accroît l’intérêt de partager une instance unique pour des raisons d’économie de mémoire.

5.3.3. Convention Locale.ROOT

Toutes les méthodes SIS recevant ou retournant une valeur de type Locale acceptent la valeur Locale.ROOT. Cette valeur est interprétée comme signifiant de ne pas localiser le texte. La notion de texte non-localisé est un peu fausse, puisqu’il faut bien choisir une convention de format. Mais cette convention, bien que très proche de l’anglais, sera généralement légèrement différente. Par exemple:

5.3.4. Traitement des caractères

Les chaînes de caractères en Java utilisent l’encodage UTF-16. Il existe une correspondance directe entre les valeurs de type char et la très grande majorité des caractères, ce qui facilite l’utilisation des chaînes lorsque ces caractères suffisent. Mais certains caractères Unicode ne sont pas représentables par un seul char. Ces caractères supplémentaires comprennent certains idéogrammes, mais aussi des symboles routiers et géographiques dans la plage 1F680 à 1F700. Le support de ces caractères supplémentaires nécessite des itérations un peu plus complexes que le cas classique où l’on supposait une correspondance directe. Ainsi, au lieu de la boucle de gauche ci-dessous, les applications internationales devraient généralement utiliser la boucle de droite:

SIS supporte les caractères supplémentaires en utilisant la boucle de droite lorsque nécessaire. Mais la boucle de gauche reste occasionnellement utilisée lorsqu’il est connu que les caractères recherchés ne sont pas des caractères supplémentaires, même si la chaîne dans laquelle on fait la recherche peut en contenir.

5.3.4.1. Interprétation des espaces blancs

Le Java standard fournit deux méthodes pour déterminer si un caractères est un espace blanc: Character​.isWhitespace(…) et Character​.isSpaceChar(…). Ces deux méthodes diffèrent dans leurs interprétations des espaces insécables, des tabulations et des retours à la ligne. La première méthode est conforme à l’interprétation couramment utilisée dans des langages telles que le Java, C/C++ et XML, qui considère les tabulations et retours à la ligne comme des espaces blancs, alors que les espaces insécables sont interprétés comme des caractères non-blanc. La seconde méthode — strictement conforme à la définition Unicode — fait l’interprétation inverse.

SIS emploie ces deux méthodes dans des contextes différents. isWhitespace(…) est utilisée pour séparer les éléments d’une liste (nombres, dates, mots, etc.), tandis que isSpaceChar(…) est utilisée pour ignorer les espaces blancs à l’intérieur d’un seul élément.

Exemple: Supposons une liste de nombres représentés selon les conventions françaises. Chaque nombre peut contenir des espace insécables comme séparateurs des milliers, tandis que les différents nombres de la liste peuvent être séparés par des espaces ordinaires, des tabulations ou des retours à la ligne. Pendant l’analyse d’un nombre, on veut considérer les espaces insécables comme faisant partie du nombre, alors qu’une tabulation ou un retour à la ligne indique très probablement une séparation entre ce nombre et le nombre suivant. On utilisera donc isSpaceChar(…). Inversement, lors de la séparation des nombres de la liste, on veut considérer les tabulations et les retours à la ligne comme des séparateurs mais pas les espaces insécables. On utilisera donc isWhitespace(…). Le rôle des espaces ordinaires, qui pourraient s’appliquer aux deux cas, doit être décidé en amont.

Dans la pratique, cette distinction se traduit pas une utilisation de isSpaceChar(…) dans les implémentations de java.text.Format, et une utilisation de isWhitespace(…) dans pratiquement tout le reste de la bibliothèque SIS.

6. Représentation des objets en XML

Les objets définis par les standards OGC/ISO doivent pouvoir être échangés sur internet entre des machines distantes, utilisant des logiciels différents écrits dans des langages différents. Quelques uns des formats les plus connus sont le WKT (Well-Known Text) et le WKB (Well-Known Binary). Mais le format le plus exhaustif et souvent considéré comme la référence est le XML, au point où la façon de représenter les objets ISO dans ce format fait parfois l’objet d’un standard international à part entière. Ainsi, les classes de méta-données sont décrites dans le standard ISO 19115-1 (une spécification dite abstraite), alors que la représentation de ces classes en XML est décrite par les standards ISO 19115-3 et 19139.

Les différents standards OGC/ISO n’emploient pas tous la même stratégie pour exprimer les objets en XML. Le standard ISO 19115-3 en particulier emploie une approche plus verbeuse que les autres normes, et fera l’objet d’une section particulière. Mais la plupart des formats XML ont en commun de définir des types et des attributs supplémentaires qui ne font pas partie des spécifications abstraites d’origines. Ces attributs supplémentaires sont habituellement propres au XML et peuvent ne pas apparaître directement dans l’API de Apache SIS. Certains de ces attributs, notamment id, uuid et xlink:href, restent toutefois accessibles sous forme de paires clé-valeurs.

Les documents XML peuvent employer les préfixes de leur choix, mais les préfixes suivants sont couramment employés dans la pratique. Ils apparaissent donc par défaut dans les documents produits par Apache SIS. Ces préfixes sont définis dans la classe org.apache.sis.xml.Namespaces.

Préfixes d’espaces de noms XML courants
Préfixe Espace de nom
gco http://www.isotc211.org/2005/gco
gfc http://www.isotc211.org/2005/gfc
gmd http://www.isotc211.org/2005/gmd
gmi http://www.isotc211.org/2005/gmi
gmx http://www.isotc211.org/2005/gmx
gml http://www.opengis.net/gml/3.2
xlink http://www.w3.org/1999/xlink

6.1. Représentation des méta-données selon ISO 19115-3

Pour chaque classe de méta-donnée, il existe un type XML portant le même nom que dans la spécification abstraite (par exemple gmd:MD_Metadata et gmd:CI_Citation). Tous ces types peuvent être employés comme racine d’un document XML. Ainsi, il est possible d’écrire un document représentant un objet MD_Metadata complet, ou d’écrire un document représentant seulement un objet CI_Citation.

Le standard ISO 19139 dispose le contenu de ces objets d’une manière inhabituelle: pour chaque propriété dont le type de la valeur est lui-même une autre classe du standard ISO 19139, la valeur est enveloppée dans un élément qui représente son type plutôt que d’être écrite directement. Par exemple dans un objet de type CI_Citation, la valeur de la propriété citedResponsibleParty est enveloppée dans un élément CI_Responsibility. Cette pratique double la profondeur de l’arborescence, en introduisant une duplication à tous les niveaux pour chaque valeur, comme dans l’exemple suivant:

<MD_Metadata>
  <identificationInfo>
    <MD_DataIdentification>
      <citation>
        <CI_Citation>
          <citedResponsibleParty>
            <CI_Responsibility>
              <party>
                <CI_Party>
                  <contactInfo>
                    <CI_Contact>
                      <onlineResource>
                        <CI_OnlineResource>
                          <linkage>
                            <URL>http://www.opengeospatial.org</URL>
                          </linkage>
                        </CI_OnlineResource>
                      </onlineResource>
                    </CI_Contact>
                  </contactInfo>
                </CI_Party>
              </party>
            </CI_Responsibility>
          </citedResponsibleParty>
        </CI_Citation>
      </citation>
    </MD_DataIdentification>
  </identificationInfo>
</MD_Metadata>

L’exemple précédent, comme tous les documents conformes à ISO 19139, est donc constitué d’une alternance systématique de deux types d’éléments XML imbriqués:

  1. Il y a d’abord le nom de la propriété, qui commence toujours par une lettre minuscule (en ignorant les préfixes). Dans les API Java, chaque propriété correspond à une méthode de la classe englobante. Dans l’exemple ci-haut, gmd:identificationInfo (une propriété de la classe MD_Metadata) correspond à la méthode Metadata​.getIdentificationInfo().

  2. Sous chaque propriété se trouve le type de la valeur, sauf si elle a été remplacée par une référence (la sous-section suivante suivante approfondira ce sujet). Le type de la valeur est un élément XML dont le nom commence toujours par une lettre majuscule, en ignorant les préfixes. Dans l’exemple ci-haut nous avions MD_DataIdentification, qui correspond à l’interface Java DataIdentification. C’est cet élément XML qui contient les valeurs de la propriété.

Afin de réduire la complexité des bibliothèques, GeoAPI et Apache SIS n’exposent publiquement qu’une vision unifiée de ces deux types d’éléments. L’API public correspond essentiellement au deuxième groupe. Toutefois, lors de l’écriture d’un document XML, des éléments du premier groupe doivent être temporairement recréés. Les classes qui y correspondent sont définies dans des paquets internes de SIS. Ces classes peuvent être ignorées, sauf si le développeur souhaite implémenter ses propres classes dont les instances devront être lues et écrites par JAXB.

6.1.1. Identification d’instances déjà définies

L’élément englobant peut contenir un attribut id ou uuid. Si un de ces attributs est présent, l’élément englobé peut être complètement omis; il sera remplacé au moment de la lecture par l’élément référencé par l’attribut. Dans l’exemple suivant, la partie gauche définie un élément associé à l’identifiant “mon_id”, alors que la partie droite référence cet élément:

Le choix de l’attribut à utiliser dépend de la portée de l’élément référencé:

Dans la bibliothèque SIS, tous les objets susceptibles d’être identifiés dans un document XML implémentent l’interface org.apache.sis.xml.IdentifiedObject. Chaque instance de cette interface fournit une vue de ses identifiants sous forme de Map<Citation,String>, dans lequel la clé Citation identifie le type d’identifiant et la valeur est l’identifiant lui-même. Quelques constantes représentant différents types d’identifiants sont énumérées dans IdentifierSpace, notamment ID, UUID et HREF. Chacune de ces clés peut être associée à une valeur d’un type différent (habituellement String, UUID ou URI) selon la clé. Par exemple le code suivant définit une valeur pour l’attribut uuid:

import org.apache.sis.metadata.iso.DefaultMetadata;
import org.apache.sis.xml.IdentifierSpace;
import java.util.UUID;

public class MyClass {
    public void myMethod() {
        UUID identifier = UUID.randomUUID();
        DefaultMetadata metadata = new DefaultMetadata();
        metadata.getIdentifierMap().putSpecialized(IdentifierSpace.UUID, identifier);
    }
}

Bien que ce mécanisme aie été définit dans le but de mieux supporter les représentations des attributs XML du groupe gco:ObjectIdentification, il permet aussi de manière opportuniste de manipuler d’autres types d’identifiants. Par exemple les attributs ISBN et ISSN de Citation peuvent être manipulés de cette manière. Les méthodes de l’interface IdentifiedObject fournissent donc un endroit unique où peuvent être manipulés tous types d’identifiants (pas seulement XML) associés à un objet.

6.1.2. Représentation de valeurs manquantes

Lorsqu’une propriété n’est pas définie, la méthode correspondante de GeoAPI retourne généralement null. Toutefois les choses se compliquent lorsque la propriété manquante est une valeur considérée comme obligatoire par le standard ISO 19115. Le standard ISO 19139 autorise l’omission de propriétés obligatoires à la condition d’indiquer pourquoi la valeur est manquante. Les raisons peuvent être que la propriété ne s’applique pas (inapplicable), que la valeur existe probablement mais n’est pas connue (unknown), que la valeur pourrait ne pas exister (missing), qu’elle ne peut pas être divulguée (withheld), etc. La transmission de cette information nécessite l’utilisation d’un objet non-nul même lorsque la valeur est manquante. SIS procède en retournant un objet qui, en plus d’implémenter l’interface GeoAPI attendue, implémente aussi l’interface org.apache.sis.xml.NilObject. Cette interface marque les instances dont toutes les méthodes retournent une collection vide, un tableau vide, null, NaN, 0 ou false, dans cet ordre de préférence selon ce que les types de retours des méthodes permettent. Chaque instance implémentant NilObject fournit une méthode getNilReason() indiquant pourquoi l’objet est nul.

Dans l’exemple suivant, la partie gauche montre un élément CI_Citation contenant un élément CI_Series, alors que dans la partie droite la série est inconnue. Si l’élément CI_Series avait été complètement omis, alors la méthode Citation​.getSeries() retournerait null en Java. Mais en présence d’un attribut nilReason, l’implémentation SIS de getSeries() retournera plutôt un objet implémentant à la fois les interfaces Series et NilReason, et dont la méthode getNilReason() retournera la constante UNKNOWN.

7. Annexes

7.1. Réduire la dépendance directe envers Apache SIS

Les chapitres précédents utilisaient des méthodes statiques de Apache SIS par commodité. Dans certains cas, il est possible de remplacer ces méthodes statiques par du code ne faisant appel qu’à des méthodes de GeoAPI. Ces remplacements peuvent être intéressants pour les applications qui souhaiteraient limiter les dépendances directes envers Apache SIS, par exemple afin de faciliter d’éventuelles migrations entre SIS et d’autres implémentations de GeoAPI. Mais cela peut amener ces applications à écrire leur propres méthodes de commodités. Les sections suivantes donnent quelques pistes pour faciliter cette tâche.

7.1.1. Correspondances entre GeoAPI et les spécifications abstraites

Pour chaque classe, méthode et constante définie à partir d’un standard OGC ou ISO, GeoAPI indique sa provenance à l’aide d’annotations définies dans le paquet org.opengis.annotation. Cette correspondante est décrite dans le chapitre à propos de GeoAPI. Les méthodes d’introspections du Java permettent d’accéder à ces informations pendant l’exécution d’une application. La classe org.apache.sis.util.iso.Types fournit des méthodes de commodités telles que getStandardName(Class) à cette fin, mais on peut éviter ces méthodes. L’exemple suivant affiche le nom standard de la méthode getTitle() de l’interface Citation:

Class<?> type   = Citation.class;
Method   method = type.getMethod("getTitle", (Class<?>[]) null);
UML      annot  = method.getAnnotation(UML.class);
String   id     = annot.identifier();
System.out.println("Le nom standard de la méthode " + method.getName() + " est " + id);

L’opération inverse — obtenir la classe et méthode Java d’un nom standard — est un peu plus lourde. Elle nécessite la lecture du fichier class-index.properties fournit dans le paquet org.opengis.annotation. L’exemple suivant lit ce fichier juste avant de rechercher le nom de l’interface correspondant à CI_Citation. Toutefois les utilisateurs sont encouragés à ne lire ce fichier qu’une fois et de conserver son contenu dans une cache de leur application.

Properties isoToGeoAPI = new Properties();
try (InputStream in = UML.class.getResourceAsStream("class-index.properties")) {
    isoToGeoAPI.load(in);
}
String isoName = "CI_Citation";
String geoName = isoToGeoAPI.getProperty(isoName);
Class<?>  type = Class.forName(geoName);
System.out.println("L’interface GeoAPI pour le type ISO " + isoName + " est " + type);

La méthode de commodité de org.apache.sis.util.iso.Types correspondante à cette operation est forStandardName(String).

7.1.2. Obtenir une implémentation des interfaces de GeoAPI

GeoAPI définit des fabriques (Factory) permettant de créer des implémentations de ses interfaces. Par exemple DatumFactory fournit des méthodes permettant de créer des instances implémentant les interfaces du paquet org.opengis.referencing.datum. Ces Factory doivent être implémentées par les bibliothèques géospatiales et déclarées comme services tel que défini par la classe standard java.util.ServiceLoader. La javadoc de ServiceLoader explique la façon de procéder. Mais pour résumer, les bibliothèques doivent créer dans le répertoire META-INF/services/ un fichier dont le nom correspond au nom complet de l’interface de la fabrique (org.opengis.referencing.datum.DatumFactory dans l’exemple précédent). Ce fichier texte doit contenir sur une ligne le nom complet de la classe implémentant cette interface. Il peut s’agir d’une classe cachée aux yeux des utilisateurs, car ils n’ont pas besoin de connaître son existence.

Si la bibliothèque a bien déclaré ses fabriques comme des services, alors les utilisateurs peuvent les obtenir en utilisant ServiceLoader comme dans l’exemple ci-dessous. Cet exemple ne prend que la première fabrique trouvée; s’il existe plusieurs fabriques, par exemple lorsque plusieurs bibliothèques cohabitent, alors le choix est laissé à l’utilisateur.

import org.opengis.referencing.GeodeticDatum;
import org.opengis.referencing.DatumFactory;
import java.util.ServiceLoader;

public class MyApplication {
    public void createMyDatum() {
        ServiceLoader  loader = ServiceLoader.load(DatumFactory.class);
        DatumFactory  factory = loader.iterator().next();
        GeodeticDatum myDatum = factory.createGeodeticDatum(…);
    }
}

7.1.2.1. Fournir sa propre implémentation

Implémenter soi-même GeoAPI n’est pas si difficile si on se contente de besoins bien précis. Un développeur peut se concentrer sur une poignée d’interfaces parmi les centaines de disponibles, tout en disposant des autres interfaces comme autant de points d’extensions à éventuellement implémenter au gré des besoins.

Le modèle conceptuel représenté par les interfaces est complexe. Mais cette complexité peut être réduite en combinant certaines interfaces. Par exemple plusieurs bibliothèques, même réputées, ne font pas la distinction entre Système de coordonnées (CS) et Système de référence des coordonnées (CRS). Un développeur qui lui non-plus ne souhaite pas faire cette distinction peut implémenter ces deux interfaces par la même classe. Il peut en résulter une implémentation dont la hiérarchie de classes est plus simple que la hiérarchie des interfaces de GeoAPI. Le module geoapi-examples, discuté plus loin, fournit de telles combinaisons. Le tableau suivant énumère quelques combinaisons possibles:

Interface principale Interface auxiliaire Usage
CoordinateReferenceSystem CoordinateSystem Description d’un système de référence spatial (CRS).
GeodeticDatum Ellipsoid Description d’un référentiel geodétique.
CoordinateOperation MathTransform Opération de transformation de coordonnées.
IdentifiedObject ReferenceIdentifier Objet (typiquement un CRS) que l’on peut identifier par un code.
Citation InternationalString Référence bibliographique composée d’un simple titre.
GeographicBoundingBox Extent Étendue spatiale en degrés de longitude et de latitude.
ParameterValue ParameterDescriptor Description d’un paramètre (nom, type) associée à sa valeur.
ParameterValueGroup ParameterDescriptorGroup Description d’un ensemble de paramètres associés à leurs valeurs.

Le module geoapi-examples fournit des exemples d’implémentations simples. Plusieurs de ces classes implémentent plus d’une interface à la fois afin de proposer un modèle conceptuel plus simple. La Javadoc de ce module énumère les paquets et classes clés avec les combinaisons effectuées. Ce module illustre non-seulement comment GeoAPI peut-être implémenté, mais aussi comment l’implémentation peut être testée en utilisant geoapi-conformance.

Bien que sa mission première soit de servir d’inspiration aux implémenteurs, geoapi-examples a tout-de-même été conçu de manière à être utilisable par des applications ayant des besoins très simples. Tous les exemples étant dans le domaine publique, les développeurs sont invités à adapter librement des copies de ces classes si nécessaires. Toutefois si des modifications sont apportées hors du cadre du projet GeoAPI, le bon usage veut que les copies modifiées soient placées dans un paquet portant un autre nom que org.opengis.

Pour des besoins un peu plus poussés, les développeurs sont invités à examiner les modules geoapi-proj4 et geoapi-netcdf. Ces deux modules fournissent des exemples d’adaptateurs permettant d’utiliser, via les interfaces de GeoAPI, une partie des fonctionnalités de bibliothèques externes (Proj.4 et NetCDF). L’avantage de passer par ces interfaces est de disposer d’un modèle unifié pour exploiter deux API très différents, tout en se gardant la possibilité de basculer plus facilement à une autre bibliothèque si désiré.

7.2. Les suites de tests

En plus des tests propres au projet, Apache SIS utilise aussi des tests définis par GeoAPI. Ces tests ont l’avantage d’utiliser une source externe définissant ce que doivent être les résultats (par exemple les valeurs des coordonnées obtenues par une projection cartographique). On réduit ainsi le risque que des tests soient davantage des tests anti-régression que des tests de validité. Ces tests peuvent aussi être utilisés par des projets autres que Apache SIS.

Le module geoapi-conformance fournit des validateurs, une suite de tests JUnit et des générateurs de rapports sous forme de pages HTML. Ce module peut être utilisé avec n’importe quelle implémentation de GeoAPI. Pour les développeurs d’une bibliothèque géospatiale, il offre les avantages suivants:

7.2.1. Validations des instances

GeoAPI peut valider une instance de ses interfaces en vérifiant que certaines contraintes sont respectées. Certaines contraintes ne peuvent pas être exprimées dans la signature de la méthode. Ces contraintes sont généralement décrites textuellement dans les spécifications abstraites ou dans la javadoc.

Exemple: La conversion ou transformation d’une coordonnée (CC_CoordinateOperation) peut nécessiter l’enchaînement de plusieurs étapes. Dans une telle chaîne d’opérations (CC_ConcatenatedOperation), pour chaque étape (CC_SingleOperation) le nombre de dimensions en sortie doit être égal au nombre de dimensions attendu en entré par l’opération suivante. Exprimée en langage Java, cette contrainte stipule que pour tout index 0 < i < nn est le nombre d’opérations, on a coordOperation[i].targetDimensions == coordOperation[i-1].sourceDimensions.

La façon la plus simple d’effectuer ces vérifications est d’appeler les méthodes statiques validate(…) de la classe org.opengis.test.Validators. Ces méthodes portant toutes le même nom, il suffit d’écrire “validate(valeur)” et de laisser le compilateur choisir la méthode la plus appropriée en fonction du type d’objet donné en argument. Si le type d’objet n’est pas connu au moment de la compilation, alors la méthode dispatch(Object) peut se charger de rediriger le travail à la méthode validate(…) appropriée.

Toutes les fonctions validate(…) suivent le fil des dépendances, c’est-à-dire qu’elles valideront aussi chaque composantes de l’objet à valider. Par exemple la validation d’un objet GeographicCRS impliquera la validation de sa composante GeodeticDatum, qui impliquera elle-même la validation de sa composante Ellipsoid, et ainsi de suite. Il est donc inutile de valider soi-même les composantes, à moins de vouloir isoler le test d’un élément souvent problématique.

Par défaut, les validations sont aussi strictes que possible. Il est possible toutefois d’assouplir certaines règles. L’assouplissement le plus fréquent consiste à tolérer l’absence d’attributs qui étaient sensés être obligatoires. Cette règle et quelques autres peuvent être modifiées globalement pour tous les tests exécutés dans la JVM courante comme dans l’exemple suivant:

import org.opengis.metadata.Metadata;
import org.opengis.test.Validators;
import org.junit.Test;

public class MyTest {
    /*
     * Tolérer l’absence d’attributs obligatoires dans les paquets metadata et citation.
     * Cette modification s’appliquera à tous les tests exécutés dans la JVM courante.
     * S’il existe plusieurs classes de tests, cette initialisation peut être effectuée
     * dans une classe parente à toutes les classes de tests.
     */
    static {
        Validators.DEFAULT.metadata.requireMandatoryAttributes = false;
        Validators.DEFAULT.citation.requireMandatoryAttributes = false;
    }

    @Test
    public void testMyMetadata() {
        Metadata myObject = …; // Construisez un objet ici.
        Validators.validate(myObject);
    }
}

Les règles peuvent aussi être modifiées pour une suite de tests particulière, sans affecter la configuration par défaut de la JVM courante. Cette approche nécessite de créer une nouvelle instance du validateur dont on souhaite modifier la configuration.

import org.opengis.metadata.Metadata;
import org.opengis.test.ValidatorContainer;
import org.junit.Test;

public class MyTest {
    private final ValidatorContainer validators;

    public MyTest() {
        validators = new ValidatorContainer();
        validators.metadata.requireMandatoryAttributes = false;
        validators.citation.requireMandatoryAttributes = false;
    }

    @Test
    public void testMyMetadata() {
        Metadata myObject = …; // Construisez un objet ici.
        validators.validate(myObject);
    }
}

7.2.2. Exécution des tests pré-définis

Des classes de tests JUnit sont définies dans des sous-paquets de org.opengis.test. Toutes les classes de tests portent un nom se terminant en "Test". GeoAPI définie aussi une classe org.opengis.test.TestSuite englobant l’ensemble des tests définis dans le module geoapi-conformance, mais Apache SIS ne l’utilise pas. Apache SIS hérite plutôt des classes *Test de GeoAPI au cas-par-cas, dans les modules appropriés. L’exemple ci-dessous donne un exemple de test de GeoAPI personnalisé. La Javadoc de la classe parente documente en détails les tests effectués. Dans cet exemple, un seul test est modifié et tous les autres tests sont hérités tels quels (il n’est pas nécessaire de les répéter dans la sous-classe). Toutefois, cet exemple ajoute une vérification supplémentaire, annotée @After, qui sera exécutée après chacun des tests.

import org.junit.*;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.opengis.test.referencing.ParameterizedTransformTest;
import static org.junit.Assert.*;

@RunWith(JUnit4.class)
public class MyTest extends ParameterizedTransformTest {
    /**
     * Spécifie aux tests de GeoAPI notre propre fabrique de transformations de coordonnées.
     * GeoAPI testera les objets construits par cette fabrique.
     */
    public MyTest() {
        super(new MyMathTransformFactory());
    }

    /**
     * Modifie le comportement d’un test. Cet exemple assouplit un peu les exigences de ce test,
     * en acceptant des erreurs jusqu’à 10 centimètres plutôt que la valeur par défaut de 1 cm.
     * Ce changement ne s’applique qu’à cette méthode est n’affecte pas les autres tests hérités.
     */
    @Test
    @Override
    public void testLambertAzimuthalEqualArea() throws FactoryException, TransformException {
        tolerance = 0.1; // Tolérance de 10 cm.
        super.testLambertAzimuthalEqualArea();
    }

    /**
     * Vérification supplémentaire effectuée après chaque test, hérité ou non.
     * Dans cet exemple, nous vérifions que la transformation qui a été testée
     * travaillait bien dans des espaces bi-dimensionnels.
     */
    @After
    public void ensureAllTransformAreMath2D() {
        assertTrue(transform instanceof MathTransform2D);
    }
}

7.3. Notes de design

Les chapitres suivants expliquent les raisons derrières certains choix d'implémentation de Apache SIS.

7.3.1. Utilisation des transformations affines

Parmi les sortes d’opérations qu’un SIG doit effectuer sur les coordonnées spatiales, les transformations affines sont à la fois relativement simples et très fréquentes. Les transformations affines peuvent représenter n’importe quelle combinaison d’échelles, de cisaillements, de retournements, de rotations ou de translations, qui sont toutes des opérations linéaires. Les transformations affines ne peuvent pas effectuer des opérations non-linéaires telles que les projections cartographiques, mais couvrent néanmoins de nombreux autres cas:

Les transformations affines peuvent se combiner efficacement. Peu importe le nombre de transformations affines que l’on enchaîne, le résultat sera exprimable par une seule transformation affine. Cette propriété est plus facilement visible lorsque les transformations affines sont exprimées sous forme de matrices: pour combiner les opérations, il suffit de multiplier les matrices. Le cas des conversions des coordonnées pixels en coordonnées géographiques ci-dessous donne un exemple.

Exemple: supposons que nous disposons d’une image dont les coordonnées des pixels sont représentées par (x,y). Supposons aussi que les contraintes suivantes sont respectées:

  • Il n’y a ni cisaillement, ni rotation, ni retournement de l’image.
  • Tous les pixels ont la même largeur en degrés de longitude.
  • Tous les pixels ont la même hauteur en degrés de latitude.
  • Les coordonnées des pixels commencent à (0,0) inclusivement.

Alors la conversion des coordonnées pixels (x,y) vers les coordonnées géographiques (λ,φ) peut être représentée par les équations suivantes, où Nx est la largeur et Ny la hauteur de l’image en nombre de pixels.

λ = Sλx+Tλ         where         Sλ = λmax- λmin Nx     and     Tλ = λmin φ = Sφy+Tφ         where         Sφ = φmax- φmin Ny     and     Tφ = φmin

Les équations ci-dessus peuvent être représentées sous forme de matrices comme ci-dessous:

λ φ 1 = Sλ 0 Tλ 0 Sφ Tφ 0 0 1 × x y 1

Dans ce cas particulier, les facteurs d’échelles S correspondent à la taille des pixels en degrés et les termes de translations T correspondent aux coordonnées géographiques d’un coin de l’image (pas nécessairement le coin inférieur gauche si certains axes ont été retournés). Cette interprétation directe n’est possible que lorsque les contraintes énumérées plus haut sont respectées. Les coefficients des matrices deviennent plus complexes si l’image a un cisaillement ou une rotation, ou si les coordonnées des pixels ne commencent pas à (0,0). Toutefois il n’est pas nécessaire d’utiliser des équations plus complexes pour gérer des cas plus génériques. L’exemple ci-dessous prend comme point de départ la matrice d’une « conversion initiale » dans laquelle les coefficients S et T sont les valeurs relativement simples données ci-dessus. Ensuite la direction de l’axe des y est inversée de manière à correspondre à la convention habituelle des systèmes de coordonnées des images (changement 1), et les axes sont intervertis de manière à avoir la latitude avant la longitude (changement 2). Notez que les concaténations de transformations affines écrites sous forme de multiplications matricielles se lisent de droite à gauche: A×B×C est équivalent à d’abord appliquer l’opération C, suivit de l’opération B et finalement l’opération A.

Un principe clé est qu’il n’y a pas besoin d’écrire un code dédié à ce type d’opérations sur les axes. Ces opérations, et bien d’autres, sont prises en compte naturellement par l’algèbre matricielle. On y gagne en généricité du code et en performance. Apache SIS suit ce principe en utilisant les tranformations affine pour toutes les opérations où elles peuvent s’appliquer. Il n’y a par exemple aucun code dédié au changement de l’ordre des ordonnées dans une coordonnée.

7.3.1.1. Intégration avec les bibliothèques graphiques

A peu près toutes les bibliothèques graphiques supportent une forme de transformation de coordonnées, souvent les transformations affines ou une légère généralisation. Chaque bibliothèque défini son propre API. Quelques exemples sont:

Implémentations des transformations affines dans des bibliothèques graphiques
Bibliothèque Implementation de la transformation Dimensions
Java2D java.awt.geom.AffineTransform 2
Java3D javax.media.j3d.Transform3D 3
JavaFX javafx.scene.transform.Affine 2 ou 3
Java Advanced Imaging (JAI) javax.media.jai.PerspectiveTransform 2
Android android.graphics.Matrix 2

Toutefois dans plusieurs cas, les transformations affines sont les seuls types d’opérations supportées par la bibliothèque graphique. Apache SIS a besoin de gérer un plus grand nombre de type d’opérations, parmi lesquelles les transformations affines ne sont que des cas particuliers. Les projections cartographiques et les changements de référentiels notamment, ne peuvent pas être représentés par des transformations affines. SIS a aussi besoin de transformer des points ayant un nombre arbitraire de dimensions, alors que les API cités ci-haut restreignent leur usage à un nombre fixe de dimensions. Pour ces raisons, SIS ne peut pas utiliser directement ces API. SIS utilise plutôt une interface plus abstraite, org.opengis.referencing.transform.MathTransform. Mais dans le cas particulier où la transformation est réellement affine, SIS peut essayer d’utiliser une implémentation existante, surtout Java2D. Par exemple le code suivant peut être utilisé dans les situations où un objet Java2D est désiré:

MathTransform mt = ...;    // N’importe quelle instance créée par Apache SIS.
if (mt instanceof AffineTransform) {
    AffineTransform at = (AffineTransform) mt;
    // Utiliser l’API de Java2D API à partir d’ici.
}

Toutefois SIS n’utilisera Java2D que sur une base opportuniste. Le forçage de type ci-haut n’est pas garantit de réussir, même si l’instance de MathTransform répond aux conditions qui devrait permettre un usage de Java2D.

7.3.2. Particularités d’une bibliothèque de calculs matriciels pour un SIG

Les SIG font un usage intensif de matrices afin d’afficher leurs cartes ou transformer des coordonnées. On pourrait croire que le marché est suffisamment bien pourvu en excellentes bibliothèques de calculs matriciels, libres ou commerciales. Pourtant, les SIG ont des besoins spécifiques qui divergent un peu des objectifs de plusieurs bibliothèques existantes. Des manipulations de matrices comme celles décrites dans le chapitre sur les transformations affines interviennent dans quasiment toutes les opérations appliquées par Apache SIS sur des coordonnées. Mais l’analyse de ces opérations révèle quelques patterns:

Ces points font que, pour un SIG comme Apache SIS, la précision d’une bibliothèque de calculs matriciels est plus importante que la performance. Paradoxalement, un bon moyen de gagner en performance est justement d’investir davantage de temps de CPU pour effectuer des opérations matricielles plus précises dans la phase de préparation (non d’exécution) de la transformation de coordonnées, car on augmente ainsi les chances de détecter correctement quelles opérations s’annulent. L’effort investit dans cette détection permet de sauver du temps là où ça compte: quand viendra le moment de boucler sur des millions de coordonnées à transformer.

Mais les bibliothèques dédiées aux calculs matriciels sont souvent conçues pour opérer de manière très performante sur des matrices de grandes tailles, ayant par exemple des milliers de lignes et colonnes. Elles sont ainsi conçues pour être capable de résoudre efficacement des systèmes d’équations linéaires comportant des centaines d’inconnues. Les problèmes qu’elles résolvent sont certes difficiles, mais assez différents de ceux qui intéressent Apache SIS. Pour cette raison, et aussi à cause d’un autre besoin spécifique détaillé dans les paragraphes suivants, Apache SIS utilise ses propres fonctions de calculs matriciels. Ces fonctions tentent de résoudre le problème de précision en utilisant l’arithmétique « double-double » (une technique permettant de simuler une précision d’environ 120 bits) au prix de la performance pendant une étape (la préparation de la transformation) où elle n’est pas jugée critique.

7.3.2.1. Que faire des matrices qui ne sont pas carrées (et pourquoi)

Apache SIS a très souvent besoin d’inverser des matrices, afin d’obtenir une conversion de coordonnées dans la direction inverse de la conversion originale. Mais on n’inverse habituellement que des matrices carrées. Or, SIS a besoin d’effectuer des inversions de matrices non-carrées. Selon que l’on ait plus de lignes ou plus de colonnes:

Pour mieux comprendre les difficultés que causerait une transposition trop directe des bibliothèques d’algèbre linéaire aux SIG, imaginons une conversion (φ₁, λ₁, h) → (φ₂, λ₂) qui projetterait les points d’un espace 3D vers une surface 2D. Les termes φ sont des latitudes, λ des longitudes et (φ₂, λ₂) n’égale pas forcement (φ₁, λ₁) si la hauteur h n’est pas perpendiculaire à la surface.

φ2 λ2 1 = 1 0 0 0 0 1 0 0 0 0 0 1 × φ1 λ1 h 1

Pour des bibliothèques d’algèbre linéaire, la matrice ci-dessus représente un système d’équations sous-déterminé, et donc insoluble. C’est-à-dire qu’on ne peut pas inverser cette conversion pour obtenir (φ₂, λ₂) → (φ₁, λ₁, h) puisqu’on ne sait pas quelle valeur donner à h, ce qui implique qu’on ne peut pas trouver (φ₁, λ₁) non-plus car ces valeurs dépendent peut-être de h. Toutefois dans le cas des SIG, l’axe des hauteurs ellipsoïdales h est perpendiculaire à la surface de l’ellipsoïde sur laquelle les latitudes et longitudes géodésiques (φ, λ) sont représentées (notez que cette affirmation ne s’applique pas aux latitudes et longitudes géocentriques). Cette perpendicularité rend φ₁ et λ₁ indépendants de h. Dans ce genre de cas, on peut encore sauver les meubles.

Apache SIS procède en vérifiant si les valeurs de h sont indépendantes des valeurs de φ et λ. Nous reconnaissons ce cas en vérifiant quels coefficients de la matrice ont la valeur zéro. Si SIS arrive à identifier des dimensions indépendantes, il peut les exclure temporairement de manière à inverser sans ambiguïté la conversion dans les dimensions restantes. S’il ne trouve pas de dimension indépendante, alors une exception est levée. Mais si une inversion a été possible, alors il reste à décider du sort des dimensions que SIS avait temporairement exclues. Par défaut, SIS assignera la valeur NaN (Not-a-Number) à ces dimensions. Toutefois dans le cas particulier des hauteurs ellipsoïdales h dans une opération (φ₂, λ₂) → (φ₁, λ₁, h), la valeur zéro peut aussi être appropriée dans l’hypothèse où les coordonnées sont habituellement proches de la surface de l’ellipsoïde. Mais dans tous les cas, le choix du coefficient à mettre à 0 ou NaN est basé sur la présomption que la matrice représente une transformation affine. Ce n’est pas une opération qui peut être effectuée sur des matrices arbitraires.

Le traitement particulier décrit ci-haut permet à Apache SIS de résoudre certains systèmes d’équations sous-déterminés que l’on rencontre couramment dans les SIG. Dans notre exemple utilisant NaN comme valeur par défaut, la coordonnée h reste inconnue – nous ne faisons pas surgir de l’information du néant – mais au moins les coordonnées (φ, λ) ont pu être récupérées. Le problème inverse, celui des systèmes surdéterminés, est plus subtil. Une approche classique des bibliothèques d’algèbre linéaire est de résoudre les systèmes surdéterminés par la méthode des moindres carrées. Transposée à notre exemple, cette approche proposerait une conversion (φ₂, λ₂, h) → (φ₁, λ₁) qui semble le meilleur compromis pour diverses valeurs de φ₂, λ₂ et h, tout en n’étant (sauf cas particuliers) une solution exacte pour personne. De plus, les éventuelles combinaisons linéaires entre ces trois variables sont délicates compte tenu de l’hétérogénéité des unités de mesures, avec par exemple les h en mètres et (φ, λ) en degrés. Apache SIS procède plutôt comme pour les systèmes sous-déterminés: en exigeant que certaines dimensions soient indépendantes des autres, faute de quoi la matrice sera considérée non-inversible. En conséquence dans le cas des systèmes surdéterminés SIS refusera d’effectuer certaines opérations que les bibliothèques d’algèbre linéaire auraient faite, mais en cas de succès les conversions obtenues seront garanties exactes (aux erreurs d’arrondissement prêts).

7.3.2.2. La bibliothèque matricielle de Apache SIS

En résumé, les besoins qui ont amené Apache SIS à fournir ses propres fonctions de calculs matriciels sont:

Cette bibliothèque est fournie dans le paquet org.apache.sis.matrix du module sis-referencing.