package com.revolsys.record.io.format.shp;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.NoSuchElementException;
import com.revolsys.collection.iterator.AbstractIterator;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.cs.esri.EsriCoordinateSystems;
import com.revolsys.geometry.model.ClockDirection;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.io.FileUtil;
import com.revolsys.io.IoConstants;
import com.revolsys.io.PathName;
import com.revolsys.io.endian.EndianInput;
import com.revolsys.io.endian.EndianInputStream;
import com.revolsys.io.endian.EndianMappedByteBuffer;
import com.revolsys.io.endian.LittleEndianRandomAccessFile;
import com.revolsys.logging.Logs;
import com.revolsys.record.Record;
import com.revolsys.record.RecordFactory;
import com.revolsys.record.Records;
import com.revolsys.record.io.RecordReader;
import com.revolsys.record.io.format.xbase.XbaseRecordReader;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordDefinitionImpl;
import com.revolsys.spring.resource.Resource;
import com.revolsys.util.Property;
public class ShapefileRecordReader extends AbstractIterator<Record> implements RecordReader {
private boolean closeFile = true;
private GeometryFactory geometryFactory;
private EndianInput in;
private EndianMappedByteBuffer indexIn;
private final String name;
private int position;
private RecordDefinition recordDefinition;
private RecordFactory recordFactory;
private Resource resource;
private RecordDefinition returnRecordDefinition;
private int shapeType;
private PathName typeName;
private XbaseRecordReader xbaseRecordReader;
public ShapefileRecordReader(final Resource resource, final RecordFactory factory)
throws IOException {
this.recordFactory = factory;
final String baseName = resource.getBaseName();
this.name = baseName;
this.typeName = PathName.newPathName("/" + this.name);
this.resource = resource;
}
@Override
protected void closeDo() {
if (this.closeFile) {
forceClose();
}
}
public void forceClose() {
FileUtil.closeSilent(this.in, this.indexIn);
if (this.xbaseRecordReader != null) {
this.xbaseRecordReader.forceClose();
}
this.recordFactory = null;
this.geometryFactory = null;
this.in = null;
this.indexIn = null;
this.recordDefinition = null;
this.resource = null;
this.xbaseRecordReader = null;
}
@Override
protected Record getNext() {
Record record;
try {
if (this.xbaseRecordReader != null) {
if (this.xbaseRecordReader.hasNext()) {
record = this.xbaseRecordReader.next();
for (int i = 0; i < this.xbaseRecordReader.getDeletedCount(); i++) {
this.position++;
readGeometry();
}
} else {
throw new NoSuchElementException();
}
} else {
record = this.recordFactory.newRecord(this.recordDefinition);
}
try {
final Geometry geometry = readGeometry();
record.setGeometryValue(geometry);
} catch (final IllegalArgumentException e) {
Logs.error(this, "Error reading geometry from:" + this.resource + "\n" + record, e);
}
} catch (final EOFException e) {
throw new NoSuchElementException();
} catch (final IOException e) {
throw new RuntimeException("Error reading geometry " + this.resource, e);
}
if (this.returnRecordDefinition == null) {
return record;
} else {
final Record copy = this.recordFactory.newRecord(this.returnRecordDefinition);
copy.setValues(record);
return copy;
}
}
@Override
public ClockDirection getPolygonRingDirection() {
return ClockDirection.CLOCKWISE;
}
public int getPosition() {
return this.position;
}
@Override
public RecordDefinition getRecordDefinition() {
open();
return this.recordDefinition;
}
@Override
public RecordFactory getRecordFactory() {
return this.recordFactory;
}
public PathName getTypeName() {
return this.typeName;
}
@Override
protected synchronized void initDo() {
if (this.in == null) {
try {
try {
if (this.resource.isFile()) {
final File file = this.resource.getFile();
this.in = new LittleEndianRandomAccessFile(file, "r");
} else {
this.in = new EndianInputStream(this.resource.getInputStream());
}
} catch (final IllegalArgumentException | UnsupportedOperationException e) {
this.in = new EndianInputStream(this.resource.getInputStream());
}
final Resource xbaseResource = this.resource.newResourceChangeExtension("dbf");
if (xbaseResource != null && xbaseResource.exists()) {
this.xbaseRecordReader = new XbaseRecordReader(xbaseResource, this.recordFactory,
() -> updateRecordDefinition());
this.xbaseRecordReader.setTypeName(this.typeName);
this.xbaseRecordReader.setCloseFile(this.closeFile);
}
loadHeader();
int axisCount;
switch (this.shapeType) {
case ShapefileConstants.POINT_SHAPE: // 1
case ShapefileConstants.POLYLINE_SHAPE: // 3
case ShapefileConstants.POLYGON_SHAPE: // 5
case ShapefileConstants.MULTI_POINT_SHAPE: // 8
axisCount = 2;
break;
case ShapefileConstants.POINT_Z_SHAPE: // 9
case ShapefileConstants.POLYLINE_Z_SHAPE: // 10
case ShapefileConstants.POLYGON_Z_SHAPE: // 19
case ShapefileConstants.MULTI_POINT_Z_SHAPE: // 20
axisCount = 3;
break;
case ShapefileConstants.POINT_ZM_SHAPE: // 11
case ShapefileConstants.POLYLINE_ZM_SHAPE: // 13
case ShapefileConstants.POLYGON_ZM_SHAPE: // 15
case ShapefileConstants.MULTI_POINT_ZM_SHAPE: // 18
case ShapefileConstants.POINT_M_SHAPE: // 21
case ShapefileConstants.POLYLINE_M_SHAPE: // 23
case ShapefileConstants.POLYGON_M_SHAPE: // 25
case ShapefileConstants.MULTI_POINT_M_SHAPE: // 28
axisCount = 4;
break;
default:
throw new RuntimeException("Unknown shape type:" + this.shapeType);
}
this.geometryFactory = getProperty(IoConstants.GEOMETRY_FACTORY);
if (this.geometryFactory == null) {
this.geometryFactory = EsriCoordinateSystems.getGeometryFactory(this.resource);
if (this.geometryFactory != null) {
this.geometryFactory = this.geometryFactory.convertAxisCount(axisCount);
}
}
if (this.geometryFactory == null) {
this.geometryFactory = GeometryFactory.floating(0, axisCount);
}
setProperty(IoConstants.GEOMETRY_FACTORY, this.geometryFactory);
if (this.xbaseRecordReader != null) {
this.xbaseRecordReader.hasNext();
}
if (this.recordDefinition == null) {
this.recordDefinition = Records.newGeometryRecordDefinition();
}
this.recordDefinition.setGeometryFactory(this.geometryFactory);
} catch (final IOException e) {
throw new RuntimeException("Error initializing mappedFile " + this.resource, e);
}
}
}
public boolean isCloseFile() {
return this.closeFile;
}
/**
* Load the header record from the shape mappedFile.
*
* @throws IOException If an I/O error occurs.
*/
@SuppressWarnings("unused")
private void loadHeader() throws IOException {
this.in.readInt();
this.in.skipBytes(20);
final int fileLength = this.in.readInt();
final int version = this.in.readLEInt();
this.shapeType = this.in.readLEInt();
final double minX = this.in.readLEDouble();
final double minY = this.in.readLEDouble();
final double maxX = this.in.readLEDouble();
final double maxY = this.in.readLEDouble();
final double minZ = this.in.readLEDouble();
final double maxZ = this.in.readLEDouble();
final double minM = this.in.readLEDouble();
final double maxM = this.in.readLEDouble();
}
@SuppressWarnings("unused")
private Geometry readGeometry() throws IOException {
final int recordNumber = this.in.readInt();
final int recordLength = this.in.readInt();
final int shapeType = this.in.readLEInt();
final ShapefileGeometryUtil util = ShapefileGeometryUtil.SHP_INSTANCE;
switch (shapeType) {
case ShapefileConstants.NULL_SHAPE:
switch (this.shapeType) {
case ShapefileConstants.POINT_SHAPE:
case ShapefileConstants.POINT_M_SHAPE:
case ShapefileConstants.POINT_Z_SHAPE:
case ShapefileConstants.POINT_ZM_SHAPE:
case ShapefileConstants.MULTI_POINT_SHAPE:
case ShapefileConstants.MULTI_POINT_M_SHAPE:
case ShapefileConstants.MULTI_POINT_Z_SHAPE:
case ShapefileConstants.MULTI_POINT_ZM_SHAPE:
return this.geometryFactory.point();
case ShapefileConstants.POLYLINE_SHAPE:
case ShapefileConstants.POLYLINE_M_SHAPE:
case ShapefileConstants.POLYLINE_Z_SHAPE:
case ShapefileConstants.POLYLINE_ZM_SHAPE:
return this.geometryFactory.lineString();
case ShapefileConstants.POLYGON_SHAPE:
case ShapefileConstants.POLYGON_M_SHAPE:
case ShapefileConstants.POLYGON_Z_SHAPE:
case ShapefileConstants.POLYGON_ZM_SHAPE:
return this.geometryFactory.polygon();
default:
throw new IllegalArgumentException("Shapefile shape type not supported: " + shapeType);
}
case ShapefileConstants.POINT_SHAPE:
return util.readPoint(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POINT_M_SHAPE:
return util.readPointM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POINT_Z_SHAPE:
return util.readPointZ(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POINT_ZM_SHAPE:
return util.readPointZM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.MULTI_POINT_SHAPE:
return util.readMultipoint(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.MULTI_POINT_M_SHAPE:
return util.readMultipointM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.MULTI_POINT_Z_SHAPE:
return util.readMultipointZ(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.MULTI_POINT_ZM_SHAPE:
return util.readMultipointZM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYLINE_SHAPE:
return util.readPolyline(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYLINE_M_SHAPE:
return util.readPolylineM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYLINE_Z_SHAPE:
return util.readPolylineZ(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYLINE_ZM_SHAPE:
return util.readPolylineZM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYGON_SHAPE:
return util.readPolygon(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYGON_M_SHAPE:
return util.readPolygonM(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYGON_Z_SHAPE:
return util.readPolygonZ(this.geometryFactory, this.in, recordLength);
case ShapefileConstants.POLYGON_ZM_SHAPE:
return util.readPolygonZM(this.geometryFactory, this.in, recordLength);
default:
throw new IllegalArgumentException("Shapefile shape type not supported: " + shapeType);
}
}
public void setCloseFile(final boolean closeFile) {
this.closeFile = closeFile;
if (this.xbaseRecordReader != null) {
this.xbaseRecordReader.setCloseFile(closeFile);
}
}
public void setRecordDefinition(final RecordDefinition recordDefinition) {
this.returnRecordDefinition = recordDefinition;
((RecordDefinitionImpl)recordDefinition).setPolygonRingDirection(ClockDirection.CLOCKWISE);
}
public void setTypeName(final PathName typeName) {
if (Property.hasValue(typeName)) {
this.typeName = typeName;
}
}
@Override
public String toString() {
return ShapefileConstants.DESCRIPTION + " " + this.resource;
}
private void updateRecordDefinition() {
assert this.recordDefinition == null : "Cannot override recordDefinition when set";
if (this.xbaseRecordReader != null) {
final RecordDefinitionImpl recordDefinition = this.xbaseRecordReader.getRecordDefinition();
recordDefinition.setPolygonRingDirection(ClockDirection.CLOCKWISE);
this.recordDefinition = recordDefinition;
if (recordDefinition.getGeometryFieldIndex() == -1) {
DataType geometryType = DataTypes.GEOMETRY;
switch (this.shapeType) {
case ShapefileConstants.POINT_SHAPE:
case ShapefileConstants.POINT_Z_SHAPE:
case ShapefileConstants.POINT_M_SHAPE:
case ShapefileConstants.POINT_ZM_SHAPE:
geometryType = DataTypes.POINT;
break;
case ShapefileConstants.POLYLINE_SHAPE:
case ShapefileConstants.POLYLINE_Z_SHAPE:
case ShapefileConstants.POLYLINE_M_SHAPE:
case ShapefileConstants.POLYLINE_ZM_SHAPE:
geometryType = DataTypes.MULTI_LINE_STRING;
break;
case ShapefileConstants.POLYGON_SHAPE:
case ShapefileConstants.POLYGON_Z_SHAPE:
case ShapefileConstants.POLYGON_M_SHAPE:
case ShapefileConstants.POLYGON_ZM_SHAPE:
geometryType = DataTypes.MULTI_POLYGON;
break;
case ShapefileConstants.MULTI_POINT_SHAPE:
case ShapefileConstants.MULTI_POINT_Z_SHAPE:
case ShapefileConstants.MULTI_POINT_M_SHAPE:
case ShapefileConstants.MULTI_POINT_ZM_SHAPE:
geometryType = DataTypes.MULTI_POINT;
break;
default:
break;
}
recordDefinition.addField("geometry", geometryType, true);
}
}
}
}