Package com.bbn.openmap.layer.shape

Source Code of com.bbn.openmap.layer.shape.ShapeFile

// **********************************************************************
//
// <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/layer/shape/ShapeFile.java,v $
// $RCSfile: ShapeFile.java,v $
// $Revision: 1.2.2.2 $
// $Date: 2005/08/09 21:17:47 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.layer.shape;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Vector;

import com.bbn.openmap.util.Debug;

/**
* Class representing an ESRI Shape File.
* <p>
* <H2>Usage:</H2>
* <DT>java com.bbn.openmap.layer.shape.ShapeFile -v shapeFile</DT>
* <DD>Verifies a shape file.</DD>
* <p>
* <DT>java com.bbn.openmap.layer.shape.ShapeFile -a destShapeFile
* srcShapeFile</DT>
* <DD>Appends records from srcShapeFile to destShapeFile.</DD>
* <p>
* <DT>java com.bbn.openmap.layer.shape.ShapeFile shapeFile</DT>
* <DD>Prints information about the header and the number of records.
* </DD>
* <p>
*
* @author Tom Mitchell <tmitchell@bbn.com>
* @author Ray Tomlinson
* @author Geoffrey Knauth
* @version $Revision: 1.2.2.2 $ $Date: 2005/08/09 21:17:47 $
*/
public class ShapeFile extends ShapeUtils {

    /** A Shape File's magic number. */
    public static final int SHAPE_FILE_CODE = 9994;

    /** The currently handled version of Shape Files. */
    public static final int SHAPE_FILE_VERSION = 1000;

    /** A default record size. Automatically increased on demand. */
    public static final int DEFAULT_RECORD_BUFFER_SIZE = 50000;

    /** The read/write class for shape files. */
    protected RandomAccessFile raf;

    /** The buffer that holds the 100 byte header. */
    protected byte header[];

    /** Holds the length of the file, in bytes. */
    protected long fileLength;

    /** Holds the version of the file, as an int. */
    protected int fileVersion;

    /** Holds the shape type of the file. */
    protected int fileShapeType;

    /** Holds the bounds of the file (four doubles). */
    protected ESRIBoundingBox fileBounds;

    /** A buffer for current record's header. */
    protected byte recHdr[];

    /** A buffer for the current record's data. */
    protected byte recBuf[];

    /**
     * Construct a <code>ShapeFile</code> from a file name.
     *
     * @exception IOException if something goes wrong opening or
     *            reading the file.
     */
    public ShapeFile(String name) throws IOException {
        raf = new RandomAccessFile(name, "rw");
        recHdr = new byte[ShapeUtils.SHAPE_FILE_RECORD_HEADER_LENGTH];
        recBuf = new byte[DEFAULT_RECORD_BUFFER_SIZE];
        initHeader();
    }

    /**
     * Construct a <code>ShapeFile</code> from the given
     * <code>File</code>.
     *
     * @param file A file object representing an ESRI Shape File
     *
     * @exception IOException if something goes wrong opening or
     *            reading the file.
     */
    public ShapeFile(File file) throws IOException {
        this(file.getPath());
    }

    /**
     * Reads or writes the header of a Shape file. If the file is
     * empty, a blank header is written and then read. If the file is
     * not empty, the header is read.
     * <p>
     * After this function runs, the file pointer is set to byte 100,
     * the first byte of the first record in the file.
     *
     * @exception IOException if something goes wrong reading or
     *            writing the shape file
     */
    protected void initHeader() throws IOException {
        int result = raf.read();
        if (result == -1) {
            // File is empty, write a new header into the file
            writeHeader();
        }
        readHeader();
    }

    /**
     * Writes a blank header into the shape file.
     *
     * @exception IOException if something goes wrong writing the
     *            shape file
     */
    protected void writeHeader() throws IOException {
        header = new byte[SHAPE_FILE_HEADER_LENGTH];
        writeBEInt(header, 0, SHAPE_FILE_CODE);
        writeBEInt(header, 24, 50); // empty shape file size in 16 bit
        // words
        writeLEInt(header, 28, SHAPE_FILE_VERSION);
        writeLEInt(header, 32, SHAPE_TYPE_NULL);
        writeLEDouble(header, 36, 0.0);
        writeLEDouble(header, 44, 0.0);
        writeLEDouble(header, 52, 0.0);
        writeLEDouble(header, 60, 0.0);
        raf.seek(0);
        raf.write(header, 0, SHAPE_FILE_HEADER_LENGTH);
    }

    /**
     * Reads and parses the header of the file. Values from the header
     * are stored in the fields of this class.
     *
     * @exception IOException if something goes wrong reading the file
     * @see #header
     * @see #fileVersion
     * @see #fileLength
     * @see #fileShapeType
     * @see #fileBounds
     */
    protected void readHeader() throws IOException {
        header = new byte[ShapeUtils.SHAPE_FILE_HEADER_LENGTH];
        raf.seek(0); // Make sure we're at the beginning of
        // the file
        raf.read(header, 0, ShapeUtils.SHAPE_FILE_HEADER_LENGTH);
        int fileCode = ShapeUtils.readBEInt(header, 0);
        if (fileCode != SHAPE_FILE_CODE) {
            throw new IOException("Invalid file code, "
                    + "probably not a shape file");
        }

        fileVersion = ShapeUtils.readLEInt(header, 28);
        if (fileVersion != SHAPE_FILE_VERSION) {
            throw new IOException("Unable to read shape files with version "
                    + fileVersion);
        }

        fileLength = ShapeUtils.readBEInt(header, 24);
        fileLength *= 2; // convert from 16-bit words to 8-bit
                            // bytes
        fileShapeType = ShapeUtils.readLEInt(header, 32);
        fileBounds = ShapeUtils.readBox(header, 36);
    }

    /**
     * Returns the length of the file in bytes.
     *
     * @return the file length
     */
    public long getFileLength() {
        return fileLength;
    }

    /**
     * Returns the version of the file. The only currently supported
     * version is 1000 (which represents version 1).
     *
     * @return the file version
     */
    public int getFileVersion() {
        return fileVersion;
    }

    /**
     * Returns the shape type of the file. Shape files do not mix
     * shape types; all the shapes are of the same type.
     *
     * @return the file's shape type
     */
    public int getShapeType() {
        return fileShapeType;
    }

    /**
     * Sets the shape type of the file. If the file has a shape type
     * already, it cannot be set. If it does not have a shape type, it
     * is set and written to the file in the header.
     * <p>
     * Shape types are enumerated in the class ShapeUtils.
     *
     * @param newShapeType the new shape type
     * @exception IOException if something goes wrong writing the file
     * @exception IllegalArgumentException if file already has a shape
     *            type
     * @see ShapeUtils
     */
    public void setShapeType(int newShapeType) throws IOException,
            IllegalArgumentException {
        if (fileShapeType == SHAPE_TYPE_NULL) {
            fileShapeType = newShapeType;
            long filePtr = raf.getFilePointer();
            writeLEInt(header, 32, fileShapeType);
            raf.seek(0);
            raf.write(header, 0, 100);
            raf.seek(filePtr);
        } else {
            throw new IllegalArgumentException("file already has a valid"
                    + " shape type: " + fileShapeType);
        }
    }

    /**
     * Returns the bounding box of this shape file. The bounding box
     * is the smallest rectangle that encloses all the shapes in the
     * file.
     *
     * @return the bounding box
     */
    public ESRIBoundingBox getBoundingBox() {
        return fileBounds;
    }

    /**
     * Returns the next record from the shape file as an
     * <code>ESRIRecord</code>. Each successive call gets the next
     * record. There is no way to go back a record. When there are no
     * more records, <code>null</code> is returned.
     *
     * @return a record, or null if there are no more records
     * @exception IOException if something goes wrong reading the file
     */
    public ESRIRecord getNextRecord() throws IOException {
        // Debug.output("getNextRecord: ptr = " +
        // raf.getFilePointer());
        int result = raf.read(recHdr,
                0,
                ShapeUtils.SHAPE_FILE_RECORD_HEADER_LENGTH);
        if (result == -1) { // EOF
            // Debug.output("getNextRecord: EOF");
            return null;
        }

        int contentLength = ShapeUtils.readBEInt(recHdr, 4);
        int bytesToRead = contentLength * 2;
        int fullRecordSize = bytesToRead + 8;
        if (recBuf.length < fullRecordSize) {
            if (Debug.debugging("shape")) {
                Debug.output("record size: " + fullRecordSize);
            }
            recBuf = new byte[fullRecordSize];
        }
        System.arraycopy(recHdr,
                0,
                recBuf,
                0,
                ShapeUtils.SHAPE_FILE_RECORD_HEADER_LENGTH);
        raf.read(recBuf,
                ShapeUtils.SHAPE_FILE_RECORD_HEADER_LENGTH,
                bytesToRead);

        switch (fileShapeType) {

        case ShapeUtils.SHAPE_TYPE_NULL:
            throw new IOException("Can't parse NULL shape type");

        case ShapeUtils.SHAPE_TYPE_POINT:
            return new ESRIPointRecord(recBuf, 0);

        case ShapeUtils.SHAPE_TYPE_ARC:
            // case ShapeUtils.SHAPE_TYPE_POLYLINE:
            return new ESRIPolygonRecord(recBuf, 0);

        case ShapeUtils.SHAPE_TYPE_POLYGON:
            return new ESRIPolygonRecord(recBuf, 0);

        case ShapeUtils.SHAPE_TYPE_MULTIPOINT:
            throw new IOException("Multipoint shape not yet implemented");

        default:
            throw new IOException("Unknown shape type: " + fileShapeType);
        }

    }

    /**
     * Adds a record to the end of this file. The record is written to
     * the file at the end of the last record.
     *
     * @param r the record to be added
     * @exception IOException if something goes wrong writing to the
     *            file
     */
    public void add(ESRIRecord r) throws IOException {
        if (r.getShapeType() == fileShapeType) {
            verifyRecordBuffer(r.getBinaryStoreSize());
            int nBytes = r.write(recBuf, 0);
            // long len = raf.length();
            // Debug.output("seek to " + len);
            raf.seek(raf.length());
            raf.write(recBuf, 0, nBytes);
        } else {
            Debug.error("ShapeFile.add(): type=" + r.getShapeType()
                    + " does not match file type=" + fileShapeType);
        }
    }

    /**
     * Closes the shape file and disposes of resources.
     *
     * @exception IOException if something goes wrong closing the file
     */
    public void close() throws IOException {
        raf.close();
        raf = null;
    }

    /**
     * Verifies the contents of a shape file. The header is verified
     * for file length, bounding box, and shape type. The records are
     * verified for shape type and record number. The file is verified
     * for proper termination (EOF at the end of a record).
     *
     * @param repair NOT CURRENTLY USED - would signal that the file
     *        should be repaired if possible
     * @param verbose NOT CURRENTLY USED - would cause the verifier to
     *        display progress and status
     * @exception IOException if something goes wrong reading or
     *            writing the file
     */
    public void verify(boolean repair, boolean verbose) throws IOException {
        // Is file length stored in header correctly?
        // Is file bounding box correct?
        // Does file have a valid shape type?
        // Is each record the correct shape type?
        // Does each record header have the correct record number?
        // Do we reach EOF at the end of a record?
        boolean headerChanged = false;
        long fLen = raf.length();
        if (verbose) {
            Debug.output("Checking file length...");
            System.out.flush();
        }
        if (fileLength == fLen) {
            if (verbose) {
                Debug.output("correct.");
            }
        } else {
            if (verbose) {
                Debug.output("incorrect (got " + fileLength + ", should be "
                        + fLen + ")");
            }
            if (repair) {
                fileLength = fLen;
                writeBEInt(header, 24, ((int) fLen / 2));
                headerChanged = true;
                if (verbose) {
                    Debug.output("...repaired.");
                }
            }
        }

        // loop through file to verify:
        // record numbers
        // Shape types
        // bounding box
        // correct EOF

        raf.seek(100);
        ESRIRecord r;
        int nRecords = 0;
        Vector v = new Vector();
        ESRIBoundingBox bounds = new ESRIBoundingBox();
        long recStart = raf.getFilePointer();
        byte intBuf[] = new byte[4];
        while ((r = getNextRecord()) != null) {
            long recEnd = raf.getFilePointer();
            // Debug.output("verify - start: " + recStart +
            // "; end: " + recEnd);
            nRecords++;
            v.addElement(r);
            if (r.getRecordNumber() != nRecords) {
                // Debug.output("updating record number for record "
                // + nRecords);
                writeBEInt(intBuf, 0, nRecords);
                raf.seek(recStart);
                raf.write(intBuf, 0, 4);
                raf.seek(recEnd);
            }
            if (fileShapeType == SHAPE_TYPE_NULL) {
                Debug.output("updating shape type in header.");
                fileShapeType = r.getShapeType();
                writeLEInt(header, 32, fileShapeType);
                headerChanged = true;
            }
            if (r.getShapeType() != fileShapeType) {
                Debug.output("invalid shape type " + r.getShapeType()
                        + ", expecting " + fileShapeType);
            }

            bounds.addBounds(r.getBoundingBox());
            recStart = recEnd;
        }

        if (!fileBounds.equals(bounds)) {
            Debug.output("adjusting bounds");
            Debug.output("from min: " + fileBounds.min);
            Debug.output("to min: " + bounds.min);
            Debug.output("from max: " + fileBounds.max);
            Debug.output("to max: " + bounds.max);
            writeBox(header, 36, bounds);
            headerChanged = true;
            fileBounds = bounds;
        }

        if (headerChanged) {
            Debug.output("writing changed header");
            raf.seek(0);
            raf.write(header, 0, 100);
        }
    }

    /**
     * Verifies that the record buffer is big enough to hold the given
     * number of bytes. If it is not big enough a new buffer is
     * created that can hold the given number of bytes.
     *
     * @param size the number of bytes the buffer needs to hold
     */
    protected void verifyRecordBuffer(int size) {
        if (recBuf.length < size) {
            recBuf = new byte[size];
        }
    }

    /**
     * The driver for the command line interface. Reads the command
     * line arguments and executes appropriate calls.
     * <p>
     * See the file documentation for usage.
     *
     * @param args the command line arguments
     * @exception IOException if something goes wrong reading or
     *            writing the file
     */
    public static void main(String args[]) throws IOException {
        Debug.init(System.getProperties());
        int argc = args.length;
        if (argc == 1) {
            ShapeFile sf = new ShapeFile(args[0]);
            Debug.output("Shape file: " + args[0]);
            Debug.output("version: " + sf.getFileVersion());
            Debug.output("length: " + sf.getFileLength());
            Debug.output("bounds:");
            Debug.output("\tmin: " + sf.getBoundingBox().min);
            Debug.output("\tmax: " + sf.getBoundingBox().max);
            int nRecords = 0;
            while (sf.getNextRecord() != null) {
                nRecords++;
            }
            Debug.output("records: " + nRecords);
        } else if ("-a".equals(args[0])) {
            // Append a shape file to another shape file
            String destFile = args[1];
            String srcFile = args[2];

            ShapeFile in = new ShapeFile(srcFile);
            ShapeFile out = new ShapeFile(destFile);

            if (in.getShapeType() != out.getShapeType()) {
                try {
                    out.setShapeType(in.getShapeType());
                } catch (IllegalArgumentException e) {
                    Debug.error("Incompatible shape types.");
                    System.exit(1);
                }
            }

            ESRIRecord r;
            while ((r = in.getNextRecord()) != null) {
                out.add(r);
            }
            out.verify(true, true);
        } else if ("-v".equals(args[0])) {
            // Verify a shape file
            String shpFile = args[1];

            ShapeFile s = new ShapeFile(shpFile);

            s.verify(true, true);
        } else {
            Debug.output("Usage:");
            Debug.output("ShapeFile file.shp  -- displays information about file.shp");
            Debug.output("ShapeFile -a dest.shp src.shp  -- appends records from src.shp to dest.shp");
            Debug.output("ShapeFile -v file.shp  -- verifies file.shp");
        }
    }
}
TOP

Related Classes of com.bbn.openmap.layer.shape.ShapeFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.