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.

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

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(…);
    }
}

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