/*
* $URL $
* $Author:paul.austin@revolsys.com $
* $Date:2007-06-09 09:28:28 -0700 (Sat, 09 Jun 2007) $
* $Revision:265 $
* Copyright 2004-2005 Revolution Systems Inc.
*
* Licensed 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 com.revolsys.record.io.format.shp;
import java.io.IOException;
import java.lang.reflect.Method;
import org.apache.log4j.Logger;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.cs.esri.EsriCoordinateSystems;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.ClockDirection;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.io.IoConstants;
import com.revolsys.io.endian.EndianOutput;
import com.revolsys.io.endian.ResourceEndianOutput;
import com.revolsys.record.Record;
import com.revolsys.record.io.format.xbase.XBaseFieldDefinition;
import com.revolsys.record.io.format.xbase.XbaseRecordWriter;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordDefinitionImpl;
import com.revolsys.spring.resource.Resource;
import com.revolsys.util.MathUtil;
public class ShapefileRecordWriter extends XbaseRecordWriter {
private static final Logger LOG = Logger.getLogger(ShapefileRecordWriter.class);
private static final ShapefileGeometryUtil SHP_WRITER = ShapefileGeometryUtil.SHP_INSTANCE;
private BoundingBox envelope = BoundingBox.empty();
private DataType geometryDataType;
private GeometryFactory geometryFactory;
private String geometryFieldName = "geometry";
private Method geometryWriteMethod;
private boolean hasGeometry = false;
private ResourceEndianOutput indexOut;
private ResourceEndianOutput out;
private int recordNumber = 1;
private final Resource resource;
private int shapeType = ShapefileConstants.NULL_SHAPE;
public ShapefileRecordWriter(final RecordDefinition recordDefinition, final Resource resource) {
super(recordDefinition, resource.newResourceChangeExtension("dbf"));
this.resource = resource;
}
@Override
protected int addDbaseField(final String name, final DataType dataType,
final Class<?> typeJavaClass, final int length, final int scale) {
if (Geometry.class.isAssignableFrom(typeJavaClass)) {
if (this.hasGeometry) {
return super.addDbaseField(name, DataTypes.STRING, String.class, 254, 0);
} else {
this.hasGeometry = true;
addFieldDefinition(name, XBaseFieldDefinition.OBJECT_TYPE, 0, 0);
return 0;
}
} else {
return super.addDbaseField(name, dataType, typeJavaClass, length, scale);
}
}
@Override
public void close() {
super.close();
try {
updateHeader(this.out);
if (this.indexOut != null) {
updateHeader(this.indexOut);
}
} catch (final IOException e) {
LOG.error(e.getMessage(), e);
} finally {
this.out = null;
this.indexOut = null;
}
}
private void doubleNotNaN(final ResourceEndianOutput out, final double value) throws IOException {
if (!Double.isFinite(value)) {
out.writeLEDouble(0);
} else {
out.writeLEDouble(value);
}
}
@Override
public ClockDirection getPolygonRingDirection() {
return ClockDirection.CLOCKWISE;
}
@Override
protected void init() throws IOException {
super.init();
final RecordDefinitionImpl recordDefinition = (RecordDefinitionImpl)getRecordDefinition();
if (recordDefinition != null) {
this.geometryFieldName = recordDefinition.getGeometryFieldName();
if (this.geometryFieldName != null) {
this.out = new ResourceEndianOutput(this.resource);
writeHeader(this.out);
if (!hasField(this.geometryFieldName)) {
addFieldDefinition(this.geometryFieldName, XBaseFieldDefinition.OBJECT_TYPE, 0, 0);
}
final Resource indexResource = this.resource.newResourceChangeExtension("shx");
if (indexResource != null) {
this.indexOut = new ResourceEndianOutput(indexResource);
writeHeader(this.indexOut);
}
this.geometryFactory = getProperty(IoConstants.GEOMETRY_FACTORY);
final Object geometryType = getProperty(IoConstants.GEOMETRY_TYPE);
if (geometryType != null) {
this.geometryDataType = DataTypes.getDataType(geometryType.toString());
}
}
}
}
@Override
protected void preFirstWrite(final Record record) throws IOException {
if (this.geometryFieldName != null) {
final Geometry geometry = record.getGeometry();
if (geometry != null) {
if (this.geometryFactory == null) {
this.geometryFactory = geometry.getGeometryFactory();
}
if (this.geometryDataType == null) {
this.geometryDataType = record.getRecordDefinition().getGeometryField().getDataType();
if (DataTypes.GEOMETRY.equals(this.geometryDataType)) {
final String geometryType = geometry.getGeometryType();
this.geometryDataType = DataTypes.getDataType(geometryType);
}
}
this.shapeType = ShapefileGeometryUtil.SHP_INSTANCE.getShapeType(this.geometryFactory,
this.geometryDataType);
this.geometryWriteMethod = ShapefileGeometryUtil.getWriteMethod(this.geometryFactory,
this.geometryDataType);
}
EsriCoordinateSystems.writePrjFile(this.resource, this.geometryFactory);
}
}
@Override
public String toString() {
return "ShapefileWriter(" + this.resource + ")";
}
private void updateHeader(final ResourceEndianOutput out) throws IOException {
if (out != null) {
out.seek(24);
final int sizeInShorts = (int)(out.length() / 2);
out.writeInt(sizeInShorts);
out.seek(32);
out.writeLEInt(this.shapeType);
doubleNotNaN(out, this.envelope.getMinX());
doubleNotNaN(out, this.envelope.getMinY());
doubleNotNaN(out, this.envelope.getMaxX());
doubleNotNaN(out, this.envelope.getMaxY());
doubleNotNaN(out, this.envelope.getMin(2));
doubleNotNaN(out, this.envelope.getMax(2));
doubleNotNaN(out, this.envelope.getMin(3));
doubleNotNaN(out, this.envelope.getMax(3));
out.close();
}
}
@Override
protected boolean writeField(final Record record, final XBaseFieldDefinition field)
throws IOException {
if (field.getFullName().equals(this.geometryFieldName)) {
final long recordIndex = this.out.getFilePointer();
Geometry geometry = record.getGeometry();
if (geometry != null) {
geometry = geometry.convertGeometry(this.geometryFactory);
}
this.out.writeInt(this.recordNumber++);
if (geometry == null || geometry.isEmpty()) {
writeNull(this.out);
} else {
this.envelope = this.envelope.expandToInclude(geometry.getBoundingBox());
SHP_WRITER.write(this.geometryWriteMethod, this.out, geometry);
}
if (this.indexOut != null) {
final long recordLength = this.out.getFilePointer() - recordIndex;
final int offsetShort = (int)(recordIndex / MathUtil.BYTES_IN_SHORT);
this.indexOut.writeInt(offsetShort);
final int lengthShort = (int)(recordLength / MathUtil.BYTES_IN_SHORT) - 4;
this.indexOut.writeInt(lengthShort);
}
return true;
} else {
return super.writeField(record, field);
}
}
private void writeHeader(final EndianOutput out) throws IOException {
out.writeInt(ShapefileConstants.FILE_CODE);
for (int i = 0; i < 5; i++) { // Unused
out.writeInt(0);
}
out.writeInt(0); // File length updated on close
out.writeLEInt(ShapefileConstants.VERSION);
out.writeLEInt(0); // Shape Type updated on close
// shape type and bounding box will be updated on file close
for (int i = 0; i < 8; i++) {
out.writeLEDouble(0);
}
}
private int writeNull(final EndianOutput out) throws IOException {
final int recordLength = MathUtil.BYTES_IN_INT;
out.writeInt(recordLength);
out.writeLEInt(ShapefileConstants.NULL_SHAPE);
return ShapefileConstants.NULL_SHAPE;
}
}