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 a798c10c97 feat(Shapefile): simplify raw API, reduce scope of all
unnecessary attributes, full javadoc
a798c10c97 is described below
commit a798c10c972e96fbca6ad417c76979a28e2bf919
Author: jsorel <[email protected]>
AuthorDate: Wed Dec 6 18:12:56 2023 +0100
feat(Shapefile): simplify raw API, reduce scope of all unnecessary
attributes, full javadoc
SIS-188 : DBFField properties have been reduced
SIS-188 : classes removed
SIS-189 : exception removed
---
.../main/module-info.java | 3 +-
.../sis/storage/shapefile/ShapefileProvider.java | 24 ++++
.../sis/storage/shapefile/ShapefileStore.java | 100 ++++++++++----
.../{dbf/DBFRecord.java => cpg/package-info.java} | 17 +--
.../apache/sis/storage/shapefile/dbf/DBFField.java | 89 ++++++++++--
.../sis/storage/shapefile/dbf/DBFHeader.java | 63 +++++++--
.../sis/storage/shapefile/dbf/DBFReader.java | 48 +++++--
.../sis/storage/shapefile/dbf/DBFWriter.java | 39 ++++--
.../dbf/{DBFRecord.java => package-info.java} | 28 ++--
.../apache/sis/storage/shapefile/package-info.java | 20 ++-
.../shapefile/shp/ShapeGeometryEncoder.java | 151 ++++++++++++++++-----
.../sis/storage/shapefile/shp/ShapeHeader.java | 15 +-
.../sis/storage/shapefile/shp/ShapeReader.java | 29 ++++
.../sis/storage/shapefile/shp/ShapeRecord.java | 55 ++++----
.../sis/storage/shapefile/shp/ShapeType.java | 83 +++++++----
.../sis/storage/shapefile/shp/ShapeWriter.java | 48 +++++--
.../storage/shapefile/{ => shp}/package-info.java | 13 +-
.../sis/storage/shapefile/shx/IndexReader.java | 27 +++-
.../sis/storage/shapefile/shx/IndexWriter.java | 37 +++--
.../storage/shapefile/{ => shx}/package-info.java | 9 +-
.../org/apache/sis/storage/shapefile/Snippets.java | 106 +++++++++++++++
.../sis/storage/shapefile/dbf/DBFIOTest.java | 46 +++----
.../apache/sis/storage/shapefile/dbf/Snippets.java | 95 +++++++++++++
.../sis/storage/shapefile/shp/ShapeIOTest.java | 4 +-
.../apache/sis/storage/shapefile/shp/Snippets.java | 81 +++++++++++
25 files changed, 979 insertions(+), 251 deletions(-)
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
index e1b6d97f85..9bb7b321f2 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
@@ -26,8 +26,9 @@ module org.apache.sis.storage.shapefile {
exports org.apache.sis.storage.shapefile;
exports org.apache.sis.storage.shapefile.cpg;
- exports org.apache.sis.storage.shapefile.shp;
exports org.apache.sis.storage.shapefile.dbf;
+ exports org.apache.sis.storage.shapefile.shp;
+ exports org.apache.sis.storage.shapefile.shx;
provides org.apache.sis.storage.DataStoreProvider
with org.apache.sis.storage.shapefile.ShapefileProvider;
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 4a77845225..ecb18705b6 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,8 +36,14 @@ import org.apache.sis.storage.StorageConnector;
*/
public final class ShapefileProvider extends DataStoreProvider {
+ /**
+ * Format name.
+ */
public static final String NAME = "esri shapefile";
+ /**
+ * Format mime type.
+ */
public static final String MIME_TYPE = "application/x-shapefile";
/**
@@ -48,23 +54,38 @@ public final class ShapefileProvider extends
DataStoreProvider {
.setRequired(true)
.create(URI.class, null);
+ /**
+ * Shapefile store creation parameters.
+ */
public static final ParameterDescriptorGroup PARAMETERS_DESCRIPTOR =
new
ParameterBuilder().addName(NAME).addName("EsriShapefileParameters").createGroup(
PATH);
+ /**
+ * Default constructor.
+ */
public ShapefileProvider() {
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public String getShortName() {
return NAME;
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public ParameterDescriptorGroup getOpenParameters() {
return PARAMETERS_DESCRIPTOR;
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public ProbeResult probeContent(StorageConnector connector) throws
DataStoreException {
final Path path = connector.getStorageAs(Path.class);
@@ -74,6 +95,9 @@ public final class ShapefileProvider extends
DataStoreProvider {
return ProbeResult.UNSUPPORTED_STORAGE;
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public DataStore open(StorageConnector connector) throws
DataStoreException {
final Path path = connector.getStorageAs(Path.class);
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 6c755a466e..9a77f574bc 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
@@ -99,7 +99,6 @@ 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.dbf.DBFWriter;
import org.apache.sis.storage.shapefile.shp.ShapeGeometryEncoder;
import org.apache.sis.storage.shapefile.shp.ShapeHeader;
@@ -149,18 +148,33 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
*/
private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ /**
+ * Construct store from given path.
+ *
+ * @param path path to .shp file
+ */
public ShapefileStore(Path path) {
this.shpPath = path;
this.userDefinedCharSet = null;
this.files = new ShpFiles(shpPath);
}
+ /**
+ * Construct store from given connector.
+ *
+ * @param cnx not null
+ * @throws IllegalArgumentException if connector could not provide a valid
Path instance
+ * @throws DataStoreException if connector could not provide a valid Path
instance
+ */
public ShapefileStore(StorageConnector cnx) throws
IllegalArgumentException, DataStoreException {
this.shpPath = cnx.getStorageAs(Path.class);
this.userDefinedCharSet = cnx.getOption(OptionKey.ENCODING);
this.files = new ShpFiles(shpPath);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public Optional<ParameterValueGroup> getOpenParameters() {
final Parameters parameters =
Parameters.castOrWrap(ShapefileProvider.PARAMETERS_DESCRIPTOR.createValue());
@@ -168,63 +182,96 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
return Optional.of(parameters);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public void close() throws DataStoreException {
}
- /*
- Redirect FeatureSet interface to View
- */
+ /**
+ * {@inheritDoc }
+ */
@Override
public Optional<GenericName> getIdentifier() throws DataStoreException {
return featureSetView.getIdentifier();
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public Metadata getMetadata() throws DataStoreException {
return featureSetView.getMetadata();
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public FeatureType getType() throws DataStoreException {
return featureSetView.getType();
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public FeatureSet subset(Query query) throws UnsupportedQueryException,
DataStoreException {
return featureSetView.subset(query);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public Stream<Feature> features(boolean parallel) throws
DataStoreException {
return featureSetView.features(parallel);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public Optional<Envelope> getEnvelope() throws DataStoreException {
return featureSetView.getEnvelope();
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public void updateType(FeatureType featureType) throws DataStoreException {
featureSetView.updateType(featureType);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public void add(Iterator<? extends Feature> iterator) throws
DataStoreException {
featureSetView.add(iterator);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public void removeIf(Predicate<? super Feature> predicate) throws
DataStoreException {
featureSetView.removeIf(predicate);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public void replaceIf(Predicate<? super Feature> predicate,
UnaryOperator<Feature> unaryOperator) throws DataStoreException {
featureSetView.replaceIf(predicate, unaryOperator);
}
+ /**
+ * {@inheritDoc }
+ */
@Override
public Path[] getComponentFiles() throws DataStoreException {
return featureSetView.getComponentFiles();
@@ -416,11 +463,11 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
//move dbf to record offset, some shp record might
have been skipped because of filter
long offset = (long)header.headerSize +
((long)(shpRecord.recordNumber-1)) * ((long)header.recordSize);
dbfreader.moveToOffset(offset);
- final DBFRecord dbfRecord = dbfreader.next();
+ final Object[] dbfRecord = dbfreader.next();
final Feature next = type.newInstance();
next.setPropertyValue(GEOMETRY_NAME,
shpRecord.geometry);
for (int i = 0; i < dbfPropertiesIndex.length;
i++) {
-
next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName,
dbfRecord.fields[i]);
+
next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName,
dbfRecord[i]);
}
action.accept(next);
return true;
@@ -453,11 +500,11 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
@Override
public boolean tryAdvance(Consumer action) {
try {
- final DBFRecord dbfRecord = dbfreader.next();
+ final Object[] dbfRecord = dbfreader.next();
if (dbfRecord == null) return false;
final Feature next = type.newInstance();
for (int i = 0; i < dbfPropertiesIndex.length;
i++) {
-
next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName,
dbfRecord.fields[i]);
+
next.setPropertyValue(header.fields[dbfPropertiesIndex[i]].fieldName,
dbfRecord[i]);
}
action.accept(next);
return true;
@@ -603,6 +650,7 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
final ShapeHeader shpHeader = new ShapeHeader();
shpHeader.bbox = new ImmutableEnvelope(new GeneralEnvelope(4));
final DBFHeader dbfHeader = new DBFHeader();
+ dbfHeader.lastUpdate = LocalDate.now();
dbfHeader.fields = new DBFField[0];
final Charset charset = userDefinedCharSet == null ?
StandardCharsets.UTF_8 : userDefinedCharSet;
CoordinateReferenceSystem crs =
CommonCRS.WGS84.normalizedGeographic();
@@ -617,16 +665,16 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
if (length == null || length == 0) length = 255;
if (Geometry.class.isAssignableFrom(valueClass)) {
- if (shpHeader.shapeType != 0) {
+ if (shpHeader.shapeType != null) {
throw new DataStoreException("Shapefile format
can only contain one geometry");
}
- if (Point.class.isAssignableFrom(valueClass))
shpHeader.shapeType = ShapeType.VALUE_POINT;
+ if (Point.class.isAssignableFrom(valueClass))
shpHeader.shapeType = ShapeType.POINT;
else if
(MultiPoint.class.isAssignableFrom(valueClass))
- shpHeader.shapeType =
ShapeType.VALUE_MULTIPOINT;
+ shpHeader.shapeType = ShapeType.MULTIPOINT;
else if
(LineString.class.isAssignableFrom(valueClass) ||
MultiLineString.class.isAssignableFrom(valueClass))
- shpHeader.shapeType = ShapeType.VALUE_POLYLINE;
+ shpHeader.shapeType = ShapeType.POLYLINE;
else if
(Polygon.class.isAssignableFrom(valueClass) ||
MultiPolygon.class.isAssignableFrom(valueClass))
- shpHeader.shapeType = ShapeType.VALUE_POLYGON;
+ shpHeader.shapeType = ShapeType.POLYGON;
else throw new DataStoreException("Unsupported
geometry type " + valueClass);
Object cdt =
at.characteristics().get(AttributeConvention.CRS);
@@ -663,21 +711,21 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
//write shapefile
try (ShapeWriter writer = new
ShapeWriter(ShpFiles.openWriteChannel(files.shpFile, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))) {
- writer.write(shpHeader);
+ writer.writeHeader(shpHeader);
} catch (IOException ex) {
throw new DataStoreException("Failed to create shapefile
(shp).", ex);
}
//write shx
try (IndexWriter writer = new
IndexWriter(ShpFiles.openWriteChannel(files.getShx(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
- writer.write(shpHeader);
+ writer.writeHeader(shpHeader);
} catch (IOException ex) {
throw new DataStoreException("Failed to create shapefile
(shx).", ex);
}
//write dbf
try (DBFWriter writer = new
DBFWriter(ShpFiles.openWriteChannel(files.getDbf(true),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
- writer.write(dbfHeader);
+ writer.writeHeader(dbfHeader);
} catch (IOException ex) {
throw new DataStoreException("Failed to create shapefile
(dbf).", ex);
}
@@ -1078,9 +1126,9 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
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);
+ shpWriter.writeHeader(shpHeader);
+ shxWriter.writeHeader(shpHeader);
+ dbfWriter.writeHeader(dbfHeader);
} catch (IOException ex) {
try {
tempFiles.deleteFiles();
@@ -1095,7 +1143,6 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
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) {
@@ -1122,18 +1169,18 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
} else {
throw new IOException("Feature geometry property is not a
geometry");
}
- shpWriter.write(shpRecord);
+ shpWriter.writeRecord(shpRecord);
final long recordEndPosition = shpWriter.getSteamPosition();
//write index
- shxWriter.write(Math.toIntExact(recordStartPosition),
Math.toIntExact(recordEndPosition - recordStartPosition));
+ shxWriter.writeRecord(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);
+ Object[] fields = new Object[dbfHeader.fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ fields[i] =
feature.getPropertyValue(dbfHeader.fields[i].fieldName);
}
- dbfWriter.write(dbfRecord);
+ dbfWriter.writeRecord(fields);
}
/**
@@ -1160,5 +1207,4 @@ public final class ShapefileStore extends DataStore
implements WritableFeatureSe
}
}
-
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/cpg/package-info.java
similarity index 73%
copy from
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
copy to
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/cpg/package-info.java
index 1f33f949d7..93d9357898 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/cpg/package-info.java
@@ -14,21 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.storage.shapefile.dbf;
-
/**
- * A DBF record is an array of field values.
- *
- * @author Johann Sorel (Geomatys)
+ * Shapefile CPG files utilities.
*/
-public final class DBFRecord {
-
- public static final DBFRecord DELETED = new DBFRecord();
-
- public Object[] fields;
-
- public DBFRecord() {
- }
-
-}
+package org.apache.sis.storage.shapefile.cpg;
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
index 564bd7ba9d..341b4d4a86 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
@@ -27,6 +27,8 @@ import org.apache.sis.io.stream.ChannelDataOutput;
/**
+ * Immutable DBase III field description.
+ * This class holds the different field attributes with value read and write
capabilities.
*
* @author Johann Sorel (Geomatys)
*/
@@ -35,60 +37,83 @@ public final class DBFField {
/**
* Binary, String : b or B
*/
- public static final int TYPE_BINARY = 'b';
+ public static final char TYPE_BINARY = 'b';
/**
* Characters : c or C
*/
- public static final int TYPE_CHAR = 'c';
+ public static final char TYPE_CHAR = 'c';
/**
* Date : d or D
*/
- public static final int TYPE_DATE = 'd';
+ public static final char TYPE_DATE = 'd';
/**
* Numeric : n or N
*/
- public static final int TYPE_NUMBER = 'n';
+ public static final char TYPE_NUMBER = 'n';
/**
* Logical : l or L
*/
- public static final int TYPE_LOGIC = 'l';
+ public static final char TYPE_LOGIC = 'l';
/**
* Memo, String : m or M
*/
- public static final int TYPE_MEMO = 'm';
+ public static final char TYPE_MEMO = 'm';
/**
* TimeStamp : 8 bytes, two longs, first for date, second for time.
* The date is the number of days since 01/01/4713 BC.
* Time is hours * 3600000L + minutes * 60000L + Seconds * 1000L
*/
- public static final int TYPE_TIMESTAMP = '@';
+ public static final char TYPE_TIMESTAMP = '@';
/**
* Long : i or I on 4 bytes, first bit is the sign, 0 = negative
*/
- public static final int TYPE_LONG = 'i';
+ public static final char TYPE_LONG = 'i';
/**
* Autoincrement : same as Long
*/
- public static final int TYPE_INC = '+';
+ public static final char TYPE_INC = '+';
/**
* Floats : f or F
*/
- public static final int TYPE_FLOAT = 'f';
+ public static final char TYPE_FLOAT = 'f';
/**
* Double : o or O, real double on 8bytes, not string encoded
*/
- public static final int TYPE_DOUBLE = 'o';
+ public static final char TYPE_DOUBLE = 'o';
/**
* OLE : g or G
*/
- public static final int TYPE_OLE = 'g';
+ public static final char TYPE_OLE = 'g';
+ /**
+ * Field name.
+ */
public final String fieldName;
+ /**
+ * Field type.
+ */
public final char fieldType;
+ /**
+ * Reserved, found with different names in different spec.
+ * Unused in current implementation.
+ */
public final int fieldAddress;
+ /**
+ * Field length in binary.
+ */
public final int fieldLength;
+ /**
+ * Field decimal count in binary.
+ */
public final int fieldDecimals;
- public final Charset charset;
+ /**
+ * Used to decode strings.
+ * Can be null.
+ */
+ private final Charset charset;
+ /**
+ * Java value type matching field type.
+ */
public final Class valueClass;
private final ReadMethod reader;
@@ -96,6 +121,16 @@ public final class DBFField {
//used by decimal format only;
private NumberFormat format;
+ /**
+ * Field constructor.
+ *
+ * @param fieldName field name
+ * @param fieldType field data type
+ * @param fieldAddress unused for now, has a meaning in some
specifications but not all
+ * @param fieldLength total field length in bytes
+ * @param fieldDecimals number of decimals for floating points
+ * @param charset String base field encoding
+ */
public DBFField(String fieldName, char fieldType, int fieldAddress, int
fieldLength, int fieldDecimals, Charset charset) {
this.fieldName = fieldName;
this.fieldType = fieldType;
@@ -131,6 +166,14 @@ public final class DBFField {
}
}
+ /**
+ * Read field description.
+ *
+ * @param channel to read from
+ * @param charset field text encoding
+ * @return dbf field description
+ * @throws IOException if an error occured while parsing field
+ */
public static DBFField read(ChannelDataInput channel, Charset charset)
throws IOException {
byte[] n = channel.readBytes(11);
int nameSize = 0;
@@ -144,6 +187,12 @@ public final class DBFField {
return new DBFField(fieldName, fieldType, fieldAddress, fieldLength,
fieldDecimals, charset);
}
+ /**
+ * Write field description.
+ *
+ * @param channel to write into
+ * @throws IOException if an error occured while writing field
+ */
public void write(ChannelDataOutput channel) throws IOException {
byte[] bytes = fieldName.getBytes(StandardCharsets.US_ASCII);
if (bytes.length > 11) throw new IOException("Field name length must
not be longer then 11 characters.");
@@ -156,10 +205,24 @@ public final class DBFField {
channel.repeat(14, (byte) 0);
}
+ /**
+ * Read a value of this field type.
+ *
+ * @param channel to read from
+ * @return decoded field value
+ * @throws IOException if an error occured while parsing field value
+ */
public Object readValue(ChannelDataInput channel) throws IOException {
return reader.readValue(channel);
}
+ /**
+ * Write a value of this field type.
+ *
+ * @param channel to write into
+ * @param value field value
+ * @throws IOException if an error occured while writing field value
+ */
public void writeValue(ChannelDataOutput channel, Object value) throws
IOException {
writer.writeValue(channel, value);
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
index dde089f6d6..8c0bca0aa0 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
@@ -19,11 +19,13 @@ package org.apache.sis.storage.shapefile.dbf;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
+import java.time.LocalDate;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.io.stream.ChannelDataOutput;
/**
+ * Mutable DBase III file header.
*
* @author Johann Sorel (Geomatys)
*/
@@ -32,21 +34,40 @@ public final class DBFHeader {
private static final int FIELD_SIZE = 32;
private static final int FIELD_DESCRIPTOR_TERMINATOR = 0x0D;
- public int year;
- public int month;
- public int day;
+ /**
+ * Date of last update.
+ */
+ public LocalDate lastUpdate;
+ /**
+ * Number of records in the file.
+ */
public int nbRecord;
+ /**
+ * Total header size.
+ */
public int headerSize;
+ /**
+ * Size of a single record.
+ */
public int recordSize;
+ /**
+ * Array of field descriptors.
+ */
public DBFField[] fields;
+ /**
+ * Default constructor.
+ */
public DBFHeader() {
}
+ /**
+ * Duplicate constructor.
+ *
+ * @param toCopy to copy from
+ */
public DBFHeader(DBFHeader toCopy) {
- this.year = toCopy.year;
- this.month = toCopy.month;
- this.day = toCopy.day;
+ this.lastUpdate = toCopy.lastUpdate;
this.nbRecord = toCopy.nbRecord;
this.headerSize = toCopy.headerSize;
this.recordSize = toCopy.recordSize;
@@ -65,14 +86,22 @@ public final class DBFHeader {
}
}
+ /**
+ * Read header.
+ *
+ * @param channel to read from
+ * @param charset field text encoding
+ * @throws IOException if an error occured while parsing header
+ */
public void read(ChannelDataInput channel, Charset charset) throws
IOException {
channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
if (channel.readByte()!= 0x03) {
throw new IOException("Unvalid database III magic");
}
- year = channel.readUnsignedByte();
- month = channel.readUnsignedByte();
- day = channel.readUnsignedByte();
+ int year = channel.readUnsignedByte();
+ int month = channel.readUnsignedByte();
+ int day = channel.readUnsignedByte();
+ lastUpdate = LocalDate.of(year+1900, month, day);
nbRecord = channel.readInt();
headerSize = channel.readUnsignedShort();
recordSize = channel.readUnsignedShort();
@@ -87,12 +116,18 @@ public final class DBFHeader {
}
}
+ /**
+ * Write header.
+ *
+ * @param channel to write into
+ * @throws IOException if an error occured while writing header
+ */
public void write(ChannelDataOutput channel) throws IOException {
channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
channel.writeByte(0x03);
- channel.writeByte(year);
- channel.writeByte(month);
- channel.writeByte(day);
+ channel.writeByte(lastUpdate.getYear()-1900);
+ channel.writeByte(lastUpdate.getMonthValue());
+ channel.writeByte(lastUpdate.getDayOfMonth());
channel.writeInt(nbRecord);
channel.writeShort(headerSize);
channel.writeShort(recordSize);
@@ -106,9 +141,7 @@ public final class DBFHeader {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("DBFHeader{");
- sb.append("year=").append(year);
- sb.append(",month=").append(month);
- sb.append(",day=").append(day);
+ sb.append("lastUpdate=").append(lastUpdate);
sb.append(",nbRecord=").append(nbRecord);
sb.append(",headerSize=").append(headerSize);
sb.append(",recordSize=").append(recordSize);
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
index 848743b5ac..40e7c97f0e 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
@@ -22,12 +22,17 @@ import org.apache.sis.io.stream.ChannelDataInput;
/**
- * Seekable dbf file reader.
+ * Seekable DBase file reader.
*
* @author Johann Sorel (Geomatys)
*/
public final class DBFReader implements AutoCloseable {
+ /**
+ * Unique instance used to mark a record which has been deleted.
+ */
+ public static final Object[] DELETED_RECORD = new Object[0];
+
static final int TAG_PRESENT = 0x20;
static final int TAG_DELETED = 0x2a;
static final int TAG_EOF = 0x1A;
@@ -38,9 +43,12 @@ public final class DBFReader implements AutoCloseable {
private int nbRead = 0;
/**
+ * Constructor.
+ *
* @param channel to read from
* @param charset text encoding
* @param fieldsToRead fields index in the header to decode, other fields
will be skipped. must be in increment order.
+ * @throws IOException if a decoding error occurs on the header
*/
public DBFReader(ChannelDataInput channel, Charset charset, int[]
fieldsToRead) throws IOException {
this.channel = channel;
@@ -49,24 +57,36 @@ public final class DBFReader implements AutoCloseable {
this.fieldsToRead = fieldsToRead;
}
+ /**
+ * Get decoded header.
+ *
+ * @return Dbase file header
+ */
public DBFHeader getHeader() {
return header;
}
+ /**
+ * Move channel to given position.
+ *
+ * @param position new position
+ * @throws IOException if the stream cannot be moved to the given position.
+ */
public void moveToOffset(long position) throws IOException {
channel.seek(position);
}
/**
+ * Get next record.
*
- * @return record or DBFRecord.DELETED if this record has been deleted.
+ * @return record or DBFReader.DELETED_RECORD if this record has been
deleted.
* @throws IOException if a decoding error occurs
*/
- public DBFRecord next() throws IOException {
+ public Object[] next() throws IOException {
if (nbRead >= header.nbRecord) {
//reached records end
//we do not trust the EOF if we already have the expected count
- //some writes do not have it
+ //some incorrect files do not have it
return null;
}
nbRead++;
@@ -74,27 +94,26 @@ public final class DBFReader implements AutoCloseable {
final int marker = channel.readUnsignedByte();
if (marker == TAG_DELETED) {
channel.seek(channel.getStreamPosition() + header.recordSize);
- return DBFRecord.DELETED;
+ return DELETED_RECORD;
} else if (marker == TAG_EOF) {
return null;
} else if (marker != TAG_PRESENT) {
throw new IOException("Unexpected record marker " + marker);
}
- final DBFRecord record = new DBFRecord();
- record.fields = new Object[header.fields.length];
+ Object[] record;
if (fieldsToRead == null) {
//read all fields
- record.fields = new Object[header.fields.length];
+ record = new Object[header.fields.length];
for (int i = 0; i < header.fields.length; i++) {
- record.fields[i] = header.fields[i].readValue(channel);
+ record[i] = header.fields[i].readValue(channel);
}
} else {
//read only selected fields
- record.fields = new Object[fieldsToRead.length];
+ record = new Object[fieldsToRead.length];
for (int i = 0,k = 0; i < header.fields.length; i++) {
if (k < fieldsToRead.length && fieldsToRead[k] == i) {
- record.fields[k++] = header.fields[i].readValue(channel);
+ record[k++] = header.fields[i].readValue(channel);
} else {
//skip this field
channel.seek(channel.getStreamPosition() +
header.fields[i].fieldLength);
@@ -105,8 +124,11 @@ public final class DBFReader implements AutoCloseable {
return record;
}
-
-
+ /**
+ * Release reader resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
channel.channel.close();
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
index ec8acf06b3..ea7acbd53e 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
@@ -32,33 +32,54 @@ public final class DBFWriter implements AutoCloseable{
private int writtenNbRecord = 0 ;
private DBFHeader header;
+ /**
+ * Constructor.
+ *
+ * @param channel to write into
+ */
public DBFWriter(ChannelDataOutput channel) {
this.channel = channel;
}
- public void write(DBFHeader header) throws IOException {
+ /**
+ * Write DBase header.
+ *
+ * This should be the first call on the writer.
+ * Header number of records will be updated in the close method.
+ *
+ * @param header to write
+ * @throws IOException If an I/O error occurs
+ */
+ public void writeHeader(DBFHeader header) throws IOException {
this.header = new DBFHeader(header);
this.header.updateSizes(); //recompute sizes
this.header.nbRecord = 0; //force to zero, will be replaced when
closing writer.
this.header.write(channel);
}
- public void write(DBFRecord record) throws IOException {
+ /**
+ * Write a DBase record.
+ *
+ * @param fieldValues record fields to write
+ * @throws IOException If an I/O error occurs
+ */
+ public void writeRecord(Object ... fieldValues) throws IOException {
channel.writeByte(DBFReader.TAG_PRESENT);
for (int i = 0; i < header.fields.length; i++) {
- header.fields[i].writeValue(channel, record.fields[i]);
+ header.fields[i].writeValue(channel, fieldValues[i]);
}
writtenNbRecord++;
}
- public void flush() throws IOException {
- channel.writeByte(DBFReader.TAG_EOF);
- channel.flush();
- }
-
+ /**
+ * Write end of file tag, update written number of record and release
resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
- flush();
+ channel.writeByte(DBFReader.TAG_EOF);
+ channel.flush();
//update the nbRecord in the header
channel.seek(4);
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/package-info.java
similarity index 56%
rename from
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
rename to
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/package-info.java
index 1f33f949d7..3565b802da 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/package-info.java
@@ -14,21 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.storage.shapefile.dbf;
-
/**
- * A DBF record is an array of field values.
+ * DBase III format reader and writer.
+ * <p>
+ * This package is used and made for ESRI shapefile format which at it's
creation time
+ * was related to DBase III.
+ *
+ * <h2>Reading example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.dbf.Snippets"
region="read"}
*
- * @author Johann Sorel (Geomatys)
+ * <h2>Writing example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.dbf.Snippets"
region="write"}
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/.dbf">Format from Wikipedia</a>
+ * @see <a
href="http://www.dbase.com/KnowledgeBase/int/db7_file_fmt.htm">Format from
dbase.com</a>
+ * @see <a href="https://wiki.dbfmanager.com/dbf-structure">Format from
wiki.dbfmanager.com</a>
*/
-public final class DBFRecord {
-
- public static final DBFRecord DELETED = new DBFRecord();
-
- public Object[] fields;
-
- public DBFRecord() {
- }
-
-}
+package org.apache.sis.storage.shapefile.dbf;
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
index 174e38140d..e2ac355205 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
@@ -16,11 +16,25 @@
*/
/**
- * Shapefile.
+ * Shapefile format DataStore implementation.
*
- * <div class="warning">This is an experimental package,
- * not yet target for any Apache SIS release at this time.</div>
+ * <h2>Reading example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.Snippets" region="read"}
+ *
+ * <h2>Writing example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.Snippets" region="write"}
+ *
+ * For raw access to DBF and SHP, use the related packages :
+ * <ul>
+ * <li>{@link org.apache.sis.storage.shapefile.shp}</li>
+ * <li>{@link org.apache.sis.storage.shapefile.shx}</li>
+ * <li>{@link org.apache.sis.storage.shapefile.dbf}</li>
+ * <li>{@link org.apache.sis.storage.shapefile.cpg}</li>
+ * </ul>
+ * The shapefile datastore layer is very thin and the only performance overheap
+ * is the mapping from DBFRecord/ShpRecord to Feature.
*
* @author Johann Sorel (Geomatys)
+ * @see <a
href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf">ESRI
Shapefile Specification</a>
*/
package org.apache.sis.storage.shapefile;
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 c069f1bd39..98aef27229 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
@@ -24,12 +24,10 @@ 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;
import org.locationtech.jts.algorithm.RayCrossingCounter;
-import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.io.stream.ChannelDataInput;
import org.apache.sis.io.stream.ChannelDataOutput;
@@ -40,51 +38,71 @@ import org.apache.sis.io.stream.ChannelDataOutput;
* This class should be kept separate because I might be used in ESRI
geodatabase format.
*
* @author Johann Sorel (Geomatys)
+ * @param <T> encoder geometry type
*/
public abstract class ShapeGeometryEncoder<T extends Geometry> {
private static final GeometryFactory GF = new GeometryFactory();
- protected final int shapeType;
+ /**
+ * Encoder shape type.
+ */
+ protected final ShapeType shapeType;
+ /**
+ * Encoder java value class.
+ */
protected final Class<T> geometryClass;
+ /**
+ * Number of dimension in the geometry.
+ */
protected final int dimension;
+ /**
+ * Number of measures in the geometry.
+ */
protected final int measures;
+ /**
+ * Sum of dimension and measures.
+ */
protected final int nbOrdinates;
/**
+ * Get encoder for given shape type.
*
* @param shapeType shape type to encode
* @return requested encoder
*/
- public static ShapeGeometryEncoder getEncoder(int shapeType) {
+ public static ShapeGeometryEncoder getEncoder(ShapeType shapeType) {
switch(shapeType) {
//2D
- case ShapeType.VALUE_NULL: return Null.INSTANCE;
- case ShapeType.VALUE_POINT: return PointXY.INSTANCE;
- case ShapeType.VALUE_POLYLINE: return Polyline.INSTANCE;
- case ShapeType.VALUE_POLYGON: return Polygon.INSTANCE;
- case ShapeType.VALUE_MULTIPOINT: return MultiPointXY.INSTANCE;
+ case NULL: return Null.INSTANCE;
+ case POINT: return PointXY.INSTANCE;
+ case POLYLINE: return Polyline.INSTANCE;
+ case POLYGON: return Polygon.INSTANCE;
+ case MULTIPOINT: return MultiPointXY.INSTANCE;
//2D+1
- case ShapeType.VALUE_POINT_M: return PointXYM.INSTANCE;
- case ShapeType.VALUE_POLYLINE_M: return Polyline.INSTANCE_M;
- case ShapeType.VALUE_POLYGON_M: return Polygon.INSTANCE_M;
- case ShapeType.VALUE_MULTIPOINT_M: return MultiPointXYM.INSTANCE;
+ case POINT_M: return PointXYM.INSTANCE;
+ case POLYLINE_M: return Polyline.INSTANCE_M;
+ case POLYGON_M: return Polygon.INSTANCE_M;
+ case MULTIPOINT_M: return MultiPointXYM.INSTANCE;
//3D+1
- case ShapeType.VALUE_POINT_ZM: return PointXYZM.INSTANCE;
- case ShapeType.VALUE_POLYLINE_ZM: return Polyline.INSTANCE_ZM;
- case ShapeType.VALUE_POLYGON_ZM: return Polygon.INSTANCE_ZM;
- case ShapeType.VALUE_MULTIPOINT_ZM: return MultiPointXYZM.INSTANCE;
- case ShapeType.VALUE_MULTIPATCH_ZM: return MultiPatch.INSTANCE;
+ case POINT_ZM: return PointXYZM.INSTANCE;
+ case POLYLINE_ZM: return Polyline.INSTANCE_ZM;
+ case POLYGON_ZM: return Polygon.INSTANCE_ZM;
+ case MULTIPOINT_ZM: return MultiPointXYZM.INSTANCE;
+ case MULTIPATCH_ZM: return MultiPatch.INSTANCE;
default: throw new IllegalArgumentException("unknown shape type");
}
}
/**
+ * Constructor.
+ *
* @param shapeType shape type code.
+ * @param geometryClass java geometry class
* @param dimension number of dimensions in processed geometries.
* @param measures number of measures in processed geometries.
*/
- protected ShapeGeometryEncoder(int shapeType, Class<T> geometryClass, int
dimension, int measures) {
+ protected ShapeGeometryEncoder(ShapeType shapeType, Class<T>
geometryClass, int dimension, int measures) {
this.shapeType = shapeType;
this.geometryClass = geometryClass;
this.dimension = dimension;
@@ -93,13 +111,17 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
/**
- * @return shape type code.
+ * Get shape type.
+ *
+ * @return shape type.
*/
- public int getShapeType() {
+ public ShapeType getShapeType() {
return shapeType;
}
/**
+ * Get java geometry value class.
+ *
* @return geometry class handled by this encoder
*/
public Class<T> getValueClass() {
@@ -107,6 +129,8 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
/**
+ * Get number of dimensions in processed geometries.
+ *
* @return number of dimensions in processed geometries.
*/
public final int getDimension() {
@@ -114,6 +138,8 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
/**
+ * Get number of measures in processed geometries.
+ *
* @return number of measures in processed geometries.
*/
public final int getMeasures() {
@@ -128,6 +154,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
* @param record to read into
* @param filter optional filter envelope to stop geometry decoding as
soon as possible
* @return true if geometry pass the filter
+ * @throws IOException If an I/O error occurs
*/
public abstract boolean decode(ChannelDataInput channel, ShapeRecord
record, Rectangle2D.Double filter) throws IOException;
@@ -136,6 +163,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
*
* @param channel to write into
* @param shape geometry to encode
+ * @throws IOException If an I/O error occurs
*/
public abstract void encode(ChannelDataOutput channel, ShapeRecord shape)
throws IOException;
@@ -146,6 +174,12 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
*/
public abstract int getEncodedLength(Geometry geom);
+ /**
+ * Calculate geometry bounding box.
+ *
+ * @param geom to compute
+ * @return geometry bounding box
+ */
public abstract GeneralEnvelope getBoundingBox(Geometry geom);
/**
@@ -155,6 +189,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
* @param shape to write into
* @param filter optional filter envelope to stop geometry decoding as
soon as possible
* @return true if filter match or is null
+ * @throws IOException If an I/O error occurs
*/
protected boolean readBBox2D(ChannelDataInput channel, ShapeRecord shape,
Rectangle2D.Double filter) throws IOException {
final double minX = channel.readDouble();
@@ -178,6 +213,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
*
* @param channel to write into
* @param shape to read from
+ * @throws IOException If an I/O error occurs
*/
protected void writeBBox2D(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
final Envelope env2d = shape.geometry.getEnvelopeInternal();
@@ -188,11 +224,14 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
/**
+ * Read encoded lines.
+ *
* @param channel to read from
* @param shape to write into
* @param filter optional filter envelope to stop geometry decoding as
soon as possible
* @param asRing true to produce LinearRing instead of LineString
* @return null if filter do no match
+ * @throws IOException If an I/O error occurs
*/
protected LineString[] readLines(ChannelDataInput channel, ShapeRecord
shape, Rectangle2D.Double filter, boolean asRing) throws IOException {
if (!readBBox2D(channel, shape, filter)) return null;
@@ -234,6 +273,15 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
return lines;
}
+ /**
+ * Read lines ordinates.
+ *
+ * @param channel to read from
+ * @param shape to update
+ * @param lines to update
+ * @param ordinateIndex ordinate index to read
+ * @throws IOException If an I/O error occurs
+ */
protected void readLineOrdinates(ChannelDataInput channel, ShapeRecord
shape, LineString[] lines, int ordinateIndex) throws IOException {
final int nbDim = getDimension() + getMeasures();
shape.bbox.setRange(ordinateIndex, channel.readDouble(),
channel.readDouble());
@@ -246,6 +294,13 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
}
+ /**
+ * Write given lines.
+ *
+ * @param channel to write into
+ * @param shape to write
+ * @throws IOException if an I/O exception occurs
+ */
protected void writeLines(ChannelDataOutput channel, ShapeRecord shape)
throws IOException {
writeBBox2D(channel, shape);
final List<LineString> lines = extractRings(shape.geometry);
@@ -277,6 +332,16 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
if (nbOrdinates == 4) writeLineOrdinates(channel, shape, lines, 3,
nbPts);
}
+ /**
+ * Write given lines ordinates.
+ *
+ * @param channel to write into
+ * @param shape to write
+ * @param lines to write
+ * @param ordinateIndex line coordinate ordinate to write
+ * @param nbPts number of points
+ * @throws IOException if an I/O exception occurs
+ */
protected void writeLineOrdinates(ChannelDataOutput channel, ShapeRecord
shape,List<LineString> lines, int ordinateIndex, int nbPts) throws IOException {
final double[] values = new double[nbPts];
@@ -297,6 +362,12 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
channel.writeDoubles(values);
}
+ /**
+ * Extract all linear elements of given geometry.
+ *
+ * @param geom to extract lines from.
+ * @return list of lines
+ */
protected List<LineString> extractRings(Geometry geom) {
final List<LineString> lst = new ArrayList();
extractRings(geom, lst);
@@ -384,6 +455,12 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
}
}
+ /**
+ * Compute geometry bounding box.
+ *
+ * @param geom to compute
+ * @return geometry bounding box
+ */
protected GeneralEnvelope getLinesBoundingBox(Geometry geom) {
final List<LineString> lines = extractRings(geom);
@@ -434,7 +511,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final Null INSTANCE = new Null();
private Null() {
- super(ShapeType.VALUE_NULL, Geometry.class, 2, 0);
+ super(ShapeType.NULL, Geometry.class, 2, 0);
}
@Override
@@ -462,7 +539,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final PointXY INSTANCE = new PointXY();
private PointXY() {
- super(ShapeType.VALUE_POINT, Point.class, 2,0);
+ super(ShapeType.POINT, Point.class, 2,0);
}
@Override
@@ -504,7 +581,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final PointXYM INSTANCE = new PointXYM();
private PointXYM() {
- super(ShapeType.VALUE_POINT_M, Point.class, 2, 1);
+ super(ShapeType.POINT_M, Point.class, 2, 1);
}
@Override
@@ -552,7 +629,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final PointXYZM INSTANCE = new PointXYZM();
private PointXYZM() {
- super(ShapeType.VALUE_POINT_ZM, Point.class, 3, 1);
+ super(ShapeType.POINT_ZM, Point.class, 3, 1);
}
@Override
@@ -598,7 +675,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final MultiPointXY INSTANCE = new MultiPointXY();
private MultiPointXY() {
- super(ShapeType.VALUE_MULTIPOINT, MultiPoint.class, 2, 0);
+ super(ShapeType.MULTIPOINT, MultiPoint.class, 2, 0);
}
@Override
@@ -650,7 +727,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final MultiPointXYM INSTANCE = new MultiPointXYM();
private MultiPointXYM() {
- super(ShapeType.VALUE_MULTIPOINT_M, MultiPoint.class, 2, 1);
+ super(ShapeType.MULTIPOINT_M, MultiPoint.class, 2, 1);
}
@Override
@@ -722,7 +799,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final MultiPointXYZM INSTANCE = new MultiPointXYZM();
private MultiPointXYZM() {
- super(ShapeType.VALUE_MULTIPOINT_ZM, MultiPoint.class, 3, 1);
+ super(ShapeType.MULTIPOINT_ZM, MultiPoint.class, 3, 1);
}
@Override
@@ -810,11 +887,11 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
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 static final Polyline INSTANCE = new
Polyline(ShapeType.POLYLINE, 2, 0);
+ private static final Polyline INSTANCE_M = new
Polyline(ShapeType.POLYLINE_M, 3, 0);
+ private static final Polyline INSTANCE_ZM = new
Polyline(ShapeType.POLYLINE_ZM, 3, 1);
- private Polyline(int shapeType, int dimension, int measures) {
+ private Polyline(ShapeType shapeType, int dimension, int measures) {
super(shapeType, MultiLineString.class, dimension, measures);
}
@@ -850,11 +927,11 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
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 static final Polygon INSTANCE = new Polygon(ShapeType.POLYGON,
2, 0);
+ private static final Polygon INSTANCE_M = new
Polygon(ShapeType.POLYGON_M, 3, 0);
+ private static final Polygon INSTANCE_ZM = new
Polygon(ShapeType.POLYGON_ZM, 3, 1);
- private Polygon(int shapeType, int dimension, int measures) {
+ private Polygon(ShapeType shapeType, int dimension, int measures) {
super(shapeType, MultiPolygon.class, dimension, measures);
}
@@ -897,7 +974,7 @@ public abstract class ShapeGeometryEncoder<T extends
Geometry> {
private static final MultiPatch INSTANCE = new MultiPatch();
private MultiPatch() {
- super(ShapeType.VALUE_MULTIPATCH_ZM, MultiPolygon.class, 3, 1);
+ super(ShapeType.MULTIPATCH_ZM, MultiPolygon.class, 3, 1);
}
@Override
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 dd89bdd7c8..1230638311 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
@@ -48,17 +48,24 @@ public final class ShapeHeader {
/**
* Shape type.
*/
- public int shapeType;
+ public ShapeType shapeType;
/**
* Shapefile bounding box without CRS.
* Ordinates are in X,Y,Z,M order.
*/
public ImmutableEnvelope bbox;
+ /**
+ * Default constructor
+ */
public ShapeHeader() {
-
}
+ /**
+ * Copy constructor.
+ *
+ * @param toCopy header to copy
+ */
public ShapeHeader(ShapeHeader toCopy) {
this.fileLength = toCopy.fileLength;
this.shapeType = toCopy.shapeType;
@@ -85,7 +92,7 @@ public final class ShapeHeader {
if (version != 1000) {
throw new IOException("Incorrect file version, expected 1000 but
was " + version);
}
- shapeType = channel.readInt();
+ shapeType = ShapeType.get(channel.readInt());
final double[] bb = channel.readDoubles(8);
GeneralEnvelope bbox = new GeneralEnvelope(4);
bbox.setRange(0, bb[0], bb[2]);
@@ -107,7 +114,7 @@ public final class ShapeHeader {
channel.writeInt(fileLength/2);
channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
channel.writeInt(1000);
- channel.writeInt(shapeType);
+ channel.writeInt(shapeType.getCode());
channel.writeDouble(bbox.getMinimum(0));
channel.writeDouble(bbox.getMinimum(1));
channel.writeDouble(bbox.getMaximum(0));
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 b1e097dfad..2564bd4e1f 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
@@ -36,6 +36,13 @@ public final class ShapeReader implements AutoCloseable{
private final ShapeGeometryEncoder geomParser;
private final Rectangle2D.Double filter;
+ /**
+ * Construct reader from given channel and an optional rectangle filter.
+ *
+ * @param channel to read from
+ * @param filter optional filtering rectangle
+ * @throws IOException if a decoding error occurs
+ */
public ShapeReader(ChannelDataInput channel, Rectangle2D.Double filter)
throws IOException {
Objects.nonNull(channel);
this.channel = channel;
@@ -45,14 +52,31 @@ public final class ShapeReader implements AutoCloseable{
geomParser = ShapeGeometryEncoder.getEncoder(header.shapeType);
}
+ /**
+ * Get header.
+ *
+ * @return shapefile header
+ */
public ShapeHeader getHeader() {
return header;
}
+ /**
+ * Move channel to given position.
+ *
+ * @param position new position
+ * @throws IOException if the stream cannot be moved to the given position.
+ */
public void moveToOffset(long position) throws IOException {
channel.seek(position);
}
+ /**
+ * Get next record.
+ *
+ * @return record or null if there is no more record
+ * @throws IOException if a decoding error occurs
+ */
public ShapeRecord next() throws IOException {
final ShapeRecord record = new ShapeRecord();
try {
@@ -72,6 +96,11 @@ public final class ShapeReader implements AutoCloseable{
}
}
+ /**
+ * Release reader resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
channel.channel.close();
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 77a0776864..f288e045f4 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,18 +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;
-
/**
+ * A record in a shape file.
+ * Contains a unique record number and it's associated geometry.
+ *
* @author Johann Sorel (Geomatys)
*/
public final class ShapeRecord {
@@ -40,19 +37,36 @@ public final class ShapeRecord {
*/
public int recordNumber;
/**
- * Encoded geometry.
+ * Record geometry
*/
- public byte[] content;
-
public Geometry geometry;
-
+ /**
+ * Geometry bounding box
+ */
public GeneralEnvelope bbox;
+ /**
+ * Default constructor
+ */
+ public ShapeRecord() {
+ }
+
+ /**
+ * Constructor with initialization.
+ *
+ * @param recordNumber initial record number
+ * @param geometry initial geometry
+ */
+ public ShapeRecord(int recordNumber, Geometry geometry) {
+ this.recordNumber = recordNumber;
+ this.geometry = geometry;
+ }
+
/**
* Read this shape record.
*
* @param channel input channel, not null
- * @param io geometry decoder, if null gemetry content will be stored in
content array, otherwise geometry will be parsed
+ * @param io geometry decoder
* @param filter optional filter envelope to stop geometry decoding as
soon as possible
* @return true if geometry pass the filter or if there is no filter
* @throws IOException if an error occurred while reading.
@@ -66,31 +80,26 @@ public final class ShapeRecord {
final long position = channel.getStreamPosition();
channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
final int shapeType = channel.readInt();
- if (io == null) {
- content = channel.readBytes(byteSize);
- return true;
- } else {
- final boolean match = io.decode(channel,this, filter);
- if (!match) {
- //move to record end
- channel.seek(position + byteSize);
- }
- return match;
+ final boolean match = io.decode(channel,this, filter);
+ if (!match) {
+ //move to record end
+ channel.seek(position + byteSize);
}
+ return match;
}
/**
* Write this shape record.
* @param channel output channel to write into, not null
* @param io geometry encoder
- * @throws IOException
+ * @throws IOException if an error occurred while writing.
*/
public void write(ChannelDataOutput channel, ShapeGeometryEncoder io)
throws IOException {
channel.buffer.order(ByteOrder.BIG_ENDIAN);
channel.writeInt(recordNumber);
channel.writeInt((io.getEncodedLength(geometry) + 4) / 2); // +4 for
shape type and /2 because size is in 16bit words
channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
- channel.writeInt(io.getShapeType());
+ channel.writeInt(io.getShapeType().getCode());
io.encode(channel, this);
}
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeType.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeType.java
index 08b0d452b7..2eb9a32b56 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeType.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/ShapeType.java
@@ -35,56 +35,93 @@ import java.util.Map;
*/
public enum ShapeType {
+ /**
+ * Null geometry type
+ */
NULL (0),
+ /**
+ * Point geometry type
+ */
POINT(1),
+ /**
+ * Polyline geometry type
+ */
POLYLINE(3),
+ /**
+ * Polygon geometry type
+ */
POLYGON(5),
+ /**
+ * MultiPoint geometry type
+ */
MULTIPOINT(8),
+ /**
+ * Point with M geometry type
+ */
POINT_M(11),
+ /**
+ * Polyline with M geometry type
+ */
POLYLINE_M(13),
+ /**
+ * Polygon with M geometry type
+ */
POLYGON_M(15),
+ /**
+ * MultiPoint with M geometry type
+ */
MULTIPOINT_M(18),
+ /**
+ * Point with Z and M geometry type
+ */
POINT_ZM(21),
+ /**
+ * Polyline with Z and M geometry type
+ */
POLYLINE_ZM(23),
+ /**
+ * Polygon with Z and M geometry type
+ */
POLYGON_ZM(25),
+ /**
+ * MultiPoint with Z and M geometry type
+ */
MULTIPOINT_ZM(28),
+ /**
+ * MultiPatch with Z and M geometry type
+ */
MULTIPATCH_ZM(31);
- public static final int VALUE_NULL = 0;
- public static final int VALUE_POINT = 1;
- public static final int VALUE_POLYLINE = 3;
- public static final int VALUE_POLYGON = 5;
- public static final int VALUE_MULTIPOINT = 8;
- public static final int VALUE_POINT_M = 11;
- public static final int VALUE_POLYLINE_M = 13;
- public static final int VALUE_POLYGON_M = 15;
- public static final int VALUE_MULTIPOINT_M = 18;
- public static final int VALUE_POINT_ZM = 21;
- public static final int VALUE_POLYLINE_ZM = 23;
- public static final int VALUE_POLYGON_ZM = 25;
- public static final int VALUE_MULTIPOINT_ZM = 28;
- public static final int VALUE_MULTIPATCH_ZM = 31;
-
- // used for initializing the enumeration
- public final int value;
+ private final int code;
private ShapeType (int value ) {
- this.value = value;
+ this.code = value;
}
- public int getValue() {
- return value;
+ /**
+ * Get geometry type code.
+ *
+ * @return geometry type code
+ */
+ public int getCode() {
+ return code;
}
private static final Map<Integer, ShapeType> lookup = new HashMap<Integer,
ShapeType>();
static {
for (ShapeType ste : EnumSet.allOf(ShapeType.class)) {
- lookup.put(ste.getValue(), ste);
+ lookup.put(ste.getCode(), ste);
}
}
- public static ShapeType get(int value) {
- return lookup.get(value);
+ /**
+ * Get geometry type for given code.
+ *
+ * @param code geometry code
+ * @return ShapeType for given code
+ */
+ public static ShapeType get(int code) {
+ return lookup.get(code);
}
}
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 cabd09d663..ccce608141 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
@@ -20,6 +20,7 @@ import java.io.IOException;
import org.apache.sis.io.stream.ChannelDataOutput;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.ImmutableEnvelope;
+import org.locationtech.jts.geom.Geometry;
/**
@@ -35,15 +36,26 @@ public final class ShapeWriter implements AutoCloseable{
private GeneralEnvelope bbox;
private ShapeGeometryEncoder io;
- public ShapeWriter(ChannelDataOutput channel) throws IOException {
+ /**
+ * Construct writer above given channel.
+ *
+ * @param channel to write into
+ */
+ public ShapeWriter(ChannelDataOutput channel) {
this.channel = channel;
}
+ /**
+ * Get header, not null after writeHeader has been call.
+ * @return shapefile header
+ */
public ShapeHeader getHeader() {
return header;
}
/**
+ * Get current position in the stream.
+ *
* @return current position in the stream
*/
public long getSteamPosition() {
@@ -53,8 +65,10 @@ public final class ShapeWriter implements AutoCloseable{
/**
* Header will be copied and modified.
* Use getHeader to obtain the new header.
+ * @param header to write, not null
+ * @throws IOException If an I/O error occurs
*/
- public void write(ShapeHeader header) throws IOException {
+ public void writeHeader(ShapeHeader header) throws IOException {
this.header = new ShapeHeader(header);
this.header.fileLength = 0;
this.header.bbox = new ImmutableEnvelope(new GeneralEnvelope(4));
@@ -62,7 +76,24 @@ public final class ShapeWriter implements AutoCloseable{
io = ShapeGeometryEncoder.getEncoder(header.shapeType);
}
- public void write(ShapeRecord record) throws IOException {
+ /**
+ * Write a new record.
+ *
+ * @param recordNumber record number
+ * @param geometry record geometry
+ * @throws IOException If an I/O error occurs
+ */
+ public void writeRecord(int recordNumber, Geometry geometry) throws
IOException {
+ writeRecord(new ShapeRecord(recordNumber, geometry));
+ }
+
+ /**
+ * Write a new record.
+ *
+ * @param record new record
+ * @throws IOException If an I/O error occurs
+ */
+ public void writeRecord(ShapeRecord record) throws IOException {
record.write(channel, io);
final GeneralEnvelope geomBox = io.getBoundingBox(record.geometry);
if (bbox == null) {
@@ -73,13 +104,14 @@ public final class ShapeWriter implements AutoCloseable{
}
}
- public void flush() throws IOException {
- channel.flush();
- }
-
+ /**
+ * Release writer resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
- flush();
+ channel.flush();
//update header and rewrite it
//update the file length
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/package-info.java
similarity index 66%
copy from
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
copy to
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/package-info.java
index 174e38140d..e5c4125d38 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shp/package-info.java
@@ -16,11 +16,14 @@
*/
/**
- * Shapefile.
+ * Shapefile format reader and writer.
*
- * <div class="warning">This is an experimental package,
- * not yet target for any Apache SIS release at this time.</div>
+ * <h2>Reading example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.shp.Snippets"
region="read"}
*
- * @author Johann Sorel (Geomatys)
+ * <h2>Writing example</h2>
+ *{@snippet class="org.apache.sis.storage.shapefile.shp.Snippets"
region="write"}
+ *
+ * @see <a
href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf">ESRI
Shapefile Specification</a>
*/
-package org.apache.sis.storage.shapefile;
+package org.apache.sis.storage.shapefile.shp;
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
index ad0d49d525..c0cdcd40c8 100644
---
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
@@ -32,22 +32,42 @@ public final class IndexReader implements AutoCloseable{
private final ChannelDataInput channel;
private final ShapeHeader header;
+ /**
+ * Constructor.
+ *
+ * @param channel to read from
+ * @throws IOException if a decoding error occurs on the header
+ */
public IndexReader(ChannelDataInput channel) throws IOException {
this.channel = channel;
header = new ShapeHeader();
header.read(channel);
}
+ /**
+ * Get shapefile header.
+ *
+ * @return shapefile header
+ */
public ShapeHeader getHeader() {
return header;
}
+ /**
+ * Move channel to given position.
+ *
+ * @param position new position
+ * @throws IOException if the stream cannot be moved to the given position.
+ */
public void moveToOffset(long position) throws IOException {
channel.seek(position);
}
/**
- * @return offset and length of the record in the shp file
+ * Get next record.
+ *
+ * @return next offset and length of the record in the shp file, null if
no more records
+ * @throws IOException if a decoding error occurs
*/
public int[] next() throws IOException {
try {
@@ -58,6 +78,11 @@ public final class IndexReader implements AutoCloseable{
}
}
+ /**
+ * Release reader resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
channel.channel.close();
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexWriter.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexWriter.java
index b8666de7d0..2a242521aa 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexWriter.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/IndexWriter.java
@@ -32,35 +32,56 @@ public final class IndexWriter implements AutoCloseable{
private ShapeHeader header;
- public IndexWriter(ChannelDataOutput channel) throws IOException {
+ /**
+ * Constructor.
+ *
+ * @param channel to write into
+ */
+ public IndexWriter(ChannelDataOutput channel) {
this.channel = channel;
}
+ /**
+ * Get shapefile header, null before the call to writeHeader.
+ *
+ * @return shapefile header
+ */
public ShapeHeader getHeader() {
return header;
}
/**
* Header will be copied and modified.
* Use getHeader to obtain the new header.
+ *
+ * @param header to write
+ * @throws IOException If an I/O error occurs
*/
- public void write(ShapeHeader header) throws IOException {
+ public void writeHeader(ShapeHeader header) throws IOException {
this.header = new ShapeHeader(header);
this.header.fileLength = 0;
header.write(channel);
}
- public void write(int offset, int length) throws IOException {
+ /**
+ * Write a new record.
+ *
+ * @param offset record offset
+ * @param length record length
+ * @throws IOException If an I/O error occurs
+ */
+ public void writeRecord(int offset, int length) throws IOException {
channel.writeInt(offset);
channel.writeInt(length);
}
- public void flush() throws IOException {
- channel.flush();
- }
-
+ /**
+ * Release writer resources.
+ *
+ * @throws IOException If an I/O error occurs
+ */
@Override
public void close() throws IOException {
- flush();
+ channel.flush();
//update header and rewrite it
//update the file length
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/package-info.java
similarity index 78%
copy from
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
copy to
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/package-info.java
index 174e38140d..e8a06a540e 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/package-info.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/shx/package-info.java
@@ -16,11 +16,6 @@
*/
/**
- * Shapefile.
- *
- * <div class="warning">This is an experimental package,
- * not yet target for any Apache SIS release at this time.</div>
- *
- * @author Johann Sorel (Geomatys)
+ * Shapefile Shx index reader and writer.
*/
-package org.apache.sis.storage.shapefile;
+package org.apache.sis.storage.shapefile.shx;
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
new file mode 100644
index 0000000000..99f9057133
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/Snippets.java
@@ -0,0 +1,106 @@
+/*
+ * 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.file.Paths;
+import java.util.List;
+import java.util.stream.Stream;
+import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.FeatureQuery;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.filter.BinarySpatialOperator;
+import org.opengis.filter.FilterFactory;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Snippets {
+
+ public void read() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="read"
+ //open datastore
+ try (ShapefileStore store = new
ShapefileStore(Paths.get("/path/to/file.shp"))) {
+
+ //print feature type
+ System.out.println(store.getType());
+
+ //print all features
+ try (Stream<Feature> features = store.features(false)) {
+ features.forEach(System.out::println);
+ }
+
+ //print only features in envelope and only selected attributes
+ GeneralEnvelope bbox = new
GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
+ bbox.setRange(0, -10, 30);
+ bbox.setRange(1, 45, 55);
+ FilterFactory<Feature, Object, Object> ff =
DefaultFilterFactory.forFeatures();
+ BinarySpatialOperator<Feature> bboxFilter =
ff.bbox(ff.property("geometry"), bbox);
+
+ FeatureQuery query = new FeatureQuery();
+ query.setProjection("att1", "att4", "att5");
+ query.setSelection(bboxFilter);
+
+ //print selected features
+ try (Stream<Feature> features =
store.subset(query).features(false)) {
+ features.forEach(System.out::println);
+ }
+
+ }
+ // @end
+
+ }
+
+ public void write() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="write"
+ //open a channel
+ try (ShapefileStore store = new
ShapefileStore(Paths.get("/path/to/file.shp"))) {
+
+ //define the feature type
+ FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+ ftb.setName("test");
+ ftb.addAttribute(Integer.class).setName("id");
+ ftb.addAttribute(String.class).setName("text");
+
ftb.addAttribute(Point.class).setName("geometry").setCRS(CommonCRS.WGS84.geographic());
+ FeatureType type = ftb.build();
+
+ store.updateType(type);
+ type = store.getType();
+
+ //create features
+ GeometryFactory gf = new GeometryFactory();
+ Feature feature = type.newInstance();
+ feature.setPropertyValue("geometry", gf.createPoint(new
Coordinate(10,20)));
+ feature.setPropertyValue("id", 1);
+ feature.setPropertyValue("text", "some text 1");
+
+ //add feature in the store
+ store.add(List.of(feature).iterator());
+ }
+ // @end
+ }
+
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
index e94274e828..bf86fae187 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
@@ -66,9 +66,9 @@ public class DBFIOTest {
try (DBFReader reader = new DBFReader(cdi, StandardCharsets.UTF_8,
null)) {
final DBFHeader header = reader.getHeader();
- assertEquals(123, header.year);
- assertEquals(10, header.month);
- assertEquals(27, header.day);
+ assertEquals(123, header.lastUpdate.getYear()-1900);
+ assertEquals(10, header.lastUpdate.getMonthValue());
+ assertEquals(27, header.lastUpdate.getDayOfMonth());
assertEquals(2, header.nbRecord);
assertEquals(193, header.headerSize);
assertEquals(120, header.recordSize);
@@ -100,18 +100,18 @@ public class DBFIOTest {
assertEquals(0, header.fields[4].fieldDecimals);
- final DBFRecord record1 = reader.next();
- assertEquals(1L, record1.fields[0]);
- assertEquals("text1", record1.fields[1]);
- assertEquals(10L, record1.fields[2]);
- assertEquals(20.0, record1.fields[3]);
- assertEquals(LocalDate.of(2023, 10, 27), record1.fields[4]);
+ final Object[] record1 = reader.next();
+ assertEquals(1L, record1[0]);
+ assertEquals("text1", record1[1]);
+ assertEquals(10L, record1[2]);
+ assertEquals(20.0, record1[3]);
+ assertEquals(LocalDate.of(2023, 10, 27), record1[4]);
- final DBFRecord record2 = reader.next();
- assertEquals(2L, record2.fields[0]);
- assertEquals(40L, record2.fields[2]);
- assertEquals(60.0, record2.fields[3]);
- assertEquals(LocalDate.of(2023, 10, 28), record2.fields[4]);
+ final Object[] record2 = reader.next();
+ assertEquals(2L, record2[0]);
+ assertEquals(40L, record2[2]);
+ assertEquals(60.0, record2[3]);
+ assertEquals(LocalDate.of(2023, 10, 28), record2[4]);
//no more records
assertNull(reader.next());
@@ -138,10 +138,10 @@ public class DBFIOTest {
try (DBFReader reader = new DBFReader(cdi,
StandardCharsets.US_ASCII, null);
DBFWriter writer = new DBFWriter(cdo)) {
- writer.write(reader.getHeader());
+ writer.writeHeader(reader.getHeader());
- for (DBFRecord record = reader.next(); record != null; record
= reader.next()) {
- writer.write(record);
+ for (Object[] record = reader.next(); record != null; record =
reader.next()) {
+ writer.writeRecord(record);
}
}
@@ -166,13 +166,13 @@ public class DBFIOTest {
try (DBFReader reader = new DBFReader(cdi, StandardCharsets.UTF_8, new
int[]{1,3})) {
final DBFHeader header = reader.getHeader();
- final DBFRecord record1 = reader.next();
- assertEquals("text1", record1.fields[0]);
- assertEquals(20.0, record1.fields[1]);
+ final Object[] record1 = reader.next();
+ assertEquals("text1", record1[0]);
+ assertEquals(20.0, record1[1]);
- final DBFRecord record2 = reader.next();
- assertEquals("text2", record2.fields[0]);
- assertEquals(60.0, record2.fields[1]);
+ final Object[] record2 = reader.next();
+ assertEquals("text2", record2[0]);
+ assertEquals(60.0, record2[1]);
//no more records
assertNull(reader.next());
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/Snippets.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/Snippets.java
new file mode 100644
index 0000000000..0813919903
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/Snippets.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dbf;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDate;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.io.stream.ChannelDataOutput;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Snippets {
+
+ public void read() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="read"
+ //open a channel
+ StorageConnector cnx = new
StorageConnector(Paths.get("/path/to/file.dbf"));
+ ChannelDataInput channel = cnx.getStorageAs(ChannelDataInput.class);
+ try (DBFReader reader = new DBFReader(channel, StandardCharsets.UTF_8,
null)) {
+
+ //print the DBase fields
+ DBFHeader header = reader.getHeader();
+ for (DBFField field : header.fields) {
+ System.out.println(field);
+ }
+
+ //iterate over records
+ for (Object[] record = reader.next(); record != null; record =
reader.next()){
+
+ if (record == DBFReader.DELETED_RECORD) {
+ //a deleted record, those should be ignored
+ continue;
+ }
+
+ //print record values
+ for (int i = 0; i < header.fields.length; i++) {
+ System.out.println(header.fields[i].fieldName + " : " +
record[i]);
+ }
+ }
+ }
+ // @end
+ }
+
+ public void write() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="write"
+ //open a channel
+ StorageConnector cnx = new
StorageConnector(Paths.get("/path/to/file.dbf"));
+ cnx.setOption(OptionKey.OPEN_OPTIONS, new
OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING});
+ ChannelDataOutput channel = cnx.getStorageAs(ChannelDataOutput.class);
+
+ //define the header
+ Charset charset = StandardCharsets.UTF_8;
+ DBFHeader header = new DBFHeader();
+ header.lastUpdate = LocalDate.now();
+ header.fields = new DBFField[] {
+ new DBFField("id", DBFField.TYPE_NUMBER, 0, 8, 0, charset),
+ new DBFField("desc", DBFField.TYPE_CHAR, 0, 255, 0, charset),
+ new DBFField("value", DBFField.TYPE_NUMBER, 0, 11, 6, charset)
+ };
+
+ //write records
+ try (DBFWriter writer = new DBFWriter(channel)) {
+ writer.writeHeader(header);
+ writer.writeRecord(1, "A short description", 3.14);
+ writer.writeRecord(2, "Another short description", 123.456);
+ // ... more records
+ }
+ // @end
+ }
+
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/ShapeIOTest.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/ShapeIOTest.java
index cb36bd7458..0cd236e313 100644
---
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/ShapeIOTest.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/ShapeIOTest.java
@@ -79,10 +79,10 @@ public class ShapeIOTest {
try (ShapeReader reader = new ShapeReader(cdi, null);
ShapeWriter writer = new ShapeWriter(cdo)) {
- writer.write(reader.getHeader());
+ writer.writeHeader(reader.getHeader());
for (ShapeRecord record = reader.next(); record != null;
record = reader.next()) {
- writer.write(record);
+ writer.writeRecord(record);
}
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/Snippets.java
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/Snippets.java
new file mode 100644
index 0000000000..9ba5ee2e12
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/shp/Snippets.java
@@ -0,0 +1,81 @@
+/*
+ * 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.shp;
+
+import java.io.IOException;
+import java.nio.file.OpenOption;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.io.stream.ChannelDataOutput;
+import org.apache.sis.setup.OptionKey;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Snippets {
+
+ public void read() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="read"
+ //open a channel
+ StorageConnector cnx = new
StorageConnector(Paths.get("/path/to/file.shp"));
+ ChannelDataInput channel = cnx.getStorageAs(ChannelDataInput.class);
+ try (ShapeReader reader = new ShapeReader(channel, null)) {
+
+ //print the DBase fields
+ ShapeHeader header = reader.getHeader();
+ System.out.println(header.shapeType);
+
+ //iterate over records
+ for (ShapeRecord record = reader.next(); record != null; record =
reader.next()){
+ System.out.println(record.recordNumber);
+ System.out.println(record.bbox);
+ System.out.println(record.geometry.toText());
+ }
+ }
+ // @end
+ }
+
+ public void write() throws IllegalArgumentException, DataStoreException,
IOException{
+ // @start region="write"
+ //open a channel
+ StorageConnector cnx = new
StorageConnector(Paths.get("/path/to/file.shp"));
+ cnx.setOption(OptionKey.OPEN_OPTIONS, new
OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING});
+ ChannelDataOutput channel = cnx.getStorageAs(ChannelDataOutput.class);
+
+ //define the header
+ ShapeHeader header = new ShapeHeader();
+ header.shapeType = ShapeType.POINT;
+
+ //write records
+ GeometryFactory gf = new GeometryFactory();
+ try (ShapeWriter writer = new ShapeWriter(channel)) {
+ writer.writeHeader(header);
+ int recordNumber = 1;
+ writer.writeRecord(recordNumber++, gf.createPoint(new
Coordinate(10,20)));
+ writer.writeRecord(recordNumber++, gf.createPoint(new
Coordinate(-7, 45)));
+ // ... more records
+ }
+ // @end
+ }
+
+}