Introduction to Apache SIS®
Table des matières
A geospatial information community is a collection of systems or individuals capable of exchanging their geospatial data through the use of common standards, allowing them to communicate with one another. As there are many ways to represent geospatial information, each community tends to structure this information in light of its areas of interest. This diversity complicates the task of the users of (SIS) geographic information systems by confronting them with an apparently chaotic variety of data formats and structures. The characteristics of these structures vary according to the phenomenon observed and methods of measurement, as well as the habits of the organizations producing the data. Such a variety represents an obstacle in studies that require heterogeneous combinations of data, especially when they originate in communities that are traditionally distinct. For example, a researcher studying cholera might be interested in populations of shrimp as a propagation vector of the disease. But as doctors and oceanographers are not usually in the habit of sharing their work, the participants of such a study may be limited by the effort required to convert the data.
We cannot impose a uniform format on all the collections of data, as the diversity of formats is tied to factors such as the constraints imposed by the measuring apparatus, and the statistical distribution of values. A more flexible solution is to ensure the interoperability of data across a common programming interface (API). This API is not necessarily defined in a programming language; the actual tendency is rather to define conventions that use existing web protocols, which we can translate into various programming languages. But in order for this approach to be viable, the API must be generally accepted by independent developers. In other words, the API must come as near as possible to industrial standards.
For example, one task that would benefit from a successful standardization is the accessing of relational databases. The industry has established a common language - the SQL standard - that the creators of Java have embedded in standard JDBC programming interfaces. Today, these interfaces are implemented by many software programs, both free and commercial. Like databases, methods of accessing geographic information have been standardized. In this case, however, the efforts have been more recent, and their integration in software - especially in older programs - is incomplete and not always coherent. At the time of writing, no product to our knowledge has implemented all of the specifications in their entirety. However, there are many implementations that cover a fairly large spectrum. One of these is the Apache SIS® library that is described in this document.
Apache SIS is characterized by a sustained effort to comply with standards, going so far as to participate in certain OGC projects. In general, complying with standards demands a greater effort than would be required for an isolated development, but rewards us with a double advantage: not only does it improve the interoperability of our data with that of external projects, it also points towards a robust way of elaborating the conceptual model reflected in the API. In effect, the groups of experts who conceived of the standards anticipated difficulties that sometimes escape the engineer at the beginning of a project, but which risk catching up with them before the end.
The elements defined in an information language, such as classes and methods in Java or elements in an XML document, appear in monospaced font. In order to facilitate an understanding of the relationships between Apache SIS and the standards, these elements are also represented using the following colour codes:
CD_Ellipsoid.
Ellipsoid.
DefaultEllipsoid.
String.
Most standards used by Apache SIS have been devised by the Open Geospatial Consortium (OGC), sometimes in collaboration with the International Organization for Standardization (ISO). Some ISO standards themselves become European standards via the INSPIRE Directive, or French standards via AFNOR. These standards offer two key features:
These standards are made available to the international community for free, as specifications (PDF files) or as schemas (XSD files). Standardization organizations do not create software; to obtain an implementation of these specifications, users must choose one of the compliant products available on the market, or develop their own solutions. Such voluntary compliance with these specifications allow independent communities to more easily exchange geographic information.
Besides these formal standardization organizations, there are organizations that are not officially dedicated to the creation of standards, but whose work has largely been adopted as de facto standards. In particular, the EPSG database offers numeric codes which allow the easy identification of coordinates among several thousand. This database is offered by petroleum companies that have an interest in the results of their exploration in a particular place, knowing that they don't always control the production of the maps they use. Other examples of de facto standards include GeoTIFF for data distributed on a grid (such as images), and Shapefile for vector data (such as geometric shapes).
The work of the OGC is done by email, teleconferences, and at in-person meetings. The OGC organizes four meetings per year, each lasting five days, and hosted by member organizations that sponsor the event (companies, universities, research centres, etc). The host continent alternates between Europe and North America, with a growing presence in Asia since 2011. These meetings are usually attended by between 50 and 100 participants from among the hundreds of members of the OGC. Some participants are present at almost all the meetings, forming the pillars of the organization. The meetings of the OGC offer opportunities for exchange among members from diverse backgrounds.
The creation of a OGC standard begins with a gathering of organizations or individuals with a common interest in an issue. A working group is proposed as a Discussion Working Group (DWG) if the work is still in the exploratory stages, or as a Standard Working Group (SWG) if the work of standardization is ready to proceed. DWGs are open to all members of the OGC, while SWGs require that their participants enter into an agreement not to hinder the distribution of the standard through intellectual property claims.
In order to be accepted, a standardization project must be supported by a minimum number of members belonging to distinct organizations. These founding members draft a charter defining the objectives of the SWG, which must be approved by the Technical Committee of the OGC. Each founding member is endowed with the right to vote, with a limit of one voting member per organization. Each new member that wishes to join the SWG after its creation is granted the role of observer, and receives on request the right to vote after several months of observation.
A SWG may contain several dozen members, but the volunteers performing the bulk of the work are usually fewer. Their proposals are submitted to the entire membership of the group, who may accept them by unanimous consent. Any objections must be debated, and an alternative proposed. SWGs usually try to debate an issue until a consensus emerges rather than move ahead despite negative votes, even if those opposed are in a minority. The decisions of the group are then integrated into the specifications by a member who assumes the role of editor.
As far as possible, the working group must structure the specifications as a nucleus around which various extensions might be built. A series of tests must accompany the standard, allowing implementations to be classified by the level of test passed. There must be at least one reference implementation that passes all the tests in order to demonstrate that the standard is usable.
When the standard is judged ready, the SWG votes on a motion proposing its submission to a vote of the higher authorities of the OGC. This process takes several months. There is a faster process for approving de facto standards, but it is applied sparingly.
All proposals for standards are first examined by the OGC Architecture Board (OAB). This board ensures that the standard conforms to the requirements of the OGC in form, modularization, and in terms of integration with other standards. If the OAB approves it, the standard is next submitted to a vote of the members of the Technical Committee (TC). This committee consists of the principal members of the OGC, and only they are capable of granting final approval. If approved, the standard is made publicly available for comments during a period of several months. At the end of this period, the SWG must examine and respond to each comment. The eventual modifications of the standard are submitted to the OAB, then the standard is published in its final form. This distribution is announced in a press release by the OGC.
Certain members of the OGC and the TC also act as liaisons with the International Organization for Standardization (ISO). Cooperation between the two organizations goes two ways: the OGC adopts the ISO standards as a foundation on which to develop new standards, and certain new OGC standards become ISO standards.
All users, whether or not they are members of the Open Geospatial Consortium, may propose modifications to OGC standards. A list of current proposals for changes, along with a form for submitting new proposals, is available online. Each proposal is reviewed by the SWG.
Some working groups use other parallel systems for submissions. In particular, the GeoAPI project continues to use a JIRA task system hosted outside of the structures of the OGC. This set-up exists in part for historical reasons, and in part because developments on the GeoAPI project are done more at the level of source code rather than at the level of documents or class diagrams.
OGC standards are specified in several dozen documents. Each document outlines a service - for example, the transformation of coordinates. The function of each service is described by a collection of object classes and their interactions. These elements are illustrated by UML (Unified Modeling Language) diagrams in specifications called "abstracts."
Abstract specifications do not refer to any specific information language. Their concepts may be applied more or less directly to a programming language, a database or an XML schema. There is always an element of arbitrariness in the method of applying an abstract specification, given that adjustments are often necessary to take into account the constraints or conventions of the target language. For example:
At the turn of the millennium, the abstract specifications were explicitly concretized in implementation specifications. The term "implementation" is used here in the sense of all types of interfaces (Java or others) derived from UML diagrams, and not implementations in the Java sense. Such specifications exist for SQL, CORBA, COM, and Java languages. As these languages are capable of executing procedures, the specifications of this period define not only data structures, but also operations that apply to these structures.
Thereafter, enthusiasm for "Web 2.0" increased interest XML over other languages. Older implementation specifications became fell into disuse, and XSD schemas became the principle concretization of abstract specifications. Even the way abstract specifications are designed has evolved: they are less likely to define operations, and so what remains is closer to descriptions of database schemas. Some operations that were defined in older standards now appear, in another form, in web service specifications. Finally, the term "implementation specification" has been abandoned, to be subsumed under the term "OGC standard." But despite their disuse, old implementation specifications remain useful to programs in Java, because:
The Apache SIS project is based on the most recent specifications, drawing from the archives of the OGC to complete certain abstract standards or make them more usable. Some old definitions are preserved as "methods of convenience," not always bringing new functionality, but facilitating the practical use of a library.
The following table lists the principal norms used by the project.
Many norms are published both as ISO standards and as
OGC standards, and their corresponding names are listed next to one another in the first two columns.
Standards that are depreciated but still used appear struck through.
Finally, GeoAPI packets will be introduced in upcoming chapters.
| ISO Norm | OGC Norm | Titre | GeoAPI Packet |
|---|---|---|---|
| Abstract Specifications | |||
| ISO 19103 | Conceptual schema language | org.opengis.util |
|
| ISO 19115 | Topic 11 | Metadata | org.opengis.metadata |
| ISO 19111 | Topic 2 | Spatial referencing by coordinates | org.opengis.referencing |
| ISO 19108 | Temporal Schema | org.opengis.temporal |
|
| ISO 19107 | Topic 1 | Feature geometry | org.opengis.geometry |
| ISO 19101 | Topic 5 | Features | org.opengis.feature |
| ISO 19123 | Topic 6 | Schema for coverage geometry and functions | org.opengis.coverage |
| ISO 19156 | Topic 20 | Observations and measurements | org.opengis.observation |
| Implementation Specifications | |||
| ISO 19139 | Metadata XML schema implementation | ||
| ISO 13249 | SQL spatial | ||
org.opengis.referencing |
|||
org.opengis.coverage |
|||
| SLD | Styled Layer Descriptor | org.opengis.style |
|
| Web Services | |||
| 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 | ||
Standards sometimes favour the application of certain generic terms to particular contexts,
which may differ from the context in which other communities use these terms.
For example, the terms domain and range may apply to arbitrary functions in order to designate
a set of possible values of inputs and outputs respectively.
But the functions to which they are applied by certain ISO standards are not the same as the functions to which they are applied by other libraries.
For example, ISO 19123 applies these terms to CV_Coverage objects,
seen as functions in which the domain is the set of spatio-temporal coordinates encompassed by the data,
and the range is the set of values encompassed.
But UCAR's NetCDF library applies these terms instead to the function of converting pixel indices (its domain) to spatial-temporal coordinates (its range).
Thus the UCAR library's range may be the domain of ISO 19123.
The Apache SIS library prefers as much as possible to use terms in the sense of OGC and ISO norms. Particular care must be taken, however, with the interfaces between SIS and certain other external libraries, in order to reduce the risk of confusion.
The GeoAPI project offers a set of Java interfaces for geospatial applications.
In a series of org.opengis.* packets, GeoAPI defines structures representing metadata,
reference systems of coordinates and operations that perform cartographic projections.
In one part that is not yet standardized - called pending - GeoAPI defines structures that represent geo-referenced images,
geometries, filters that can be applied to queries, and other features.
These interfaces closely follow the specifications of the OGC,
while interpreting and adapting them to meet the needs of Java developers - for example, conforming with naming conventions.
These interfaces benefit both client applications and libraries:
Developers of client applications benefit from the greater knowledge base available on the Internet (due to the many publications related to OGC standards), as well as increased interoperability. Interoperability is facilitated by a better separation between applications that call GeoAPI functions, and libraries that implement GeoAPI. The separation is similar to that offered by the JDBC (Java Database Connectivity) interfaces of standard Java. Using the interfaces' API, developers can ignore the underlying implementation. For example, they can perform cartographic projections with the help of the Proj.4 library, or the Apache SIS library, without having to change their programs when they change libraries.
The developers of libraries inherit the expertise of the specifications' authors, via the models that represent interfaces. GeoAPI also provides a framework within which developers can prioritize the implementation of the features they most need, while providing points on which to build future developments. For example, clients can call a GeoAPI function even if it is not yet supported by the library, and simply get a null value until a new version of the library returns a relevant value.
Since OGC standards are defined by well-tested software engineering methods,
it is possible to automatically generate Java interfaces using relatively common tools.
One of the most commonly-used approaches is to transform XSD schemas
into Java interfaces using command line utility xjc.
As this utility is included in most Java distributions (it is one of the JAXB tools),
this approach is favoured by many projects found on the Internet.
Other approaches use tools integrated into the Eclipse Development Environment,
which is based on UML schemas rather than XSD ones.
A similar approach was attempted in the early days of the GeoAPI project, but was quickly abandoned. We favor a manual approach for the following reasons:
Some XSD schemas are much more verbose than the original UML schemas.
Converting from XSD schemas introduces - at least in the case of metadata -
almost double the number of interfaces actually defined by the standard, without adding any new features.
XSD schemas also define attributes specific to XML documents (id,
uuid, xlink:href, etc.), that do not exist in the original UML diagrams,
and which we do not necessarily wish to display in a Java API.
Converting from UML schemas avoids this problem, but tools capable of performing this operation are less common.
Example:
XSD metadata schemas insert a <gmd:CI_Citation> element
inside a <gmd:citation>,
a <gmd:CI_OnlineResource> element inside a <gmd:onlineResource>,
and so on for the hundreds of classes defined by ISO standard 19115.
This redundancy is certainly not necessary in a Java program.
OGC standards use different naming conventions than Java.
In particular, the names of almost all OGC classes begin with a two-letter prefix,
such as MD_Identifier.
This prefixes fulfill the same role as package names in Java.
GeoAPI adapts this practice by using interface names without prefixes and placing these interfaces in packages corresponding to the prefixes,
but with more descriptive names.
Occasionally we also change the names; for example, to avoid acronyms, or to conform to an established convention such as JavaBeans.
Example:
The OGC class MD_Identifier becomes the
Identifier interface in the org.opengis.metadata package.
The OGC class SC_CRS becomes the CoordinateReferenceSystem interface,
and the usesDatum association becomes a getDatum() method,
rather than the "getUsesDatum()" that would result from an automatic conversion tool.
We do not allow programs to blindly apply rules that ignore the conventions of the community whose schemas we translate.
The standards may contain structures that do not have a direct equivalent in Java, such as unions similar to what we would find in C/C++. The strategy used to obtain an equivalent feature in Java depends on the context: multiple inheritance of interfaces, modification of the hierarchy, or simply omitting the union. These decisions are made case-by-case based on a needs analysis.
Example:
ISO Standard 19111 defines different types of coordinate systems, such as spherical, cylindrical, polar or Cartesian.
It then defines several subsets of these types of coordinate systems systems.
These subsets, represented by unions, serve to specify that a class may only be associated with a particular type of coordinate system.
For example, a union of types may be associated with an image, named CS_ImageCS,
which may only contain CS_CartesianCS and CS_AffineCS.
In this case, we get the desired effect in Java through a modification of the hierarchy of classes:
we define the CartesianCS interface as a specialization of AffineCS,
which is semantically correct.
But it is not possible to apply a similar strategy to other unions without violating the semantics.
Several specifications overlap. GeoAPI performs the work of integration by replacing some duplicate structures with references to equivalent structures from the standards that best represent them.
Example:
ISO Standard 19115, which defines metadata structures,
also attempts to describe a few structures representing coordinate reference systems (CRS).
Yet these are also the focus of another standard: ISO 19111.
At the same time, ISO 19111:2007 states in section 3 that it reuses all of the elements of ISO 19115 except
MD_CRS and its components.
GeoAPI interfaces reduce the redundancy by applying the exclusion recommended by ISO 19111 to the entire project.
The complexity of some standards have increased for historical reasons rather than technical ones, related to the standardization process. GeoAPI reduces the technical debt by designing interfaces with each element in its proper place, regardless of the chronological order in which the standards were published.
Exemple:
ISO Standard 19115-2 is an extension of ISO Standard 19115-1, adding image metadata structures.
These metadata were defined in a separate standard because they were not yet ready when the first part of the standard was published.
As it was not possible for administrative reasons to add attributes to already-published classes,
the new attributes were added in a sub-class bearing almost the same name.
Thus, ISO Standard 19115-2 defines the class MI_Band,
which extends the class MD_Band from ISO Standard 19115-1 by adding attributes that would have appeared
directly in the parent class if there had been time.
In GeoAPI, we have chosen to "repair" these anomalies by fusing these two classes into a single interface.
Deviations from the standards are documented in each class and method affected.
Each mention of a deviation is also collected on a single page in order to provide an overview.
Since these deviations blur the relationships between the standards and certain Java interfaces,
the correspondence between these languages is explained by @UML annotations and property files described in the following section.
@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.
In particular, the @UML annotations indicates the standard,
the name of the element in that standard, and also its obligation level.
For example, in the following code snippet, the first @UML code indicates that the Java interface that follows
(ProjectedCRS) is defined using the SC_ProjectedCRS type of ISO Standard 19111.
The second @UML annotation, this time applied to the getCoordinateSystem() method,
indicates that this method is defined using the coordinateSystem association of ISO Standard 19111,
and that this association is mandatory - meaning, in Java, that the method is not allowed to return a null value.
packageorg.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 interfaceProjectedCRSextendsGeneralDerivedCRS{/** * Returns the coordinate system, which must be Cartesian. */@UML(obligation = MANDATORY, specification = ISO_19111, identifier = "coordinateSystem")CartesianCS getCoordinateSystem(); }
Java introspection methods allow access to this information during the execution of an application.
This is useful for displaying names for users familiar with OGC standards,
or for writing elements in an XML document.
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);UMLannot = method.getAnnotation(UML.class); String ident = annot.identifier(); System.out.println("The standard name for the method " + method.getName() + " is " + ident);
The class org.apache.sis.util.iso.Types provides the commodity method
getStandardName(Class) to perform this operation.
The reverse operation - getting the Java class and method of 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 ISO " + isoName + " is " + type);
The class org.apache.sis.util.iso.Types provides the commodity method
forStandardName(String) to perform this operation.
Come classes and methods have neither an @UML annotation, nor an entry in the class-index.properties file.
They are either extensions of GeoAPI, or else types defined in other libraries, such as standard JDK.
In this last case, the correspondence to ISO standards is implicit.
The following table describes this correspondance for ISO Standard 19103 types.
Standard Java's original types are preferred when applicable,
but where necessary their equivalents in the form of objects are used in order to authorize null values.
| Type ISO | Type JDK | Remarks |
|---|---|---|
| Numbers | ||
Integer |
int |
Sometimes java.lang.Integer for optional attributes. |
Integer (in some cases) |
long |
Sometimes java.lang.Long for optional attributes. |
Real |
double |
Sometimes java.lang.Double for optional attributes. |
Decimal |
java.math.BigDecimal |
|
Number |
java.lang.Number |
|
| Texts | ||
FreeText |
(no equivalent) | See org.opengis.util.InternationalString below. |
CharacterString |
java.lang.String |
Often org.opengis.util.InternationalString (see below). |
LocalisedCharacterString |
java.lang.String |
|
Sequence<Character> |
java.lang.CharSequence |
|
Character |
char |
|
| Dates and hours | ||
Date |
java.util.Date |
|
Time |
java.util.Date |
|
DateTime |
java.util.Date |
|
| Collections | ||
Collection |
java.util.Collection |
|
Bag |
java.util.Collection |
A Bag is similar to a
Set without being restricted by uniqueness. |
Set |
java.util.Set |
|
Sequence |
java.util.List |
|
Dictionary |
java.util.Map |
|
KeyValuePair |
java.util.Map.Entry |
|
| Enumerations | ||
Enumeration |
java.lang.Enum |
|
CodeList |
(pas d’équivalent) | See org.opengis.util.CodeList below. |
| Various | ||
Boolean |
boolean |
Sometimes java.lang.Boolean for optional attributes. |
Any |
java.lang.Object |
|
The nearest equivalent for CharacterString is the String class,
but GeoAPI often uses the InternationalString interface, allowing the client to choose the language.
For example, it is useful on a server that simultaneously provides pages in multiple languages.
By returning translations when objects are used rather than at the time of their creation,
we allow the SIS library to provide the same instances of Metadata
or Coverage (for example) for the same data, regardless of the client's language.
Translations may be made on the fly with the help of the application's ResourceBundle,
or may be provided directly with the data (as in the case of Metadata).
An Enumeration corresponds to an Enum in Java.
Both define all authorized values, without allowing the user to add any.
A CodeList is similar to an enumeration, except that users may add their own items.
Standard JDK does not offer this possibility.
GeoAPI defines an abstract CodeList class that reproduces some of the functions of Enum while being extensible.
Extensions are derived from valueOf(String) static methods, which, in contrast to Enum,
creates new instances if the name provided does not correspond to the name of an existing instance.
MediumNamecdRom =MediumName.CD_ROM;MediumNameusbKey =MediumName.valueOf("USB_KEY");// There is no constraint on this value.assertMediumName.valueOf("CD_ROM") == cdRom : "valueOf must return existing constants."; assertMediumName.valueOf("USB_KEY") == usbKey : "valueOf must hide the previously requested values.";
GeoAPI defines factories (Factory) that can create implementations of its interfaces.
For example, DatumFactory provides methods that can create instances which implement the interfaces of a
org.opengis.referencing.datum package.
A Factory must be implemented by a geospatial library,
and declared as a service as defined by by the standard 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 existance.
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.
importorg.opengis.referencing.GeodeticDatum; importorg.opengis.referencing.DatumFactory; import java.util.ServiceLoader; public class MyApplication { public void createMyDatum() { ServiceLoader loader = ServiceLoader.load(DatumFactory.class);DatumFactoryfactory = loader.iterator().next();GeodeticDatummyDatum = factory.createGeodeticDatum(…); } }
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 referential. |
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 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 project consists of a standardized part (geoapi)
and an experimental part (geoapi-pending).
As these two parts are mutually exclusive, users must take care not to mix them in the same project.
This separation is guaranteed for all projects that depend only on the Maven central repository
(including the final versions of Apache SIS),
as the geoapi-pending module is never deployed on this central repository.
By contrast, snapshots of certain SIS branches may depend on geoapi-pending.
GeoAPI modules are:
geoapi — includes interfaces covered by the
GeoAPI standard of the OGC.
The final versions of Apache SIS depend on this module.
geoapi-pending — contains a
copy of all interfaces in the geoapi module
(not a dependence) with additions that have not yet been approved as an OGC standard.
Some additions appear in interfaces normally defined by the geoapi module, hence the need to copy them.
Snapshot versions of jdk6 branches, and Apache SIS's jdk7 and jdk8 depend on this module,
but this dependence becomes a dependence on the geoapi standard module when the branches are joined to the trunk.
geoapi-conformance — includes a JUnit test suite that developers may use to test their implementations.
Snapshot and milestones versions depend on the geoapi-pending module,
while the final versions depend on geoapi.
geoapi-examples — includes examples of relatively simple implementations.
These examples are placed in the public domain in order to encourage users to copy and adapt them to their needs if
Apache SIS services are unsuitable.
geoapi-proj4 — contains a partial implementation of org.opengis.referencing
packages as adaptors based on the C/C++ library Proj.4.
This module may be used as an alternative to the sis-referencing module for certain functions.
geoapi-netcdf — contains a partial implementation of org.opengis.referencing
and org.opengis.coverage packages as adaptors based on the NetCDF library
of the UCAR.
The series of tests in this module was developed in such a way as to be reusable for other projects.
Apache SIS uses them to test its own sis-netcdf module.
geoapi-openoffice — contains an add-in for the OpenOffice.org office suite.
geoapi and geoapi-pending modules provide interfaces derived from the
UML schemas of the international standards.
The conceptual model will be explained in detail in the chapters describing the implementation of Apache SIS.
However, we can get an overview of its content by consulting the page listing
GeoAPI methods with their original standards.
The geoapi-conformance module provides validators, a JUnit test suite, and report generators
in the form of HTML pages.
This module may be used with any GeoAPI implementation.
For developers of a geospatial library, it offers the following advantages:
geoapi-conformance has its own test suite and is applied to other implementations.GeoAPI can validate an instance of its interfaces by checking that certain constraints are observed. These constraints, which may only be expressed in the method signature, are usually described textually in the abstract specifications or in the javadoc.
Example:
The transformation of a coordinate (CC_CoordinateOperation) may require a sequence of several steps.
In such a sequence of operations (CC_ConcatenatedOperation), for each step (CC_SingleOperation)
the number of dimensions of the output must equal the number of dimensions of the input in the following operation.
Expressed in Java, this constraint stipulates that for the entire index 0 < i < n where n
is the number of operations, we have coordOperation[i].sourceDimensions == coordOperation[i-1].targetDimensions.
The easiest way to perform these verifications is to call the static methods validate(…) of the class
org.opengis.test.Validators.
As all of these methods bear the same name, it is enough to write “validate(value)” and then allow the compiler
to choose the most appropriate method for the type of object given in the argument.
If the object type is not known at the time of compilation,
the method dispatch(Object) may redirect the work to the appropriate validate(…) method.
All validate(…) functions follow a chain of dependencies,
meaning that they will also validate each component of the object to be validated.
For example, the validation of a GeographicCRS implies the validation of its component
GeodeticDatum, which itself implies the validation of its component Ellipsoid, and so on.
Thus it is useless to validate the components themselves, unless you wish to isolate the test for a particular item known to cause problems.
By default, validations are as strict as possible. It is always possible to relax certain rules. The most common is to tolerate the absence of attributes that would normally be mandatory. This rule and a few others may be modified globally for all tests executed by the standard JVM, as in the following example:
importorg.opengis.metadata.Metadata; importorg.opengis.test.Validators; import org.junit.Test; public class MyTest {/* * Tolerate the absence of mandatory attributes in metadata and citation packages. * This modification applies to all tests executed by the standard JVM. * If there are multiple test classes, this initialization may be performed * in a parent class to all test classes. */static {Validators.DEFAULT.metadata.requireMandatoryAttributes= false;Validators.DEFAULT.citation.requireMandatoryAttributes= false; } @Test public void testMyMetadata() {MetadatamyObject = …;// Create an object here.Validators.validate(myObject); } }
Rules may also be modified for a particular test suite without affecting the default configuration of the standard JVM. This approach requires the creation of a new instance of the validator that we wish to modify the configuration.
importorg.opengis.metadata.Metadata; importorg.opengis.test.ValidatorContainer; import org.junit.Test; public class MyTest { private finalValidatorContainervalidators; public MyTest() { validators = newValidatorContainer(); validators.metadata.requireMandatoryAttributes= false; validators.citation.requireMandatoryAttributes= false; } @Test public void testMyMetadata() {MetadatamyObject = …;// Create an object here.validators.validate(myObject); } }
JUnit tests are defined in the org.opengis.test sub-packages.
All test classes bear a name ending in "Test".
In addition, an org.opengis.test.TestSuite class includes all test classes defined in the
geoapi-conformance module.
One way to execute all of these tests at once is to create a sub-class of TestSuite,
perhaps including a static block performing a configuration of validators as in the previous example,
and inherit the tests defined in GeoAPI.
Apache SIS inherits GeoAPI's *Test classes on a case-by-case basis,
in the appropriate modules.
The TestSuite class is used instead in an integration test encompassing all SIS modules.
The example below gives an example of a personalizaed GeoAPI test:
The parent test javadoc
documents the tests performed in detail.
In this example, only one test is modified and all the others are inherited as they are (it is not necessary to repeat them in the sub-class).
However, this example adds a supplemental verrification, annotated with @After, which will be executed after each test.
import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; importorg.opengis.test.referencing.ParameterizedTransformTest; import static org.junit.Assert.*; @RunWith(JUnit4.class) public class MyTest extendsParameterizedTransformTest{/** * Specify our own coordinate transformation factory for the GeoAPI tests. * GeoAPI will test the objects created by this factory. */public MyTest() { super(new MyMathTransformFactory()); }/** * Changes the behaviour of a test. This example relaxes the requirements of this test a little, * by accepting errors of up to 10 centimetres, rather than the default value of 1 cm. * This change only applies to this method, and does not affect the other inherited tests. */@Test @Override public void testLambertAzimuthalEqualArea() throwsFactoryException,TransformException{tolerance= 0.1; // Tolérance de 10 cm. super.testLambertAzimuthalEqualArea(); }/** * Supplemental verification performed after each test, inherited or not. * In this example, we are verifying that the transformation tested * works correctly in two-dimensional spaces. */@After public void ensureAllTransformAreMath2D() { assertTrue(transforminstanceofMathTransform2D); } }
The geoapi-examples module provides examples of simple implementations.
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é.
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 (une spécification dite abstraite), alors que la représentation de ces classes en XML est décrite par le standard ISO 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 19139 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é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 |
Pour chaque classe de méta-donnée, il existe un type XML nommé comme 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 constitué d’une alternance systématique de deux types d’éléments XML.
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.
Par exemple dans l’exemple ci-haut, gmd:identificationInfo
correspond à la méthode Metadata.getIdentificationInfo().
Contrairement aux API Java toutefois, les documents XML
ne placent pas les propriétés filles directement en dessous.
À la place, ces éléments n’acceptent que les informations suivantes:
gmd:idref, gco:uuidref et xlink:href)
que les schémas XSD de l’OGC
nomment collectivement gco:ObjectReference.
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 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 propriétés filles. Cet élément accepte aussi un groupe d’attributs
(notamment id et uuid) que les schémas XSD
de l’OGC nomment collectivement gco:ObjectIdentification.
Ces attributs n’ont pas de méthodes Java dédiées, mais sont accessibles indirectement via l’interface IdentifiedObject
décrite dans la sous-section suivante.
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 lus et écrits par JAXB.
L’élément englobant peut contenir un attribut id,
uuid ou xlink:href.
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:
| Définir un identifiant | Utiliser l’identifiant défini |
|---|---|
<MD_MetaData>
<identificationInfo>
<MD_DataIdentification id="mon_id">
|
<MD_MetaData> <identificationInfo idref="mon_id"/> </MD_MetaData> |
Le choix de l’attribut à utiliser dépend de la portée de l’élément référencé:
id n’est valide qu’à l’intérieur du document XML
qui définit l’objet ainsi référencé.
uuid peut être valide à l’extérieur du document XML,
mais quelqu’un doit maintenir une base de données fournissant les objets pour chaque UUID donnés.
xlink:href peut faire référence à un autre document XML accessible sur internet.
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:
importorg.apache.sis.metadata.iso.DefaultMetadata; importorg.apache.sis.xml.IdentifierSpace; import java.util.UUID; public class MyClass { public void myMethod() { UUID identifier = UUID.randomUUID();DefaultMetadatametadata = newDefaultMetadata(); 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.
Lorsqu’un attribut n’est pas défini, la méthode correspondante de GeoAPI retourne généralement null.
Toutefois les choses se compliquent lorsque l’attribut manquant est une valeur considérée comme obligatoire par le standard ISO 19115.
Le standard ISO 19139 autorise l’omission d’attributs obligatoires à la condition d’indiquer pourquoi la valeur est manquante.
Les raisons peuvent être que l’attribut 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.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.
| Information présente | Information absente |
|---|---|
<CI_Citation>
<series>
<CI_Series>
|
<CI_Citation> <series nilReason="unknown"/> </CI_Citation> |
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.
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():
A.equals(B) implique B.equals(A) (symétrie);A.equals(B) et B.equals(C) implique A.equals(C) (transitivité);A.equals(B) implique A.hashCode() == B.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:
STRICT — Les objets comparés doivent être de la même classe
et tous leurs attributs strictement égaux, y compris d’éventuels attributs publics propres à l’implémentation.
BY_CONTRACT — Les objets comparés doivent implémenter la même interface de GeoAPI (ou tout autre standard),
mais n’ont pas besoin d’être de la même classe d’implémentation. Seuls les attributs définis dans l’interface sont comparés;
tout autres attributs propres à l’implémentation — même s’ils sont publics — sont ignorés.
IGNORE_METADATA — Comme BY_CONTRACT,
mais ne compare que les attributs qui influencent les opérations (calculs numériques ou autre) effectuées par l’objet.
Par exemple dans un référentiel géodésique, la longitude (par rapport à Greenwich) du méridien d’origine sera pris en compte
alors que le nom de ce méridien sera ignoré.
APPROXIMATIVE — Comme IGNORE_METADATA,
mais tolère de légères différences dans les valeurs numériques.
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.
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.
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 exceptions.
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 données
lorsque cette information est disponible.
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 dont les instances existent
en un seul exemplaire 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.
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:
toString() plutôt qu’à l’aide d’un java.text.NumberFormat.
Il en résulte des différences dans le nombre de chiffres significatifs, l’utilisation de la notation exponentielle et l’absence de séparateur des milliers.
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:
| Boucle à éviter | Boucle recommandée |
|---|---|
for (int i=0; i<string.length(); i++) {
char c = string.charAt(i);
if (Character.isWhitespace(c)) {
|
for (int i=0; i<string.length();) {
int c = string.codePointAt(i);
if (Character.isWhitespace(c)) {
|
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.
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.
Ce chapitre introduit quelques aspects de la norme ISO 19107 (Spatial schema) et les classes de Apache SIS qui les implémentent.
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.Collection
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(…).
La classe parente de toutes les 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.
Toutes les géométries sont des spécialisations de TransfiniteSet.
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.
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.
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:
lowerCorner:
le point de départ lorsque l’on parcourt l’intérieur de l’enveloppe dans la direction des valeurs croissantes.
upperCorner:
le point d’arrivé lorsque l’on a parcouru l’intérieur de l’enveloppe dans la direction des valeurs croissantes.
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.
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 par 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 sans pour autant les confondre.
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.
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:
Un coverage est une fonction qui, à partir d’une coordonnée spécifiée en entrée, retourne une valeur d’attribut. L’ensemble des valeurs pouvant être données en entrée est appelé le domaine (domain en anglais), alors que l’ensemble des valeurs pouvant être retournées est appelé range en anglais. Le domaine est souvent l’espace spatio-temporel couvert par les données, mais rien dans SIS n’empêche les couvertures de s’étendre à d’autres dimensions. Par exemple les études en thermodynamique utilisent souvent un espace dont les dimensions sont la température et la pression.
Exemple: les valeurs des pixels d’une image pourraient contenir des mesures d’élévation du terrain. Si une fonction h = f(φ,λ) permet d’obtenir (éventuellement à l’aide d’interpolations) l’élévation h en fonction d’une coordonnée géographique (φ,λ), alors l’enveloppe géographique de l’image définie le domain, la fonction f est le coverage, et l’ensemble des valeurs de h que peut retourner cette fonction est le range.
Les différents types de couvertures peuvent se caractériser par la géométrie de leurs cellules.
En particulier, une couverture n’est pas nécessairement composée de cellules quadrilatérales.
Toutefois les cellules quadrilatérales étant de loin les plus fréquentes (puisque c’est la géométrie classique des pixels des images),
on utilisera souvent le terme grid coverage pour désigner les couvertures composées de telles cellules.
Dans SIS, la géométrie de ces couvertures est décrite par la classe GridGeometry.
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.