| /* |
| * 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.Map; |
| import java.util.LinkedHashMap; |
| import java.util.function.Consumer; |
| import java.util.logging.LogRecord; |
| import java.time.Instant; |
| import java.time.DateTimeException; |
| import java.io.IOException; |
| import org.apache.sis.internal.feature.MovingFeature; |
| |
| // Branch-dependent imports |
| import org.opengis.feature.Attribute; |
| import org.opengis.feature.Feature; |
| |
| |
| /** |
| * An extension of the feature iterator that merge the line segments in a single trajectory. |
| * Line segments can be specified on a single line in the CSV file. All lines for the same |
| * feature are combined together in a single trajectory. For example iteration over the following |
| * file will produce 3 {@code Feature} instances instead of 4, because the two lines of features "a" |
| * will be merged in a single feature instance: |
| * |
| * {@preformat text |
| * a, 10, 150, 11.0 2.0 12.0 3.0 |
| * b, 10, 190, 10.0 2.0 11.0 3.0 |
| * 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 |
| * } |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.8 |
| * @since 0.8 |
| * @module |
| */ |
| final class MovingFeatureIterator extends FeatureIterator implements Consumer<LogRecord> { |
| /** |
| * 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. |
| */ |
| MovingFeatureIterator(final Store store) { |
| super(store); |
| builders = new LinkedHashMap<>(); |
| } |
| |
| /** |
| * Creates all moving features. |
| * This method can only be invoked after {@link #readMoving(Consumer, boolean)} completion. |
| * This method is ignored if the CSV file contains only static features. |
| */ |
| Feature[] createMovingFeatures() { |
| int n = 0; |
| final int np = values.length - TRAJECTORY_COLUMN; |
| final Feature[] features = new Feature[builders.size()]; |
| for (final Map.Entry<String,MovingFeature> entry : builders.entrySet()) { |
| features[n++] = createMovingFeature(entry.getKey(), entry.getValue(), np); |
| } |
| return features; |
| } |
| |
| /** |
| * Creates the moving feature of the given name. |
| * This method can only be invoked after {@link #readMoving(Consumer, boolean)}. |
| * |
| * @param featureName name of the feature to create. |
| * @param np number of properties, ignoring the ones before the trajectory column. |
| */ |
| @SuppressWarnings("unchecked") |
| private Feature createMovingFeature(final String featureName, final MovingFeature mf, final int np) { |
| final Feature feature = store.featureType.newInstance(); |
| feature.setPropertyValue(propertyNames[0], featureName); |
| mf.storeTimeRange(propertyNames[1], propertyNames[2], feature); |
| int column = 0; |
| if (store.hasTrajectories()) { |
| mf.storeGeometry(featureName, column, store.spatialDimensionCount(), store.geometries, |
| (Attribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this); |
| column++; |
| } |
| while (column < np) { |
| mf.storeAttribute(column, (Attribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column])); |
| column++; |
| } |
| return feature; |
| } |
| |
| /** |
| * Executes the given action for the next moving feature or for all remaining moving features. |
| * This method assumes that the 4 first columns are as documented in the code inside constructor. |
| * |
| * @param action the action to execute as soon as the {@code mfidref} change, or {@code null} if none. |
| * @param all {@code true} for executing the given action on all remaining features. |
| * @return {@code false} if there is no remaining feature after this method call. |
| * @throws IOException if an I/O error occurred while reading a feature. |
| * @throws IllegalArgumentException if parsing of a number failed, or other error. |
| * @throws DateTimeException if parsing of a date failed. |
| */ |
| boolean readMoving(final Consumer<? super Feature> action, final boolean all) throws IOException { |
| final FixedSizeList elements = new FixedSizeList(values); |
| final int np = values.length - TRAJECTORY_COLUMN; |
| String line; |
| while ((line = store.readLine()) != null) { |
| Store.split(line, elements); |
| int n = elements.size(); |
| for (int i=0; i<n; i++) { |
| values[i] = converters[i].apply((String) values[i]); |
| } |
| final String mfIdRef = (String) values[0]; |
| final long startTime = ((Instant) values[1]).toEpochMilli(); |
| final long endTime = ((Instant) values[2]).toEpochMilli(); |
| String publish = null; |
| if (!mfIdRef.equals(identifier)) { |
| publish = identifier; |
| identifier = mfIdRef; |
| builder = builders.computeIfAbsent(mfIdRef, (k) -> new MovingFeature(np)); |
| } |
| builder.addTimeRange(startTime, endTime); |
| for (int i=0; i<np; i++) { |
| builder.addValue(i, startTime, endTime, values[i + TRAJECTORY_COLUMN]); |
| } |
| /* |
| * If we started a new feature and the features are stored in sequential order, |
| * we can publish the previous one right away. |
| */ |
| if (publish != null && action != null) { |
| action.accept(createMovingFeature(publish, builders.remove(publish), np)); |
| if (!all) return true; |
| } |
| elements.clear(); |
| } |
| return false; |
| } |
| |
| /** |
| * Invoked when a warning occurred while computing the geometry. |
| */ |
| @Override |
| public void accept(final LogRecord warning) { |
| store.log(warning); |
| } |
| } |