org.kalypso.shape.shp.SHPFile.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypso.shape.shp.SHPFile.java

Source

/** This file is part of kalypso/deegree.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * history:
 *
 * Files in this package are originally taken from deegree and modified here
 * to fit in kalypso. As goals of kalypso differ from that one in deegree
 * interface-compatibility to deegree is wanted but not retained always.
 *
 * If you intend to use this software in other ways than in kalypso
 * (e.g. OGC-web services), you should consider the latest version of deegree,
 * see http://www.deegree.org .
 *
 * all modifications are licensed as deegree,
 * original copyright:
 *
 * Copyright (C) 2001 by:
 * EXSE, Department of Geography, University of Bonn
 * http://www.giub.uni-bonn.de/exse/
 * lat/lon GmbH
 * http://www.lat-lon.de
 */

package org.kalypso.shape.shp;

import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.eclipse.core.runtime.Assert;
import org.kalypso.shape.FileMode;
import org.kalypso.shape.ShapeHeader;
import org.kalypso.shape.ShapeType;
import org.kalypso.shape.geometry.ISHPGeometry;
import org.kalypso.shape.geometry.SHPEnvelope;
import org.kalypso.shape.geometry.SHPMultiPoint;
import org.kalypso.shape.geometry.SHPMultiPointm;
import org.kalypso.shape.geometry.SHPMultiPointz;
import org.kalypso.shape.geometry.SHPNullShape;
import org.kalypso.shape.geometry.SHPPoint;
import org.kalypso.shape.geometry.SHPPointm;
import org.kalypso.shape.geometry.SHPPointz;
import org.kalypso.shape.geometry.SHPPolyLine;
import org.kalypso.shape.geometry.SHPPolyLinem;
import org.kalypso.shape.geometry.SHPPolyLinez;
import org.kalypso.shape.geometry.SHPPolygon;
import org.kalypso.shape.geometry.SHPPolygonz;
import org.kalypso.shape.shx.SHXRecord;
import org.kalypso.shape.tools.BufferedRandomAccessFile;
import org.kalypso.shape.tools.DataUtils;
import org.kalypsodeegree.model.geometry.ByteUtils;

/**
 * Class representing an ESRI Shape File.
 * <p>
 * Uses class ByteUtils modified from the original package com.bbn.openmap.layer.shape <br>
 * Copyright (C) 1998 BBN Corporation 10 Moulton St. Cambridge, MA 02138 <br>
 * <br>
 * Original Author: Andreas Poth
 */

public class SHPFile extends AbstractSHPFile {
    public static SHPFile create(final File file, final ShapeType shapeType) throws IOException {
        if (file.isFile() && file.exists())
            file.delete();

        final RandomAccessFile raf = new RandomAccessFile(file, "rw");

        final ShapeHeader header = new ShapeHeader(ShapeHeader.SHAPE_FILE_HEADER_LENGTH, shapeType, null);
        header.write(raf);
        raf.close();

        return new SHPFile(file, FileMode.WRITE);
    }

    /**
     * Opens a .shp file which must already exist.
     */
    public SHPFile(final File file, final FileMode mode) throws IOException {
        super(openInput(file, mode), mode);
    }

    private static DataInput openInput(final File file, final FileMode mode) throws IOException {
        final String rwMode = mode == FileMode.READ ? "r" : "rw"; //$NON-NLS-1$ //$NON-NLS-2$

        switch (mode) {
        case READ:
            return new BufferedRandomAccessFile(file, rwMode, 1024 * 8);
        case WRITE:
            return new RandomAccessFile(file, rwMode);
        }

        throw new IllegalStateException();
    }

    private RandomAccessFile getRandomAccessFile() {
        return (RandomAccessFile) getInput();
    }

    @Override
    public void close() throws IOException {
        final RandomAccessFile raf = getRandomAccessFile();

        if (isWriting()) {
            // Update header with length
            final long fileLength = raf.length();
            updateHeader((int) fileLength, getMBR());

            /* and write the header */
            raf.seek(0);

            final ShapeHeader header = getHeader();
            header.write(raf);
        }

        raf.close();
    }

    /**
     * returns the minimum bound rectangle of RecNo's Geometrie of the shape-file <BR>
     */
    public SHPEnvelope getEnvelope(final SHXRecord record) throws IOException {
        final RandomAccessFile raf = getRandomAccessFile();

        final int position = record.getOffset() * 2;
        raf.seek(position + 8);
        final ShapeType shpType = ShapeType.valueOf(DataUtils.readLEInt(raf));

        /*
         * only for PolyLines, Polygons and MultiPoints minimum bounding rectangles are defined
         */
        if (shpType == ShapeType.POLYLINE || shpType == ShapeType.POLYGON || shpType == ShapeType.MULTIPOINT
                || shpType == ShapeType.POLYLINEZ || shpType == ShapeType.POLYGONZ
                || shpType == ShapeType.MULTIPOINTZ)
            return new SHPEnvelope(raf);

        return null;
    }

    /**
     * method: getByRecNo (int RecNo) <BR>
     * returns a ShapeRecord-Geometry by RecorcNumber <BR>
     */
    public ISHPGeometry getShape(final SHXRecord record) throws IOException {
        final RandomAccessFile raf = getRandomAccessFile();

        final int position = record.getOffset() * 2;
        final int contentLength = record.getLength() * 2;

        final byte[] recBuf = new byte[contentLength];
        raf.seek(position + 8);
        raf.readFully(recBuf);

        final ShapeType shpType = ShapeType.valueOf(ByteUtils.readLEInt(recBuf, 0));
        if (shpType == ShapeType.NULL)
            return new SHPNullShape();

        // create a geometry out of record buffer with shape type
        switch (shpType) {
        case NULL:
            return new SHPNullShape();
        case POINT:
            return new SHPPoint(recBuf);
        case MULTIPOINT:
            return SHPMultiPoint.read(recBuf);
        case POLYLINE:
            return new SHPPolyLine(recBuf);
        case POLYGON:
            return new SHPPolygon(recBuf);
        case POINTZ:
            return new SHPPointz(recBuf);
        case POLYLINEZ:
            return new SHPPolyLinez(recBuf);
        case POLYGONZ:
            return new SHPPolygonz(recBuf);
        case MULTIPOINTZ:
            return SHPMultiPointz.read(recBuf);
        case POINTM:
            return new SHPPointm(recBuf);
        case MULTIPOINTM:
            return SHPMultiPointm.read(recBuf);
        case POLYLINEM:
            return new SHPPolyLinem(recBuf);
        case POLYGONM:
            return new SHPPolyLinem(recBuf);
        }

        throw new UnsupportedOperationException("Unknown shape type: " + shpType);
    }

    /**
     * Adds a new shape entry to the end of the file.<br>
     * This method is atomic in that sense that if an exception occurs while a shape convertet to bytes, inetad a
     * Null-Shape will be written.
     */
    public SHXRecord addShape(final ISHPGeometry shape, final int recordNumber) throws IOException, SHPException {
        if (shape == null)
            throw new SHPException("shape == null not allowed. Add SHPNullShape instead.");

        if (!(shape instanceof SHPNullShape) && shape.getType() != getShapeType())
            throw new SHPException("Cannot add shape, wrong type.");

        final RandomAccessFile raf = getRandomAccessFile();

        final long currentFileLength = raf.length();

        /* Convert shape into bytes and write them in one go */
        final byte[] bytes = writeRecordAsBytes(recordNumber, shape);
        raf.seek(currentFileLength);
        raf.write(bytes);

        /* Update header */
        final SHPEnvelope shapeMbr = shape.getEnvelope();
        expandMbr(shapeMbr);

        /* Create and return index record */
        final int contentLength = bytes.length - RECORD_HEADER_BYTES;
        return new SHXRecord((int) currentFileLength / 2, contentLength / 2);
    }

    private byte[] writeRecordAsBytes(final int recordNumber, final ISHPGeometry shape) throws IOException {
        final int contentLength = shape.length() + 4;
        final int recordLength = RECORD_HEADER_BYTES + contentLength;

        final ByteArrayOutputStream out = new ByteArrayOutputStream(recordLength);
        final DataOutputStream os = new DataOutputStream(out);

        /* Record Header */
        os.writeInt(recordNumber);
        os.writeInt(contentLength / 2);

        /* record Content */
        DataUtils.writeLEInt(os, shape.getType().getType());
        shape.write(os);

        // TODO: why flush here?
        os.flush();

        final byte[] byteArray = out.toByteArray();

        Assert.isTrue(recordLength == byteArray.length);

        return byteArray;
    }

    private void expandMbr(final SHPEnvelope shapeMbr) {
        final SHPEnvelope mbr = getMBR();
        final SHPEnvelope newMbr = mbr == null ? shapeMbr : mbr.expand(shapeMbr);

        // PERFORMANCE: we always set -1 as file length here. The file length will e updated as soon as the
        // header gets really written
        updateHeader(-1, newMbr);
    }

    private boolean isWriting() {
        return getMode() == FileMode.WRITE;
    }
}