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 924462e3c6 feat(Shapefile): add shapefile store
924462e3c6 is described below
commit 924462e3c6f2f8beb8fec8f6e15689856214d08b
Author: jsorel <[email protected]>
AuthorDate: Fri Nov 3 10:16:38 2023 +0100
feat(Shapefile): add shapefile store
---
.../org.apache.sis.feature/main/module-info.java | 1 +
.../src/org.apache.sis.util/main/module-info.java | 1 +
.../sis/storage/shapefile/ShapefileProvider.java | 83 +++++
.../sis/storage/shapefile/ShapefileStore.java | 382 +++++++++++++++++++++
.../shapefile/shp/ShapeGeometryEncoder.java | 53 +--
.../sis/storage/shapefile/shx/IndexReader.java | 65 ++++
.../sis/storage/shapefile/ShapefileStoreTest.java | 92 +++++
7 files changed, 655 insertions(+), 22 deletions(-)
diff --git a/endorsed/src/org.apache.sis.feature/main/module-info.java
b/endorsed/src/org.apache.sis.feature/main/module-info.java
index 33a7fd41d2..b9f78b899c 100644
--- a/endorsed/src/org.apache.sis.feature/main/module-info.java
+++ b/endorsed/src/org.apache.sis.feature/main/module-info.java
@@ -52,6 +52,7 @@ module org.apache.sis.feature {
org.apache.sis.storage.xml,
org.apache.sis.storage.netcdf,
org.apache.sis.portrayal,
+ org.apache.sis.storage.shapefile, // In the "incubator"
sub-project.
org.apache.sis.gui; // In the "optional"
sub-project.
exports org.apache.sis.geometry.wrapper to
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java
b/endorsed/src/org.apache.sis.util/main/module-info.java
index dd5262f706..ed334ac345 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -129,6 +129,7 @@ module org.apache.sis.util {
org.apache.sis.storage.earthobservation,
org.apache.sis.cql, // In the "incubator"
sub-project.
org.apache.sis.portrayal,
+ org.apache.sis.storage.shapefile,
org.apache.sis.cloud.aws,
org.apache.sis.console,
org.apache.sis.gui, // In the "optional"
sub-project.
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
new file mode 100644
index 0000000000..3d371337a3
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.storage.shapefile;
+
+import java.net.URI;
+import java.nio.file.Path;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreProvider;
+import static org.apache.sis.storage.DataStoreProvider.LOCATION;
+import org.apache.sis.storage.ProbeResult;
+import org.apache.sis.storage.StorageConnector;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+
+/**
+ * Shapefile format datastore provider.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @see <a
href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf">ESRI
Shapefile Specification</a>
+ */
+public final class ShapefileProvider extends DataStoreProvider {
+
+ public static final String NAME = "Shapefile";
+
+ public static final String MIME_TYPE = "application/x-shapefile";
+
+ /**
+ * URI to the shp file.
+ */
+ public static final ParameterDescriptor<URI> PATH = new ParameterBuilder()
+ .addName(LOCATION)
+ .setRequired(true)
+ .create(URI.class, null);
+
+ public static final ParameterDescriptorGroup PARAMETERS_DESCRIPTOR =
+ new
ParameterBuilder().addName(NAME).addName("ShapefileParameters").createGroup(
+ PATH);
+
+ public ShapefileProvider() {
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public ParameterDescriptorGroup getOpenParameters() {
+ return PARAMETERS_DESCRIPTOR;
+ }
+
+ @Override
+ public ProbeResult probeContent(StorageConnector connector) throws
DataStoreException {
+ final Path path = connector.getStorageAs(Path.class);
+ if (path != null &&
path.getFileName().toString().toLowerCase().endsWith(".shp")) {
+ return new ProbeResult(true, MIME_TYPE, null);
+ }
+ return ProbeResult.UNSUPPORTED_STORAGE;
+ }
+
+ @Override
+ public DataStore open(StorageConnector connector) throws
DataStoreException {
+ final Path path = connector.getStorageAs(Path.class);
+ return new ShapefileStore(path);
+ }
+
+}
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
new file mode 100644
index 0000000000..03aa111d42
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
@@ -0,0 +1,382 @@
+/*
+ * 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.storage.shapefile;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.sis.feature.builder.AttributeRole;
+import org.apache.sis.feature.builder.AttributeTypeBuilder;
+import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.io.stream.ChannelDataOutput;
+import org.apache.sis.io.stream.IOUtilities;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.storage.AbstractFeatureSet;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.Query;
+import org.apache.sis.storage.UnsupportedQueryException;
+import org.apache.sis.storage.WritableFeatureSet;
+import org.apache.sis.storage.shapefile.cpg.CpgFiles;
+import org.apache.sis.storage.shapefile.dbf.DBFField;
+import org.apache.sis.storage.shapefile.dbf.DBFHeader;
+import org.apache.sis.storage.shapefile.dbf.DBFReader;
+import org.apache.sis.storage.shapefile.dbf.DBFRecord;
+import org.apache.sis.storage.shapefile.shp.ShapeGeometryEncoder;
+import org.apache.sis.storage.shapefile.shp.ShapeHeader;
+import org.apache.sis.storage.shapefile.shp.ShapeReader;
+import org.apache.sis.storage.shapefile.shp.ShapeRecord;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Metadata;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.FactoryException;
+import org.opengis.util.GenericName;
+
+/**
+ * Shapefile datastore.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class ShapefileStore extends DataStore implements FeatureSet {
+
+ private static final String GEOMETRY_NAME = "geometry";
+
+ private final Path shpPath;
+ private final ShpFiles files;
+ /**
+ * Internal class to inherit AbstractFeatureSet.
+ */
+ private final AsFeatureSet featureSetView = new AsFeatureSet();
+ private FeatureType type;
+ private Charset charset;
+
+ /**
+ * Lock to control read and write operations.
+ */
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+ public ShapefileStore(Path path) {
+ this.shpPath = path;
+ this.files = new ShpFiles(shpPath);
+ }
+
+ @Override
+ public Optional<ParameterValueGroup> getOpenParameters() {
+ final Parameters parameters =
Parameters.castOrWrap(ShapefileProvider.PARAMETERS_DESCRIPTOR.createValue());
+
parameters.parameter(ShapefileProvider.LOCATION).setValue(shpPath.toUri());
+ return Optional.of(parameters);
+ }
+
+ @Override
+ public void close() throws DataStoreException {
+ }
+
+
+ /*
+ Redirect FeatureSet interface to View
+ */
+ @Override
+ public Optional<GenericName> getIdentifier() throws DataStoreException {
+ return featureSetView.getIdentifier();
+ }
+
+ @Override
+ public Metadata getMetadata() throws DataStoreException {
+ return featureSetView.getMetadata();
+ }
+
+ @Override
+ public FeatureType getType() throws DataStoreException {
+ return featureSetView.getType();
+ }
+
+ @Override
+ public FeatureSet subset(Query query) throws UnsupportedQueryException,
DataStoreException {
+ return featureSetView.subset(query);
+ }
+
+ @Override
+ public Stream<Feature> features(boolean parallel) throws
DataStoreException {
+ return featureSetView.features(parallel);
+ }
+
+ @Override
+ public Optional<Envelope> getEnvelope() throws DataStoreException {
+ return featureSetView.getEnvelope();
+ }
+
+ private class AsFeatureSet extends AbstractFeatureSet implements
WritableFeatureSet {
+
+ private AsFeatureSet() {
+ super(null);
+ }
+
+ @Override
+ public synchronized FeatureType getType() throws DataStoreException {
+ if (type == null) {
+ if (!Files.isRegularFile(shpPath)) {
+ throw new DataStoreException("Shape files do not exist.
Update FeatureType first to initialize this empty datastore");
+ }
+
+ final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+ ftb.setName(files.baseName);
+
+ //read shp header to obtain geometry type
+ final Class geometryClass;
+ try (final ShapeReader reader = new
ShapeReader(ShpFiles.openReadChannel(shpPath))) {
+ final ShapeHeader header = reader.getHeader();
+ geometryClass =
ShapeGeometryEncoder.getEncoder(header.shapeType).getValueClass();
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to parse shape file
header.", ex);
+ }
+
+ //read prj file for projection
+ final Path prjFile = files.getPrj(false);
+ final CoordinateReferenceSystem crs;
+ if (prjFile != null) {
+ try {
+ crs = CRS.fromWKT(Files.readString(prjFile,
StandardCharsets.UTF_8));
+ } catch (IOException | FactoryException ex) {
+ throw new DataStoreException("Failed to parse prj
file.", ex);
+ }
+ } else {
+ //shapefile often do not have a .prj, mostly those are in
CRS:84.
+ //we do not raise an error otherwise we would not be able
to read a lot of data.
+ crs = CommonCRS.WGS84.normalizedGeographic();
+ }
+
+
ftb.addAttribute(geometryClass).setName(GEOMETRY_NAME).setCRS(crs).addRole(AttributeRole.DEFAULT_GEOMETRY);
+
+ //read cpg for dbf file charset
+ final Path cpgFile = files.getCpg(false);
+ if (cpgFile != null) {
+ try (final SeekableByteChannel channel =
Files.newByteChannel(cpgFile, StandardOpenOption.READ)) {
+ charset = CpgFiles.read(channel);
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to parse cpg
file.", ex);
+ }
+ } else {
+ charset = StandardCharsets.UTF_8;
+ }
+
+ //read dbf for attributes
+ final Path dbfFile = files.getDbf(false);
+ if (dbfFile != null) {
+ try (DBFReader reader = new
DBFReader(ShpFiles.openReadChannel(dbfFile), charset)) {
+ final DBFHeader header = reader.getHeader();
+ boolean hasId = false;
+ for (DBFField field : header.fields) {
+ final AttributeTypeBuilder atb =
ftb.addAttribute(field.getEncoder().getValueClass()).setName(field.fieldName);
+ //no official but 'id' field is common
+ if (!hasId &&
"id".equalsIgnoreCase(field.fieldName) ||
"identifier".equalsIgnoreCase(field.fieldName)) {
+
atb.addRole(AttributeRole.IDENTIFIER_COMPONENT);
+ hasId = true;
+ }
+ }
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to parse dbf file
header.", ex);
+ }
+ } else {
+ throw new DataStoreException("DBF file is missing.");
+ }
+
+ type = ftb.build();
+ }
+ return type;
+ }
+
+ @Override
+ public Stream<Feature> features(boolean parallel) throws
DataStoreException {
+ final FeatureType type = getType();
+ final ShapeReader shpreader;
+ final DBFReader dbfreader;
+ try {
+ shpreader = new
ShapeReader(ShpFiles.openReadChannel(files.shpFile));
+ dbfreader = new
DBFReader(ShpFiles.openReadChannel(files.getDbf(false)), charset);
+ } catch (IOException ex) {
+ throw new DataStoreException("Faild to open shp and dbf
files.", ex);
+ }
+ final DBFHeader header = dbfreader.getHeader();
+
+ final Spliterator spliterator = new
Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) {
+ @Override
+ public boolean tryAdvance(Consumer action) {
+ try {
+ final ShapeRecord shpRecord = shpreader.next();
+ if (shpRecord == null) return false;
+ final DBFRecord dbfRecord = dbfreader.next();
+ final Feature next = type.newInstance();
+ next.setPropertyValue(GEOMETRY_NAME,
shpRecord.geometry);
+ for (int i = 0; i < header.fields.length; i++) {
+ next.setPropertyValue(header.fields[i].fieldName,
dbfRecord.fields[i]);
+ }
+ action.accept(next);
+ return true;
+ } catch (IOException ex) {
+ throw new BackingStoreException(ex.getMessage(), ex);
+ }
+ }
+ };
+ final Stream<Feature> stream = StreamSupport.stream(spliterator,
false);
+ return stream.onClose(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ shpreader.close();
+ dbfreader.close();
+ } catch (IOException ex) {
+ throw new BackingStoreException(ex.getMessage(), ex);
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public void updateType(FeatureType newType) throws DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void add(Iterator<? extends Feature> features) throws
DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void removeIf(Predicate<? super Feature> filter) throws
DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void replaceIf(Predicate<? super Feature> filter,
UnaryOperator<Feature> updater) throws DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ /**
+ * Manipulate the different shape files.
+ */
+ private static class ShpFiles {
+
+ private final String baseName;
+ private final boolean baseUpper;
+ private final Path shpFile;
+ private Path shxFile;
+ private Path dbfFile;
+ private Path prjFile;
+ private Path cpgFile;
+
+ public ShpFiles(Path shpFile) {
+ this.shpFile = shpFile;
+ final String fileName = shpFile.getFileName().toString();
+ baseUpper =
Character.isUpperCase(fileName.codePointAt(fileName.length()-1));
+ this.baseName = IOUtilities.filenameWithoutExtension(fileName);
+ shxFile = findSibling("shx");
+ dbfFile = findSibling("dbf");
+ prjFile = findSibling("prj");
+ cpgFile = findSibling("cpg");
+ }
+
+ /**
+ * @param create true to create the path even if file do not exist.
+ * @return file if it exist or create is true, null otherwise
+ */
+ public Path getShx(boolean create) {
+ if (create && shxFile == null) {
+ return shpFile.getParent().resolve(baseName + "." + (baseUpper
? "SHX" : "shx"));
+ }
+ return shxFile;
+ }
+
+ /**
+ * @param create true to create the path even if file do not exist.
+ * @return file if it exist or create is true, null otherwise
+ */
+ public Path getDbf(boolean create) {
+ if (create && dbfFile == null) {
+ return shpFile.getParent().resolve(baseName + "." + (baseUpper
? "DBF" : "dbf"));
+ }
+ return dbfFile;
+ }
+
+ /**
+ * @param create true to create the path even if file do not exist.
+ * @return file if it exist or create is true, null otherwise
+ */
+ public Path getPrj(boolean create) {
+ if (create && prjFile == null) {
+ return shpFile.getParent().resolve(baseName + "." + (baseUpper
? "PRJ" : "prj"));
+ }
+ return prjFile;
+ }
+
+ /**
+ * @param create true to create the path even if file do not exist.
+ * @return file if it exist or create is true, null otherwise
+ */
+ public Path getCpg(boolean create) {
+ if (create && cpgFile == null) {
+ return shpFile.getParent().resolve(baseName + "." + (baseUpper
? "CPG" : "cpg"));
+ }
+ return cpgFile;
+ }
+
+ private Path findSibling(String extension) {
+ Path candidate = shpFile.getParent().resolve(baseName + "." +
extension);
+ if (java.nio.file.Files.isRegularFile(candidate)) return candidate;
+ candidate = shpFile.getParent().resolve(baseName + "." +
extension.toUpperCase());
+ if (java.nio.file.Files.isRegularFile(candidate)) return candidate;
+ return null;
+ }
+
+ private static ChannelDataInput openReadChannel(Path path) throws
IOException {
+ final SeekableByteChannel channel = Files.newByteChannel(path,
StandardOpenOption.READ);
+ return new ChannelDataInput(path.getFileName().toString(),
channel, ByteBuffer.allocate(8192), false);
+ }
+
+ private static ChannelDataOutput openWriteChannel(Path path) throws
IOException, IllegalArgumentException, DataStoreException {
+ final WritableByteChannel wbc = Files.newByteChannel(path,
StandardOpenOption.WRITE);
+ return new ChannelDataOutput(path.getFileName().toString(), wbc,
ByteBuffer.allocate(8000));
+ }
+ }
+
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
index c9ccec984f..853bd16391 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeGeometryEncoder.java
@@ -35,11 +35,12 @@ import org.locationtech.jts.algorithm.RayCrossingCounter;
*
* @author Johann Sorel (Geomatys)
*/
-public abstract class ShapeGeometryEncoder {
+public abstract class ShapeGeometryEncoder<T extends Geometry> {
private static final GeometryFactory GF = new GeometryFactory();
protected final int shapeType;
+ protected final Class<T> geometryClass;
protected final int dimension;
protected final int measures;
protected final int nbOrdinates;
@@ -77,8 +78,9 @@ public abstract class ShapeGeometryEncoder {
* @param dimension number of dimensions in processed geometries.
* @param measures number of measures in processed geometries.
*/
- protected ShapeGeometryEncoder(int shapeType, int dimension, int measures)
{
+ protected ShapeGeometryEncoder(int shapeType, Class<T> geometryClass, int
dimension, int measures) {
this.shapeType = shapeType;
+ this.geometryClass = geometryClass;
this.dimension = dimension;
this.measures = measures;
this.nbOrdinates = dimension + measures;
@@ -90,6 +92,13 @@ public abstract class ShapeGeometryEncoder {
public int getShapeType() {
return shapeType;
}
+
+ /**
+ * @return geometry class handled by this encoder
+ */
+ public Class<T> getValueClass() {
+ return geometryClass;
+ }
/**
* @return number of dimensions in processed geometries.
@@ -297,12 +306,12 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class Null extends ShapeGeometryEncoder {
+ private static class Null extends ShapeGeometryEncoder<Geometry> {
private static final Null INSTANCE = new Null();
private Null() {
- super(ShapeType.VALUE_NULL, 2,0);
+ super(ShapeType.VALUE_NULL, Geometry.class, 2, 0);
}
@Override
@@ -320,12 +329,12 @@ public abstract class ShapeGeometryEncoder {
}
- private static class PointXY extends ShapeGeometryEncoder {
+ private static class PointXY extends ShapeGeometryEncoder<Point> {
private static final PointXY INSTANCE = new PointXY();
private PointXY() {
- super(ShapeType.VALUE_POINT, 2,0);
+ super(ShapeType.VALUE_POINT, Point.class, 2,0);
}
@Override
@@ -352,11 +361,11 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class PointXYM extends ShapeGeometryEncoder {
+ private static class PointXYM extends ShapeGeometryEncoder<Point> {
private static final PointXYM INSTANCE = new PointXYM();
private PointXYM() {
- super(ShapeType.VALUE_POINT_M, 2,1);
+ super(ShapeType.VALUE_POINT_M, Point.class, 2, 1);
}
@Override
@@ -386,12 +395,12 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class PointXYZM extends ShapeGeometryEncoder {
+ private static class PointXYZM extends ShapeGeometryEncoder<Point> {
private static final PointXYZM INSTANCE = new PointXYZM();
private PointXYZM() {
- super(ShapeType.VALUE_POINT_ZM, 3,1);
+ super(ShapeType.VALUE_POINT_ZM, Point.class, 3, 1);
}
@Override
@@ -424,11 +433,11 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class MultiPointXY extends ShapeGeometryEncoder {
+ private static class MultiPointXY extends ShapeGeometryEncoder<MultiPoint>
{
private static final MultiPointXY INSTANCE = new MultiPointXY();
private MultiPointXY() {
- super(ShapeType.VALUE_MULTIPOINT, 2,0);
+ super(ShapeType.VALUE_MULTIPOINT, MultiPoint.class, 2, 0);
}
@Override
@@ -460,12 +469,12 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class MultiPointXYM extends ShapeGeometryEncoder {
+ private static class MultiPointXYM extends
ShapeGeometryEncoder<MultiPoint> {
private static final MultiPointXYM INSTANCE = new MultiPointXYM();
private MultiPointXYM() {
- super(ShapeType.VALUE_MULTIPOINT_M, 2,1);
+ super(ShapeType.VALUE_MULTIPOINT_M, MultiPoint.class, 2, 1);
}
@Override
@@ -511,12 +520,12 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class MultiPointXYZM extends ShapeGeometryEncoder {
+ private static class MultiPointXYZM extends
ShapeGeometryEncoder<MultiPoint> {
private static final MultiPointXYZM INSTANCE = new MultiPointXYZM();
private MultiPointXYZM() {
- super(ShapeType.VALUE_MULTIPOINT_ZM, 3,1);
+ super(ShapeType.VALUE_MULTIPOINT_ZM, MultiPoint.class, 3, 1);
}
@Override
@@ -572,14 +581,14 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class Polyline extends ShapeGeometryEncoder {
+ private static class Polyline extends
ShapeGeometryEncoder<MultiLineString> {
private static final Polyline INSTANCE = new
Polyline(ShapeType.VALUE_POLYLINE, 2, 0);
private static final Polyline INSTANCE_M = new
Polyline(ShapeType.VALUE_POLYLINE_M, 3, 0);
private static final Polyline INSTANCE_ZM = new
Polyline(ShapeType.VALUE_POLYLINE_ZM, 3, 1);
private Polyline(int shapeType, int dimension, int measures) {
- super(shapeType, dimension, measures);
+ super(shapeType, MultiLineString.class, dimension, measures);
}
@Override
@@ -604,14 +613,14 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class Polygon extends ShapeGeometryEncoder {
+ private static class Polygon extends ShapeGeometryEncoder<MultiPolygon> {
private static final Polygon INSTANCE = new
Polygon(ShapeType.VALUE_POLYGON, 2, 0);
private static final Polygon INSTANCE_M = new
Polygon(ShapeType.VALUE_POLYGON_M, 3, 0);
private static final Polygon INSTANCE_ZM = new
Polygon(ShapeType.VALUE_POLYGON_ZM, 3, 1);
private Polygon(int shapeType, int dimension, int measures) {
- super(shapeType, dimension, measures);
+ super(shapeType, MultiPolygon.class, dimension, measures);
}
@Override
@@ -641,12 +650,12 @@ public abstract class ShapeGeometryEncoder {
}
}
- private static class MultiPatch extends ShapeGeometryEncoder {
+ private static class MultiPatch extends ShapeGeometryEncoder<MultiPolygon>
{
private static final MultiPatch INSTANCE = new MultiPatch();
private MultiPatch() {
- super(ShapeType.VALUE_MULTIPATCH_ZM, 3, 1);
+ super(ShapeType.VALUE_MULTIPATCH_ZM, MultiPolygon.class, 3, 1);
}
@Override
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexReader.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexReader.java
new file mode 100644
index 0000000000..2e02455a92
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexReader.java
@@ -0,0 +1,65 @@
+/*
+ * 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.storage.shapefile.shx;
+
+import org.apache.sis.storage.shapefile.shp.*;
+import org.apache.sis.io.stream.ChannelDataInput;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Seekable shx index file reader.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class IndexReader implements AutoCloseable{
+
+ private final ChannelDataInput channel;
+ private final ShapeHeader header;
+
+ public IndexReader(ChannelDataInput channel) throws IOException {
+ this.channel = channel;
+ header = new ShapeHeader();
+ header.read(channel);
+ }
+
+ public ShapeHeader getHeader() {
+ return header;
+ }
+
+ public void moveToOffset(long position) throws IOException {
+ channel.seek(position);
+ }
+
+ /**
+ * @return offset and length of the record in the shp file
+ */
+ public int[] next() throws IOException {
+ try {
+ return channel.readInts(2);
+ } catch (EOFException ex) {
+ //no more records
+ return null;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ channel.channel.close();
+ }
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
new file mode 100644
index 0000000000..d8d1503736
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/ShapefileStoreTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.storage.shapefile;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.util.Iterator;
+import java.util.stream.Stream;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.shapefile.shp.ShapeIOTest;
+import org.junit.Test;
+import org.locationtech.jts.geom.Point;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class ShapefileStoreTest {
+
+ @Test
+ public void testStream() throws URISyntaxException, DataStoreException {
+ final URL url =
ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
+ final ShapefileStore store = new
ShapefileStore(Paths.get(url.toURI()));
+
+ //check feature type
+ final FeatureType type = store.getType();
+ assertEquals("point", type.getName().toString());
+ assertEquals(9, type.getProperties(true).size());
+ assertNotNull(type.getProperty("sis:identifier"));
+ assertNotNull(type.getProperty("sis:envelope"));
+ assertNotNull(type.getProperty("sis:geometry"));
+ final AttributeType geomProp = (AttributeType)
type.getProperty("geometry");
+ final AttributeType idProp = (AttributeType) type.getProperty("id");
+ final AttributeType textProp = (AttributeType)
type.getProperty("text");
+ final AttributeType integerProp = (AttributeType)
type.getProperty("integer");
+ final AttributeType floatProp = (AttributeType)
type.getProperty("float");
+ final AttributeType dateProp = (AttributeType)
type.getProperty("date");
+ assertEquals(Point.class, geomProp.getValueClass());
+ assertEquals(Long.class, idProp.getValueClass());
+ assertEquals(String.class, textProp.getValueClass());
+ assertEquals(Long.class, integerProp.getValueClass());
+ assertEquals(Double.class, floatProp.getValueClass());
+ assertEquals(LocalDate.class, dateProp.getValueClass());
+
+ try (Stream<Feature> stream = store.features(false)) {
+ Iterator<Feature> iterator = stream.iterator();
+ assertTrue(iterator.hasNext());
+ Feature feature1 = iterator.next();
+ assertEquals(1L, feature1.getPropertyValue("id"));
+ assertEquals("text1", feature1.getPropertyValue("text"));
+ assertEquals(10L, feature1.getPropertyValue("integer"));
+ assertEquals(20.0, feature1.getPropertyValue("float"));
+ assertEquals(LocalDate.of(2023, 10, 27),
feature1.getPropertyValue("date"));
+ Point pt1 = (Point) feature1.getPropertyValue("geometry");
+
+ assertTrue(iterator.hasNext());
+ Feature feature2 = iterator.next();
+ assertEquals(2L, feature2.getPropertyValue("id"));
+ assertEquals("text2", feature2.getPropertyValue("text"));
+ assertEquals(40L, feature2.getPropertyValue("integer"));
+ assertEquals(60.0, feature2.getPropertyValue("float"));
+ assertEquals(LocalDate.of(2023, 10, 28),
feature2.getPropertyValue("date"));
+ Point pt2 = (Point) feature2.getPropertyValue("geometry");
+
+
+ assertFalse(iterator.hasNext());
+ }
+
+ }
+
+}