sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1734124 - in /sis/branches/JDK8: core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ storage/sis-storage/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ storage/sis-storage/src/main/java/org/apache/s...
Date Tue, 08 Mar 2016 19:14:16 GMT
Author: desruisseaux
Date: Tue Mar  8 19:14:16 2016
New Revision: 1734124

URL: http://svn.apache.org/viewvc?rev=1734124&view=rev
Log:
Read metadata from a Moving Features CSV file (SIS-319).

Added:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
  (with props)
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
  (with props)
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
  (with props)
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
  (with props)
Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
    sis/branches/JDK8/storage/sis-storage/pom.xml
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java?rev=1734124&r1=1734123&r2=1734124&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -16,24 +16,38 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Map;
+import java.util.Date;
+import java.util.Collections;
 import javax.measure.unit.Unit;
+import javax.measure.quantity.Duration;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.parameter.InvalidParameterValueException;
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.TimeCS;
+import org.opengis.referencing.datum.DatumFactory;
+import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.Conversion;
+import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.referencing.Builder;
+import org.apache.sis.referencing.CommonCRS;
 
 
 /**
@@ -69,12 +83,22 @@ public class GeodeticObjectBuilder exten
     private ParameterValueGroup parameters;
 
     /**
-     * The factor for Coordinate Reference System objects, fetched when first needed.
+     * The factory for Coordinate Reference System objects, fetched when first needed.
      */
     private CRSFactory crsFactory;
 
     /**
-     * The factor for Coordinate Operation objects, fetched when first needed.
+     * The factory for Coordinate System objects, fetched when first needed.
+     */
+    private CSFactory csFactory;
+
+    /**
+     * The factory for Datum objects, fetched when first needed.
+     */
+    private DatumFactory datumFactory;
+
+    /**
+     * The factory for Coordinate Operation objects, fetched when first needed.
      */
     private CoordinateOperationFactory copFactory;
 
@@ -95,6 +119,26 @@ public class GeodeticObjectBuilder exten
     }
 
     /**
+     * Returns the factory for Coordinate System objects. This method fetches the factory
when first needed.
+     */
+    private CSFactory getCSFactory() {
+        if (csFactory == null) {
+            csFactory = DefaultFactories.forBuildin(CSFactory.class);
+        }
+        return csFactory;
+    }
+
+    /**
+     * Returns the factory for Datum objects. This method fetches the factory when first
needed.
+     */
+    private DatumFactory getDatumFactory() {
+        if (datumFactory == null) {
+            datumFactory = DefaultFactories.forBuildin(DatumFactory.class);
+        }
+        return datumFactory;
+    }
+
+    /**
      * Returns the factory for Coordinate Operation objects. This method fetches the factory
when first needed.
      */
     private CoordinateOperationFactory getCoordinateOperationFactory() {
@@ -264,4 +308,87 @@ public class GeodeticObjectBuilder exten
             onCreate(true);
         }
     }
+
+    /**
+     * Creates a temporal CRS from the given origin and temporal unit. For this method, the
CRS name is optional:
+     * if no {@code addName(…)} method has been invoked, then a default name will be used.
+     *
+     * @param  origin The epoch in milliseconds since January 1st, 1970 at midnight UTC.
+     * @param  unit The unit of measurement.
+     * @return A temporal CRS using the given origin and units.
+     * @throws FactoryException if an error occurred while building the temporal CRS.
+     */
+    public TemporalCRS createTemporalCRS(final Date origin, final Unit<Duration> unit)
throws FactoryException {
+        /*
+         * Try to use one of the pre-defined datum and coordinate system if possible.
+         * This not only saves a little bit of memory, but also provides better names.
+         */
+        TimeCS cs = null;
+        TemporalDatum datum = null;
+        for (final CommonCRS.Temporal c : CommonCRS.Temporal.values()) {
+            if (datum == null) {
+                final TemporalDatum candidate = c.datum();
+                if (origin.equals(candidate.getOrigin())) {
+                    datum = candidate;
+                }
+            }
+            if (cs == null) {
+                final TemporalCRS crs = c.crs();
+                final TimeCS candidate = crs.getCoordinateSystem();
+                if (unit.equals(candidate.getAxis(0).getUnit())) {
+                    if (datum == candidate && properties.isEmpty()) {
+                        return crs;
+                    }
+                    cs = candidate;
+                }
+            }
+        }
+        /*
+         * Create the datum and coordinate system before the CRS if we were not able to use
a pre-defined object.
+         * In the datum case, we will use the same metadata than the CRS (domain of validity,
scope, etc.) except
+         * the identifier and the remark.
+         */
+        onCreate(false);
+        try {
+            if (cs == null) {
+                final CSFactory csFactory = getCSFactory();
+                cs = CommonCRS.Temporal.JAVA.crs().getCoordinateSystem();   // To be used
as a template, except for units.
+                cs = csFactory.createTimeCS(name(cs),
+                     csFactory.createCoordinateSystemAxis(name(cs.getAxis(0)), "t", AxisDirection.FUTURE,
unit));
+            }
+            if (properties.get(TemporalCRS.NAME_KEY) == null) {
+                properties.putAll(name(cs));
+            }
+            if (datum == null) {
+                final Object remarks    = properties.remove(TemporalCRS.REMARKS_KEY);
+                final Object identifier = properties.remove(TemporalCRS.IDENTIFIERS_KEY);
+                datum = getDatumFactory().createTemporalDatum(properties, origin);
+                properties.put(TemporalCRS.IDENTIFIERS_KEY, identifier);
+                properties.put(TemporalCRS.REMARKS_KEY,     remarks);
+                properties.put(TemporalCRS.NAME_KEY, datum.getName());      // Share the
Identifier instance.
+            }
+            return getCRSFactory().createTemporalCRS(properties, datum, cs);
+        } finally {
+            onCreate(true);
+        }
+    }
+
+    /**
+     * Creates a compound CRS, but we special processing for (two-dimensional Geographic
+ ellipsoidal heights) tupples.
+     * If any such tupple is found, a three-dimensional geographic CRS is created instead
than the compound CRS.
+     *
+     * @param  components ordered array of {@code CoordinateReferenceSystem} objects.
+     * @return The coordinate reference system for the given properties.
+     * @throws FactoryException if the object creation failed.
+     */
+    public CoordinateReferenceSystem createCompoundCRS(final CoordinateReferenceSystem...
components) throws FactoryException {
+        return ReferencingServices.getInstance().createCompoundCRS(getCRSFactory(), getCSFactory(),
properties, components);
+    }
+
+    /**
+     * Creates a map of properties containing only the name of the given object.
+     */
+    private static Map<String,Object> name(final IdentifiedObject template) {
+        return Collections.singletonMap(IdentifiedObject.NAME_KEY, template.getName());
+    }
 }

Modified: sis/branches/JDK8/storage/sis-storage/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/pom.xml?rev=1734124&r1=1734123&r2=1734124&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/pom.xml (original)
+++ sis/branches/JDK8/storage/sis-storage/pom.xml Tue Mar  8 19:14:16 2016
@@ -121,6 +121,11 @@ Provides the interfaces and base classes
            Dependencies
        =========================================================== -->
   <dependencies>
+    <dependency>
+      <groupId>org.apache.sis.core</groupId>
+      <artifactId>sis-feature</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <!-- Test dependencies -->
     <dependency>
       <groupId>org.apache.sis.core</groupId>

Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java?rev=1734124&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage;
+
+import java.util.Collections;
+import java.nio.charset.Charset;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.util.Static;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.geometry.AbstractEnvelope;
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
+
+
+/**
+ * Helper methods for the metadata created by {@code DataStore} implementations.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class MetadataHelper extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private MetadataHelper() {
+    }
+
+    /**
+     * Creates a metadata object for a text file parsed from the given connector.
+     *
+     * @param  connector The connector.
+     * @return A new, initially almost empty, metadata object.
+     */
+    public static DefaultMetadata createForTextFile(final StorageConnector connector) {
+        final DefaultMetadata metadata = new DefaultMetadata();
+        final Charset encoding = connector.getOption(OptionKey.ENCODING);
+        if (encoding != null) {
+            metadata.setCharacterSets(Collections.singleton(encoding));
+        }
+        return metadata;
+    }
+
+    /**
+     * Adds the given extent to the given metadata.
+     *
+     * @param  addTo    The metadata where to add the extent.
+     * @param  envelope The extent to add in the given metadata, or {@code null} if none.
+     * @throws TransformException if an error occurred while converting the given envelope
to extents.
+     */
+    public static void add(final DefaultMetadata addTo, final AbstractEnvelope envelope)
throws TransformException {
+        if (envelope != null) {
+            addTo.setReferenceSystemInfo(Collections.singleton(envelope.getCoordinateReferenceSystem()));
+            if (!envelope.isAllNaN()) {
+                final DefaultExtent extent = new DefaultExtent();
+                extent.addElements(envelope);
+                final DefaultDataIdentification id = new DefaultDataIdentification();
+                id.setExtents(Collections.singleton(extent));
+                addTo.setIdentificationInfo(Collections.singleton(id));
+            }
+        }
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: 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=1734124&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -0,0 +1,581 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.csv;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.io.Reader;
+import java.io.BufferedReader;
+import java.io.LineNumberReader;
+import java.io.IOException;
+import java.net.URI;
+import java.time.Instant;
+import java.time.DateTimeException;
+import javax.measure.unit.Unit;
+import javax.measure.unit.SI;
+import javax.measure.unit.NonSI;
+import javax.measure.quantity.Duration;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+import org.opengis.metadata.Metadata;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
+import org.apache.sis.internal.storage.MetadataHelper;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.measure.Units;
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.IndexedResourceBundle;
+
+
+/**
+ * A data store which creates feature instances from a CSV file using the OGC Moving Features
specification.
+ * See package javadoc for more information on the syntax.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class Store extends DataStore {
+    /**
+     * The character at the beginning of lines to ignore in the header.
+     * Note that this is not part of OGC Moving Feature Specification.
+     */
+    private static final char COMMENT = '#';
+
+    /**
+     * The character at the beginning of metadata lines.
+     */
+    private static final char METADATA = '@';
+
+    /**
+     * The quote character. Quotes inside quoted texts must be doubled.
+     */
+    private static final char QUOTE = '"';
+
+    /**
+     * The column separator.
+     */
+    private static final char SEPARATOR = ',';
+
+    /**
+     * The separator between ordinate values in a coordinate.
+     */
+    private static final char ORDINATE_SEPARATOR = ' ';
+
+    /**
+     * The prefix for elements in the {@code @columns} line that specify the data type.
+     * Examples: {@code xsd:boolean}, {@code xsd:decimal}, {@code xsd:integer}, <i>etc</i>.
+     */
+    private static final String TYPE_PREFIX = "xsd:";
+
+    /**
+     * The file name.
+     */
+    private final String name;
+
+    /**
+     * The reader, set by the constructor and cleared when no longer needed.
+     */
+    private BufferedReader source;
+
+    /**
+     * The metadata object. Initialized to a minimal amount of information, then completed
when first needed.
+     */
+    private final DefaultMetadata metadata;
+
+    /**
+     * Values for {@link #order} specifying the appearing order of trajectories.
+     */
+    private static final byte ORDER_TIME = 1, ORDER_SEQUENTIAL = 2;
+
+    /**
+     * Appearing order of trajectories: 0 = undefined, 1 = ordered by time,
+     * 2 = elements which are parts of one track are ordered by time.
+     *
+     * @see #parseFoliation(List)
+     */
+    private final byte order;
+
+    /**
+     * Values for {@link #timeEncoding} specifying how time is encoded in the CSV file.
+     */
+    private static final byte TIME_RELATIVE = 1, TIME_ABSOLUTE = 2;
+
+    /**
+     * A code for the time encoding: 0 = no time, 1 = relative time, 2 = absolute time.
+     * Relative times are formatted as number of seconds or minutes since an epoch.
+     * Absolute times are formatted as ISO dates.
+     */
+    private byte timeEncoding;
+
+    /**
+     * Date of value zero on the time axis, in milliseconds since January 1st 1970 at midnight
UTC.
+     */
+    private long timeOrigin;
+
+    /**
+     * Number of milliseconds between two consecutive integer values on the time axis.
+     */
+    private double timeInterval;
+
+    /**
+     * The three- or four-dimensional envelope together with the CRS.
+     * This envelope contains a vertical component if the feature trajectories are 3D,
+     * and a temporal component if the CSV file contains a start time and end time.
+     *
+     * @see #parseEnvelope(List)
+     */
+    private final GeneralEnvelope envelope;
+
+    /**
+     * Description of the columns found in the CSV file.
+     *
+     * @see #parseFeatureType(List)
+     */
+    private final FeatureType featureType;
+
+    /**
+     * The features created while parsing the CSV file.
+     *
+     * @todo We should not keep them in memory, but instead use some kind of iterator or
stream.
+     */
+    private final Map<String,Feature> features;
+
+    /**
+     * 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  connector Information about the storage (URL, stream, <i>etc</i>).
+     * @throws DataStoreException if an error occurred while opening the stream.
+     */
+    public Store(final StorageConnector connector) throws DataStoreException {
+        name = connector.getStorageName();
+        final Reader r = connector.getStorageAs(Reader.class);
+        connector.closeAllExcept(r);
+        if (r == null) {
+            throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, name));
+        }
+        source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
+        GeneralEnvelope envelope    = null;
+        FeatureType     featureType = null;
+        byte            order       = 0;
+        try {
+            final List<String> elements = new ArrayList<>();
+            source.mark(1024);
+            String line;
+            while ((line = source.readLine()) != null) {
+                line = line.trim();
+                if (line.isEmpty()) continue;
+                final char c = line.charAt(0);
+                if (c == COMMENT) continue;
+                if (c != METADATA) break;
+                split(line, elements);
+                final String keyword = elements.get(0);
+                switch (keyword.toLowerCase(Locale.US)) {
+                    case "@stboundedby": {
+                        if (envelope != null) {
+                            throw new DataStoreException(duplicated("@stboundedby"));
+                        }
+                        envelope = parseEnvelope(elements);
+                        break;
+                    }
+                    case "@columns": {
+                        if (featureType != null) {
+                            throw new DataStoreException(duplicated("@columns"));
+                        }
+                        featureType = parseFeatureType(elements);
+                        break;
+                    }
+                    case "@foliation": {
+                        if (order != 0) {
+                            throw new DataStoreException(duplicated("@foliation"));
+                        }
+                        order = parseFoliation(elements);
+                        break;
+                    }
+                    default: {
+                        final LogRecord record = errors().getLogRecord(Level.WARNING, Errors.Keys.UnknownKeyword_1,
keyword);
+                        record.setSourceClassName(Store.class.getName());
+                        record.setSourceMethodName("parseHeader");
+                        listeners.warning(record);
+                        break;
+                    }
+                }
+                elements.clear();
+                source.mark(1024);
+            }
+            source.reset();
+        } catch (IOException | FactoryException | IllegalArgumentException | DateTimeException
e) {
+            throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2,
"CSV", name), e);
+        }
+        this.envelope    = envelope;
+        this.featureType = featureType;
+        this.order       = order;
+        this.metadata    = MetadataHelper.createForTextFile(connector);
+        this.features    = new LinkedHashMap<>();
+    }
+
+    /**
+     * Parses the envelope described by the header line starting with {@code @stboundedby}.
+     * The envelope returned by this method will be stored in the {@link #envelope} field.
+     *
+     * <p>Example:</p>
+     * {@preformat text
+     *   &#64;stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D, 50.23 9.23, 50.31 9.27, 2012-01-17T12:33:41Z,
2012-01-17T12:37:00Z, sec
+     * }
+     *
+     * @param  elements The line elements. The first elements should be {@code "@stboundedby"}.
+     * @return The envelope, or {@code null} if the given list does not contain enough elements.
+     */
+    @SuppressWarnings("fallthrough")
+    private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException,
FactoryException {
+        double[]       lowerCorner    = null;
+        double[]       upperCorner    = null;
+        Instant        startTime      = null;
+        Instant        endTime        = null;
+        Unit<Duration> timeUnit       = SI.SECOND;
+        boolean        isTimeAbsolute = false;
+        boolean        is3D           = false;
+        CoordinateReferenceSystem crs = null;
+        GeneralEnvelope envelope      = null;
+        switch (elements.size()) {
+            default:final String unit = elements.get(7);
+                    switch (unit.toLowerCase(Locale.US)) {
+                        case "":
+                        case "sec":
+                        case "second":   /* Already SI.SECOND. */ break;
+                        case "minute":   timeUnit = NonSI.MINUTE; break;
+                        case "hour":     timeUnit = NonSI.HOUR;   break;
+                        case "absolute": isTimeAbsolute = true;   // Fall through
+                        case "day":      timeUnit = NonSI.DAY;    break;
+                        default: throw new DataStoreException(errors().getString(Errors.Keys.UnknownUnit_1,
unit));
+                    }
+                    // Fall through
+            case 7: endTime     = Instant      .parse(       elements.get(6));
+            case 6: startTime   = Instant      .parse(       elements.get(5));
+            case 5: upperCorner = CharSequences.parseDoubles(elements.get(4), ORDINATE_SEPARATOR);
+            case 4: lowerCorner = CharSequences.parseDoubles(elements.get(3), ORDINATE_SEPARATOR);
+            case 3: final String dimension = elements.get(2);
+                    switch (dimension.toUpperCase(Locale.US)) {
+                        case "":   // Default to 2D.
+                        case "2D": break;
+                        case "3D": is3D = true; break;
+                        default: throw new DataStoreException(errors().getString(
+                                        Errors.Keys.IllegalCoordinateSystem_1, dimension));
+                    }
+                    // Fall through
+            case 2: crs = CRS.forCode(elements.get(1));
+            case 1:
+            case 0:
+        }
+        /*
+         * Complete the CRS by adding a vertical component if needed, then a temporal component.
+         * Only after the CRS has been completed we can create the envelope.
+         *
+         * Vertical component:
+         *   Ideally, should be part of the CRS created from the authority code. But if the
authority
+         *   code is only for a two-dimensional CRS, we default to an arbitrary height component.
+         *
+         * Temporal component:
+         *   Assumed never part of the authority code. We need to build the temporal component
ourselves
+         *   in order to set the origin to the start time.
+         */
+        if (crs != null) {
+            final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
+            final int spatialDimension = crs.getCoordinateSystem().getDimension();
+            int count = 0;
+            components[count++] = crs;
+            if (is3D && spatialDimension == 2) {
+                components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs();
+            }
+            final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
+            if (startTime != null) {
+                TemporalCRS temporal = null;
+                if (isTimeAbsolute) {
+                    /*
+                     * If time is absolute (i.e. is formatted as an ISO date), then the origin
does not matter.
+                     * In such case we favor the first pre-defined TemporalCRS with same
unit of measurement.
+                     */
+                    for (final CommonCRS.Temporal c : CommonCRS.Temporal.values()) {
+                        final TemporalCRS candidate = c.crs();
+                        if (timeUnit.equals(candidate.getCoordinateSystem().getAxis(0).getUnit()))
{
+                            temporal = candidate;
+                            break;
+                        }
+                    }
+                }
+                if (temporal == null) {
+                    temporal = builder.createTemporalCRS(Date.from(startTime), timeUnit);
+                }
+                components[count++] = temporal;
+                timeOrigin   = temporal.getDatum().getOrigin().getTime();
+                timeInterval = timeUnit.getConverterTo(Units.MILLISECOND).convert(1);
+                timeEncoding = isTimeAbsolute ? TIME_ABSOLUTE : TIME_RELATIVE;
+            }
+            crs = builder.addName(name).createCompoundCRS(ArraysExt.resize(components, count));
+            /*
+             * At this point we got the three- or four-dimensional spatio-temporal CRS.
+             * We can now set the envelope coordinate values.
+             */
+            envelope = new GeneralEnvelope(crs);
+            if (lowerCorner != null && upperCorner != null) {
+                int dim;
+                if ((dim = lowerCorner.length) != spatialDimension ||
+                    (dim = upperCorner.length) != spatialDimension)
+                {
+                    throw new DataStoreException(errors().getString(
+                            Errors.Keys.MismatchedDimension_2, dim, spatialDimension));
+                }
+                for (int i=0; i<spatialDimension; i++) {
+                    envelope.setRange(i, lowerCorner[i], upperCorner[i]);
+                }
+            }
+            if (startTime != null && endTime != null) {
+                envelope.setRange(spatialDimension, (startTime.toEpochMilli() - timeOrigin)
/ timeInterval,
+                                                      (endTime.toEpochMilli() - timeOrigin)
/ timeInterval);
+            }
+        }
+        return envelope;
+    }
+
+    /**
+     * Parses the columns metadata described by the header line starting with {@code @columns}.
+     * The feature type returned by this method will be stored in the {@link #featureType}
field.
+     *
+     * <p>Example:</p>
+     * {@preformat text
+     *   &#64;columns, mfidref, trajectory, state,xsd:token, "type code",xsd:integer
+     * }
+     *
+     * @param  elements The line elements. The first elements should be {@code "@columns"}.
+     * @return The column metadata, or {@code null} if the given list does not contain enough
elements.
+     */
+    private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException
{
+        final int size = elements.size();
+        final List<PropertyType> properties = new ArrayList<>();
+        for (int i=1; i<size; i++) {
+            final String name = elements.get(i);
+            Class<?> type = null;
+            if (++i < size) {
+                String tn = elements.get(i);
+                if (!tn.isEmpty() && tn.regionMatches(true, 0, TYPE_PREFIX, 0, TYPE_PREFIX.length()))
{
+                    String st = tn.substring(TYPE_PREFIX.length()).toLowerCase(Locale.US);
+                    switch (st) {
+                        case "boolean":  type = Boolean.class; break;
+                        case "decimal":  type = Double .class; break;
+                        case "integer":  type = Integer.class; break;
+                        case "string":   type = String .class; break;
+                        case "datetime": type = Instant.class; break;
+                        case "anyuri":   type = URI    .class; break;
+                        default: throw new DataStoreException(errors().getString(Errors.Keys.UnknownType_1,
tn));
+                    }
+                }
+            }
+            int minOccurrence = 0;
+            if (type == null) {
+                /*
+                 * If the column name was not followed by a type, default to a String type
except in the special
+                 * case of trajectory. Note that according the Moving Feature specification,
only the two first
+                 * columns are not followed by a type. Those columns are:
+                 *
+                 *   1) mfidref     - used in order to identify the moving feature.
+                 *   2) trajectory  - defines the spatio-temporal geometry of moving features.
+                 *                    Contains implicit "start time" and "end time" columns.
+                 *
+                 * Those two columns are mandatory in Moving Feature specification. All other
ones are optional.
+                 */
+                type = String.class;
+                switch (--i) {
+                    case 1: minOccurrence = 1; break;
+                    case 2: {
+                        if (name.equalsIgnoreCase("trajectory")) {
+                            if (timeEncoding != 0) {
+                                properties.add(createProperty("time", long[].class, 1));
+                            }
+                            type = double[].class;
+                            minOccurrence = 1;
+                        }
+                        break;
+                    }
+                }
+            }
+            properties.add(createProperty(name, type, minOccurrence));
+        }
+        return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY,
name),
+                false, null, properties.toArray(new PropertyType[properties.size()]));
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Parses the metadata described by the header line starting with {@code @foliation}.
+     * The value returned by this method will be stored in the {@link #order} field.
+     *
+     * <p>Example:</p>
+     * {@preformat text
+     *   &#64;foliation,Sequential
+     * }
+     *
+     * @param  elements The line elements. The first elements should be {@code "@foliation"}.
+     * @return The foliation metadata.
+     */
+    private byte parseFoliation(final List<String> elements) throws DataStoreException
{
+        if (elements.size() >= 2) {
+            final String value = elements.get(1);
+            switch (value.toLowerCase(Locale.US)) {
+                case "time":       break;
+                case "sequential": return ORDER_SEQUENTIAL;
+                default: throw new DataStoreException(errors().getString(Errors.Keys.UnknownEnumValue_2,
"order", value));
+            }
+        }
+        return ORDER_TIME;      // Default value.
+    }
+
+    /**
+     * Returns the metadata associated to the CSV file, or {@code null} if none.
+     *
+     * @return The metadata associated to the CSV file, or {@code null} if none.
+     * @throws DataStoreException if an error occurred during the parsing process.
+     */
+    @Override
+    public Metadata getMetadata() throws DataStoreException {
+        if (metadata.isModifiable()) {
+            try {
+                MetadataHelper.add(metadata, envelope);
+            } catch (TransformException e) {
+                throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2,
"CSV", name), e);
+            }
+            metadata.freeze();
+        }
+        return metadata;
+    }
+
+    /**
+     * Splits the content of the given line around the column separator.
+     * Quotes are taken in account. The elements are added in the given list.
+     *
+     * @param line the line to parse.
+     * @param elements an initially empty list where to add elements.
+     */
+    private static void split(final String line, final List<String> elements) {
+        int startAt = 0;
+        boolean hasQuotes = false;
+        boolean isQuoting = false;
+        final int length = line.length();
+        for (int i=0; i<length; i++) {
+            switch (line.charAt(i)) {
+                case QUOTE: {
+                    hasQuotes = true;
+                    if (isQuoting && i+1 < length && line.charAt(i+1)
== QUOTE) {
+                        i++;
+                    } else {
+                        isQuoting = !isQuoting;
+                    }
+                    break;
+                }
+                case SEPARATOR: {
+                    if (!isQuoting) {
+                        elements.add(extract(line, startAt, i, hasQuotes));
+                        startAt = i+1;
+                        hasQuotes = false;
+                    }
+                    break;
+                }
+            }
+        }
+        elements.add(extract(line, startAt, length, hasQuotes));
+    }
+
+    /**
+     * Extracts a substring from the given line and replaces double quotes by single quotes.
+     */
+    private static String extract(CharSequence text, final int lower, final int upper, final
boolean hasQuotes) {
+        if (hasQuotes) {
+            final StringBuilder buffer = new StringBuilder(upper - lower).append(text, lower,
upper);
+            for (int i=0; i<buffer.length(); i++) {
+                if (buffer.charAt(i) == QUOTE) {
+                    buffer.deleteCharAt(i);
+                    // If the deleted char was followed by another quote, that second quote
will be preserved.
+                }
+            }
+            text = CharSequences.trimWhitespaces(buffer);
+        } else {
+            text = CharSequences.trimWhitespaces(text, lower, upper).toString();
+        }
+        return text.toString();
+    }
+
+    /**
+     * Returns an error message for a duplicated element.
+     */
+    private String duplicated(final String name) {
+        return errors().getString(Errors.Keys.DuplicatedElement_1, name);
+    }
+
+    /**
+     * Returns the resources to use for producing error messages.
+     */
+    private IndexedResourceBundle errors() {
+        return Errors.getResources(getLocale());
+    }
+
+    /**
+     * Closes this data store and releases any underlying resources.
+     *
+     * @throws DataStoreException If an error occurred while closing this data store.
+     */
+    @Override
+    public void close() throws DataStoreException {
+        final BufferedReader s = source;
+        source = null;                  // Cleared first in case of failure.
+        features.clear();
+        if (s != null) try {
+            s.close();
+        } catch (IOException e) {
+            throw new DataStoreException(e);
+        }
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java?rev=1734124&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/**
+ * {@link org.apache.sis.storage.DataStore} implementation for Coma Separated Values (CSV)
files.
+ * This package implements the following specifications (more may be added in any future
version):
+ *
+ * <ul>
+ *   <li><a href="http://docs.opengeospatial.org/is/14-084r2/14-084r2.html">OGC®
Moving Features Encoding Extension:
+ *     Simple Comma Separated Values (CSV)</a> with some rules relaxed. The Apache
SIS implementation allows the CSV
+ *     file to have no date, thus allowing use of OGC 14-084 syntax for static features in
addition to moving ones.</li>
+ * </ul>
+ *
+ * The above extends the <a href="http://www.ietf.org/rfc/rfc4180.txt">Common Format
and MIME Type for
+ * Comma-Separated Values (CSV) Files</a> specification.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+package org.apache.sis.internal.storage.csv;

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: 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=1734124&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.csv;
+
+import java.io.StringReader;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.metadata.extent.SpatialTemporalExtent;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
+
+
+/**
+ * Tests {@link Store}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class StoreTest extends TestCase {
+    /**
+     * An example of Moving Features file.
+     * Derived from the example provided in OGC 14-084r2.
+     */
+    private static final String TEST_DATA =
+            "@stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D,  50.23 9.23 , 50.31 9.27,  2012-01-17T12:33:41Z,
2012-01-17T12:37:00Z, sec\n" +
+            "@columns, mfidref, trajectory, state,xsd:string, \"\"\"type\"\" code\",xsd:integer\n"
+
+            "a,10,150,11.0 2.0 12.0 3.0,walking,1\n" +
+            "b,10,190,10.0 2.0 11.0 3.0,walking,2\n" +
+            "a,150,190,12.0 3.0 10.0 3.0,walking,2\n" +
+            "c,10,190,12.0 1.0 10.0 2.0 11.0 3.0,vehicle,1\n";
+
+    /**
+     * Tests {@link Store#getMetadata()}.
+     *
+     * @throws DataStoreException if an error occurred while parsing the data.
+     */
+    @Test
+    @org.junit.Ignore("Pending completion of sis-referencing")
+    public void testGetMetadata() throws DataStoreException {
+        final Metadata metadata;
+        try (Store store = new Store(new StorageConnector(new StringReader(TEST_DATA))))
{
+            metadata = store.getMetadata();
+        }
+        final SpatialTemporalExtent extent = (SpatialTemporalExtent) getSingleton(getSingleton(getSingleton(
+                metadata.getIdentificationInfo()).getExtents()).getTemporalElements());
+        final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getSpatialExtent());
+        assertEquals("westBoundLongitude", 50.23, bbox.getWestBoundLongitude(), STRICT);
+        assertEquals("eastBoundLongitude", 50.31, bbox.getEastBoundLongitude(), STRICT);
+        assertEquals("southBoundLatitude",  9.23, bbox.getSouthBoundLatitude(), STRICT);
+        assertEquals("northBoundLatitude",  9.27, bbox.getNorthBoundLatitude(), STRICT);
+        assertNull("Should not have a vertical extent.", extent.getVerticalExtent());
+        assertNotNull("Should have a temporal extent", extent.getExtent());
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java?rev=1734124&r1=1734123&r2=1734124&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
[UTF-8] Tue Mar  8 19:14:16 2016
@@ -43,6 +43,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.storage.xml.StoreTest.class,
     org.apache.sis.internal.storage.wkt.StoreProviderTest.class,
     org.apache.sis.internal.storage.wkt.StoreTest.class,
+    org.apache.sis.internal.storage.csv.StoreTest.class,
     org.apache.sis.storage.DataStoresTest.class,
     org.apache.sis.index.GeoHashCoderTest.class
 })



Mime
View raw message