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 94bffce331 feat(Shapefile): support updateType, add a first writing
support
94bffce331 is described below
commit 94bffce33101a36a15f7d5a6a3d8d618455a47a9
Author: jsorel <[email protected]>
AuthorDate: Wed Nov 29 17:18:45 2023 +0100
feat(Shapefile): support updateType, add a first writing support
---
.../sis/storage/shapefile/ShapefileStore.java | 433 +++++++++++++++++----
.../shapefile/shp/ShapeGeometryEncoder.java | 231 +++++++++--
.../sis/storage/shapefile/shp/ShapeRecord.java | 4 +
.../sis/storage/shapefile/shp/ShapeWriter.java | 14 +-
.../sis/storage/shapefile/ShapefileStoreTest.java | 122 +++++-
.../test/module-info.java | 3 +
6 files changed, 678 insertions(+), 129 deletions(-)
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 303b6d6f20..2d6fe5e827 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
@@ -24,9 +24,7 @@ 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.nio.file.*;
import java.time.LocalDate;
import java.util.AbstractMap;
import java.util.ArrayList;
@@ -34,12 +32,14 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
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;
+import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
@@ -49,6 +49,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+
+import org.apache.sis.geometry.ImmutableEnvelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
@@ -76,6 +78,9 @@ import org.apache.sis.geometry.wrapper.Geometries;
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.io.wkt.Convention;
+import org.apache.sis.io.wkt.WKTFormat;
+import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.CommonCRS;
@@ -587,116 +592,205 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
}
@Override
- public void updateType(FeatureType newType) throws DataStoreException {
- if (true) throw new UnsupportedOperationException("Not supported
yet.");
+ public synchronized void updateType(FeatureType newType) throws
DataStoreException {
if (!isDefaultView()) throw new DataStoreException("Resource not
writable in current filter state");
if (Files.exists(shpPath)) {
throw new DataStoreException("Update type is possible only
when files do not exist. It can be used to create a new shapefile but not to
update one.");
}
- final ShapeHeader shpHeader = new ShapeHeader();
- final DBFHeader dbfHeader = new DBFHeader();
- final Charset charset = userDefinedCharSet == null ?
StandardCharsets.UTF_8 : userDefinedCharSet;
- CoordinateReferenceSystem crs =
CommonCRS.WGS84.normalizedGeographic();
-
- for (PropertyType pt : newType.getProperties(true)) {
- if (pt instanceof AttributeType) {
- final AttributeType at = (AttributeType) pt;
- final Class valueClass = at.getValueClass();
-
- Integer length =
AttributeConvention.getMaximalLengthCharacteristic(newType, pt);
- if (length == 0) length = 255;
+ lock.writeLock().lock();
+ try {
+ final ShapeHeader shpHeader = new ShapeHeader();
+ shpHeader.bbox = new ImmutableEnvelope(new GeneralEnvelope(4));
+ final DBFHeader dbfHeader = new DBFHeader();
+ dbfHeader.fields = new DBFField[0];
+ final Charset charset = userDefinedCharSet == null ?
StandardCharsets.UTF_8 : userDefinedCharSet;
+ CoordinateReferenceSystem crs =
CommonCRS.WGS84.normalizedGeographic();
+
+ for (PropertyType pt : newType.getProperties(true)) {
+ if (pt instanceof AttributeType) {
+ final AttributeType at = (AttributeType) pt;
+ final Class valueClass = at.getValueClass();
+ final String attName = at.getName().tip().toString();
+
+ Integer length =
AttributeConvention.getMaximalLengthCharacteristic(newType, pt);
+ if (length == null || length == 0) length = 255;
+
+ if (Geometry.class.isAssignableFrom(valueClass)) {
+ if (shpHeader.shapeType != 0) {
+ throw new DataStoreException("Shapefile format
can only contain one geometry");
+ }
+ if (Point.class.isAssignableFrom(valueClass))
shpHeader.shapeType = ShapeType.VALUE_POINT;
+ else if
(MultiPoint.class.isAssignableFrom(valueClass))
+ shpHeader.shapeType =
ShapeType.VALUE_MULTIPOINT;
+ else if
(LineString.class.isAssignableFrom(valueClass) ||
MultiLineString.class.isAssignableFrom(valueClass))
+ shpHeader.shapeType = ShapeType.VALUE_POLYLINE;
+ else if
(Polygon.class.isAssignableFrom(valueClass) ||
MultiPolygon.class.isAssignableFrom(valueClass))
+ shpHeader.shapeType = ShapeType.VALUE_POLYGON;
+ else throw new DataStoreException("Unsupported
geometry type " + valueClass);
+
+ Object cdt =
at.characteristics().get(AttributeConvention.CRS);
+ if (cdt instanceof AttributeType) {
+ Object defaultValue = ((AttributeType)
cdt).getDefaultValue();
+ if (defaultValue instanceof
CoordinateReferenceSystem) {
+ crs = (CoordinateReferenceSystem)
defaultValue;
+ }
+ }
- if (Geometry.class.isAssignableFrom(valueClass)) {
- if (shpHeader.shapeType != 0) {
- throw new DataStoreException("Shapefile format can
only contain one geometry");
- }
- if (Point.class.isAssignableFrom(valueClass))
shpHeader.shapeType = ShapeType.VALUE_POINT;
- else if
(MultiPoint.class.isAssignableFrom(valueClass)) shpHeader.shapeType =
ShapeType.VALUE_MULTIPOINT;
- else if (LineString.class.isAssignableFrom(valueClass)
|| MultiLineString.class.isAssignableFrom(valueClass)) shpHeader.shapeType =
ShapeType.VALUE_POLYLINE;
- else if (Polygon.class.isAssignableFrom(valueClass) ||
MultiPolygon.class.isAssignableFrom(valueClass)) shpHeader.shapeType =
ShapeType.VALUE_POLYGON;
- else throw new DataStoreException("Unsupported
geometry type " + valueClass);
-
- Object cdt =
at.characteristics().get(AttributeConvention.CRS_CHARACTERISTIC);
- if (cdt instanceof CoordinateReferenceSystem) {
- crs = (CoordinateReferenceSystem) cdt;
+ } else if (String.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_CHAR, 0, length, 0, charset));
+ } else if (Byte.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 4, 0, null));
+ } else if (Short.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 6, 0, null));
+ } else if (Integer.class.isAssignableFrom(valueClass))
{
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 9, 0, null));
+ } else if (Long.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 19, 0, null));
+ } else if (Float.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 11, 8, null));
+ } else if (Double.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_NUMBER, 0, 33, 30, null));
+ } else if
(LocalDate.class.isAssignableFrom(valueClass)) {
+ dbfHeader.fields =
ArraysExt.append(dbfHeader.fields, new DBFField(attName, (char)
DBFField.TYPE_DATE, 0, 20, 0, null));
+ } else {
+ LOGGER.log(Level.WARNING, "Shapefile writing,
field {0} is not supported", pt.getName());
}
-
- } else if (String.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_CHAR, 0, length, 0, charset));
- } else if (Byte.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_NUMBER, 0, 4, 0, null));
- } else if (Short.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_NUMBER, 0, 6, 0, null));
- } else if (Integer.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_NUMBER, 0, 9, 0, null));
- } else if (Long.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_NUMBER, 0, 19, 0, null));
- } else if (Float.class.isAssignableFrom(valueClass)) {
- dbfHeader.fields = ArraysExt.append(dbfHeader.fields,
new DBFField(idField, (char)DBFField.TYPE_NUMBER, 0, 33, 0, null));
- } else if (Double.class.isAssignableFrom(valueClass)) {
-
- } else if (LocalDate.class.isAssignableFrom(valueClass)) {
-
} else {
LOGGER.log(Level.WARNING, "Shapefile writing, field
{0} is not supported", pt.getName());
}
- } else {
- LOGGER.log(Level.WARNING, "Shapefile writing, field {0} is
not supported", pt.getName());
}
- }
- //write shapefile
- try (ShapeWriter writer = new
ShapeWriter(ShpFiles.openWriteChannel(files.shpFile))) {
- writer.write(shpHeader);
- } catch (IOException ex){
- throw new DataStoreException("Failed to create shapefile
(shp).", ex);
- }
-
- //write shx
- try (IndexWriter writer = new
IndexWriter(ShpFiles.openWriteChannel(files.shxFile))) {
- writer.write(shpHeader);
- } catch (IOException ex){
- throw new DataStoreException("Failed to create shapefile
(shx).", ex);
- }
+ //write shapefile
+ try (ShapeWriter writer = new
ShapeWriter(ShpFiles.openWriteChannel(files.shpFile, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))) {
+ writer.write(shpHeader);
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create shapefile
(shp).", ex);
+ }
- //write dbf
- try (DBFWriter writer = new
DBFWriter(ShpFiles.openWriteChannel(files.dbfFile))) {
- writer.write(dbfHeader);
- } catch (IOException ex){
- throw new DataStoreException("Failed to create shapefile
(dbf).", ex);
- }
+ //write shx
+ try (IndexWriter writer = new
IndexWriter(ShpFiles.openWriteChannel(files.getShx(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ writer.write(shpHeader);
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create shapefile
(shx).", ex);
+ }
- //write cpg
- try {
- CpgFiles.write(charset, files.cpgFile);
- } catch (IOException ex) {
- throw new DataStoreException("Failed to create shapefile
(cpg).", ex);
- }
+ //write dbf
+ try (DBFWriter writer = new
DBFWriter(ShpFiles.openWriteChannel(files.getDbf(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ writer.write(dbfHeader);
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create shapefile
(dbf).", ex);
+ }
- //write prj
- //todo
+ //write cpg
+ try {
+ CpgFiles.write(charset, files.getCpg(true));
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create shapefile
(cpg).", ex);
+ }
+ //write prj
+ try {
+ final WKTFormat format = new WKTFormat(Locale.ENGLISH,
null);
+ format.setConvention(Convention.WKT1_COMMON_UNITS);
+ format.setNameAuthority(Citations.ESRI);
+ format.setIndentation(WKTFormat.SINGLE_LINE);
+ Files.writeString(files.getPrj(true), format.format(crs),
StandardCharsets.ISO_8859_1, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create shapefile
(prj).", ex);
+ }
+ //update file list
+ files.scan();
+ } finally {
+ lock.writeLock().unlock();
+ }
}
@Override
public void add(Iterator<? extends Feature> features) throws
DataStoreException {
if (!isDefaultView()) throw new DataStoreException("Resource not
writable in current filter state");
- throw new UnsupportedOperationException("Not supported yet.");
+ if (!Files.exists(shpPath)) throw new
DataStoreException("FeatureType do not exist, use updateType before modifying
features.");
+
+ final Writer writer = new Writer(charset);
+ try {
+ //write existing features
+ try (Stream<Feature> stream = features(false)) {
+ Iterator<Feature> iterator = stream.iterator();
+ while (iterator.hasNext()) {
+ writer.write(iterator.next());
+ }
+ }
+
+ //write new features
+ while (features.hasNext()) {
+ writer.write(features.next());
+ }
+
+ writer.finish(true);
+ } catch (IOException ex) {
+ try {
+ writer.finish(false);
+ } catch (IOException e) {
+ ex.addSuppressed(e);
+ }
+ throw new DataStoreException("Writing failed", ex);
+ }
}
@Override
public void removeIf(Predicate<? super Feature> filter) throws
DataStoreException {
if (!isDefaultView()) throw new DataStoreException("Resource not
writable in current filter state");
- throw new UnsupportedOperationException("Not supported yet.");
+ if (!Files.exists(shpPath)) throw new
DataStoreException("FeatureType do not exist, use updateType before modifying
features.");
+
+ final Writer writer = new Writer(charset);
+ try {
+ //write existing features not matching filter
+ try (Stream<Feature> stream = features(false)) {
+ Iterator<Feature> iterator =
stream.filter(filter.negate()).iterator();
+ while (iterator.hasNext()) {
+ writer.write(iterator.next());
+ }
+ }
+ writer.finish(true);
+ } catch (IOException ex) {
+ try {
+ writer.finish(false);
+ } catch (IOException e) {
+ ex.addSuppressed(e);
+ }
+ throw new DataStoreException("Writing failed", ex);
+ }
}
@Override
public void replaceIf(Predicate<? super Feature> filter,
UnaryOperator<Feature> updater) throws DataStoreException {
if (!isDefaultView()) throw new DataStoreException("Resource not
writable in current filter state");
- throw new UnsupportedOperationException("Not supported yet.");
+ if (!Files.exists(shpPath)) throw new
DataStoreException("FeatureType do not exist, use updateType before modifying
features.");
+
+ final Writer writer = new Writer(charset);
+ try {
+ //write existing features applying modifications
+ try (Stream<Feature> stream = features(false)) {
+ Iterator<Feature> iterator = stream.iterator();
+ while (iterator.hasNext()) {
+ Feature feature = iterator.next();
+ if (filter.test(feature)) {
+ feature = updater.apply(feature);
+ }
+ if (feature != null) writer.write(feature);
+ }
+ }
+ writer.finish(true);
+ } catch (IOException ex) {
+ try {
+ writer.finish(false);
+ } catch (IOException e) {
+ ex.addSuppressed(e);
+ }
+ throw new DataStoreException("Writing failed", ex);
+ }
}
@Override
@@ -707,7 +801,7 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
final Path dbf = files.getDbf(false);
final Path prj = files.getPrj(false);
final Path cpg = files.getCpg(false);
- if (shp != null && Files.exists(shp)) paths.add(shp);
+ if ( Files.exists(shp)) paths.add(shp);
if (shx != null && Files.exists(shx)) paths.add(shx);
if (dbf != null && Files.exists(dbf)) paths.add(dbf);
if (prj != null && Files.exists(prj)) paths.add(prj);
@@ -734,6 +828,14 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
final String fileName = shpFile.getFileName().toString();
baseUpper =
Character.isUpperCase(fileName.codePointAt(fileName.length()-1));
this.baseName = IOUtilities.filenameWithoutExtension(fileName);
+ scan();
+ }
+
+ /**
+ * Search related files.
+ * Should be called after data have been modified.
+ */
+ private void scan() {
shxFile = findSibling("shx");
dbfFile = findSibling("dbf");
prjFile = findSibling("prj");
@@ -784,6 +886,45 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
return cpgFile;
}
+ /**
+ * Create a set of temporary files for edition.
+ */
+ private ShpFiles createTempFiles() throws IOException{
+ final Path tmp = Files.createTempFile("tmp", ".shp");
+ Files.delete(tmp);
+ return new ShpFiles(tmp);
+ }
+
+ /**
+ * Delete files permanently.
+ */
+ private void deleteFiles() throws IOException{
+ Files.deleteIfExists(shpFile);
+ if (shxFile != null) Files.deleteIfExists(shxFile);
+ if (dbfFile != null) Files.deleteIfExists(dbfFile);
+ if (cpgFile != null) Files.deleteIfExists(cpgFile);
+ if (prjFile != null) Files.deleteIfExists(prjFile);
+ }
+
+ /**
+ * Override target files by current ones.
+ */
+ private void replace(ShpFiles toReplace) throws IOException{
+ replace(shpFile, toReplace.shpFile);
+ replace(shxFile, toReplace.getShx(true));
+ replace(dbfFile, toReplace.getDbf(true));
+ replace(cpgFile, toReplace.getCpg(true));
+ replace(prjFile, toReplace.getPrj(true));
+ }
+
+ private static void replace(Path current, Path toReplace) throws
IOException{
+ if (current == null) {
+ Files.deleteIfExists(toReplace);
+ } else {
+ Files.move(current, toReplace,
StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
private Path findSibling(String extension) {
Path candidate = shpFile.getParent().resolve(baseName + "." +
extension);
if (java.nio.file.Files.isRegularFile(candidate)) return candidate;
@@ -797,9 +938,14 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
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));
+ private static ChannelDataOutput openWriteChannel(Path path,
OpenOption ... options) throws IOException, IllegalArgumentException,
DataStoreException {
+ final WritableByteChannel wbc;
+ if (options != null && options.length > 0) {
+ wbc = Files.newByteChannel(path, ArraysExt.append(options,
StandardOpenOption.WRITE));
+ } else {
+ wbc = Files.newByteChannel(path, StandardOpenOption.WRITE);
+ }
+ return new ChannelDataOutput(path.getFileName().toString(), wbc,
ByteBuffer.allocate(8192));
}
}
@@ -896,5 +1042,124 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
return env;
}
+ private class Writer {
+
+ private final ShpFiles tempFiles;
+ private final ShapeWriter shpWriter;
+ private final DBFWriter dbfWriter;
+ private final IndexWriter shxWriter;
+ private final ShapeHeader shpHeader;
+ private final DBFHeader dbfHeader;
+ private String defaultGeomName = null;
+ private int inc = 0;
+
+ private Writer(Charset charset) throws DataStoreException{
+ try {
+ tempFiles = files.createTempFiles();
+ } catch (IOException ex) {
+ throw new DataStoreException("Failed to create temp files",
ex);
+ }
+
+ try {
+ //get original headers and information
+ try (ShapeReader reader = new
ShapeReader(ShpFiles.openReadChannel(files.shpFile), null)) {
+ shpHeader = new ShapeHeader(reader.getHeader());
+ }
+ try (DBFReader reader = new
DBFReader(ShpFiles.openReadChannel(files.dbfFile), charset, null)) {
+ dbfHeader = new DBFHeader(reader.getHeader());
+ }
+
+ //unchanged files
+ ShpFiles.replace(files.cpgFile, tempFiles.getCpg(true));
+ ShpFiles.replace(files.prjFile, tempFiles.getPrj(true));
+
+ //start new files
+
+ //write shapefile
+ shpWriter = new
ShapeWriter(ShpFiles.openWriteChannel(tempFiles.shpFile,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ dbfWriter = new
DBFWriter(ShpFiles.openWriteChannel(tempFiles.getDbf(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ shxWriter = new
IndexWriter(ShpFiles.openWriteChannel(tempFiles.getShx(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ shpWriter.write(shpHeader);
+ shxWriter.write(shpHeader);
+ dbfWriter.write(dbfHeader);
+ } catch (IOException ex) {
+ try {
+ tempFiles.deleteFiles();
+ } catch (IOException e) {
+ ex.addSuppressed(e);
+ }
+ throw new DataStoreException("Failed to create temp files",
ex);
+ }
+
+ }
+
+ private void write(Feature feature) throws IOException {
+ inc++; //number starts at 1
+ final ShapeRecord shpRecord = new ShapeRecord();
+ final DBFRecord dbfRecord = new DBFRecord();
+ final long recordStartPosition = shpWriter.getSteamPosition();
+
+ if (defaultGeomName == null) {
+ //search for the geometry name
+ for (PropertyType pt : feature.getType().getProperties(true)) {
+ if (pt instanceof AttributeType) {
+ final AttributeType at = (AttributeType) pt;
+ final String attName = at.getName().toString();
+ if
(Geometry.class.isAssignableFrom(at.getValueClass())) {
+ defaultGeomName = attName;
+ }
+ }
+ }
+ if (defaultGeomName == null) {
+ throw new IOException("Failed to find a geometry attribute
in given features");
+ }
+ }
+
+ //write geometry
+ Object value = feature.getPropertyValue(defaultGeomName);
+ if (value instanceof Geometry) {
+ shpRecord.geometry = (Geometry) value;
+ shpRecord.recordNumber = inc;
+ } else {
+ throw new IOException("Feature geometry property is not a
geometry");
+ }
+ shpWriter.write(shpRecord);
+ final long recordEndPosition = shpWriter.getSteamPosition();
+
+ //write index
+ shxWriter.write(Math.toIntExact(recordStartPosition),
Math.toIntExact(recordEndPosition - recordStartPosition));
+
+ //copy dbf fields
+ dbfRecord.fields = new Object[dbfHeader.fields.length];
+ for (int i = 0; i < dbfRecord.fields.length; i++) {
+ dbfRecord.fields[i] =
feature.getPropertyValue(dbfHeader.fields[i].fieldName);
+ }
+ dbfWriter.write(dbfRecord);
+ }
+
+ /**
+ * Close file writers and replace original files if true.
+ */
+ private void finish(boolean replaceOriginals) throws IOException {
+ try {
+ shpWriter.close();
+ dbfWriter.close();
+ shxWriter.close();
+ tempFiles.scan();
+ if (replaceOriginals) {
+ lock.writeLock().lock();
+ try {
+ //swap files
+ tempFiles.replace(files);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ } finally {
+ tempFiles.deleteFiles();
+ }
+ }
+ }
+
}
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 f3ab943720..c069f1bd39 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
@@ -22,6 +22,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+
+import org.apache.sis.geometry.GeneralDirectPosition;
+import org.apache.sis.util.ArraysExt;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.locationtech.jts.algorithm.Orientation;
@@ -119,6 +122,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
/**
* Decode geometry and store it in ShapeRecord.
+ * This method creates and fill the record bbox if it is null.
*
* @param channel to read from
* @param record to read into
@@ -142,6 +146,8 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
*/
public abstract int getEncodedLength(Geometry geom);
+ public abstract GeneralEnvelope getBoundingBox(Geometry geom);
+
/**
* Read 2D Bounding box from channel.
*
@@ -174,10 +180,11 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
* @param shape to read from
*/
protected void writeBBox2D(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
- channel.writeDouble(shape.bbox.getMinimum(0));
- channel.writeDouble(shape.bbox.getMinimum(1));
- channel.writeDouble(shape.bbox.getMaximum(0));
- channel.writeDouble(shape.bbox.getMaximum(1));
+ final Envelope env2d = shape.geometry.getEnvelopeInternal();
+ channel.writeDouble(env2d.getMinX());
+ channel.writeDouble(env2d.getMinY());
+ channel.writeDouble(env2d.getMaxX());
+ channel.writeDouble(env2d.getMaxY());
}
/**
@@ -266,19 +273,28 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
//Z and M
- if (nbOrdinates >= 3) writeLineOrdinates(channel, shape, lines, 2);
- if (nbOrdinates == 4) writeLineOrdinates(channel, shape, lines, 3);
+ if (nbOrdinates >= 3) writeLineOrdinates(channel, shape, lines, 2,
nbPts);
+ if (nbOrdinates == 4) writeLineOrdinates(channel, shape, lines, 3,
nbPts);
}
- protected void writeLineOrdinates(ChannelDataOutput channel, ShapeRecord
shape,List<LineString> lines, int ordinateIndex) throws IOException {
- channel.writeDouble(shape.bbox.getMinimum(ordinateIndex));
- channel.writeDouble(shape.bbox.getMaximum(ordinateIndex));
+ protected void writeLineOrdinates(ChannelDataOutput channel, ShapeRecord
shape,List<LineString> lines, int ordinateIndex, int nbPts) throws IOException {
+
+ final double[] values = new double[nbPts];
+ double minK = Double.MAX_VALUE;
+ double maxK = -Double.MAX_VALUE;
+ int i = 0;
for (LineString line : lines) {
final CoordinateSequence cs = line.getCoordinateSequence();
for (int k = 0, kn =cs.size(); k < kn; k++) {
- channel.writeDouble(cs.getOrdinate(k, ordinateIndex));
+ values[i] = cs.getOrdinate(k, ordinateIndex);
+ minK = Double.min(minK, values[i]);
+ maxK = Double.max(maxK, values[i]);
+ i++;
}
}
+ channel.writeDouble(minK);
+ channel.writeDouble(maxK);
+ channel.writeDoubles(values);
}
protected List<LineString> extractRings(Geometry geom) {
@@ -368,6 +384,51 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
}
+ protected GeneralEnvelope getLinesBoundingBox(Geometry geom) {
+ final List<LineString> lines = extractRings(geom);
+
+ int nbOrdinate = 0;
+ GeneralEnvelope env = null;
+
+ for (int k = 0, kn = lines.size(); k < kn; k++) {
+ final LineString line = lines.get(k);
+ final CoordinateSequence cs = line.getCoordinateSequence();
+
+ if (nbOrdinate == 0) {
+ nbOrdinate = cs.getDimension();
+ }
+
+ for (int i = 0, n = cs.size(); i < n; i++) {
+ if (env == null) {
+ env = new GeneralEnvelope(nbOrdinate);
+ switch (nbOrdinate) {
+ case 4 :
+ double m = cs.getOrdinate(i, 3);
+ env.setRange(3, m, m);
+ case 3 :
+ double z = cs.getOrdinate(i, 2);
+ env.setRange(2, z, z);
+ case 2 :
+ double y = cs.getOrdinate(i, 1);
+ env.setRange(1, y, y);
+ double x = cs.getOrdinate(i, 0);
+ env.setRange(0, x, x);
+ }
+ } else {
+ switch (nbOrdinate) {
+ case 4 :
+ env.add(new
GeneralDirectPosition(cs.getOrdinate(i,0), cs.getOrdinate(i,1),
cs.getOrdinate(i,2), cs.getOrdinate(i,3))); break;
+ case 3 :
+ env.add(new
GeneralDirectPosition(cs.getOrdinate(i,0), cs.getOrdinate(i,1),
cs.getOrdinate(i,2))); break;
+ case 2 :
+ env.add(new
GeneralDirectPosition(cs.getOrdinate(i,0), cs.getOrdinate(i,1))); break;
+ }
+ }
+ }
+ }
+ return env;
+ }
+
private static class Null extends ShapeGeometryEncoder<Geometry> {
private static final Null INSTANCE = new Null();
@@ -390,6 +451,10 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
public void encode(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
}
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ return new GeneralEnvelope(0);
+ }
}
private static class PointXY extends ShapeGeometryEncoder<Point> {
@@ -417,14 +482,22 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
public void encode(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
final Point pt = (Point) shape.geometry;
final Coordinate coord = pt.getCoordinate();
- channel.writeDouble(coord.getX());
- channel.writeDouble(coord.getY());
+ final double[] xy = new double[]{coord.getX(), coord.getY()};
+ channel.writeDoubles(xy);
}
@Override
public int getEncodedLength(Geometry geom) {
return 2*8; //2 ordinates
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final Point pt = (Point) geom;
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xy = new double[]{coord.getX(), coord.getY()};
+ return new GeneralEnvelope(xy, xy);
+ }
}
private static class PointXYM extends ShapeGeometryEncoder<Point> {
@@ -453,15 +526,25 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
public void encode(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
final Point pt = (Point) shape.geometry;
final Coordinate coord = pt.getCoordinate();
- channel.writeDouble(coord.getX());
- channel.writeDouble(coord.getY());
- channel.writeDouble(coord.getM());
+ final double[] xym = new double[]{coord.getX(), coord.getY(),
coord.getM()};
+ channel.writeDoubles(xym);
+ if (shape.bbox == null) {
+ shape.bbox = new GeneralEnvelope(xym,xym);
+ }
}
@Override
public int getEncodedLength(Geometry geom) {
return 3*8; //3 ordinates
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final Point pt = (Point) geom;
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xym = new double[]{coord.getX(), coord.getY(),
coord.getM()};
+ return new GeneralEnvelope(xym, xym);
+ }
}
private static class PointXYZM extends ShapeGeometryEncoder<Point> {
@@ -493,16 +576,22 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
public void encode(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
final Point pt = (Point) shape.geometry;
final Coordinate coord = pt.getCoordinate();
- channel.writeDouble(coord.getX());
- channel.writeDouble(coord.getY());
- channel.writeDouble(coord.getZ());
- channel.writeDouble(coord.getM());
+ final double[] xyzm = new double[]{coord.getX(), coord.getY(),
coord.getZ(), coord.getM()};
+ channel.writeDoubles(xyzm);
}
@Override
public int getEncodedLength(Geometry geom) {
return 4*8; //4 ordinates
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final Point pt = (Point) geom;
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xyzm = new double[]{coord.getX(), coord.getY(),
coord.getZ(), coord.getM()};
+ return new GeneralEnvelope(xyzm, xyzm);
+ }
}
private static class MultiPointXY extends ShapeGeometryEncoder<MultiPoint>
{
@@ -529,8 +618,8 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
channel.writeInt(nbPts);
for (int i = 0; i < nbPts; i++) {
final Point pt = (Point) geometry.getGeometryN(i);
- channel.writeDouble(pt.getX());
- channel.writeDouble(pt.getY());
+ final double[] xy = new double[]{pt.getX(), pt.getY()};
+ channel.writeDoubles(xy);
}
}
@@ -540,6 +629,20 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
+ 4 //nbPts
+ ((MultiPoint) geom).getNumGeometries() * 2 * 8;
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final GeneralEnvelope env = new GeneralEnvelope(2);
+ final MultiPoint pts = (MultiPoint) geom;
+ for (int i = 0, n = pts.getNumGeometries(); i < n; i++) {
+ final Point pt = (Point)pts.getGeometryN(i);
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xy = new double[]{coord.getX(), coord.getY()};
+ if (i == 0) env.setEnvelope(xy[0], xy[1], xy[0], xy[1]);
+ else env.add(new GeneralDirectPosition(xy));
+ }
+ return env;
+ }
}
private static class MultiPointXYM extends
ShapeGeometryEncoder<MultiPoint> {
@@ -575,15 +678,21 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
channel.writeInt(nbPts);
for (int i = 0; i < nbPts; i++) {
final Point pt = (Point) geometry.getGeometryN(i);
- channel.writeDouble(pt.getX());
- channel.writeDouble(pt.getY());
+ final double[] xy = new double[]{pt.getX(), pt.getY()};
+ channel.writeDoubles(xy);
}
- channel.writeDouble(shape.bbox.getMinimum(2));
- channel.writeDouble(shape.bbox.getMaximum(2));
+ final double[] m = new double[nbPts];
+ double minM = Double.MAX_VALUE;
+ double maxM = -Double.MAX_VALUE;
for (int i = 0; i < nbPts; i++) {
final Point pt = (Point) geometry.getGeometryN(i);
- channel.writeDouble(pt.getCoordinate().getM());
+ m[i] = pt.getCoordinate().getM();
+ minM = Double.min(minM, m[i]);
+ maxM = Double.max(maxM, m[i]);
}
+ channel.writeDouble(minM);
+ channel.writeDouble(maxM);
+ channel.writeDoubles(m);
}
@Override
@@ -592,6 +701,20 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
+ 4 //nbPts
+ ((MultiPoint) geom).getNumGeometries() * 3 * 8;
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final GeneralEnvelope env = new GeneralEnvelope(3);
+ final MultiPoint pts = (MultiPoint) geom;
+ for (int i = 0, n = pts.getNumGeometries(); i < n; i++) {
+ final Point pt = (Point)pts.getGeometryN(i);
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xym = new double[]{coord.getX(), coord.getY(),
coord.getM()};
+ if (i == 0) env.setEnvelope(xym[0], xym[1], xym[2], xym[0],
xym[1], xym[2]);
+ else env.add(new GeneralDirectPosition(xym));
+ }
+ return env;
+ }
}
private static class MultiPointXYZM extends
ShapeGeometryEncoder<MultiPoint> {
@@ -634,18 +757,33 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
channel.writeDouble(pt.getX());
channel.writeDouble(pt.getY());
}
- channel.writeDouble(shape.bbox.getMinimum(2));
- channel.writeDouble(shape.bbox.getMaximum(2));
+
+ final double[] z = new double[nbPts];
+ double minZ = Double.MAX_VALUE;
+ double maxZ = -Double.MAX_VALUE;
for (int i = 0; i < nbPts; i++) {
final Point pt = (Point) geometry.getGeometryN(i);
- channel.writeDouble(pt.getCoordinate().getZ());
+ z[i] = pt.getCoordinate().getZ();
+ minZ = Double.min(minZ, z[i]);
+ maxZ = Double.max(maxZ, z[i]);
}
- channel.writeDouble(shape.bbox.getMinimum(3));
- channel.writeDouble(shape.bbox.getMaximum(3));
+ channel.writeDouble(minZ);
+ channel.writeDouble(maxZ);
+ channel.writeDoubles(z);
+
+
+ final double[] m = new double[nbPts];
+ double minM = Double.MAX_VALUE;
+ double maxM = -Double.MAX_VALUE;
for (int i = 0; i < nbPts; i++) {
final Point pt = (Point) geometry.getGeometryN(i);
- channel.writeDouble(pt.getCoordinate().getM());
+ m[i] = pt.getCoordinate().getM();
+ minM = Double.min(minM, m[i]);
+ maxM = Double.max(maxM, m[i]);
}
+ channel.writeDouble(minM);
+ channel.writeDouble(maxM);
+ channel.writeDoubles(m);
}
@Override
@@ -654,6 +792,20 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
+ 4 //nbPts
+ ((MultiPoint) geom).getNumGeometries() * 4 * 8;
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ final GeneralEnvelope env = new GeneralEnvelope(4);
+ final MultiPoint pts = (MultiPoint) geom;
+ for (int i = 0, n = pts.getNumGeometries(); i < n; i++) {
+ final Point pt = (Point)pts.getGeometryN(i);
+ final Coordinate coord = pt.getCoordinate();
+ final double[] xyzm = new double[]{coord.getX(), coord.getY(),
coord.getZ(), coord.getM()};
+ if (i == 0) env.setEnvelope(xyzm[0], xyzm[1], xyzm[2],
xyzm[3], xyzm[0], xyzm[1], xyzm[2], xyzm[3]);
+ else env.add(new GeneralDirectPosition(xyzm));
+ }
+ return env;
+ }
}
private static class Polyline extends
ShapeGeometryEncoder<MultiLineString> {
@@ -689,6 +841,11 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
+ nbGeom * 4 //offsets table
+ nbPoints * nbOrdinates * 8; //all ordinates
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ return getLinesBoundingBox(geom);
+ }
}
private static class Polygon extends ShapeGeometryEncoder<MultiPolygon> {
@@ -728,6 +885,11 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
+ nbGeom * 4 //offsets table
+ nbPoints * nbOrdinates * 8; //all ordinates
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ return getLinesBoundingBox(geom);
+ }
}
private static class MultiPatch extends ShapeGeometryEncoder<MultiPolygon>
{
@@ -752,5 +914,10 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
public int getEncodedLength(Geometry geom) {
throw new UnsupportedOperationException("Not supported yet.");
}
+
+ @Override
+ public GeneralEnvelope getBoundingBox(Geometry geom) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
}
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeRecord.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeRecord.java
index b98ddfd0fc..77a0776864 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeRecord.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeRecord.java
@@ -19,11 +19,15 @@ package org.apache.sis.storage.shapefile.shp;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.ByteOrder;
+
+import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.io.stream.ChannelDataOutput;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Point;
/**
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeWriter.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeWriter.java
index 2459802a3e..cabd09d663 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeWriter.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeWriter.java
@@ -43,6 +43,13 @@ public final class ShapeWriter implements AutoCloseable{
return header;
}
+ /**
+ * @return current position in the stream
+ */
+ public long getSteamPosition() {
+ return channel.getBitOffset();
+ }
+
/**
* Header will be copied and modified.
* Use getHeader to obtain the new header.
@@ -57,11 +64,12 @@ public final class ShapeWriter implements AutoCloseable{
public void write(ShapeRecord record) throws IOException {
record.write(channel, io);
+ final GeneralEnvelope geomBox = io.getBoundingBox(record.geometry);
if (bbox == null) {
- bbox = new GeneralEnvelope(record.bbox.getDimension());
- bbox.setEnvelope(record.bbox);
+ bbox = new GeneralEnvelope(geomBox.getDimension());
+ bbox.setEnvelope(geomBox);
} else {
- bbox.add(record.bbox);
+ bbox.add(geomBox);
}
}
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
index 6d0152954f..6fd3fcf34c 100644
---
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
@@ -16,13 +16,20 @@
*/
package org.apache.sis.storage.shapefile;
+import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.Iterator;
+import java.util.List;
import java.util.stream.Stream;
+import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.util.Utilities;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.geometry.GeneralEnvelope;
@@ -30,6 +37,7 @@ import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.FeatureQuery;
import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.feature.internal.AttributeConvention;
// Test dependencies
import static org.junit.jupiter.api.Assertions.*;
@@ -49,6 +57,8 @@ import org.opengis.filter.FilterFactory;
*/
public class ShapefileStoreTest {
+ private static final GeometryFactory GF = new GeometryFactory();
+
@Test
public void testStream() throws URISyntaxException, DataStoreException {
final URL url =
ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
@@ -171,21 +181,65 @@ public class ShapefileStoreTest {
try (final ShapefileStore store = new
ShapefileStore(Paths.get(url.toURI()))) {
Path[] componentFiles = store.getComponentFiles();
assertEquals(5, componentFiles.length);
- componentFiles[0].toString().endsWith("point.shp");
- componentFiles[1].toString().endsWith("point.shx");
- componentFiles[2].toString().endsWith("point.dbf");
- componentFiles[3].toString().endsWith("point.prj");
- componentFiles[4].toString().endsWith("point.cpg");
+ assertTrue(componentFiles[0].toString().endsWith("point.shp"));
+ assertTrue(componentFiles[1].toString().endsWith("point.shx"));
+ assertTrue(componentFiles[2].toString().endsWith("point.dbf"));
+ assertTrue(componentFiles[3].toString().endsWith("point.prj"));
+ assertTrue(componentFiles[4].toString().endsWith("point.cpg"));
}
}
/**
* Test creating a new shapefile.
*/
- @Ignore
@Test
- public void testCreate() throws URISyntaxException, DataStoreException {
- //todo
+ public void testCreate() throws URISyntaxException, DataStoreException,
IOException {
+ final Path temp = Files.createTempFile("test", ".shp");
+ Files.delete(temp);
+ final String name = temp.getFileName().toString().split("\\.")[0];
+ try (final ShapefileStore store = new ShapefileStore(temp)) {
+ Path[] componentFiles = store.getComponentFiles();
+ assertEquals(0, componentFiles.length);
+
+ {//create type
+ final FeatureType type = createType();
+ store.updateType(type);
+ }
+
+ {//check files have been created
+ componentFiles = store.getComponentFiles();
+ assertEquals(5, componentFiles.length);
+ assertTrue(componentFiles[0].toString().endsWith(name+".shp"));
+ assertTrue(componentFiles[1].toString().endsWith(name+".shx"));
+ assertTrue(componentFiles[2].toString().endsWith(name+".dbf"));
+ assertTrue(
componentFiles[3].toString().endsWith(name+".prj"));
+ assertTrue(componentFiles[4].toString().endsWith(name+".cpg"));
+ }
+
+ {// check created type
+ FeatureType type = store.getType();
+ assertEquals(name, type.getName().toString());
+ System.out.println(type.toString());
+ assertEquals(9, type.getProperties(true).size());
+ assertNotNull(type.getProperty("sis:identifier"));
+ assertNotNull(type.getProperty("sis:envelope"));
+ assertNotNull(type.getProperty("sis:geometry"));
+ final var geomProp = (AttributeType)
type.getProperty("geometry");
+ final var idProp = (AttributeType) type.getProperty("id");
+ final var textProp = (AttributeType) type.getProperty("text");
+ final var integerProp = (AttributeType)
type.getProperty("integer");
+ final var floatProp = (AttributeType)
type.getProperty("float");
+ final var dateProp = (AttributeType) type.getProperty("date");
+ final AttributeType crsChar = (AttributeType)
geomProp.characteristics().get(AttributeConvention.CRS);
+
assertTrue(Utilities.equalsIgnoreMetadata(CommonCRS.WGS84.geographic(),crsChar.getDefaultValue()));
+ assertEquals(Point.class, geomProp.getValueClass());
+ assertEquals(Integer.class, idProp.getValueClass());
+ assertEquals(String.class, textProp.getValueClass());
+ assertEquals(Integer.class, integerProp.getValueClass());
+ assertEquals(Double.class, floatProp.getValueClass());
+ assertEquals(LocalDate.class, dateProp.getValueClass());
+ }
+ }
}
/**
@@ -193,8 +247,22 @@ public class ShapefileStoreTest {
*/
@Ignore
@Test
- public void testAddFeatures() throws URISyntaxException,
DataStoreException {
- //todo
+ public void testAddFeatures() throws URISyntaxException,
DataStoreException, IOException {
+ final Path temp = Files.createTempFile("test", ".shp");
+ Files.delete(temp);
+ try (final ShapefileStore store = new ShapefileStore(temp)) {
+ FeatureType type = createType();
+ store.updateType(type);
+ type = store.getType();
+
+ Feature feature1 = createFeature1(type);
+ Feature feature2 = createFeature2(type);
+ store.add(List.of(feature1, feature2).iterator());
+
+ Object[] result = store.features(false).toArray();
+
+
+ }
}
/**
@@ -215,4 +283,38 @@ public class ShapefileStoreTest {
//todo
}
+ private static FeatureType createType() {
+ final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+ ftb.setName("test");
+ ftb.addAttribute(Integer.class).setName("id");
+ ftb.addAttribute(String.class).setName("text");
+ ftb.addAttribute(Integer.class).setName("integer");
+ ftb.addAttribute(Float.class).setName("float");
+ ftb.addAttribute(LocalDate.class).setName("date");
+
ftb.addAttribute(Point.class).setName("geometry").setCRS(CommonCRS.WGS84.geographic());
+ return ftb.build();
+ }
+
+ private static Feature createFeature1(FeatureType type) {
+ Feature feature = type.newInstance();
+ feature.setPropertyValue("geometry", GF.createPoint(new
Coordinate(10,20)));
+ feature.setPropertyValue("id", 1);
+ feature.setPropertyValue("text", "some text 1");
+ feature.setPropertyValue("integer", 123);
+ feature.setPropertyValue("float", 123.456);
+ feature.setPropertyValue("date", LocalDate.of(2023, 5, 12));
+ return feature;
+ }
+
+ private static Feature createFeature2(FeatureType type) {
+ Feature feature = type.newInstance();
+ feature.setPropertyValue("geometry", GF.createPoint(new
Coordinate(30,40)));
+ feature.setPropertyValue("id", 2);
+ feature.setPropertyValue("text", "some text 2");
+ feature.setPropertyValue("integer", 456);
+ feature.setPropertyValue("float", 456.789);
+ feature.setPropertyValue("date", LocalDate.of(2030, 6, 21));
+ return feature;
+ }
+
}
diff --git a/incubator/src/org.apache.sis.test.incubator/test/module-info.java
b/incubator/src/org.apache.sis.test.incubator/test/module-info.java
index 66086cb5c7..fed9415c58 100644
--- a/incubator/src/org.apache.sis.test.incubator/test/module-info.java
+++ b/incubator/src/org.apache.sis.test.incubator/test/module-info.java
@@ -24,4 +24,7 @@ module org.apache.sis.test.incubator {
requires transitive junit;
requires transitive org.junit.jupiter.api;
requires transitive org.opengis.geoapi.conformance;
+ requires org.apache.sis.feature;
+ requires org.apache.sis.storage;
+ requires org.locationtech.jts;
}