sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1800189 - in /sis/branches/JDK8: core/sis-feature/src/main/java/org/apache/sis/internal/feature/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ s...
Date Wed, 28 Jun 2017 18:03:07 GMT
Author: desruisseaux
Date: Wed Jun 28 18:03:06 2017
New Revision: 1800189

URL: http://svn.apache.org/viewvc?rev=1800189&view=rev
Log:
First draft of a Moving Feature CSV reader that build a Polyline from data splitted on many
lines.

Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/DateList.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/DateList.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/DateList.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/DateList.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/DateList.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -57,7 +57,7 @@ final class DateList extends AbstractLis
         for (final long t : millis) {
             if (t < min) min = t;
         }
-        long max = Long.MIN_VALUE;
+        long max = 1;
         for (int i=0; i<millis.length; i++) {
             final long t = (millis[i] -= min);
             if (t > max) max = t;

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -35,6 +35,7 @@ import java.time.Instant;
 import java.util.function.Consumer;
 import org.opengis.feature.Attribute;
 import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
 
 
 /**
@@ -71,7 +72,7 @@ public final class MovingFeature {
     /**
      * Number of {@code Property} instances added for each index of the {@link #properties}
table.
      */
-    private int[] count;
+    private final int[] count;
 
     /**
      * A dynamic property value together with the period of time in which this property is
valid.
@@ -110,12 +111,31 @@ public final class MovingFeature {
     }
 
     /**
+     * Overall start and end time over all properties.
+     */
+    private long tmin = Long.MAX_VALUE,
+                 tmax = Long.MIN_VALUE;
+
+    /**
      * Creates a new moving feature.
      *
      * @param numProperties  maximal number of dynamic properties.
      */
     public MovingFeature(final int numProperties) {
         properties = new Period[numProperties];
+        count      = new int   [numProperties];
+    }
+
+    /**
+     * Adds a time range.
+     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String,
Feature)}.
+     *
+     * @param  startTime  beginning in milliseconds since Java epoch of the period when the
property value is valid.
+     * @param  endTime    end in milliseconds since Java epoch of the period when the the
property value is valid.
+     */
+    public final void addTimeRange(final long startTime, final long endTime) {
+        if (startTime < tmin) tmin = startTime;
+        if (  endTime > tmax) tmax =   endTime;
     }
 
     /**
@@ -137,6 +157,21 @@ public final class MovingFeature {
     }
 
     /**
+     * Stores the start time and end time in the given feature.
+     *
+     * @param  startTime  name of the property where to store the start time.
+     * @param  endTime    name of the property where to store the end time.
+     * @param  dest       feature where to store the start time and end time.
+     */
+    public final void storeTimeRange(final String startTime, final String endTime, final
Feature dest) {
+        if (tmin < tmax) {
+            final Instant t = Instant.ofEpochMilli(tmin);
+            dest.setPropertyValue(startTime, t);
+            dest.setPropertyValue(endTime, (tmin == tmax) ? t : Instant.ofEpochMilli(tmax));
+        }
+    }
+
+    /**
      * Sets the values of the given attribute to the values collected by this {@code MovingFeatures}.
      * This method sets also the {@code "datetimes"} characteristic.
      *
@@ -180,19 +215,19 @@ public final class MovingFeature {
             final Geometries<G> factory, final Attribute<G> dest, final Consumer<LogRecord>
warningListener)
     {
         int n = count[index];
-        final Vector[] coords = new Vector[n];
+        final Vector[] vectors = new Vector[n];
         for (Period p = properties[index]; p != null; p = p.previous) {
-            coords[--n] = Vector.create(p.value, false);
+            vectors[--n] = Vector.create(p.value, false);
         }
         if (n != 0) {
             // Should never happen unless this object has been modified concurrently in another
thread.
             throw new CorruptedObjectException();
         }
         int    warnings = 10;                   // Maximal number of warnings, for avoiding
to flood the logger.
-        int    numPts   = 0;                    // Total number of points in all vectors,
ignoring invalid ones.
+        int    numPts   = 0;                    // Total number of points in all vectors,
ignoring null vectors.
         Vector previous = null;                 // If non-null, shall be non-empty.
-        for (int i=0; i<coords.length; i++) {
-            Vector v = coords[i];
+        for (int i=0; i<vectors.length; i++) {
+            Vector v = vectors[i];
             int length;
             if (v == null || (length = v.size()) == 0) {
                 continue;
@@ -217,10 +252,10 @@ public final class MovingFeature {
                     v = v.subList(dimension, length);                               // Skip
the first coordinate.
                     length -= dimension;
                     if (length == 0) {
-                        coords[i] = null;
+                        vectors[i] = null;
                         continue;
                     }
-                    coords[i] = v;
+                    vectors[i] = v;
                 }
             }
             numPts += length;
@@ -231,17 +266,21 @@ public final class MovingFeature {
          * We will create the geometry at the end of this method. Before that, interpolate
          * the dates and times.
          */
-        int i = coords.length;
+        int i = vectors.length;
         numPts /= dimension;
         final long[] times = new long[numPts];
         for (Period p = properties[index]; p != null; p = p.previous) {
-            final Vector v = coords[--i];
+            final Vector v = vectors[--i];
             if (v != null) {
                 int c = v.size() / dimension;
-                final long startTime = p.startTime;
-                final double scale = (p.endTime - startTime) / (double) Math.max(c-1, 1);
-                while (--c >= 0) {
-                    times[--numPts] = startTime + Math.round(scale * c);
+                if (c == 1) {
+                    times[--numPts] = p.endTime;
+                } else {
+                    final long startTime = p.startTime;
+                    final double scale = (p.endTime - startTime) / (double) (c-1);
+                    while (--c >= 0) {
+                        times[--numPts] = startTime + Math.round(scale * c);
+                    }
                 }
             }
         }
@@ -252,7 +291,7 @@ public final class MovingFeature {
         /*
          * Store the geometry and characteristics in the attribute.
          */
-        dest.setValue(factory.createPolyline(dimension, coords));
+        dest.setValue(factory.createPolyline(dimension, vectors));
         final Attribute<Instant> c = TIME.newInstance();
         c.setValues(new DateList(times));
         dest.characteristics().values().add(c);

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -17,54 +17,30 @@
 package org.apache.sis.internal.storage.csv;
 
 import org.apache.sis.internal.converter.SurjectiveConverter;
-import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.util.CharSequences;
-import org.apache.sis.math.Vector;
 
 
 /**
  * The converter to use for converting a text into a geometry.
- * The geometry class depends on the library available at runtime.
- *
- * @param  <G>  the geometry class.
+ * This converter performs only the first step, the conversion to a {@code double[]} array.
+ * The second step (the conversion to a geometry object) is performed after we collected
all arrays.
+ * The resulting geometry class depends on the library available at runtime.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  * @since   0.8
  * @module
  */
-final class GeometryParser<G> extends SurjectiveConverter<String,G> {
-    /**
-     * The unique instance using the default geometry library.
-     */
-    private static final GeometryParser<?> INSTANCE = new GeometryParser<>(Geometries.implementation(null),
(short) 2);
-
+final class GeometryParser extends SurjectiveConverter<String,double[]> {
     /**
-     * The factory to use for creating polylines.
+     * The unique instance.
      */
-    private final Geometries<G> geometries;
-
-    /**
-     * The number of dimensions other than time in the coordinate reference system.
-     * Shall be 2 or 3 according Moving Features CSV encoding specification, but Apache SIS
-     * may be tolerant to other values (depending on the backing geometry library).
-     */
-    private final short spatialDimensionCount;
-
-    /**
-     * Creates a new converter from CSV encoded trajectories to geometries.
-     */
-    private GeometryParser(final Geometries<G> geometries, final short spatialDimensionCount)
{
-        this.geometries = geometries;
-        this.spatialDimensionCount = spatialDimensionCount;
-    }
+    static final GeometryParser INSTANCE = new GeometryParser();
 
     /**
-     * Returns a parser instance for the given geometry factory.
+     * For the singleton instance.
      */
-    static GeometryParser<?> instance(final Geometries<?> geometries, final short
spatialDimensionCount) {
-        return (spatialDimensionCount == 2 && INSTANCE.geometries == geometries)
-               ? INSTANCE : new GeometryParser<>(geometries, spatialDimensionCount);
+    private GeometryParser() {
     }
 
     /**
@@ -76,26 +52,18 @@ final class GeometryParser<G> extends Su
     }
 
     /**
-     * Returns the type of converted elements. The returned type shall be the same than
-     * the type selected by {@code Store.parseFeatureType(…)} for the "trajectory" column.
+     * Returns the type of converted elements.
      */
     @Override
-    @SuppressWarnings("unchecked")
-    public Class<G> getTargetClass() {
-        return (Class<G>) geometries.polylineClass;
+    public Class<double[]> getTargetClass() {
+        return double[].class;
     }
 
     /**
-     * Converts an element from the CSV file to the geometry type.
+     * Converts an element from the CSV file to the array type.
      */
     @Override
-    public G apply(final String text) {
-        /*
-         * We could avoid the "unchecked" warning by using getTargetClass().cast(…), but
it would be
-         * a false sense of safety since 'getTargetClass()' is itself unchecked. The real
check will
-         * be performed by DefaultFeatureType.setPropertyValue(…) anyway.
-         */
-        return geometries.createPolyline(spatialDimensionCount,
-                Vector.create(CharSequences.parseDoubles(text, Store.ORDINATE_SEPARATOR),
false));
+    public double[] apply(final String text) {
+        return CharSequences.parseDoubles(text, Store.ORDINATE_SEPARATOR);
     }
 }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.storage.csv;
 
+import java.util.Map;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -43,10 +45,12 @@ import org.apache.sis.feature.DefaultFea
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.storage.io.IOUtilities;
 import org.apache.sis.internal.storage.FeatureStore;
 import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.MovingFeature;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.metadata.sql.MetadataStoreException;
@@ -71,6 +75,7 @@ import java.util.function.Consumer;
 import java.util.Spliterator;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+import org.opengis.feature.Attribute;
 import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
@@ -120,6 +125,12 @@ public final class Store extends Feature
     private static final String TYPE_PREFIX = "xsd:";
 
     /**
+     * Index of the column containing trajectory coordinates.
+     * Columns before the trajectory are Moving Feature identifier {@code mfidref}, start
time and end time.
+     */
+    private static final int TRAJECTORY_COLUMN = 3;
+
+    /**
      * The reader, set by the constructor and cleared when no longer needed.
      */
     private BufferedReader source;
@@ -182,16 +193,30 @@ public final class Store extends Feature
     private TimeEncoding timeEncoding;
 
     /**
+     * {@code true} if this reader should create a separated {@code Feature} instance for
each line in the CSV file.
+     * By default, this is {@code true} if the CSV files does not seem to contain moving
features.
+     * But the user can also force this value to {@code true}, for example for testing purposes.
+     */
+    private boolean dissociate;
+
+    /**
+     * All parsed moving features, or {@code null} if none or if not yet parsed. If {@link
#dissociate}
+     * is {@code false}, then this list will be created by {@link #features()} when first
needed.
+     */
+    private transient List<Feature> movingFeatures;
+
+    /**
      * Creates a new CSV store from the given file, URL or stream.
      *
      * <p>If the CSV file is known to be a Moving Feature file, then the given connector
should
      * have an {@link org.apache.sis.setup.OptionKey#ENCODING} associated to the UTF-8 value.</p>
      *
-     * @param  provider   the factory that created this {@code DataStore} instance, or {@code
null} if unspecified.
-     * @param  connector  information about the storage (URL, stream, <i>etc</i>).
+     * @param  provider    the factory that created this {@code DataStore} instance, or
{@code null} if unspecified.
+     * @param  connector   information about the storage (URL, stream, <i>etc</i>).
+     * @param  dissociate  {@code true} for forcing the creation of a different {@code Feature}
instance for each line.
      * @throws DataStoreException if an error occurred while opening the stream.
      */
-    public Store(final StoreProvider provider, final StorageConnector connector) throws DataStoreException
{
+    public Store(final StoreProvider provider, final StorageConnector connector, boolean
dissociate) throws DataStoreException {
         super(provider, connector);
         final Reader r = connector.getStorageAs(Reader.class);
         connector.closeAllExcept(r);
@@ -200,6 +225,7 @@ public final class Store extends Feature
         }
         source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
         geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
+        this.dissociate = dissociate;
         GeneralEnvelope envelope    = null;
         FeatureType     featureType = null;
         Foliation       foliation   = null;
@@ -221,6 +247,7 @@ public final class Store extends Feature
                             throw new DataStoreContentException(duplicated("@stboundedby"));
                         }
                         envelope = parseEnvelope(elements);
+                        dissociate |= (timeEncoding == null);   // Need to be updated before
parseFeatureType(…) execution.
                         break;
                     }
                     case "@columns": {
@@ -260,6 +287,7 @@ public final class Store extends Feature
         this.envelope    = envelope;
         this.featureType = featureType;
         this.foliation   = foliation;
+        this.dissociate |= (timeEncoding == null);
     }
 
     /**
@@ -425,10 +453,12 @@ public final class Store extends Feature
      *   &#64;columns, mfidref, trajectory, state,xsd:token, "type code",xsd:integer
      * }
      *
-     * @param  elements  the line elements. The first elements should be {@code "@columns"}.
+     * @param  elements  the line elements. The first element should be {@code "@columns"}.
      * @return the column metadata, or {@code null} if the given list does not contain enough
elements.
      */
+    @SuppressWarnings("rawtypes")               // "rawtypes" because of generic array creation.
     private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException
{
+        AttributeType[] characteristics = {};
         final int size = elements.size();
         final List<PropertyType> properties = new ArrayList<>();
         for (int i=1; i<size; i++) {
@@ -450,6 +480,7 @@ public final class Store extends Feature
                 }
             }
             int minOccurrence = 0;
+            int maxOccurrence = dissociate ? 1 : Integer.MAX_VALUE;
             if (type == null) {
                 /*
                  * If the column name was not followed by a type, default to a String type
except in the special
@@ -464,22 +495,33 @@ public final class Store extends Feature
                  */
                 type = String.class;
                 switch (--i) {
-                    case 1: minOccurrence = 1; break;
-                    case 2: {
+                    case 0:                                             // "@column" (should
not happen actually)
+                    case 1: {
+                        minOccurrence = 1;                              // "mfidref"
+                        maxOccurrence = 1;
+                        break;
+                    }
+                    case 2: {                                           // "trajectory" or
property.
                         if (name.equalsIgnoreCase("trajectory")) {
                             hasTrajectories = true;
                             if (timeEncoding != null) {
-                                properties.add(createProperty("startTime", Instant.class,
1));
-                                properties.add(createProperty(  "endTime", Instant.class,
1));
+                                properties.add(createProperty("startTime", Instant.class,
1, 1, null));
+                                properties.add(createProperty(  "endTime", Instant.class,
1, 1, null));
+                            }
+                            if (dissociate) {
+                                type = double[].class;
+                            } else {
+                                type = geometries.polylineClass;
+                                characteristics = new AttributeType[] {MovingFeature.TIME};
                             }
-                            type = geometries.polylineClass;
                             minOccurrence = 1;
+                            maxOccurrence = 1;
                         }
                         break;
                     }
                 }
             }
-            properties.add(createProperty(name, type, minOccurrence));
+            properties.add(createProperty(name, type, minOccurrence, maxOccurrence, characteristics));
         }
         String name = super.getDisplayName();
         final int s = name.lastIndexOf('.');
@@ -493,8 +535,11 @@ public final class Store extends Feature
     /**
      * Creates a property type for the given name and type.
      */
-    private static PropertyType createProperty(final String name, final Class<?> type,
final int minOccurrence) {
-        return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY,
name), type, minOccurrence, 1, null);
+    private static PropertyType createProperty(final String name, final Class<?> type,
+            final int minOccurrence, final int maxOccurrence, final AttributeType<?>[]
characteristics)
+    {
+        return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY,
name),
+                type, minOccurrence, maxOccurrence, null, characteristics);
     }
 
     /**
@@ -579,18 +624,30 @@ public final class Store extends Feature
      * Returns the stream of features.
      *
      * @return a stream over all features in the CSV file.
+     * @throws DataStoreException if an error occurred while creating the feature stream.
      *
      * @todo Needs to reset the position when doing another pass on the features.
+     * @todo If sequential order, publish Feature as soon as identifier changed.
      */
     @Override
-    public Stream<Feature> features() {
-        return StreamSupport.stream(new Iter(), false);
+    public synchronized Stream<Feature> features() throws DataStoreException {
+        if (dissociate) {
+            return StreamSupport.stream(new Iter(), false);
+        }
+        if (movingFeatures == null) try {
+            final Iter iter = new Iter();
+            iter.readMoving(null, true);
+            movingFeatures = UnmodifiableArrayList.wrap(iter.createMovingFeatures());
+        } catch (IOException | IllegalArgumentException | DateTimeException e) {
+            throw new DataStoreException(canNotParseFile(), e);
+        }
+        return movingFeatures.stream();
     }
 
     /**
      * Implementation of the iterator returned by {@link #features()}.
      */
-    private final class Iter implements Spliterator<Feature> {
+    private final class Iter implements Spliterator<Feature>, Consumer<LogRecord>
{
         /**
          * Converters from string representations to the values to store in the {@link #values}
array.
          */
@@ -609,6 +666,21 @@ public final class Store extends Feature
         private final String[] propertyNames;
 
         /**
+         * Identifier of the feature in process of being parsed.
+         */
+        private String identifier;
+
+        /**
+         * Where to store the property values and the trajectory of the feature in process
of being parsed.
+         */
+        private MovingFeature builder;
+
+        /**
+         * All builders by feature name (not only the one being parsed).
+         */
+        private final Map<String,MovingFeature> builders;
+
+        /**
          * Creates a new iterator.
          */
         @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
@@ -643,9 +715,9 @@ public final class Store extends Feature
                          * Fall through in order to process trajectory.
                          */
                     }
-                    case 3: {
+                    case TRAJECTORY_COLUMN: {
                         if (hasTrajectories) {
-                            c = GeometryParser.instance(geometries, spatialDimensionCount);
+                            c = GeometryParser.INSTANCE;
                             break;
                         }
                         /*
@@ -661,10 +733,99 @@ public final class Store extends Feature
                 }
                 converters[i] = c;
             }
+            builders = new LinkedHashMap<>();
+        }
+
+        /**
+         * Creates all moving features.
+         * This method can only be invoked after {@link #readMoving(Consumer, boolean)} completion.
+         * This method is ignored if the CSV file contains only static features.
+         */
+        Feature[] createMovingFeatures() {
+            int n = 0;
+            final int np = values.length - TRAJECTORY_COLUMN;
+            final Feature[] features = new Feature[builders.size()];
+            for (final Map.Entry<String,MovingFeature> entry : builders.entrySet())
{
+                features[n++] = createMovingFeature(entry.getKey(), entry.getValue(), np);
+            }
+            return features;
+        }
+
+        /**
+         * Creates the moving feature of the given name.
+         * This method can only be invoked after {@link #readMoving(Consumer, boolean)}.
+         *
+         * @param  featureName  name of the feature to create.
+         * @param  np           number of properties, ignoring the ones before the trajectory
column.
+         */
+        @SuppressWarnings("unchecked")
+        private Feature createMovingFeature(final String featureName, final MovingFeature
mf, final int np) {
+            final Feature feature = featureType.newInstance();
+            feature.setPropertyValue(propertyNames[0], featureName);
+            mf.storeTimeRange(propertyNames[1], propertyNames[2], feature);
+            int column = 0;
+            if (hasTrajectories) {
+                mf.storeGeometry(featureName, column, spatialDimensionCount, geometries,
+                        (Attribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]),
this);
+                column++;
+            }
+            while (column < np) {
+                mf.storeAttribute(column, (Attribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN
+ column]));
+                column++;
+            }
+            return feature;
+        }
+
+        /**
+         * Executes the given action for the next moving feature or for all remaining moving
features.
+         * This method assumes that the 4 first columns are as documented in the constructor.
+         *
+         * @param  action  the action to execute as soon as the {@code mfidref} change, or
{@code null} if none.
+         * @param  all     {@code true} for executing the given action on all remaining features.
+         * @return {@code false} if there is no remaining feature after this method call.
+         * @throws IOException if an I/O error occurred while reading a feature.
+         * @throws IllegalArgumentException if parsing of a number failed, or other error.
+         * @throws DateTimeException if parsing of a date failed.
+         */
+        boolean readMoving(final Consumer<? super Feature> action, final boolean all)
throws IOException {
+            final FixedSizeList elements = new FixedSizeList(values);
+            final int np = values.length - TRAJECTORY_COLUMN;
+            String line;
+            while ((line = source.readLine()) != null) {
+                split(line, elements);
+                int n = elements.size();
+                for (int i=0; i<n; i++) {
+                    values[i] = converters[i].apply((String) values[i]);
+                }
+                final String  mfidref   =  (String)  values[0];
+                final long    startTime = ((Instant) values[1]).toEpochMilli();
+                final long    endTime   = ((Instant) values[2]).toEpochMilli();
+                String        publish   = null;
+                if (!mfidref.equals(identifier)) {
+                    publish    = identifier;
+                    identifier = mfidref;
+                    builder    = builders.computeIfAbsent(mfidref, (k) -> new MovingFeature(np));
+                }
+                builder.addTimeRange(startTime, endTime);
+                for (int i=0; i<np; i++) {
+                    builder.addValue(i, startTime, endTime, values[i + TRAJECTORY_COLUMN]);
+                }
+                /*
+                 * If we started a new feature and the features are stored in sequential
order,
+                 * we can publish the previous one right away.
+                 */
+                if (publish != null && action != null) {
+                    action.accept(createMovingFeature(publish, builders.remove(publish),
np));
+                    if (!all) return true;
+                }
+                elements.clear();
+            }
+            return false;
         }
 
         /**
          * Executes the given action for the next feature or for all remaining features.
+         * The features are assumed static, with one feature per line.
          *
          * <p><b>Multi-threading:</b>
          * There is no need for {@code synchronize(Store.this)} statement since this method
uses only final and
@@ -678,7 +839,7 @@ public final class Store extends Feature
          * @throws IllegalArgumentException if parsing of a number failed, or other error.
          * @throws DateTimeException if parsing of a date failed.
          */
-        private boolean read(final Consumer<? super Feature> action, boolean all) throws
IOException {
+        private boolean read(final Consumer<? super Feature> action, final boolean
all) throws IOException {
             final FixedSizeList elements = new FixedSizeList(values);
             String line;
             while ((line = source.readLine()) != null) {
@@ -754,6 +915,16 @@ public final class Store extends Feature
         public int characteristics() {
             return ORDERED | NONNULL | IMMUTABLE;
         }
+
+        /**
+         * Invoked when a warning occurred while computing the geometry.
+         */
+        @Override
+        public void accept(final LogRecord warning) {
+            warning.setSourceClassName(Store.class.getName());
+            warning.setSourceMethodName("stream");
+            listeners.warning(warning);
+        }
     }
 
     /**
@@ -835,8 +1006,7 @@ public final class Store extends Feature
      * The error message will contain the line number if available.
      */
     final String canNotParseFile() {
-        final Object[] parameters = IOUtilities.errorMessageParameters(StoreProvider.NAME,
getDisplayName(), source);
-        return errors().getString(IOUtilities.errorMessageKey(parameters), parameters);
+        return IOUtilities.canNotReadFile(getLocale(), StoreProvider.NAME, getDisplayName(),
source);
     }
 
     /**

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -138,6 +138,6 @@ public final class StoreProvider extends
      */
     @Override
     public DataStore open(final StorageConnector connector) throws DataStoreException {
-        return new Store(this, connector);
+        return new Store(this, connector, false);
     }
 }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -528,6 +528,23 @@ public final class IOUtilities extends S
     }
 
     /**
+     * Returns the error message for a file that can not be parsed.
+     * The error message will contain the line number if available.
+     *
+     * @param  locale    the language for the error message.
+     * @param  format    abbreviation of the file format (e.g. "CSV", "GML", "WKT", <i>etc</i>).
+     * @param  filename  name of the file or the data store.
+     * @param  store     the input or output object, or {@code null}.
+     * @return the parameters for a localized error message for a file that can not be processed.
+     *
+     * @since 0.8
+     */
+    public static String canNotReadFile(final Locale locale, final String format, final String
filename, final Object store) {
+        final Object[] parameters = errorMessageParameters(format, filename, store);
+        return Resources.forLocale(locale).getString(errorMessageKey(parameters), parameters);
+    }
+
+    /**
      * Returns the {@link Resources.Keys} value together with the parameters given by {@code
errorMessageParameters(…)}.
      *
      * @param   parameters  the result of {@code errorMessageParameters(…)} method call.

Modified: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.storage.csv;
 
+import java.util.Arrays;
 import java.util.Iterator;
 import java.io.StringReader;
 import org.opengis.metadata.Metadata;
@@ -29,6 +30,7 @@ import com.esri.core.geometry.Point2D;
 import com.esri.core.geometry.Polyline;
 
 import static org.junit.Assert.*;
+import static java.util.Collections.singletonList;
 import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
@@ -50,6 +52,11 @@ import org.opengis.feature.AttributeType
  */
 public final strictfp class StoreTest extends TestCase {
     /**
+     * {@code true} if testing a moving feature, or {@code false} (the default) if testing
a static feature.
+     */
+    private boolean isMovingFeature;
+
+    /**
      * An example of Moving Features file.
      * Derived from the example provided in OGC 14-084r2.
      */
@@ -79,7 +86,7 @@ public final strictfp class StoreTest ex
     @Test
     public void testGetMetadata() throws DataStoreException {
         final Metadata metadata;
-        try (Store store = new Store(null, new StorageConnector(testData()))) {
+        try (Store store = new Store(null, new StorageConnector(testData()), true)) {
             metadata = store.getMetadata();
         }
         final Extent extent = getSingleton(getSingleton(metadata.getIdentificationInfo()).getExtents());
@@ -97,9 +104,9 @@ public final strictfp class StoreTest ex
      * @throws DataStoreException if an error occurred while parsing the data.
      */
     @Test
-    public void testGetFeatures() throws DataStoreException {
-        try (Store store = new Store(null, new StorageConnector(testData()))) {
-            verifyFeatureType(store.featureType);
+    public void testStaticFeatures() throws DataStoreException {
+        try (Store store = new Store(null, new StorageConnector(testData()), true)) {
+            verifyFeatureType(store.featureType, double[].class, 1);
             assertEquals("foliation", Foliation.TIME, store.foliation);
             final Iterator<Feature> it = store.features().iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11,
2, 12, 3},        "walking", 1);
@@ -111,16 +118,44 @@ public final strictfp class StoreTest ex
     }
 
     /**
+     * Tests reading the data as a moving features. In the following data:
+     *
+     * {@preformat text
+     *     a,  10, 150, 11.0 2.0 12.0 3.0, walking, 1
+     *     b,  10, 190, 10.0 2.0 11.0 3.0, walking, 2
+     *     a, 150, 190, 12.0 3.0 10.0 3.0
+     *     c,  10, 190, 12.0 1.0 10.0 2.0 11.0 3.0, vehicle, 1
+     * }
+     *
+     * the two rows for the "a" features shall be merged in a single trajectory.
+     *
+     * @throws DataStoreException if an error occurred while parsing the data.
+     */
+    @Test
+    public void testMovingFeatures() throws DataStoreException {
+        isMovingFeature = true;
+        try (Store store = new Store(null, new StorageConnector(testData()), false)) {
+            verifyFeatureType(store.featureType, Polyline.class, Integer.MAX_VALUE);
+            assertEquals("foliation", Foliation.TIME, store.foliation);
+            final Iterator<Feature> it = store.features().iterator();
+            assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:51", new double[] {11,
2, 12, 3, 10, 3}, singletonList("walking"), Arrays.asList(1, 2));
+            assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10,
2, 11, 3},        singletonList("walking"), singletonList(2));
+            assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12,
1, 10, 2, 11, 3}, singletonList("vehicle"), singletonList(1));
+            assertFalse(it.hasNext());
+        }
+    }
+
+    /**
      * Verifies that the feature type is equal to the expected one.
      */
-    private static void verifyFeatureType(final FeatureType type) {
+    private static void verifyFeatureType(final FeatureType type, final Class<?> geometryType,
final int maxOccurs) {
         final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,
  1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,
 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,
 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    Polyline.class,
1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,
  0);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class,
 0);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,
  1, 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,
 1, 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,
 1, 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    geometryType,
  1, 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,
  0, maxOccurs);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class,
 0, maxOccurs);
         assertFalse(it.hasNext());
     }
 
@@ -128,27 +163,37 @@ public final strictfp class StoreTest ex
      * Asserts that the given property type has the given information.
      */
     private static void assertPropertyTypeEquals(final AttributeType<?> p,
-            final String name, final Class<?> valueClass, final int minOccurs)
+            final String name, final Class<?> valueClass, final int minOccurs, final
int maxOccurs)
     {
         assertEquals("name",       name,       p.getName().toString());
         assertEquals("valueClass", valueClass, p.getValueClass());
         assertEquals("minOccurs",  minOccurs,  p.getMinimumOccurs());
-        assertEquals("maxOccurs",  1,          p.getMaximumOccurs());
+        assertEquals("maxOccurs",  maxOccurs,  p.getMaximumOccurs());
     }
 
     /**
      * Asserts that the property of the given name in the given feature has expected information.
      */
-    private static void assertPropertyEquals(final Feature f, final String mfidref,
+    private void assertPropertyEquals(final Feature f, final String mfidref,
             final String startTime, final String endTime, final double[] trajectory,
-            final String state, final int typeCode)
+            final Object state, final Object typeCode)
     {
         assertEquals("mfidref",   mfidref,            f.getPropertyValue("mfidref"));
         assertEquals("startTime", instant(startTime), f.getPropertyValue("startTime"));
         assertEquals("endTime",   instant(endTime),   f.getPropertyValue("endTime"));
         assertEquals("state",     state,              f.getPropertyValue("state"));
         assertEquals("typeCode",  typeCode,           f.getPropertyValue("\"type\" code"));
-        final Polyline polyline = (Polyline)          f.getPropertyValue("trajectory");
+        if (isMovingFeature) {
+            assertPolylineEquals(trajectory, (Polyline) f.getPropertyValue("trajectory"));
+        } else {
+            assertArrayEquals("trajectory", trajectory, (double[]) f.getPropertyValue("trajectory"),
STRICT);
+        }
+    }
+
+    /**
+     * Asserts that the given polyline contains the expected coordinate values.
+     */
+    private static void assertPolylineEquals(final double[] trajectory, final Polyline polyline)
{
         assertEquals("pointCount", trajectory.length / 2, polyline.getPointCount());
         for (int i=0; i < trajectory.length;) {
             final Point2D xy = polyline.getXY(i / 2);

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java?rev=1800189&r1=1800188&r2=1800189&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
[UTF-8] Wed Jun 28 18:03:06 2017
@@ -556,8 +556,7 @@ parse:  switch (value.length()) {
      * @return a localized error message for a file that can not be parsed.
      */
     protected final String canNotParseFile() {
-        final Object[] parameters = IOUtilities.errorMessageParameters(owner.getFormatName(),
owner.getDisplayName(), reader);
-        return errors().getString(IOUtilities.errorMessageKey(parameters), parameters);
+        return IOUtilities.canNotReadFile(owner.getLocale(), owner.getFormatName(), owner.getDisplayName(),
reader);
     }
 
     /**



Mime
View raw message