sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1801833 [4/4] - in /sis/trunk: ./ core/sis-feature/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/main/java/org/apache/sis/feature/builder/ core/sis-feature/src/main/java/org/apache/sis/internal/feature/ core/...
Date Thu, 13 Jul 2017 12:45:04 GMT
Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -21,8 +21,10 @@ import org.apache.sis.util.CharSequences
 
 
 /**
- * The converter to use for converting a text into a geometry. In current implementation,
- * geometries are line strings represented by an array of ordinate values.
+ * The converter to use for converting a text into a geometry.
+ * 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
@@ -58,7 +60,7 @@ final class GeometryParser extends Surje
     }
 
     /**
-     * Converts an element from the CSV file to our current pseudo-geometry type.
+     * Converts an element from the CSV file to the array type.
      */
     @Override
     public double[] apply(final String text) {

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] Thu Jul 13 12:45:03 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;
@@ -42,12 +44,15 @@ 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;
-import org.apache.sis.internal.storage.FeatureStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreReferencingException;
@@ -65,6 +70,7 @@ import org.apache.sis.measure.Units;
 // Branch-dependent imports
 import org.apache.sis.internal.jdk8.Instant;
 import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
 import org.apache.sis.feature.AbstractIdentifiedType;
 import org.apache.sis.internal.jdk8.DateTimeException;
 import org.apache.sis.internal.jdk8.Consumer;
@@ -116,6 +122,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;
@@ -154,6 +166,18 @@ public final class Store extends Feature
     private boolean hasTrajectories;
 
     /**
+     * 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 short spatialDimensionCount;
+
+    /**
+     * The factory to use for creating geometries.
+     */
+    private final Geometries<?> geometries;
+
+    /**
      * Appearing order of trajectories (time or sequential), or {@code null} if unspecified.
      *
      * @see #parseFoliation(List)
@@ -166,16 +190,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<AbstractFeature> 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);
@@ -183,9 +221,11 @@ public final class Store extends Feature
             throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, super.getDisplayName()));
         }
         source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
-        GeneralEnvelope envelope = null;
+        geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
+        this.dissociate = dissociate;
+        GeneralEnvelope    envelope    = null;
         DefaultFeatureType featureType = null;
-        Foliation foliation = null;
+        Foliation          foliation   = null;
         try {
             final List<String> elements = new ArrayList<>();
             source.mark(1024);
@@ -204,6 +244,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": {
@@ -233,16 +274,17 @@ public final class Store extends Feature
             }
             source.reset();
         } catch (IOException e) {
-            throw new DataStoreException(getLocale(), "CSV", super.getDisplayName(), source).initCause(e);
+            throw new DataStoreException(getLocale(), StoreProvider.NAME, super.getDisplayName(),
source).initCause(e);
         } catch (FactoryException e) {
-            throw new DataStoreReferencingException(getLocale(), "CSV", super.getDisplayName(),
source).initCause(e);
+            throw new DataStoreReferencingException(getLocale(), StoreProvider.NAME, super.getDisplayName(),
source).initCause(e);
         } catch (IllegalArgumentException | DateTimeException e) {
-            throw new DataStoreContentException(getLocale(), "CSV", super.getDisplayName(),
source).initCause(e);
+            throw new DataStoreContentException(getLocale(), StoreProvider.NAME, super.getDisplayName(),
source).initCause(e);
         }
         this.encoding    = connector.getOption(OptionKey.ENCODING);
         this.envelope    = envelope;
         this.featureType = featureType;
         this.foliation   = foliation;
+        this.dissociate |= (timeEncoding == null);
     }
 
     /**
@@ -261,6 +303,7 @@ public final class Store extends Feature
     private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException,
FactoryException {
         CoordinateReferenceSystem crs = null;
         int spatialDimensionCount = 2;
+        boolean    isDimExplicit  = false;
         double[]   lowerCorner    = ArraysExt.EMPTY_DOUBLE;
         double[]   upperCorner    = ArraysExt.EMPTY_DOUBLE;
         Instant    startTime      = null;
@@ -275,8 +318,9 @@ public final class Store extends Feature
                     case 0: continue;                                       // The "@stboundedby"
header.
                     case 1: crs = CRS.forCode(element); continue;
                     case 2: if (element.length() == 2 && Character.toUpperCase(element.charAt(1))
== 'D') {
+                                isDimExplicit = true;
                                 spatialDimensionCount = element.charAt(0) - '0';
-                                if (spatialDimensionCount < 2 || spatialDimensionCount
> 3) {
+                                if (spatialDimensionCount < 1 || spatialDimensionCount
> 3) {
                                     throw new DataStoreReferencingException(errors().getString(
                                         Errors.Keys.IllegalCoordinateSystem_1, element));
                                 }
@@ -323,12 +367,33 @@ public final class Store extends Feature
             int count = 0;
             final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
             components[count++] = crs;
-
-            // If the coordinates are three-dimensional but the CRS is 2D, add a vertical
axis.
-            if (spatialDimensionCount >= 3 && crs.getCoordinateSystem().getDimension()
== 2) {
-                components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs();
+            /*
+             * If the coordinates are three-dimensional but the CRS is 2D, add a vertical
axis.
+             * The vertical axis shall be the third one, however we do not enforce that rule
+             * since Apache SIS should work correctly even if the vertical axis is elsewhere.
+             */
+            int dimension = crs.getCoordinateSystem().getDimension();
+            if (isDimExplicit) {
+                if (spatialDimensionCount > dimension) {
+                    components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs();
+                    dimension++;
+                }
+                if (dimension != spatialDimensionCount) {
+                    throw new DataStoreReferencingException(errors().getString(
+                            Errors.Keys.MismatchedDimension_3, "@stboundedby(CRS)", spatialDimensionCount,
dimension));
+                }
+            }
+            if (dimension > Short.MAX_VALUE) {
+                throw new DataStoreReferencingException(errors().getString(
+                        Errors.Keys.ExcessiveNumberOfDimensions_1, dimension));
             }
-            // Add a temporal axis if we have a start time (no need for end time).
+            spatialDimensionCount = dimension;
+            /*
+             * Add a temporal axis if we have a start time (no need for end time).
+             * This block presumes that the CRS does not already have a time axis.
+             * If a time axis was already present, an exception will be thrown at
+             * builder.createCompoundCRS(…) invocation time.
+             */
             final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
             String name = crs.getName().getCode();
             if (startTime != null) {
@@ -363,7 +428,7 @@ public final class Store extends Feature
             (dim = upperCorner.length) != spatialDimensionCount)
         {
             throw new DataStoreReferencingException(errors().getString(
-                    Errors.Keys.MismatchedDimension_2, dim, spatialDimensionCount));
+                    Errors.Keys.MismatchedDimension_3, "@stboundedby(BBOX)", spatialDimensionCount,
dim));
         }
         for (int i=0; i<spatialDimensionCount; i++) {
             envelope.setRange(i, lowerCorner[i], upperCorner[i]);
@@ -372,6 +437,7 @@ public final class Store extends Feature
             envelope.setRange(spatialDimensionCount, timeEncoding.toCRS(startTime.toEpochMilli()),
                     (endTime == null) ? Double.NaN : timeEncoding.toCRS(endTime.toEpochMilli()));
         }
+        this.spatialDimensionCount = (short) spatialDimensionCount;
         return envelope;
     }
 
@@ -384,10 +450,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 DefaultFeatureType parseFeatureType(final List<String> elements) throws
DataStoreException {
+        DefaultAttributeType[] characteristics = null;
         final int size = elements.size();
         final List<AbstractIdentifiedType> properties = new ArrayList<>();
         for (int i=1; i<size; i++) {
@@ -409,6 +477,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
@@ -423,22 +492,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 DefaultAttributeType[] {MovingFeature.TIME};
                             }
-                            type = double[].class;
                             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('.');
@@ -452,8 +532,11 @@ public final class Store extends Feature
     /**
      * Creates a property type for the given name and type.
      */
-    private static AbstractIdentifiedType 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 AbstractIdentifiedType createProperty(final String name, final Class<?>
type,
+            final int minOccurrence, final int maxOccurrence, final DefaultAttributeType<?>[]
characteristics)
+    {
+        return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY,
name),
+                type, minOccurrence, maxOccurrence, null, characteristics);
     }
 
     /**
@@ -486,7 +569,7 @@ public final class Store extends Feature
         if (metadata == null) {
             final MetadataBuilder builder = new MetadataBuilder();
             try {
-                builder.setFormat(timeEncoding != null && hasTrajectories ? "CSV-MF"
: "CSV");
+                builder.setFormat(timeEncoding != null && hasTrajectories ? StoreProvider.MOVING
: StoreProvider.NAME);
             } catch (MetadataStoreException e) {
                 listeners.warning(null, e);
             }
@@ -495,7 +578,7 @@ public final class Store extends Feature
             try {
                 builder.addExtent(envelope);
             } catch (TransformException e) {
-                throw new DataStoreReferencingException(getLocale(), "CSV", getDisplayName(),
source).initCause(e);
+                throw new DataStoreReferencingException(getLocale(), StoreProvider.NAME,
getDisplayName(), source).initCause(e);
             } catch (UnsupportedOperationException e) {
                 /*
                  * Failed to set the temporal components if the sis-temporal module was
@@ -538,18 +621,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<AbstractFeature> features() {
-        return StreamSupport.stream(new Iter(), false);
+    public synchronized Stream<AbstractFeature> 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 Stream.create(movingFeatures);
     }
 
     /**
      * Implementation of the iterator returned by {@link #features()}.
      */
-    private final class Iter implements Spliterator<AbstractFeature> {
+    private final class Iter implements Spliterator<AbstractFeature>, Consumer<LogRecord>
{
         /**
          * Converters from string representations to the values to store in the {@link #values}
array.
          */
@@ -568,6 +663,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"})
@@ -602,7 +712,7 @@ public final class Store extends Feature
                          * Fall through in order to process trajectory.
                          */
                     }
-                    case 3: {
+                    case TRAJECTORY_COLUMN: {
                         if (hasTrajectories) {
                             c = GeometryParser.INSTANCE;
                             break;
@@ -620,10 +730,103 @@ 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.
+         */
+        AbstractFeature[] createMovingFeatures() {
+            int n = 0;
+            final int np = values.length - TRAJECTORY_COLUMN;
+            final AbstractFeature[] features = new AbstractFeature[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 AbstractFeature createMovingFeature(final String featureName, final MovingFeature
mf, final int np) {
+            final AbstractFeature 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,
+                        (AbstractAttribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]),
this);
+                column++;
+            }
+            while (column < np) {
+                mf.storeAttribute(column, (AbstractAttribute<?>) 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 AbstractFeature> 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.get(mfIdRef);
+                    if (builder == null) {
+                        builder = new MovingFeature(np);
+                        builders.put(mfIdRef, builder);
+                    }
+                }
+                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
@@ -637,7 +840,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 AbstractFeature> action, boolean
all) throws IOException {
+        private boolean read(final Consumer<? super AbstractFeature> action, final
boolean all) throws IOException {
             final FixedSizeList elements = new FixedSizeList(values);
             String line;
             while ((line = source.readLine()) != null) {
@@ -713,6 +916,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);
+        }
     }
 
     /**
@@ -794,8 +1007,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("CSV", getDisplayName(),
source);
-        return errors().getString(IOUtilities.errorMessageKey(parameters), parameters);
+        return IOUtilities.canNotReadFile(getLocale(), StoreProvider.NAME, getDisplayName(),
source);
     }
 
     /**

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -42,6 +42,11 @@ import org.apache.sis.internal.storage.w
 @Capabilities(Capability.READ)
 public final class StoreProvider extends DataStoreProvider {
     /**
+     * The format names for static features and moving features.
+     */
+    static final String NAME = "CSV", MOVING = "CSV-MF";
+
+    /**
      * The object to use for verifying if the first keyword is the expected one.
      */
     private static final class Peek extends FirstKeywordPeek {
@@ -108,7 +113,7 @@ public final class StoreProvider extends
      */
     @Override
     public String getShortName() {
-        return "CSV";
+        return NAME;
     }
 
     /**
@@ -133,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/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -531,6 +531,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/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] Thu Jul 13 12:45:03 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;
@@ -25,8 +26,11 @@ import org.apache.sis.storage.DataStoreE
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
+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;
 
@@ -49,6 +53,11 @@ import org.apache.sis.internal.jdk8.Inst
  */
 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.
      */
@@ -78,7 +87,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(((AbstractIdentification) getSingleton(metadata.getIdentificationInfo())).getExtents());
@@ -96,9 +105,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<AbstractFeature> it = store.features().iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11,
2, 12, 3},        "walking", 1);
@@ -110,16 +119,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<AbstractFeature> 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 DefaultFeatureType type) {
+    private static void verifyFeatureType(final DefaultFeatureType type, final Class<?>
geometryType, final int maxOccurs) {
         final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",  
    String.class,   1);
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",
    Instant.class,  1);
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",  
    Instant.class,  1);
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",
   double[].class, 1);
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",    
    String.class,   0);
-        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code",
Integer.class,  0);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",  
    String.class,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",
    Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",  
    Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",
   geometryType,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",    
    String.class,   0, maxOccurs);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code",
Integer.class,  0, maxOccurs);
         assertFalse(it.hasNext());
     }
 
@@ -127,26 +164,42 @@ public final strictfp class StoreTest ex
      * Asserts that the given property type has the given information.
      */
     private static void assertPropertyTypeEquals(final DefaultAttributeType<?> 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 AbstractFeature f, final String mfidref,
+    private void assertPropertyEquals(final AbstractFeature 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"));
-        assertArrayEquals("trajectory", trajectory, (double[]) f.getPropertyValue("trajectory"),
STRICT);
+        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"));
+        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);
+            assertEquals("x", trajectory[i++], xy.x, STRICT);
+            assertEquals("y", trajectory[i++], xy.y, STRICT);
+        }
     }
 }

Modified: sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -100,7 +100,7 @@ final class GroupAsPolylineOperation ext
      *
      * @param  geometries  accessor to the geometry implementation in use (Java2D, ESRI or
JTS).
      */
-    static DefaultAttributeType<?> getResult(final Geometries geometries) {
+    static <G> DefaultAttributeType<? extends G> getResult(final Geometries<G>
geometries) {
         return new DefaultAttributeType<>(Collections.singletonMap(NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY),
                 geometries.polylineClass, 1, 1, null);
     }

Modified: sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -97,7 +97,7 @@ final class Types {
     /**
      * Accessor to the geometry implementation in use (Java2D, ESRI or JTS).
      */
-    final Geometries geometries;
+    final Geometries<?> geometries;
 
     /**
      * A system-wide instance for {@code FeatureType} instances created using the {@link
DefaultNameFactory}.

Modified: sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java?rev=1801833&r1=1801832&r2=1801833&view=diff
==============================================================================
--- sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
[UTF-8] (original)
+++ sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
[UTF-8] Thu Jul 13 12:45:03 2017
@@ -568,8 +568,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