Goal
In this tutorial, we add support for a custom ISO 19115 metadata format.
By default, LuciadLightspeed offers a decoder for the ISO 19139 format, which defines how to store ISO 19115 metadata in the XML format. You may have metadata defined in a custom format, though. In that case, you must create a decoder yourself.
In this tutorial, we create a custom decoder that decodes an imaginary metadata format into ISO 19115 metadata objects.
The custom metadata format
The metadata format we’re going to support is a fictitious metadata format based on the standard Java properties file format.
For this tutorial, we implement 3 properties: title, abstract, and keywords.
For example:
# Custom metadata file for States.shp
title = US States
abstract = States of the United States.
keywords = state,federation
Creating the metadata decoder
Creating a metadata decoder comes down to implementing the ILcdMetadataDecoder
interface.
We start by implementing the canDecodeMetadata
method. This method checks if the metadata from a given source name can be decoded. For performance reasons, we want to make
this check fast, and allow it to return false positives. That’s why we inspect only the file extension, not the content itself.
@Override
public boolean canDecodeMetadata(String aSourceName) {
return TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties");
}
Next, we implement the decodeMetadata
method. This method tries to decode the metadata from a given source name. For our custom decoder, the source name will point
to a Java Property file. We read the title
, abstract
and keywords
properties from it and put them in the resulting TLcdISO19115Metadata
object.
@Override
public TLcdISO19115Metadata decodeMetadata(String aSourceName) throws IOException {
if (!canDecodeMetadata(aSourceName)) {
throw new IOException("Cannot decode " + aSourceName);
}
Properties properties = new Properties();
try (InputStream inputStream = fInputStreamFactory.createInputStream(aSourceName)) {
properties.load(inputStream);
}
TLcdISO19115Metadata metadata = new TLcdISO19115Metadata();
TLcdISO19115Identification identification = new TLcdISO19115DataIdentification();
metadata.getIdentificationInfo().add(identification);
// the title
String titleValue = properties.getProperty("title");
if (titleValue != null) {
TLcdISO19115Citation citation = new TLcdISO19115Citation();
identification.setCitation(citation);
citation.setTitle(titleValue);
}
String abstractValue = properties.getProperty("abstract");
if (abstractValue != null) {
identification.setAbstract(abstractValue);
}
String keywordsValue = properties.getProperty("keywords");
if (keywordsValue != null) {
TLcdISO19115Keywords keywords = new TLcdISO19115Keywords();
keywords.getKeyword().addAll(Arrays.asList(keywordsValue.split(",")));
identification.getDescriptiveKeywords().add(keywords);
}
return metadata;
}
Finally, we implement the findAndDecodeMetadata
method. This method accepts the source name of a data file. Using that source name, it searches for additional metadata files
and tries to decode them. In our implementation, we look for files having the same name as the data file, but with a .properties
extension.
@Override
public MetadataWithSource findAndDecodeMetadata(String aSourceName) throws IOException {
if (TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties")) {
// The data file itself is a .properties file. There cannot exist another dedicated .properties
// metadata file with the same name!
return null;
}
String propertyFileName = toPropertyFileName(aSourceName);
if (propertyFileName == null) {
return null;
}
return new MetadataWithSource(decodeMetadata(propertyFileName),
new TLcdModelMetadata.Source(propertyFileName, "text/plain"));
}
/**
* Translates the given source name of the data file into the name of the properties file.
*/
private String toPropertyFileName(String aSourceName) {
String baseName = aSourceName.substring(0, aSourceName.lastIndexOf('.'));
String propertyFileName = baseName + ".properties";
// check if the property file exists
if (Files.exists(Paths.get(propertyFileName))) {
return propertyFileName;
}
return null;
}
Optional: registering the metadata decoder as a service
You typically want your application to discover and use custom decoders automatically. For this purpose, we recommend that you register your metadata decoder as a service. You can do so by adding an annotation to the class:
@LcdService(service = ILcdMetadataDecoder.class)
public class PropertiesMetadataDecoder implements ILcdMetadataDecoder {
Now, when you need to decode metadata, you can use a composite metadata decoder instance that collects all registered metadata decoders, including your custom metadata decoder.
ILcdMetadataDecoder metadataDecoder =
new TLcdCompositeMetadataDecoder(TLcdServiceLoader.getInstance(ILcdMetadataDecoder.class));
For more information about how the services mechanism works, see here.
Full code
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Properties;
import com.luciad.format.metadata.model.citation.TLcdISO19115Citation;
import com.luciad.format.metadata.model.identification.TLcdISO19115DataIdentification;
import com.luciad.format.metadata.model.identification.TLcdISO19115Identification;
import com.luciad.format.metadata.model.identification.TLcdISO19115Keywords;
import com.luciad.format.metadata.model.metadataentityset.TLcdISO19115Metadata;
import com.luciad.io.ILcdInputStreamFactory;
import com.luciad.io.TLcdCompositeInputStreamFactory;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.util.TLcdStringUtil;
import com.luciad.util.service.LcdService;
import com.luciad.util.service.TLcdServiceLoader;
/**
* This class demonstrates how to implement a custom metadata decoder.
*/
@LcdService(service = ILcdMetadataDecoder.class)
public class PropertiesMetadataDecoder implements ILcdMetadataDecoder {
private final ILcdInputStreamFactory fInputStreamFactory =
new TLcdCompositeInputStreamFactory(TLcdServiceLoader.getInstance(ILcdInputStreamFactory.class));
@Override
public boolean canDecodeMetadata(String aSourceName) {
return TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties");
}
@Override
public TLcdISO19115Metadata decodeMetadata(String aSourceName) throws IOException {
if (!canDecodeMetadata(aSourceName)) {
throw new IOException("Cannot decode " + aSourceName);
}
Properties properties = new Properties();
try (InputStream inputStream = fInputStreamFactory.createInputStream(aSourceName)) {
properties.load(inputStream);
}
TLcdISO19115Metadata metadata = new TLcdISO19115Metadata();
TLcdISO19115Identification identification = new TLcdISO19115DataIdentification();
metadata.getIdentificationInfo().add(identification);
// the title
String titleValue = properties.getProperty("title");
if (titleValue != null) {
TLcdISO19115Citation citation = new TLcdISO19115Citation();
identification.setCitation(citation);
citation.setTitle(titleValue);
}
String abstractValue = properties.getProperty("abstract");
if (abstractValue != null) {
identification.setAbstract(abstractValue);
}
String keywordsValue = properties.getProperty("keywords");
if (keywordsValue != null) {
TLcdISO19115Keywords keywords = new TLcdISO19115Keywords();
keywords.getKeyword().addAll(Arrays.asList(keywordsValue.split(",")));
identification.getDescriptiveKeywords().add(keywords);
}
return metadata;
}
@Override
public MetadataWithSource findAndDecodeMetadata(String aSourceName) throws IOException {
if (TLcdStringUtil.endsWithIgnoreCase(aSourceName, ".properties")) {
// The data file itself is a .properties file. There cannot exist another dedicated .properties
// metadata file with the same name!
return null;
}
String propertyFileName = toPropertyFileName(aSourceName);
if (propertyFileName == null) {
return null;
}
return new MetadataWithSource(decodeMetadata(propertyFileName),
new TLcdModelMetadata.Source(propertyFileName, "text/plain"));
}
/**
* Translates the given source name of the data file into the name of the properties file.
*/
private String toPropertyFileName(String aSourceName) {
String baseName = aSourceName.substring(0, aSourceName.lastIndexOf('.'));
String propertyFileName = baseName + ".properties";
// check if the property file exists
if (Files.exists(Paths.get(propertyFileName))) {
return propertyFileName;
}
return null;
}
}