blob: ee3e955e366dfde80251dd5d7d705a9d7a165e79 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.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);
}
}