blob: 9ce5d48db965df3a40094a9855aaf1fb288c289c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.internal.storage.gpx;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
import java.time.temporal.Temporal;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.util.NameFactory;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.apache.sis.setup.GeometryLibrary;
import org.opengis.metadata.citation.OnlineResource;
import org.opengis.metadata.content.ContentInformation;
import org.opengis.metadata.acquisition.GeometryType;
import org.apache.sis.storage.gps.Fix;
import org.apache.sis.storage.FeatureNaming;
import org.apache.sis.storage.IllegalNameException;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.feature.AbstractIdentifiedType;
import org.apache.sis.feature.FeatureOperations;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
import org.apache.sis.feature.builder.AttributeRole;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.storage.FeatureCatalogBuilder;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.util.iso.ResourceInternationalString;
import org.apache.sis.util.iso.DefaultNameFactory;
// Branch-dependent imports
import org.apache.sis.feature.DefaultFeatureType;
import org.apache.sis.feature.DefaultAttributeType;
/**
* Feature types that may appear in GPX files. All values defined in this class are immutable and can be shared
* by many {@link Reader} instances. There is usually only one {@code Types} instance for a running JVM, but we
* nevertheless allows definition of alternative {@code Types} with names created by different factories.
*
* @author Johann Sorel (Geomatys)
* @version 0.8
* @since 0.8
* @module
*/
final class Types {
/**
* The parent of all other feature types.
*/
final DefaultFeatureType parent;
/**
* Way point GPX feature type.
*/
final DefaultFeatureType wayPoint;
/**
* Route GPX feature type.
*/
final DefaultFeatureType route;
/**
* Track GPX feature type.
*/
final DefaultFeatureType track;
/**
* Track segment GPX feature type.
*/
final DefaultFeatureType trackSegment;
/**
* The list of feature types to be given to GPC metadata objects.
*/
final Collection<ContentInformation> metadata;
/**
* Binding from names to feature type instances.
* Shall not be modified after construction.
*
* @deprecated We are not sure yet if we will keep this field. Decision is pending acquisition of
* more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
*/
@Deprecated
final FeatureNaming<DefaultFeatureType> names;
/**
* Accessor to the geometry implementation in use (Java2D, ESRI or JTS).
*/
final Geometries<?> geometries;
/**
* A system-wide instance for {@code FeatureType} instances created using the {@link DefaultNameFactory}.
* This is normally the only instance used in an application.
*/
static final Types DEFAULT;
static {
try {
DEFAULT = new Types(DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class), null, null);
} catch (FactoryException | IllegalNameException e) {
throw new AssertionError(e); // Should never happen with DefaultNameFactory implementation.
}
}
/**
* Creates new {@code FeatureTypes} with feature names and property names created using the given factory.
*
* @param factory the factory to use for creating names, or {@code null} for the default factory.
* @param locale the locale to use for formatting error messages, or {@code null} for the default locale.
* @param library the required geometry library, or {@code null} for the default.
* @throws FactoryException if an error occurred while creating an "envelope bounds" operation.
*/
Types(final NameFactory factory, final Locale locale, final GeometryLibrary library)
throws FactoryException, IllegalNameException
{
geometries = Geometries.implementation(library);
final Map<String,InternationalString[]> resources = new HashMap<>();
final ScopedName geomName = AttributeConvention.GEOMETRY_PROPERTY;
final Map<String,?> geomInfo = Collections.singletonMap(AbstractIdentifiedType.NAME_KEY, geomName);
final Map<String,?> envpInfo = Collections.singletonMap(AbstractIdentifiedType.NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY);
/*
* The parent of all FeatureTypes to be created in this constructor.
* This parent has a single property, "sis:identifier" of type Integer,
* which is not part of GPX specification.
*
* GPXEntity
* ┌────────────────┬─────────┬──────────────┐
* │ Name │ Type │ Multiplicity │
* ├────────────────┼─────────┼──────────────┤
* │ sis:identifier │ Integer │ [1 … 1] │ SIS-specific property
* └────────────────┴─────────┴──────────────┘
*/
final FeatureTypeBuilder builder = new FeatureTypeBuilder(factory, library, locale);
builder.setNameSpace(Tags.PREFIX).setName("GPXEntity").setAbstract(true);
builder.addAttribute(Integer.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
parent = builder.build();
/*
* WayPoint ⇾ GPXEntity
* ┌──────────────────┬────────────────┬───────────────────────┬──────────────┐
* │ Name │ Type │ XML type │ Multiplicity │
* ├──────────────────┼────────────────┼───────────────────────┼──────────────┤
* │ sis:identifier │ Integer │ │ [1 … 1] │
* │ sis:envelope │ Envelope │ │ [1 … 1] │
* │ sis:geometry │ Point │ (lat,lon) attributes │ [1 … 1] │
* │ ele │ Double │ xs:decimal │ [0 … 1] │
* │ time │ Temporal │ xs:dateTime │ [0 … 1] │
* │ magvar │ Double │ gpx:degreesType │ [0 … 1] │
* │ geoidheight │ Double │ xs:decimal │ [0 … 1] │
* │ name │ String │ xs:string │ [0 … 1] │
* │ cmt │ String │ xs:string │ [0 … 1] │
* │ desc │ String │ xs:string │ [0 … 1] │
* │ src │ String │ xs:string │ [0 … 1] │
* │ link │ OnlineResource │ gpx:linkType │ [0 … ∞] │
* │ sym │ String │ xs:string │ [0 … 1] │
* │ type │ String │ xs:string │ [0 … 1] │
* │ fix │ Fix │ gpx:fixType │ [0 … 1] │
* │ sat │ Integer │ xs:nonNegativeInteger │ [0 … 1] │
* │ hdop │ Double │ xs:decimal │ [0 … 1] │
* │ vdop │ Double │ xs:decimal │ [0 … 1] │
* │ pdop │ Double │ xs:decimal │ [0 … 1] │
* │ ageofdgpsdata │ Double │ xs:decimal │ [0 … 1] │
* │ dgpsid │ Integer │ gpx:dgpsStationType │ [0 … 1] │
* └──────────────────┴────────────────┴───────────────────────┴──────────────┘
*/
builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("WayPoint");
builder.addAttribute(GeometryType.POINT).setName(geomName)
.setCRS(CommonCRS.WGS84.normalizedGeographic())
.addRole(AttributeRole.DEFAULT_GEOMETRY);
builder.setDefaultMultiplicity(0, 1);
builder.addAttribute(Double .class).setName(Tags.ELEVATION);
builder.addAttribute(Temporal .class).setName(Tags.TIME);
builder.addAttribute(Double .class).setName(Tags.MAGNETIC_VAR);
builder.addAttribute(Double .class).setName(Tags.GEOID_HEIGHT);
builder.addAttribute(String .class).setName(Tags.NAME);
builder.addAttribute(String .class).setName(Tags.COMMENT);
builder.addAttribute(String .class).setName(Tags.DESCRIPTION);
builder.addAttribute(String .class).setName(Tags.SOURCE);
builder.addAttribute(OnlineResource.class).setName(Tags.LINK).setMaximumOccurs(Integer.MAX_VALUE);
builder.addAttribute(String .class).setName(Tags.SYMBOL);
builder.addAttribute(String .class).setName(Tags.TYPE);
builder.addAttribute(Fix .class).setName(Tags.FIX);
builder.addAttribute(Integer .class).setName(Tags.SATELITTES);
builder.addAttribute(Double .class).setName(Tags.HDOP);
builder.addAttribute(Double .class).setName(Tags.VDOP);
builder.addAttribute(Double .class).setName(Tags.PDOP);
builder.addAttribute(Double .class).setName(Tags.AGE_OF_GPS_DATA);
builder.addAttribute(Integer .class).setName(Tags.DGPS_ID);
wayPoint = create(builder, resources);
/*
* Route ⇾ GPXEntity
* ┌────────────────┬────────────────┬───────────────────────┬──────────────┐
* │ Name │ Type │ XML type │ Multiplicity │
* ├────────────────┼────────────────┼───────────────────────┼──────────────┤
* │ sis:identifier │ Integer │ │ [1 … 1] │
* │ sis:envelope │ Envelope │ │ [1 … 1] │
* │ sis:geometry │ Polyline │ │ [1 … 1] │
* │ name │ String │ xs:string │ [0 … 1] │
* │ cmt │ String │ xs:string │ [0 … 1] │
* │ desc │ String │ xs:string │ [0 … 1] │
* │ src │ String │ xs:string │ [0 … 1] │
* │ link │ OnlineResource │ gpx:linkType │ [0 … ∞] │
* │ number │ Integer │ xs:nonNegativeInteger │ [0 … 1] │
* │ type │ String │ xs:string │ [0 … 1] │
* │ rtept │ WayPoint │ gpx:wptType │ [0 … ∞] │
* └────────────────┴────────────────┴───────────────────────┴──────────────┘
*/
final DefaultAttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
GroupAsPolylineOperation groupOp = new GroupAsPolylineOperation(geomInfo, Tags.ROUTE_POINTS, groupResult);
builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Route");
builder.addProperty(groupOp);
builder.addProperty(FeatureOperations.envelope(envpInfo, null, groupOp));
builder.setDefaultMultiplicity(0, 1);
builder.addProperty(wayPoint.getProperty(Tags.NAME));
builder.addProperty(wayPoint.getProperty(Tags.COMMENT));
builder.addProperty(wayPoint.getProperty(Tags.DESCRIPTION));
builder.addProperty(wayPoint.getProperty(Tags.SOURCE));
builder.addProperty(wayPoint.getProperty(Tags.LINK));
builder.addAttribute(Integer.class).setName(Tags.NUMBER);
builder.addProperty(wayPoint.getProperty(Tags.TYPE));
builder.addAssociation(wayPoint).setName(Tags.ROUTE_POINTS).setMaximumOccurs(Integer.MAX_VALUE);
route = create(builder, resources);
/*
* TrackSegment ⇾ GPXEntity
* ┌────────────────┬──────────┬─────────────┬──────────────┐
* │ Name │ Type │ XML type │ Multiplicity │
* ├────────────────┼──────────┼─────────────┼──────────────┤
* │ sis:identifier │ Integer │ │ [1 … 1] │
* │ sis:envelope │ Envelope │ │ [1 … 1] │
* │ sis:geometry │ Polyline │ │ [1 … 1] │
* │ trkpt │ WayPoint │ gpx:wptType │ [0 … ∞] │
* └────────────────┴──────────┴─────────────┴──────────────┘
*/
groupOp = new GroupAsPolylineOperation(geomInfo, Tags.TRACK_POINTS, groupResult);
builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("TrackSegment");
builder.addProperty(groupOp);
builder.addProperty(FeatureOperations.envelope(envpInfo, null, groupOp));
builder.setDefaultMultiplicity(0, 1);
builder.addAssociation(wayPoint).setName(Tags.TRACK_POINTS).setMaximumOccurs(Integer.MAX_VALUE);
trackSegment = create(builder, resources);
/*
* Track ⇾ GPXEntity
* ┌────────────────┬────────────────┬───────────────────────┬──────────────┐
* │ Name │ Type │ XML type │ Multiplicity │
* ├────────────────┼────────────────┼───────────────────────┼──────────────┤
* │ sis:identifier │ Integer │ │ [1 … 1] │
* │ sis:envelope │ Envelope │ │ [1 … 1] │
* │ sis:geometry │ Polyline │ │ [1 … 1] │
* │ name │ String │ xs:string │ [0 … 1] │
* │ cmt │ String │ xs:string │ [0 … 1] │
* │ desc │ String │ xs:string │ [0 … 1] │
* │ src │ String │ xs:string │ [0 … 1] │
* │ link │ OnlineResource │ gpx:linkType │ [0 … ∞] │
* │ number │ Integer │ xs:nonNegativeInteger │ [0 … 1] │
* │ type │ String │ xs:string │ [0 … 1] │
* │ trkseg │ TrackSegment │ gpx:trksegType │ [0 … ∞] │
* └────────────────┴────────────────┴───────────────────────┴──────────────┘
*/
groupOp = new GroupAsPolylineOperation(geomInfo, Tags.TRACK_SEGMENTS, groupResult);
builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Track");
builder.addProperty(groupOp);
builder.addProperty(FeatureOperations.envelope(envpInfo, null, groupOp));
builder.setDefaultMultiplicity(0, 1);
builder.addProperty(route.getProperty(Tags.NAME));
builder.addProperty(route.getProperty(Tags.COMMENT));
builder.addProperty(route.getProperty(Tags.DESCRIPTION));
builder.addProperty(route.getProperty(Tags.SOURCE));
builder.addProperty(route.getProperty(Tags.LINK));
builder.addProperty(route.getProperty(Tags.NUMBER));
builder.addProperty(route.getProperty(Tags.TYPE));
builder.addAssociation(trackSegment).setName(Tags.TRACK_SEGMENTS).setMaximumOccurs(Integer.MAX_VALUE);
track = create(builder, resources);
final FeatureCatalogBuilder fc = new FeatureCatalogBuilder(null);
fc.define(route);
fc.define(track);
fc.define(wayPoint);
metadata = fc.build(true).getContentInfo();
names = fc.features;
}
/**
* Adds internationalized designation and definition information for all properties in the given type.
* Then, returns the result of {@link FeatureTypeBuilder#build()}.
*
* @param builder the feature type builder for which to add designations and definitions.
* @param previous previously created international strings as array of length 2.
* The first element is the designation and the second element is the definition.
*/
private static DefaultFeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
for (final PropertyTypeBuilder p : builder.properties()) {
final GenericName name = p.getName();
if (!AttributeConvention.contains(name)) {
final InternationalString[] resources = previous.computeIfAbsent(name.toString(), (key) -> new InternationalString[] {
new ResourceInternationalString("org.apache.sis.internal.storage.gpx.Designations", key),
new ResourceInternationalString("org.apache.sis.internal.storage.gpx.Definitions", key)
});
p.setDefinition (resources[1]);
p.setDesignation(resources[0]);
}
}
return builder.build();
}
}