Reduce direct dependency to Apache SIS

Previous chapters used Apache SIS static methods for convenience. In some cases, usage of those convenience methods can be replaced by Java code using only GeoAPI methods. Such replacements may be desirable for applications who want to reduce direct dependency toward Apache SIS, for example in order to ease migrations between SIS and other GeoAPI implementations. However this may require that applications write their own convenience methods. The following sections provide some tip for easing this task.

Mapping given by @UML annotations

For each class, method and constant defined by an OGC or ISO standard, GeoAPI indicates its provenance using annotations defined in the org.opengis.annotation package. This mapping is described in the chapter about GeoAPI. Java reflection methods allow access to this information during the execution of an application. Class org.apache.sis.util.iso.Types provides static convenience methods like getStandardName(Class), but one can avoid those methods. The following example displays the standard name for the method getTitle() from the Citation interface:

Class<?> type   = Citation.class;
Method   method = type.getMethod("getTitle", (Class<?>[]) null);
UML      annot  = method.getAnnotation(UML.class);
String   id     = annot.identifier();
System.out.println("The standard name for the " + method.getName() + " method is " + id);

The reverse operation — getting the Java class and method from a standard name — is a bit more complicated. It requires reading the class-index.properties file provided in the org.opengis.annotation package. The following example reads the files just before searching for the name of the interface corresponding to CI_Citation. Users are always encouraged to only read this file once and then save its contents in their application’s cache.

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

The org.apache.sis.util.iso.Types convenience method for above task is forStandardName(String).

Fetching implementations of GeoAPI interfaces

GeoAPI defines factories (Factory) that can create implementations of interfaces. For example, DatumFactory provides methods that can create instances which implement interfaces of the org.opengis.referencing.datum package. A Factory must be implemented by a geospatial library, and declared as a service as defined by the java.util.ServiceLoader class. The ServiceLoader javadoc explains this procedure. In brief, the library must create a file in the META-INF/services/ directory, with a name corresponding to the complete name of an interface in the factory (in the preceding example, org.opengis.referencing.datum.DatumFactory). On one line, this text file must include the complete name of the class that implements this interface. This class may be hidden from users, as they do not need to know of its existence.

If the library has correctly declared its factories as services, users may import them by using ServiceLoader, as in the example below. This example only takes the first factory located; if there is more than one factory - for example when multiple libraries coexist — then the choice is left to the user.

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

Implementing GeoAPI oneself in order to meet very specific needs is not difficult. A developer might concentrate on a handful of interfaces among the hundreds available, while keeping other interfaces as extension points to eventually implement as needed.

The conceptual model that the interfaces represent is complex. But this complexity may be reduced by combining certain interfaces. For example, many libraries, even well-known ones, do not distinguish between a Coordinate System (CS) and a Coordinate Reference System (CRS). A developer that also wishes not to make this distinction may implement these two interfaces with the same class. The resulting implementation may have a simpler class hierarchy than that of GeoAPI interfaces. The geoapi-examples module, discussed later, provides such combinations. The following table lists a few possible combinations:

Main Interface Auxiliary Interface Use
CoordinateReferenceSystem CoordinateSystem Description of a spatial reference system (CRS).
GeodeticDatum Ellipsoid Description of the geodetic datum.
CoordinateOperation MathTransform Coordinate transformation operations.
IdentifiedObject ReferenceIdentifier An objet (usually a CRS) that we can identify by a code.
Citation InternationalString Bibliographic reference consisting of a simple title.
GeographicBoundingBox Extent Spatial area in degrees of longitude and latitude.
ParameterValue ParameterDescriptor Description of a parameter (name, type) associated with its value.
ParameterValueGroup ParameterDescriptorGroup Description of a set of parameters associated with their values.

The geoapi-examples module provides examples of simple implementations. Many of these classes implement more than one interface at a time in order to provide a simpler conceptual model. The javadoc for this module lists key packages and classes along with the combinations performed. This module illustrates not only how GeoAPI might be implemented, but also how the implementation might be tested using geoapi-conformance.

Although its primary goal is to serve as a source of inspiration for implementors, geoapi-examples was also designed so as to be usable by applications with very simple needs. As all the examples are in the public domain, developers are invited to freely adapt copies of these classes as necessary. However, if changes are made outside the framework of the GeoAPI project, fair use demands that modified copies be placed in a package with a different name than org.opengis.

For somewhat more involved needs, developers are invited to examine the geoapi-proj4 and geoapi-netcdf modules. These two modules provide examples of adaptors that are allowed, via GeoAPI interfaces, to use some of the features of external libraries (Proj.4 and netCDF). The advantage of using these interfaces is to provide a unified model to operate two very different APIs, while retaining the ability to switch easily to another library if desired.