ucar.nc2.ft.point.FlattenedDatasetPointCollection Maven / Gradle / Ivy
package ucar.nc2.ft.point;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import ucar.nc2.ft.DsgFeatureCollection;
import ucar.nc2.ft.FeatureDatasetPoint;
import ucar.nc2.ft.PointFeature;
import ucar.nc2.ft.PointFeatureCC;
import ucar.nc2.ft.PointFeatureCCC;
import ucar.nc2.ft.PointFeatureCollection;
import ucar.nc2.ft.PointFeatureIterator;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.util.IOIterator;
/**
* An aggregate collection of all the PointFeatures in a dataset formed by flattening the nested structures within.
* This class's {@link #getPointFeatureIterator iterator} returns features in default order, with maximum read
* efficiency as the goal.
*
* @author cwardgar
* @since 2014/10/08
*/
public class FlattenedDatasetPointCollection extends PointCollectionImpl {
private final FeatureDatasetPoint fdPoint;
/**
* Constructs a FlattenedDatasetPointCollection.
*
* @param fdPoint a point dataset.
* @throws IllegalArgumentException if any of the feature collections in the dataset are not of type
* {@code PointFeatureCollection} or {@code NestedPointFeatureCollection}.
*/
public FlattenedDatasetPointCollection(FeatureDatasetPoint fdPoint) throws IllegalArgumentException {
super(fdPoint.getLocation(), CalendarDateUnit.unixDateUnit, null); // Default dateUnit and altUnits.
this.fdPoint = fdPoint;
List featCols = fdPoint.getPointFeatureCollectionList();
if (!featCols.isEmpty()) {
DsgFeatureCollection firstFeatCol = featCols.get(0);
// Replace this.dateUnit, this.altUnits, and this.extras with "typical" values from firstFeatCol.
// We can't be certain that those values are representative of ALL collections in the dataset, but it's
// a decent bet because in practice, firstFeatCol is so often the ONLY collection.
copyFieldsFrom(firstFeatCol);
}
}
private void copyFieldsFrom(DsgFeatureCollection featCol) {
this.timeUnit = featCol.getTimeUnit();
this.altUnits = featCol.getAltUnits();
}
@Override
public PointFeatureIterator getPointFeatureIterator() throws IOException {
return new FlattenedDatasetPointIterator(fdPoint);
}
protected class FlattenedDatasetPointIterator extends PointIteratorAbstract {
private final Iterator dsgFeatColIter;
private PointFeatureIterator pfIter;
private IOIterator pfcIter;
private IOIterator pfccIter;
private boolean finished = false; // set to "true" when close() is called.
public FlattenedDatasetPointIterator(FeatureDatasetPoint fdPoint) {
this.dsgFeatColIter = fdPoint.getPointFeatureCollectionList().iterator();
setCalculateBounds(FlattenedDatasetPointCollection.this.getInfo());
}
@Override
public boolean hasNext() {
try {
// pfIterHasNext() will fail the first time hasNext() is called because no DsgFeatureCollection has
// been loaded yet.
while (!pfIterHasNext()) {
if (!loadNextDsgFeatureCollection()) {
close(); // May not be called otherwise if iter is being used in a for-each.
return false;
}
}
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Attempts to find a PointFeatureIterator in the currently-loaded DsgFeatureCollection that has another
* available element (i.e. {@code hasNext() == true}). Such an iterator may already be loaded into
* {@code pfIter}. If not, we'll have to look through {@code pfcIter} and/or {@code pfccIter} to find one.
*
* That iterator, if it's found, will be assigned to {@code pfIter} and this method will return {@code true}.
* Otherwise, it'll return {@code false}, meaning that there are no more unread PointFeatures available in the
* currently-loaded DsgFeatureCollection.
*
* @return {@code true} if {@code pfIter.hasNext()} will now return {@code true}.
* @throws IOException if an I/O error occurs.
*/
private boolean pfIterHasNext() throws IOException {
if (pfIter != null) {
if (pfIter.hasNext()) {
return true;
} else {
// We'll need to load a new PointFeatureIterator below. But first, close the old one.
pfIter.close();
}
}
while (pfcIterHasNext()) {
this.pfIter = pfcIter.next().getPointFeatureIterator();
if (pfIter.hasNext()) {
return true;
}
// else: Iterator could be empty, in which case we proceed to the next loop iteration.
}
return false;
}
/**
* Attempts to find a {@code IOIterator} in the currently-loaded DsgFeatureCollection
* that has another available element (i.e. {@code hasNext() == true}). Such an iterator may already be loaded
* into {@code pfcIter}. If not, we'll have to look through {@code pfccIter} to find one.
*
* The iterator, if it's found, will be assigned to {@code pfcIter} and this method will return {@code true}.
* Otherwise, it'll return {@code false}, meaning that there are no more unread PointFeatureCollection
* iterators available in the currently-loaded DsgFeatureCollection
*
* @return {@code true} if {@code pfcIter.hasNext()} will now return {@code true}.
* @throws IOException if an I/O error occurs.
*/
private boolean pfcIterHasNext() throws IOException {
if (pfcIter != null && pfcIter.hasNext()) {
return true;
}
while (pfccIter != null && pfccIter.hasNext()) {
pfcIter = pfccIter.next().getCollectionIterator();
if (pfcIter.hasNext()) {
return true;
}
// else: Iterator could be empty, in which case we proceed to the next loop iteration.
}
return false;
}
/**
* Retrieves the next DsgFeatureCollection from {@code dsgFeatColIter} and assigns it to the appropriate data
* member. The DsgFeatureCollections returned by {@link FeatureDatasetPoint#getPointFeatureCollectionList} will
* be one of the following 3 subtypes:
*
* - {@link PointFeatureCollection}: will be assigned to {@code pfIter}
* - {@link PointFeatureCC}: will be assigned to {@code pfcIter}
* - {@link PointFeatureCCC}: will be assigned to {@code pfccIter}
*
*
* @return {@code true} if the next DsgFeatureCollection was successfully loaded into the appropriate data
* member, or {@code false} if no more remain.
* @throws IOException if an I/O error occurs.
*/
private boolean loadNextDsgFeatureCollection() throws IOException {
if (!dsgFeatColIter.hasNext()) {
return false;
}
// Clear out any iterators belonging to the previous DsgFeatureCollection
pfIter = null;
pfcIter = null;
pfccIter = null;
DsgFeatureCollection dsgFeatCol = dsgFeatColIter.next();
if (dsgFeatCol instanceof PointFeatureCollection) {
pfIter = ((PointFeatureCollection) dsgFeatCol).getPointFeatureIterator();
} else if (dsgFeatCol instanceof PointFeatureCC) {
pfcIter = ((PointFeatureCC) dsgFeatCol).getCollectionIterator();
} else if (dsgFeatCol instanceof PointFeatureCCC) {
pfccIter = ((PointFeatureCCC) dsgFeatCol).getCollectionIterator();
} else {
throw new AssertionError("CAN'T HAPPEN: FeatureDatasetPoint.getPointFeatureCollectionList() " +
"only contains PointFeatureCollection, PointFeatureCC, or PointFeatureCCC.");
}
return true;
}
@Override
public PointFeature next() {
if (pfIter == null) { // Could be null if hasNext() == false or wasn't called at all.
return null;
} else {
PointFeature pointFeat = pfIter.next();
calcBounds(pointFeat);
return pointFeat;
}
}
@Override
public void close() {
if (finished) {
return;
}
// If hasNext() was repeatedly called until it returned "false", all PointFeatureIterators should've
// already been closed. However, this may be useful in exceptional circumstances.
if (pfIter != null) {
pfIter.close();
}
finishCalcBounds();
finished = true;
}
}
}