This is an automated email from the ASF dual-hosted git repository. jsorel pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 4c9052a8f1 feat(Shapefile): implement featureset bounding box and count methods, several small fix in reader iterator 4c9052a8f1 is described below commit 4c9052a8f111b1d811803798d2083eb75edf898c Author: jsorel <johann.so...@geomatys.com> AuthorDate: Tue Nov 14 17:14:00 2023 +0100 feat(Shapefile): implement featureset bounding box and count methods, several small fix in reader iterator --- .../sis/storage/shapefile/ShapefileProvider.java | 4 +- .../sis/storage/shapefile/ShapefileStore.java | 56 +++++++++++++++++++--- .../sis/storage/shapefile/shp/ShapeHeader.java | 7 ++- .../sis/storage/shapefile/shp/ShapeReader.java | 11 ++++- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileProvider.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileProvider.java index 1420cef0e8..4a77845225 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileProvider.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileProvider.java @@ -36,7 +36,7 @@ import org.apache.sis.storage.StorageConnector; */ public final class ShapefileProvider extends DataStoreProvider { - public static final String NAME = "Shapefile"; + public static final String NAME = "esri shapefile"; public static final String MIME_TYPE = "application/x-shapefile"; @@ -49,7 +49,7 @@ public final class ShapefileProvider extends DataStoreProvider { .create(URI.class, null); public static final ParameterDescriptorGroup PARAMETERS_DESCRIPTOR = - new ParameterBuilder().addName(NAME).addName("ShapefileParameters").createGroup( + new ParameterBuilder().addName(NAME).addName("EsriShapefileParameters").createGroup( PATH); public ShapefileProvider() { diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java index 52dafdd7de..37c24a9615 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java @@ -28,11 +28,13 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Optional; +import java.util.OptionalLong; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; @@ -61,6 +63,7 @@ import org.apache.sis.filter.DefaultFilterFactory; import org.apache.sis.filter.Optimization; import org.apache.sis.filter.internal.FunctionNames; import org.apache.sis.geometry.Envelopes; +import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.io.stream.ChannelDataInput; import org.apache.sis.io.stream.ChannelDataOutput; import org.apache.sis.io.stream.IOUtilities; @@ -176,8 +179,14 @@ public final class ShapefileStore extends DataStore implements FeatureSet { private final Rectangle2D.Double filter; private final Set<String> dbfProperties; - private int[] dbfPropertiesIndex; private final boolean readShp; + + /** + * Extracted informations + */ + private int[] dbfPropertiesIndex; + private ShapeHeader shpHeader; + private DBFHeader dbfHeader; /** * Name of the field used as identifier, may be null. */ @@ -211,6 +220,7 @@ public final class ShapefileStore extends DataStore implements FeatureSet { final Class geometryClass; try (final ShapeReader reader = new ShapeReader(ShpFiles.openReadChannel(shpPath), filter)) { final ShapeHeader header = reader.getHeader(); + this.shpHeader = header; geometryClass = ShapeGeometryEncoder.getEncoder(header.shapeType).getValueClass(); } catch (IOException ex) { throw new DataStoreException("Failed to parse shape file header.", ex); @@ -250,6 +260,7 @@ public final class ShapefileStore extends DataStore implements FeatureSet { if (dbfFile != null) { try (DBFReader reader = new DBFReader(ShpFiles.openReadChannel(dbfFile), charset, null)) { final DBFHeader header = reader.getHeader(); + this.dbfHeader = header; boolean hasId = false; if (dbfProperties == null) { @@ -258,7 +269,8 @@ public final class ShapefileStore extends DataStore implements FeatureSet { dbfPropertiesIndex = new int[dbfProperties.size()]; } - for (int i = 0,idx=0; i < header.fields.length; i++) { + int idx=0; + for (int i = 0; i < header.fields.length; i++) { final DBFField field = header.fields[i]; if (dbfProperties != null && !dbfProperties.contains(field.fieldName)) { //skip unwanted fields @@ -275,6 +287,9 @@ public final class ShapefileStore extends DataStore implements FeatureSet { hasId = true; } } + //the properties collection may have contain other names, for links or geometry, trim those + dbfPropertiesIndex = Arrays.copyOf(dbfPropertiesIndex, idx); + } catch (IOException ex) { throw new DataStoreException("Failed to parse dbf file header.", ex); } @@ -287,6 +302,31 @@ public final class ShapefileStore extends DataStore implements FeatureSet { return type; } + @Override + public Optional<Envelope> getEnvelope() throws DataStoreException { + getType();//force loading headers + if (shpHeader != null && filter == null) { + final GeneralEnvelope env = new GeneralEnvelope(crs); + env.setRange(0, shpHeader.bbox.getMinimum(0), shpHeader.bbox.getMaximum(0)); + env.setRange(1, shpHeader.bbox.getMinimum(1), shpHeader.bbox.getMaximum(1)); + return Optional.of(env); + } + return super.getEnvelope(); + } + + @Override + public OptionalLong getFeatureCount() { + try { + getType();//force loading headers + if (dbfHeader != null && filter == null) { + return OptionalLong.of(dbfHeader.nbRecord); + } + } catch (DataStoreException ex) { + //do nothing + } + return super.getFeatureCount(); + } + @Override public Stream<Feature> features(boolean parallel) throws DataStoreException { final FeatureType type = getType(); @@ -442,7 +482,7 @@ public final class ShapefileStore extends DataStore implements FeatureSet { } } - FeatureSet fs = new AsFeatureSet(area, readShp, properties); + final AsFeatureSet fs = new AsFeatureSet(area, readShp, properties); //see if there are elements we could not handle final FeatureQuery subQuery = new FeatureQuery(); boolean needSubProcessing = false; @@ -471,13 +511,15 @@ public final class ShapefileStore extends DataStore implements FeatureSet { subQuery.setProjection(projection); } - return needSubProcessing ? fs.subset(subQuery) : fs; + return needSubProcessing ? fs.parentSubSet(subQuery) : fs; } return super.subset(query); } - + private FeatureSet parentSubSet(Query query) throws DataStoreException { + return super.subset(query); + } @Override public void updateType(FeatureType newType) throws DataStoreException { @@ -612,7 +654,7 @@ public final class ShapefileStore extends DataStore implements FeatureSet { boolean rebuildAnd = false; List<Filter<?>> lst = (List<Filter<?>>) ((LogicalOperator<?>)filter).getOperands(); Envelope bbox = null; - for (int i = 0, n = lst.size(); i < n; i++) { + for (int i = 0; i < lst.size(); i++) { final Filter<?> f = lst.get(i); final Entry<Envelope, Filter> split = extractBbox(f); Envelope cdtBbox = split.getKey(); @@ -644,6 +686,8 @@ public final class ShapefileStore extends DataStore implements FeatureSet { if (rebuildAnd) { if (lst.isEmpty()) { filter = null; + } else if (lst.size() == 1) { + filter = lst.get(0); } else { filter = DefaultFilterFactory.forFeatures().and((List)lst); } diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeHeader.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeHeader.java index b0413d7150..1f61d614b4 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeHeader.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeHeader.java @@ -26,7 +26,6 @@ import java.nio.ByteOrder; /** * Shapefile header. - * * @author Johann Sorel (Geomatys) * @see <a href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf">ESRI Shapefile Specification</a> */ @@ -42,7 +41,7 @@ public final class ShapeHeader { public static final int SIGNATURE = 9994; /** - * File size. + * File size, in bytes. */ public int fileLength; /** @@ -69,7 +68,7 @@ public final class ShapeHeader { } //skip unused datas channel.skipBytes(5*4); - fileLength = channel.readInt(); + fileLength = channel.readInt() * 2; //in 16bits words channel.buffer.order(ByteOrder.LITTLE_ENDIAN); final int version = channel.readInt(); @@ -95,7 +94,7 @@ public final class ShapeHeader { channel.buffer.order(ByteOrder.BIG_ENDIAN); channel.writeInt(SIGNATURE); channel.write(new byte[5*4]); - channel.writeInt(fileLength); + channel.writeInt(fileLength/2); channel.buffer.order(ByteOrder.LITTLE_ENDIAN); channel.writeInt(1000); channel.writeInt(shapeType); diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeReader.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeReader.java index 9e4b325112..5d713d62bd 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeReader.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeReader.java @@ -21,6 +21,7 @@ import org.apache.sis.io.stream.ChannelDataInput; import java.io.EOFException; import java.io.IOException; +import java.util.Objects; import org.apache.sis.geometry.Envelope2D; /** @@ -36,6 +37,7 @@ public final class ShapeReader implements AutoCloseable{ private final Rectangle2D.Double filter; public ShapeReader(ChannelDataInput channel, Rectangle2D.Double filter) throws IOException { + Objects.nonNull(channel); this.channel = channel; this.filter = filter; header = new ShapeHeader(); @@ -55,7 +57,14 @@ public final class ShapeReader implements AutoCloseable{ final ShapeRecord record = new ShapeRecord(); try { //read until we find a record matching the filter or EOF exception - while (!record.read(channel, geomParser, filter)) {} + //we do not trust EOF exception, some channel implementations with buffers may continue to say they have datas + //but they are picking in an obsolete buffer. + for (;;) { + if (header.fileLength <= channel.getStreamPosition()) { + return null; + } + if (record.read(channel, geomParser, filter)) break; + } return record; } catch (EOFException ex) { //no more records