The metadata modules provide support methods for handling the metadata objects through Java Reflection. This is an approach similar to Java Beans, in that users are encouraged to use directly the API of Plain Old Java objects every time their type is known at compile time, and fallback on the reflection technic when the type is known only at runtime. When using Java reflection, a metadata can be viewed in different ways:
Map
(from java.util
).TreeTable
(from org.apache.sis.util.collection
).org.apache.sis.metadata.sql
).The use of reflection is described below. The XML representation is described in a separated chapter.
All metadata classes provide getter, and sometime setter, methods for their properties. The following example prints all ranges of latitudes found in a metadata. It may be, for example, a metadata describing the extent of a raster file. Fetching that information requires navigating through the following steps:
Metadata
⟶ identificationInfo
⟶ extent
⟶ geographicElement
⟶ (southBoundLatitude
, northBoundLatitude
)
Some properties accept many values, in which case the code must iterate over the elements of a collection.
Furthermore, some properties are available only in a sub-interface, in which case instanceof
checks are required. The resulting code is as below:
import org.opengis.metadata.metadata;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicExtent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.metadata.identification.Identification;
import org.opengis.metadata.identification.DataIdentification;
void main() {
Metadata metadata = ...; // For example, metadata read from a data store.
for (Identification identification : metadata.getIdentificationInfo()) {
if (identification instanceof DataIdentification data) {
for (Extent extent : data.getExtents()) {
// Extents may have horizontal, vertical and temporal components.
for (GeographicExtent horizontal : extent.getGeographicElements()) {
if (horizontal instanceof GeographicBoundingBox bbox) {
double south = bbox.getSouthBoundLatitude();
double north = bbox.getNorthBoundLatitude();
System.out.println("Latitude range: " + south + " to " + north);
}
}
}
}
}
}
Because of ISO 19115 richness, interesting information may be buried deeply in the metadata tree, as in above example.
For a few frequently-used elements, some convenience methods are provided.
Those conveniences are generally defined as static methods in classes having a name in plural form.
For example the Extents
class defines static methods for fetching more easily some information from Extent
metadata elements.
For example the following method navigates through different branches where North, South, East and West data bounds may be found:
import org.opengis.metadata.metadata;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.apache.sis.metadata.iso.extent.Extents;
void main() {
Metadata metadata = ...; // For example, metadata read from a data store.
GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(extent);
if (box != null) {
// Same as in the previous example.
}
}
Those conveniences are defined as static methods in order to allow their use with different metadata implementations.
Some other classes providing static methods for specific interfaces are
Citations
, Envelopes
, Matrices
and MathTransforms
.
For every getter method, there is a corresponding setter method. However the setter methods are not defined in the GeoAPI interfaces, which are read-only. For invoking setter methods, it is necessary to use the Apache SIS implementation classes directly. It is straightforward when the metadata objects are created and populated in the same code, as there is nothing special to do. The following example creates a citation using three different ways to specify a value: at construction time using convenience constructors, using setter methods, and by adding elements directly in the collections, which are modifiable.
import org.apache.sis.metadata.iso.DefaultIdentifier;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.opengis.metadata.citation.PresentationForm; // A code list.
void main() {
// Following constructor is a convenience for setting the `title` property at construction time.
var citation = new DefaultCitation("Climate Change 2022: Impacts, Adaptation, and Vulnerability");
citation.getPresentationForms().add(PresentationForm.DOCUMENT_DIGITAL);
var identifier = new DefaultIdentifier();
identifier.setCode("10.1017/9781009325844");
identifier.setCodeSpace("DOI");
citation.getIdentifiers().add(identifier);
// Do not allow the citation to be modified after this point (optional).
citation.transitionTo(DefaultCitation.State.FINAL);
System.out.println(citation);
}
Output is as below:
Citation……………………………………… Climate Change 2022: Impacts, Adaptation, and Vulnerability ├─Identifier……………………… 10.1017/9781009325844 │ └─Code space…………… DOI └─Presentation form…… Document digital
Often, the newly created metadata will be reused many times.
It may be for example a static final constant, or a cached value.
In such case, it may be desirable to declare the metadata as unmodifiable after its construction.
This is the purpose of the call to transitionTo(State.FINAL)
in above example.
After that call, any attempt to modify the metadata will cause an UnmodifiableMetadataException
to be thrown.
Setting the values of a metadata object may be more tricky when the implementation class is unknown.
Since there is no setter methods in the GeoAPI interfaces, the metadata may need to be converted to
an Apache SIS implementation class first, and that instance needs to be modifiable.
This is a two steps process.
The first step can be done by invoking the static castOrCopy(…)
method
which is defined in every Apache SIS implementation classes.
For example there is
a DefaultCitation.castOrCopy(
method,
a Citation
)DefaultExtent.castOrCopy(
method, etc.
Those methods will either return the given instance directly when possible,
or otherwise create a shallow copy of that instance (i.e., without recursive copy of children).
The second step can be done in different ways. An easy way (while not the most efficient)
is to make an unconditional deep copy of the metadata,
in case that metadata was unmodifiable (Extent
)State.FINAL
),
and also (if desired) for avoiding to change the original metadata.
Example:
import org.opengis.metadata.citation.Citation;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.util.SimpleInternationalString;
void main() {
Citation original = ...; // Some pre-existing citation.
// Get a modifiable copy and change its title.
DefaultCitation edit = DefaultCitation.castOrCopy(original);
edit = (DefaultCitation) copy.deepCopy(DefaultCitation.State.EDITABLE);
edit.setTitle(new SimpleInternationalString("A new title"));
}
Above static methods explore fragments of metadata tree in search for requested information,
but the searches are still targeted to elements whose types and at least part of their paths are known at compile-time.
Sometimes the element to search is known only at runtime, or sometimes there is a need to iterate over all elements.
In such cases, one can view the metadata as a java.util.Map
like below:
import java.util.Map;
import org.apache.sis.metadata.MetadataStandard;
import org.apache.sis.metadata.KeyNamePolicy;
import org.apache.sis.metadata.ValueExistencePolicy;
void main() {
Object metadata = ...;
Map<String,Object> elements = MetadataStandard.ISO_19115.asValueMap(
metadata, // Any instance from the org.opengis.metadata package or a sub-package.
null, // Used for resolving ambiguities. We can ignore for this example.
KeyNamePolicy.JAVABEANS_PROPERTY, // Keys in the map will be getter method names without "get" prefix.
ValueExistencePolicy.NON_EMPTY); // Entries with null or empty values will be omitted.
// Print the names of all root metadata elements having a value.
for (String name : elements.keySet()) {
System.out.println(name);
}
}
The Map
object returned by asValueMap(…)
is live:
any change in the metadata
instance will be immediately reflected in the view.
Actually, each map.get("foo")
call is forwarded to the corresponding metadata.getFoo()
method.
Conversely, any map.put("foo", …)
or map.remove("foo")
operation applied on the view
will be forwarded to the corresponding metadata.setFoo(…)
method, if that method exists.
The view is lenient regarding keys given in arguments to Map
methods:
keys may be property names ("foo"
), method names ("getFoo"
),
or names used in ISO 19115 standard UML diagrams
(similar to property names but not always identical).
Differences in upper cases and lower cases are ignored when this tolerance does not introduce ambiguities.
For more information on metadata views, see
org.apache.sis.metadata
package javadoc.
A richer alternative to the view as a map is the view as a tree table. With this view, the metadata structure is visible as a tree, and each tree node is a table row with the following columns:
Column | Description | Writable |
---|---|---|
IDENTIFIER | The UML identifier if any, or otherwise the Java Beans name, of the metadata property. | |
INDEX | If the property is a collection, then the zero-based index of the element in that collection. | |
NAME | A human-readable name for the node, derived from above identifier and index. | |
TYPE | The base type of the value (usually a GeoAPI interface). | |
VALUE | The metadata value for the node. This column may be writable. | ✓ |
REMARKS | Remarks or warning on the property value. |
Tree table views are obtained in a way similar to map views,
but using the asTreeTable(…)
method instead of asValueMap(…)
.
Values can be obtained for each column, and can be set for the VALUE
column
if the underlying metadata object is modifiable.
The following example prints the values of the NAME
and TYPE
columns for a given metadata and all its children:
import org.apache.sis.metadata.MetadataStandard;
import org.apache.sis.metadata.ValueExistencePolicy;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.collection.TableColumn;
class Explorer {
void main() {
Object metadata = ...;
TreeTable table = MetadataStandard.ISO_19115.asTreeTable(
metadata, // Any instance from the org.opengis.metadata package or a sub-package.
null, // Used for resolving ambiguities. We can ignore for this example.
ValueExistencePolicy.NON_EMPTY); // Entries with null or empty values will be omitted.
// Print the names and types of all metadata elements having a value.
printRecursively(table.getRoot());
}
private static void printRecursively(TreeTable.Node node) {
String name = node.getValue(TableColumn.NAME);
Class<?> type = node.getValue(TableColumn.TYPE);
System.out.println(name + " : " + type.getSimpleName());
node.getChildren().forEach(Explorer::printRecursively);
}
}