blob: 79c3efac9524b03d5d2f3ec7645922c9b58627d9 [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.storage.gpx;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import java.time.Instant;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.Polyline;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.content.FeatureCatalogueDescription;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.setup.OptionKey;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.gps.Fix;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
import static org.apache.sis.test.TestUtilities.date;
import static org.apache.sis.test.TestUtilities.getSingleton;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.metadata.content.FeatureTypeInfo;
import org.opengis.feature.Feature;
/**
* Tests (indirectly) the {@link Reader} class.
* This class creates a {@link Store} instance and uses it in read-only mode.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
public final class ReaderTest extends TestCase {
/**
* The provider shared by all data stores created in this test class.
*/
private final StoreProvider provider;
/**
* Creates the provider to be shared by all data stores created in this test class.
*/
public ReaderTest() {
provider = StoreProvider.provider();
}
/**
* Creates a new GPX data store which will read the given test file.
*
* @param version identifies the version of the schema to test.
* @param resource name of the test file.
*/
private Store create(final TestData version, final String resource) throws DataStoreException {
final var connector = new StorageConnector(version.openStream(resource));
connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.ESRI);
return new Store(provider, connector);
}
/**
* Verifies that the given {@code actual} bounding box has the expected values.
* A strict equality is requested.
*/
private static void assertBoundsEquals(final double westBoundLongitude,
final double eastBoundLongitude,
final double southBoundLatitude,
final double northBoundLatitude,
final Bounds actual)
{
assertEquals(westBoundLongitude, actual.westBoundLongitude, "westBoundLongitude");
assertEquals(eastBoundLongitude, actual.eastBoundLongitude, "eastBoundLongitude");
assertEquals(southBoundLatitude, actual.southBoundLatitude, "southBoundLatitude");
assertEquals(northBoundLatitude, actual.northBoundLatitude, "northBoundLatitude");
}
/**
* Verifies that the given {@code actual} envelope has the expected values.
* A strict equality is requested.
*/
private static void assertEnvelopeEquals(final double xmin, final double xmax,
final double ymin, final double ymax,
final Envelope actual)
{
assertEquals(2, actual.getDimension(), "dimension");
assertEquals(actual.getMinimum(0), xmin, "xmin");
assertEquals(actual.getMaximum(0), xmax, "xmax");
assertEquals(actual.getMinimum(1), ymin, "ymin");
assertEquals(actual.getMaximum(1), ymax, "ymax");
}
/**
* Asserts that the string value of {@code actual} is equal to the expected value.
*
* @param expected the expected value (can be {@code null}).
* @param actual the actual value (can be {@code null}).
*/
private static void assertStringEquals(final String expected, final Object actual) {
assertEquals(expected, (actual != null) ? actual.toString() : null);
}
/**
* Tests parsing of GPX version 1.0.0 metadata.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testMetadata100() throws DataStoreException {
try (Store reader = create(TestData.V1_0, TestData.METADATA)) {
final Metadata md = (Metadata) reader.getMetadata();
verifyMetadata(md, 1);
assertNull(md.author.link);
assertNull(md.copyright);
assertEquals(StoreProvider.V1_0, reader.getVersion());
}
}
/**
* Tests parsing of GPX version 1.1.0 metadata.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testMetadata110() throws DataStoreException {
try (Store reader = create(TestData.V1_1, TestData.METADATA)) {
final Metadata md = (Metadata) reader.getMetadata();
verifyMetadata(md, 3);
assertStringEquals("http://someone-site.org", md.author.link);
assertEquals("Apache", md.copyright.author);
assertEquals(2004, md.copyright.year.intValue());
assertStringEquals("http://www.apache.org/licenses/LICENSE-2.0", md.copyright.license);
assertEquals(StoreProvider.V1_1, reader.getVersion());
}
}
/**
* Verifies that the given metadata have the expected values.
* This method verifies only the values that are common to both GPX 1.0 and GPX 1.1 test files.
*/
@SuppressWarnings("fallthrough")
static void verifyMetadata(final Metadata md, final int numLinks) {
assertEquals ("Sample", md.name);
assertEquals ("GPX test file", md.description);
assertEquals (date("2010-03-01 00:00:00"), md.time);
assertArrayEquals (new String[] {"sample", "metadata"}, md.keywords.toArray());
assertBoundsEquals(-20, 30, 10, 40, md.bounds);
assertEquals ("Jean-Pierre", md.author.name);
assertEquals ("jean.pierre@test.com", md.author.email);
assertEquals (numLinks, md.links.size());
switch (numLinks) {
default: // Fallthrough everywhere.
case 3: assertStringEquals("website", md.links.get(2).type);
assertStringEquals("http://third-address.org", md.links.get(2));
case 2: assertStringEquals("http://second-address.org", md.links.get(1));
case 1: assertStringEquals("http://first-address.org", md.links.get(0));
assertStringEquals("first", md.links.get(0).text);
case 0: break;
}
}
/**
* Verifies that the given metadata contains only bounds information
* and the hard-coded list of feature types.
*/
private static void verifyAlmostEmptyMetadata(final Metadata md) {
assertNull(md.name);
assertNull(md.description);
assertNull(md.time);
assertNull(md.keywords);
assertNull(md.author);
assertNull(md.copyright);
assertNull(md.links);
assertBoundsEquals(-20, 30, 10, 40, md.bounds);
/*
* Verifies the list of feature types declared in the given metadata. Those features
* are not listed in GPX files; they are rather hard-coded in Types.metadata constant.
*/
final var content = assertInstanceOf(FeatureCatalogueDescription.class, getSingleton(md.getContentInfo()));
assertTrue(content.isIncludedWithDataset());
final Iterator<? extends FeatureTypeInfo> it = content.getFeatureTypeInfo().iterator();
assertStringEquals("Route", it.next().getFeatureTypeName().tip());
assertStringEquals("Track", it.next().getFeatureTypeName().tip());
assertStringEquals("WayPoint", it.next().getFeatureTypeName().tip());
assertFalse(it.hasNext());
}
/**
* Tests parsing of GPX version 1.0.0 way point.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testWayPoint100() throws DataStoreException {
try (Store reader = create(TestData.V1_0, TestData.WAYPOINT)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_0, reader.getVersion());
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyPoint(it.next(), 0, false);
verifyPoint(it.next(), 1, false);
verifyPoint(it.next(), 2, false);
assertFalse(it.hasNext());
}
}
}
/**
* Tests parsing of GPX version 1.1.0 way point.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testWayPoint110() throws DataStoreException {
try (Store reader = create(TestData.V1_1, TestData.WAYPOINT)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_1, reader.getVersion());
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyPoint(it.next(), 0, true);
verifyPoint(it.next(), 1, true);
verifyPoint(it.next(), 2, true);
assertFalse(it.hasNext());
}
}
}
/**
* Tests parsing of GPX version 1.0.0 route.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testRoute100() throws DataStoreException {
try (Store reader = create(TestData.V1_0, TestData.ROUTE)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_0, reader.getVersion());
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyRoute(it.next(), false, 1);
verifyEmpty(it.next(), "rtept");
assertFalse(it.hasNext());
}
}
}
/**
* Tests parsing of GPX version 1.1.0 route.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testRoute110() throws DataStoreException {
try (Store reader = create(TestData.V1_1, TestData.ROUTE)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_1, reader.getVersion());
verifyRoute110(reader);
}
}
/**
* Verifies the routes of GPX {@code "1.1/route.xml"} test file.
* This verification is shared by {@link #testRoute110()} and {@link #testSequentialReads()}.
*/
static void verifyRoute110(final Store reader) throws DataStoreException {
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyRoute(it.next(), true, 3);
verifyEmpty(it.next(), "rtept");
assertFalse(it.hasNext());
}
}
/**
* Verifies property values for the given route.
*
* @param f the route to verify.
* @param v11 {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
* @param numLinks expected number of links.
*/
@SuppressWarnings("fallthrough")
private static void verifyRoute(final Feature f, final boolean v11, final int numLinks) {
assertEquals("Route name", f.getPropertyValue("name"));
assertEquals("Route comment", f.getPropertyValue("cmt"));
assertEquals("Route description", f.getPropertyValue("desc"));
assertEquals("Route source", f.getPropertyValue("src"));
assertEquals(v11 ? "Route type" : null, f.getPropertyValue("type"));
assertEquals(7, f.getPropertyValue("number"));
final List<?> links = assertInstanceOf(List.class, f.getPropertyValue("link"));
assertEquals(numLinks, links.size());
switch (numLinks) {
default: // Fallthrough everywhere.
case 3: assertStringEquals("http://route-address3.org", links.get(2));
case 2: assertStringEquals("http://route-address2.org", links.get(1));
case 1: assertStringEquals("http://route-address1.org", links.get(0));
case 0: break;
}
final List<?> points = assertInstanceOf(List.class, f.getPropertyValue("rtept"));
assertEquals(3, points.size());
verifyPoint((Feature) points.get(0), 0, v11);
verifyPoint((Feature) points.get(1), 1, v11);
verifyPoint((Feature) points.get(2), 2, v11);
final Polyline p = assertInstanceOf(Polyline.class, f.getPropertyValue("sis:geometry"));
assertEquals(3, p.getPointCount());
assertEquals(new Point(15, 10), p.getPoint(0));
assertEquals(new Point(25, 20), p.getPoint(1));
assertEquals(new Point(35, 30), p.getPoint(2));
assertEnvelopeEquals(15, 35, 10, 30, (Envelope) f.getPropertyValue("sis:envelope"));
}
/**
* Verifies that all properties of the given route or track are null or empty.
*
* @param f the route or track to verify.
* @param dep {@code "rtept"} if verifying a route, or {@code "trkseg"} if verifying a track.
*/
private static void verifyEmpty(final Feature f, final String dep) {
assertNull(f.getPropertyValue("name"));
assertNull(f.getPropertyValue("cmt"));
assertNull(f.getPropertyValue("desc"));
assertNull(f.getPropertyValue("src"));
assertNull(f.getPropertyValue("type"));
assertNull(f.getPropertyValue("number"));
assertTrue(assertInstanceOf(Collection.class, f.getPropertyValue("link")).isEmpty());
assertTrue(assertInstanceOf(Collection.class, f.getPropertyValue(dep)).isEmpty());
assertNull(f.getPropertyValue("sis:envelope"));
}
/**
* Tests parsing of GPX version 1.0.0 track.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testTrack100() throws DataStoreException {
try (Store reader = create(TestData.V1_0, TestData.TRACK)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_0, reader.getVersion());
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyTrack(it.next(), false, 1);
verifyEmpty(it.next(), "trkseg");
assertFalse(it.hasNext());
}
}
}
/**
* Tests parsing of GPX version 1.1.0 track.
*
* @throws DataStoreException if reader failed to be created or failed at reading
*/
@Test
public void testTrack110() throws DataStoreException {
try (Store reader = create(TestData.V1_1, TestData.TRACK)) {
verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
assertEquals(StoreProvider.V1_1, reader.getVersion());
try (Stream<Feature> features = reader.features(false)) {
final Iterator<Feature> it = features.iterator();
verifyTrack(it.next(), true, 3);
verifyEmpty(it.next(), "trkseg");
assertFalse(it.hasNext());
}
}
}
/**
* Verifies property values for the given track.
*
* @param f the track to verify.
* @param v11 {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
* @param numLinks expected number of links.
*/
@SuppressWarnings("fallthrough")
private static void verifyTrack(final Feature f, final boolean v11, final int numLinks) {
assertEquals("Track name", f.getPropertyValue("name"));
assertEquals("Track comment", f.getPropertyValue("cmt"));
assertEquals("Track description", f.getPropertyValue("desc"));
assertEquals("Track source", f.getPropertyValue("src"));
assertEquals(v11 ? "Track type" : null, f.getPropertyValue("type"));
assertEquals(7, f.getPropertyValue("number"));
final List<?> links = assertInstanceOf(List.class, f.getPropertyValue("link"));
assertEquals(numLinks, links.size());
switch (numLinks) {
default: // Fallthrough everywhere.
case 3: assertStringEquals("http://track-address3.org", links.get(2));
case 2: assertStringEquals("http://track-address2.org", links.get(1));
case 1: assertStringEquals("http://track-address1.org", links.get(0));
case 0: break;
}
final List<?> segments = assertInstanceOf(List.class, f.getPropertyValue("trkseg"));
assertEquals(2, segments.size());
final Feature seg1 = (Feature) segments.get(0);
final Feature seg2 = (Feature) segments.get(1);
final List<?> points = assertInstanceOf(List.class, seg1.getPropertyValue("trkpt"));
assertEquals(3, points.size());
verifyPoint((Feature) points.get(0), 0, v11);
verifyPoint((Feature) points.get(1), 1, v11);
verifyPoint((Feature) points.get(2), 2, v11);
assertTrue(assertInstanceOf(Collection.class, seg2.getPropertyValue("trkpt")).isEmpty());
final Polyline p = assertInstanceOf(Polyline.class, f.getPropertyValue("sis:geometry"));
assertEquals(3, p.getPointCount());
assertEquals(new Point(15, 10), p.getPoint(0));
assertEquals(new Point(25, 20), p.getPoint(1));
assertEquals(new Point(35, 30), p.getPoint(2));
assertEnvelopeEquals(15, 35, 10, 30, (Envelope) f.getPropertyValue("sis:envelope"));
}
/**
* Verifies values of the given point.
*
* @param f the point to verify.
* @param index index of the point being verified: 0, 1 or 2.
* @param v11 {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
*/
private static void verifyPoint(final Feature f, final int index, final boolean v11) {
assertEquals(index + 1, f.getPropertyValue("sis:identifier"));
switch (index) {
case 0: {
assertEquals(Instant.parse("2010-01-10T00:00:00Z"), f.getPropertyValue("time"));
assertEquals( 15.0, ((Point) f.getPropertyValue("sis:geometry")).getX());
assertEquals( 10.0, ((Point) f.getPropertyValue("sis:geometry")).getY());
assertEquals( 140.0, f.getPropertyValue("ele"));
assertEquals( 35.0, f.getPropertyValue("magvar"));
assertEquals( 112.32, f.getPropertyValue("geoidheight"));
assertEquals( "first point", f.getPropertyValue("name"));
assertEquals( "first comment", f.getPropertyValue("cmt"));
assertEquals( "first description", f.getPropertyValue("desc"));
assertEquals( "first source", f.getPropertyValue("src"));
assertEquals( "first symbol", f.getPropertyValue("sym"));
assertEquals( "first type", f.getPropertyValue("type"));
assertEquals( Fix.NONE, f.getPropertyValue("fix"));
assertEquals( 11, f.getPropertyValue("sat"));
assertEquals( 15.15, f.getPropertyValue("hdop"));
assertEquals( 14.14, f.getPropertyValue("vdop"));
assertEquals( 13.13, f.getPropertyValue("pdop"));
assertEquals( 55.55, f.getPropertyValue("ageofdgpsdata"));
assertEquals(256, f.getPropertyValue("dgpsid"));
final List<?> links = assertInstanceOf(List.class, f.getPropertyValue("link"));
assertEquals(v11 ? 3 : 1, links.size());
assertStringEquals("http://first-address1.org", links.get(0));
if (v11) {
assertStringEquals("http://first-address2.org", links.get(1));
assertStringEquals("http://first-address3.org", links.get(2));
}
assertEnvelopeEquals(15, 15, 10, 10, (Envelope) f.getPropertyValue("sis:envelope"));
break;
}
case 1: {
assertEquals(25, ((Point)f.getPropertyValue("sis:geometry")).getX());
assertEquals(20, ((Point)f.getPropertyValue("sis:geometry")).getY());
assertNull(f.getPropertyValue("ele"));
assertNull(f.getPropertyValue("time"));
assertNull(f.getPropertyValue("magvar"));
assertNull(f.getPropertyValue("geoidheight"));
assertNull(f.getPropertyValue("name"));
assertNull(f.getPropertyValue("cmt"));
assertNull(f.getPropertyValue("desc"));
assertNull(f.getPropertyValue("src"));
assertNull(f.getPropertyValue("sym"));
assertNull(f.getPropertyValue("type"));
assertNull(f.getPropertyValue("fix"));
assertNull(f.getPropertyValue("sat"));
assertNull(f.getPropertyValue("hdop"));
assertNull(f.getPropertyValue("vdop"));
assertNull(f.getPropertyValue("pdop"));
assertNull(f.getPropertyValue("ageofdgpsdata"));
assertNull(f.getPropertyValue("dgpsid"));
assertTrue(assertInstanceOf(List.class, f.getPropertyValue("link")).isEmpty());
assertEnvelopeEquals(25, 25, 20, 20, (Envelope) f.getPropertyValue("sis:envelope"));
break;
}
case 2: {
assertEquals(Instant.parse("2010-01-30T00:00:00Z"), f.getPropertyValue("time"));
assertEquals( 35.0, ((Point) f.getPropertyValue("sis:geometry")).getX());
assertEquals( 30.0, ((Point) f.getPropertyValue("sis:geometry")).getY());
assertEquals( 150.0, f.getPropertyValue("ele"));
assertEquals( 25.0, f.getPropertyValue("magvar"));
assertEquals( 142.32, f.getPropertyValue("geoidheight"));
assertEquals("third point", f.getPropertyValue("name"));
assertEquals("third comment", f.getPropertyValue("cmt"));
assertEquals("third description", f.getPropertyValue("desc"));
assertEquals("third source", f.getPropertyValue("src"));
assertEquals("third symbol", f.getPropertyValue("sym"));
assertEquals("third type", f.getPropertyValue("type"));
assertEquals(Fix.THREE_DIMENSIONAL, f.getPropertyValue("fix"));
assertEquals( 35, f.getPropertyValue("sat"));
assertEquals( 35.15, f.getPropertyValue("hdop"));
assertEquals( 34.14, f.getPropertyValue("vdop"));
assertEquals( 33.13, f.getPropertyValue("pdop"));
assertEquals( 85.55, f.getPropertyValue("ageofdgpsdata"));
assertEquals(456, f.getPropertyValue("dgpsid"));
final List<?> links = assertInstanceOf(List.class, f.getPropertyValue("link"));
assertEquals(v11 ? 2 : 1, links.size());
assertStringEquals("http://third-address1.org", links.get(0));
if (v11) {
assertStringEquals("http://third-address2.org", links.get(1));
}
assertEnvelopeEquals(35, 35, 30, 30, (Envelope) f.getPropertyValue("sis:envelope"));
break;
}
default: {
fail("unexpected index:" + index);
break;
}
}
}
/**
* Tests parsing of GPX version 1.1.0 route without reading the metadata before it.
* The reader is expected to skip metadata without unmarshalling them.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testRouteSkipMetadata() throws DataStoreException {
try (Store reader = create(TestData.V1_1, TestData.ROUTE)) {
verifyRoute110(reader);
}
}
/**
* Creates a data store for the {@code "1.1/route.xml"} test files using its URL instead of the input stream.
* Using the URL makes easier for the data store to read the same data more than once.
*/
private Store createFromURL() throws DataStoreException {
final StorageConnector connector = new StorageConnector(TestData.V1_1.getURL(TestData.ROUTE));
connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.ESRI);
connector.setOption(OptionKey.URL_ENCODING, "UTF-8");
return new Store(provider, connector);
}
/**
* Tests reading the same data more than once. For this test, the XML resource needs to be obtained
* as a URL instead of as an input stream. But Apache SIS implementation will still tries to use
* mark/reset instead of re-opening a new stream if it can.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testSequentialReads() throws DataStoreException {
final Metadata md;
try (Store reader = createFromURL()) {
verifyRoute110(reader);
/*
* Ask for metadata only after a first read, for testing the way the store manages readers.
* The new 'features()' call should reuse the reader created by 'getMetadata()' - this can
* be verified by stepping in the code with a debugger.
*/
md = (Metadata) reader.getMetadata();
verifyRoute110(reader);
/*
* One more check.
*/
assertSame(md, reader.getMetadata());
verifyRoute110(reader);
}
verifyAlmostEmptyMetadata(md);
}
/**
* Tests reading the same data more than once without waiting
* that the previous iterator finishes its iteration.
*
* @throws DataStoreException if reader failed to be created or failed at reading.
*/
@Test
public void testConcurrentReads() throws DataStoreException {
try (Store reader = createFromURL()) {
final Stream<Feature> f1 = reader.features(false);
final Iterator<Feature> i1 = f1.iterator();
verifyRoute(i1.next(), true, 3);
final Stream<Feature> f2 = reader.features(false);
final Iterator<Feature> i2 = f2.iterator();
verifyEmpty(i1.next(), "rtept");
final Stream<Feature> f3 = reader.features(false);
final Iterator<Feature> i3 = f3.iterator();
verifyRoute(i2.next(), true, 3);
verifyRoute(i3.next(), true, 3);
verifyEmpty(i3.next(), "rtept");
verifyEmpty(i2.next(), "rtept");
assertFalse(i3.hasNext());
assertFalse(i1.hasNext());
assertFalse(i2.hasNext());
f2.close();
f1.close();
f3.close();
}
}
}