package jeql.io.shapefile; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.net.URL; import jeql.io.EndianDataOutputStream; import jeql.std.function.FileFunction; import org.geotools.shapefile.PointHandler; import org.geotools.shapefile.ShapeHandler; import org.geotools.shapefile.ShapefileHeader; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; /** * Writes a stream of geometries to a .shp file. * Geometries are written in a stream, non-memory resident fashion. * The shp file header is rewritten with the final correct values. */ public class ShpStreamWriter { private static final int SHP_HDR_LEN_WORDS = 50; private static final int SHP_RECORD_HDR_LEN_WORDS = 4; private URL baseURL; private String filenameSHX; private String filename; private int coordDimension = 2; private ShapeHandler handler = null; private EndianDataOutputStream outStream; private EndianDataOutputStream outStreamSHX; private int fileLen = 0; private int filePos = 0; private int shapeType = -1; private int shapeCount = 0; private Envelope fileEnv = new Envelope(); private int geomIndex = 0; /** * Creates a writer to the given url * * @param url * The url of the shapefile */ public ShpStreamWriter(URL url) { this(url.getFile()); baseURL = url; } public ShpStreamWriter(String filename) { this.filename = filename; filenameSHX = FileFunction.pathNoExt(filename) + ".shx"; } private EndianDataOutputStream createOutputStream(String filename) throws IOException { BufferedOutputStream in = new BufferedOutputStream(new FileOutputStream( filename)); EndianDataOutputStream os = new EndianDataOutputStream(in); return os; } private void initSHP() throws IOException { if (outStream != null) return; outStream = createOutputStream(filename); ShapefileHeader.write(outStream, 0, 0, new Envelope()); filePos = SHP_HDR_LEN_WORDS; // header length in WORDS } private void initSHX() throws IOException { if (outStreamSHX != null) return; outStreamSHX = createOutputStream(filenameSHX); ShapefileHeader.write(outStreamSHX, 0, 0, new Envelope()); } private void initHandler(Geometry geom) throws Exception { if (handler != null) return; // TODO: fix to correctly determine type if (geom == null) { handler = new PointHandler(); // default } else { coordDimension = ShapefileWriter.determineCoordinateDimension(geom); handler = Shapefile.getShapeHandler(geom, coordDimension); } shapeType = handler.getShapeType(); } public void write(Geometry geom) throws IOException, Exception { initSHP(); initSHX(); initHandler(geom); // update counters fileEnv.expandToInclude(geom.getEnvelopeInternal()); shapeCount++; // write SHP record outStream.writeIntBE(geomIndex + 1); geomIndex += 1; int len = handler.getLength(geom); outStream.writeIntBE(len); fileLen += SHP_RECORD_HDR_LEN_WORDS; // length of record header in WORDS handler.write(geom, outStream); fileLen += len; // length of shape in WORDS // write SHX record outStreamSHX.writeIntBE(filePos); outStreamSHX.writeIntBE(len); filePos = filePos + len + SHP_RECORD_HDR_LEN_WORDS; } private static final String HDR_WRITE_MODE = "rws"; public void close() throws IOException { outStream.flush(); outStream.close(); // rewrite correct SHP header RandomAccessFile rafshp = new RandomAccessFile(filename, HDR_WRITE_MODE); rafshp.write(ShapefileHeader.write(fileLen, shapeType, fileEnv)); rafshp.close(); outStreamSHX.flush(); outStreamSHX.close(); // rewrite correct SHP header RandomAccessFile rafSHX = new RandomAccessFile(filenameSHX, HDR_WRITE_MODE); rafSHX.write(ShapefileHeader.writeToIndex(shapeCount, shapeType, fileEnv)); rafSHX.close(); } }