// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/dataAccess/shape/output/ShpOutputStream.java,v $ // $RCSfile: ShpOutputStream.java,v $ // $Revision: 1.7 $ // $Date: 2009/01/21 01:24:42 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.dataAccess.shape.output; import java.awt.geom.Point2D; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.logging.Logger; import com.bbn.openmap.dataAccess.shape.EsriGraphic; import com.bbn.openmap.dataAccess.shape.EsriGraphicList; import com.bbn.openmap.dataAccess.shape.EsriPointList; import com.bbn.openmap.dataAccess.shape.EsriPolygonList; import com.bbn.openmap.dataAccess.shape.EsriPolylineList; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMPoint; import com.bbn.openmap.omGraphics.OMPoly; import com.bbn.openmap.proj.coords.GeoCoordTransformation; import com.bbn.openmap.proj.coords.LatLonPoint; /** * Writes data to a .shp file * * @author Doug Van Auken */ public class ShpOutputStream { public static Logger logger = Logger.getLogger("com.bbn.openmap.dataAccess.shape.output.ShpOutputStream"); public final static int ESRI_RECORD_HEADER_LENGTH = 4; // length in 16-bit // words /** * A GeoCoordTransform to use to convert Lat/Lon values in EsriGraphics to * projected coordinates. */ protected GeoCoordTransformation transform; /** * An outputstream that writes primitive data types in little endian or big * endian */ private LittleEndianOutputStream _leos = null; /** * Creates an outputstream to write to * * @param os The output stream to write to */ public ShpOutputStream(OutputStream os) { BufferedOutputStream bos = new BufferedOutputStream(os); _leos = new LittleEndianOutputStream(bos); } /** * Get the transform being used on the coordinates of the EsriGraphics as * they are written to the stream. * * @return GeoCoordTransform if used, may be null for no transformation. */ public GeoCoordTransformation getTransform() { return transform; } /** * Set the GeoCoordTransform for the stream, so that the EsriGraphics will * have their coordinates transformed as they are written to the stream. If * null, the coordinates will be unchanged. * * @param transform */ public void setTransform(GeoCoordTransformation transform) { this.transform = transform; } /** * Determine what type of list is given and write it out. * * @param list The EsriGraphicList to write * @return The index data that is used to create the .shx file */ public int[][] writeGeometry(EsriGraphicList list) throws IOException { if (list instanceof EsriPolygonList || list instanceof EsriPolylineList) { return writePolyGeometry(list); } else if (list instanceof EsriPointList) { return writePointGeometry(list); } return null; } /** * Calculates the content length for each record, in terms of words as * defined by ESRI documentation. A word is 16 bits, so a double is 4 words * and an int is 2 words. * * @param list The EsriGraphicList to write * @return The index data that is used to create the .shx file */ protected int[][] createPointIndex(OMGraphicList list) { int[][] indexData = new int[2][list.size()]; int pos = 50; for (int i = 0; i < list.size(); i++) { // OMGraphicList sublist = // (OMGraphicList)list.getOMGraphicAt(i); int contentLength = 0; contentLength += 2; // Shape Type OMGraphic graphic = list.getOMGraphicAt(i); if (graphic instanceof EsriGraphicList) { EsriGraphicList sublist = (EsriGraphicList) graphic; contentLength += (4 * 4); // bounding box, 4 doubles contentLength += 2; // number of points, 1 int contentLength += (sublist.size() * (2 + 4)); // points, 2 doubles // each } else { contentLength += 4; // X contentLength += 4; // Y } indexData[1][i] = contentLength; indexData[0][i] = pos; pos += contentLength + 4; } return indexData; } /** * Creates a two dimensional array holding a list of shape content lengths * and shape content length offsets, as defined in Esri shape file * documentation. This array is used to create the .shx file * * @param list The list from which to create the respective array * @return The index data that is used to create the .shx file */ protected int[][] createPolyIndex(EsriGraphicList list) { double[] data; int[][] indexData = new int[2][list.size()]; int pos = 50; for (int i = 0; i < list.size(); i++) { int contentLength = 0; OMGraphic graphic = (OMGraphic) list.getOMGraphicAt(i); contentLength += 2; // Shape Type contentLength += 16; // Box contentLength += 2; // NumParts contentLength += 2; // NumPoints if (graphic instanceof OMGraphicList) { OMGraphicList sublist = (OMGraphicList) graphic; contentLength += sublist.size() * 2; // offsets? for (int j = 0; j < sublist.size(); j++) { OMPoly poly = (OMPoly) sublist.getOMGraphicAt(j); data = poly.getLatLonArray(); // each value equals 4 words contentLength += data.length * 4; } } else { contentLength += 2; // offset? // Should be an EsriPolyline data = ((OMPoly) graphic).getLatLonArray(); // each value equals 4 words contentLength += data.length * 4; } indexData[1][i] = contentLength; indexData[0][i] = pos; pos += contentLength + 4; } return indexData; } /** * Creates an array whose elements specify at what index a shapes geometry * begins * * @param contentLengths The array for which to get offsets from * @return An array of record offsets */ protected int[] getRecordOffsets(int[] contentLengths) { int[] offsets = new int[contentLengths.length]; int pos = 50; for (int i = 0; i < contentLengths.length; i++) { offsets[i] = pos; pos += contentLengths[i] + 4; } return offsets; } /** * Creates an array whose elements specifies at what index a parts geometry * begins * * @param sublist A list of shapes * @return An array of part offsets */ protected int[] getPartOffsets(OMGraphicList sublist) { int pos = 0; int[] offsets = new int[sublist.size()]; for (int j = 0; j < sublist.size(); j++) { OMPoly poly = (OMPoly) sublist.getOMGraphicAt(j); double[] data = poly.getLatLonArray(); offsets[j] = pos / 2; pos += data.length; } return offsets; } /** * Iterates through a list of shapes, summing the points per part to * determine the number of points per shape * * @param sublist A list of shapes * @return The number of points for a given shape */ protected int getPointsPerShape(OMGraphicList sublist) { int numPoints = 0; for (int i = 0; i < sublist.size(); i++) { OMPoly poly = (OMPoly) sublist.getOMGraphicAt(i); double[] data = poly.getLatLonArray(); numPoints += data.length; } numPoints /= 2; return numPoints; } protected void writeExtents(double[] extents) throws IOException { if (_leos == null) { return; } if (extents[0] == 90f && extents[1] == 180f && extents[2] == -90f && extents[3] == -180f) { // Whoa! not set from defaults correctly! // use old, hardcoded way. _leos.writeLEDouble(-180.0); _leos.writeLEDouble(-90.0); _leos.writeLEDouble(180.0); _leos.writeLEDouble(90.0); } else { if (transform == null) { _leos.writeLEDouble(extents[1]); _leos.writeLEDouble(extents[0]); _leos.writeLEDouble(extents[3]); _leos.writeLEDouble(extents[2]); } else { Point2D pnt = transform.forward(extents[0], extents[1]); // extents are written out x, y _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); pnt = transform.forward(extents[2], extents[3], pnt); _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); } } } /** * Writes polygon geometry to the class scope LittleEndianInputStream. * * @param list The list of geometry objects to save * @return A two dimensional array containing shape offsets and content * lengths */ public int[][] writePolyGeometry(EsriGraphicList list) throws IOException { OMPoly poly; _leos.writeInt(9994); // Byte 0 File Code _leos.writeInt(0); // Byte 4 Unused _leos.writeInt(0); // Byte 8 Unused _leos.writeInt(0); // Byte 12 Unused _leos.writeInt(0); // Byte 16 Unused _leos.writeInt(0); // Byte 20 Unused int[][] indexData = createPolyIndex(list); int contentLength = 50; if (!list.isEmpty()) { contentLength = indexData[0][indexData[0].length - 1] + indexData[1][indexData[0].length - 1] + ESRI_RECORD_HEADER_LENGTH; } _leos.writeInt(contentLength); // Byte 24 File Length _leos.writeLEInt(1000); // Byte 28 Version _leos.writeLEInt(list.getType()); // Byte 32 Shape Type // Writes bounding box. double[] extents = list.getExtents(); writeExtents(extents); _leos.writeDouble(0.0); // Byte 68 _leos.writeDouble(0.0); // Byte 76 _leos.writeDouble(0.0); // Byte 84 _leos.writeDouble(0.0); // Byte 92 // Temporary point used for transformations Point2D pnt = new Point2D.Double(); // Iterate through the list for (int i = 0; i < list.size(); i++) { OMGraphic graphic = list.getOMGraphicAt(i); // Record header _leos.writeInt(i + 1); // Record numbers start with 1 _leos.writeInt(indexData[1][i]); // Beginning of Geometry data _leos.writeLEInt(list.getType()); // Little endian // More stuff needs to be written out for just the OMPoly // case... // Single part, etc. if (graphic instanceof EsriGraphicList) { // Assumes that the elements of the top level list are // EsriGraphicLists, too. This will probably be // changing. EsriGraphicList sublist = (EsriGraphicList) graphic; // Writes bounding box. extents = sublist.getExtents(); writeExtents(extents); // Writes number of parts int numParts = sublist.size(); _leos.writeLEInt(numParts); // Write number of points per shape int numPoints = getPointsPerShape(sublist); _leos.writeLEInt(numPoints); // Write the offsets to each part for a given shape int[] offsets = getPartOffsets(sublist); for (int j = 0; j < offsets.length; j++) { _leos.writeLEInt(offsets[j]); } // Write the geometry for each part for (int j = 0; j < sublist.size(); j++) { poly = (OMPoly) sublist.getOMGraphicAt(j); double[] data = poly.getLatLonArray(); int n = 0; while (n < data.length) { double lat = Math.toDegrees(data[n++]); double lon = Math.toDegrees(data[n++]); if (transform == null) { _leos.writeLEDouble(lon); _leos.writeLEDouble(lat); } else { transform.forward(lat, lon, pnt); _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); } } } } else { extents = ((EsriGraphic) graphic).getExtents(); writeExtents(extents); // Writes number of parts for shape (1) _leos.writeLEInt(1); poly = (OMPoly) graphic; double[] data = poly.getLatLonArray(); // Write number of points for shape _leos.writeLEInt(data.length / 2); // Write the offsets to this shape _leos.writeLEInt(0); int n = 0; while (n < data.length) { double lat = Math.toDegrees(data[n++]); double lon = Math.toDegrees(data[n++]); if (transform == null) { _leos.writeLEDouble(lon); _leos.writeLEDouble(lat); } else { transform.forward(lat, lon, pnt); _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); } } } } _leos.flush(); _leos.close(); return indexData; } /** * Writes point geometry to the class scope LittleEndianOutputStream. * * @param list An EsriGraphicList of points * @return A two dimensional array containing shape offsets and content * lengths */ public int[][] writePointGeometry(EsriGraphicList list) throws IOException { _leos.writeInt(9994); // Big _leos.writeInt(0); // Big _leos.writeInt(0); // Big _leos.writeInt(0); // Big _leos.writeInt(0); // Big _leos.writeInt(0); // Big int[][] indexData = createPointIndex(list); int contentLength = 50; if (!list.isEmpty()) { contentLength = indexData[0][indexData[0].length - 1] + indexData[1][indexData[0].length - 1] + ESRI_RECORD_HEADER_LENGTH; } _leos.writeInt(contentLength); // Big _leos.writeLEInt(1000); // Little _leos.writeLEInt(list.getType()); // Writes bounding box. double[] extents = list.getExtents(); writeExtents(extents); _leos.writeDouble(0.0); _leos.writeDouble(0.0); _leos.writeDouble(0.0); _leos.writeDouble(0.0); // For coordinate transformations Point2D pnt = new Point2D.Double(); OMPoint point = null; for (int i = 0; i < list.size(); i++) { OMGraphic graphic = list.get(i); // Record header... _leos.writeInt(i + 1); // Record numbers start with 1 _leos.writeInt(indexData[1][i]); // Beginning of Geometry data _leos.writeLEInt(list.getType()); if (graphic instanceof OMGraphicList) { EsriGraphicList sublist = (EsriGraphicList) graphic; // Writes bounding box. extents = sublist.getExtents(); writeExtents(extents); // Write number of points per shape _leos.writeLEInt(sublist.size()); // Write the geometry for each part for (int j = 0; j < sublist.size(); j++) { point = (OMPoint) sublist.getOMGraphicAt(j); LatLonPoint pt = new LatLonPoint.Double(point.getLat(), point.getLon()); double lat = pt.getY(); double lon = pt.getX(); if (transform == null) { _leos.writeLEDouble(lon); _leos.writeLEDouble(lat); } else { transform.forward(lat, lon, pnt); _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); } } } else { point = (OMPoint) graphic; LatLonPoint pt = new LatLonPoint.Double(point.getLat(), point.getLon()); double lat = pt.getY(); double lon = pt.getX(); if (transform == null) { _leos.writeLEDouble(lon); _leos.writeLEDouble(lat); } else { transform.forward(lat, lon, pnt); _leos.writeLEDouble(pnt.getX()); _leos.writeLEDouble(pnt.getY()); } } } _leos.flush(); _leos.close(); return indexData; } }