如何在android中获取带有(.shp扩展名)的Shape File的记录数

时间:2012-07-24 13:42:48

标签: android shapefile

我正在尝试获取Shape文件中的记录数。该文件是一个多边形形状文件。我能够获得第一条记录的详细信息,但是当count增加到2时,会引发IllegalState异常并将记录计数设为0。

我的Android Activity类是

package com.example.android.skeletonapp;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class SkeletonActivity extends Activity {

    static final private int BACK_ID = Menu.FIRST;
    static final private int CLEAR_ID = Menu.FIRST + 1;

    private EditText mEditor;
    public SkeletonActivity(){}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        int recordCount = 0;
        // Inflate our UI from its XML layout description.
        setContentView(R.layout.skeleton_activity);

        // Find the text editor view inside the layout, because we
        // want to do various programmatic things with it.
        mEditor = (EditText) findViewById(R.id.editor);

        // Hook up button presses to the appropriate event handler.
        ((Button) findViewById(R.id.back)).setOnClickListener(mBackListener);
        ((Button) findViewById(R.id.clear)).setOnClickListener(mClearListener);

        mEditor.setText(getText(R.string.main_label));
        try {

             String shpFile = Environment.getDataDirectory().getAbsolutePath().toString()+"/ne_50m_urban_areas.shp";
            ShapeReader shR = new ShapeReader(shpFile, true);
                recordCount = shR.getCount(0);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        mEditor.setText("Record Count == " + recordCount);
    }


    @Override
    protected void onResume() {
        super.onResume();
    }

    /**
     * Called when your activity's options menu needs to be created.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // We are going to create two menus. Note that we assign them
        // unique integer IDs, labels from our string resources, and
        // given them shortcuts.
        menu.add(0, BACK_ID, 0, R.string.back).setShortcut('0', 'b');
        menu.add(0, CLEAR_ID, 0, R.string.clear).setShortcut('1', 'c');

        return true;
    }

    /**
     * Called right before your activity's option menu is displayed.
     */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        // Before showing the menu, we need to decide whether the clear
        // item is enabled depending on whether there is text to clear.
        menu.findItem(CLEAR_ID).setVisible(mEditor.getText().length() > 0);

        return true;
    }

    /**
     * Called when a menu item is selected.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case BACK_ID:
            finish();
            return true;
        case CLEAR_ID:
            mEditor.setText("");
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * A call-back for when the user presses the back button.
     */
    OnClickListener mBackListener = new OnClickListener() {
        public void onClick(View v) {
            finish();
        }
    };

    /**
     * A call-back for when the user presses the clear button.
     */
    OnClickListener mClearListener = new OnClickListener() {
        public void onClick(View v) {
            mEditor.setText("");
        }
    };
}

ShapeFileReader类是

package com.example.android.skeletonapp;

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ShapeReader  {
    private static final int UNKNOWN = Integer.MIN_VALUE;

    public final class Record {
        int length;

        public int number = 0;

        int offset; // Relative to the whole file

        int start = 0; // Relative to the current loaded buffer

        /** The minimum X value. */
        public double minX;

        /** The minimum Y value. */
        public double minY;

        /** The maximum X value. */
        public double maxX;

        /** The maximum Y value. */
        public double maxY;

        public ShapeType type;

        int end = 0; // Relative to the whole file

        Object shape = null;

        /** Fetch the shape stored in this record. */
        public Object shape() {
            if (shape == null) {
                buffer.position(start);
                buffer.order(ByteOrder.LITTLE_ENDIAN);
                if (type == ShapeType.NULL) {
                    shape = null;
                } else {
                    shape = handler.read(buffer, type, flatGeometry);
                }
            }
            return shape;
        }

        public int offset() {
            return offset;
        }

        /** A summary of the record. */
        public String toString() {
            return "Record " + number + " length " + length + " bounds " + minX
                    + "," + minY + " " + maxX + "," + maxY;
        }
    }     

    private ShapeHandler handler;

    private ShapefileHeader header;

    private ReadableByteChannel channel;

    ByteBuffer buffer;

    private ShapeType fileShapeType = ShapeType.UNDEFINED;

    private ByteBuffer headerTransfer;

    private final Record record = new Record();

    private final boolean randomAccessEnabled = true;

    private boolean useMemoryMappedBuffer;

    private long currentOffset = 0L;

    private int currentShape = 0;

    //private IndexFile shxReader;

    //private StreamLogging streamLogger = new StreamLogging("Shapefile Reader");

    //private GeometryFactory geometryFactory;

    private boolean flatGeometry;

    public ShapeReader(String shpFile,
            boolean useMemoryMapped) throws IOException  {
        this.channel = new FileInputStream(shpFile).getChannel();
        this.useMemoryMappedBuffer = useMemoryMapped;
        //randomAccessEnabled = channel instanceof FileChannel;
        init(true);
    }

    // ensure the capacity of the buffer is of size by doubling the original
    // capacity until it is big enough
    // this may be naiive and result in out of MemoryError as implemented...
    private ByteBuffer ensureCapacity(ByteBuffer buffer, int size,
            boolean useMemoryMappedBuffer) {
        // This sucks if you accidentally pass is a MemoryMappedBuffer of size
        // 80M
        // like I did while messing around, within moments I had 1 gig of
        // swap...
        if (buffer.isReadOnly() || useMemoryMappedBuffer) {
            return buffer;
        }

        int limit = buffer.limit();
        while (limit < size) {
            limit *= 2;
        }
        if (limit != buffer.limit()) {
            // clean up the old buffer and allocate a new one
            buffer = NIOUtilities.allocate(limit);
        }
        return buffer;
    }

    // for filling a ReadableByteChannel
    public static int fill(ByteBuffer buffer, ReadableByteChannel channel)
            throws IOException {
        int r = buffer.remaining();
        // channel reads return -1 when EOF or other error
        // because they a non-blocking reads, 0 is a valid return value!!
        while (buffer.remaining() > 0 && r != -1) {
            r = channel.read(buffer);
        }
        buffer.limit(buffer.position());
        return r;
    }

    private void init(boolean strict) throws IOException {
        if (channel instanceof FileChannel && useMemoryMappedBuffer) {
            FileChannel fc = (FileChannel) channel;
            buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
            buffer.position(0);
            this.currentOffset = 0;
        } else {
            // force useMemoryMappedBuffer to false
            this.useMemoryMappedBuffer = false;
            // start small
            buffer = NIOUtilities.allocate(1024);
            fill(buffer, channel);
            buffer.flip();
            this.currentOffset = 0;
        }
        header = new ShapefileHeader();
        header.read(buffer, strict);
        fileShapeType = header.getShapeType();
        //handler = fileShapeType.getShapeHandler(gf);
        //if (handler == null) {
        //    throw new IOException("Unsuported shape type:" + fileShapeType);
        //}

        headerTransfer = ByteBuffer.allocate(8);
        headerTransfer.order(ByteOrder.BIG_ENDIAN);

        // make sure the record end is set now...
        record.end = this.toFileOffset(buffer.position());
    }

    /**
     * Get the header. Its parsed in the constructor.
     * 
     * @return The header that is associated with this file.
     */
    public ShapefileHeader getHeader() {
        return header;
    }

    // do important cleanup stuff.
    // Closes channel !
    /**
     * Clean up any resources. Closes the channel.
     * 
     * @throws IOException
     *                 If errors occur while closing the channel.
     */
    public void close() throws IOException {
        // don't throw NPE on double close
        if(channel == null)
            return;
        try {
            if (channel.isOpen()) {
                channel.close();
                //streamLogger.close();
            }
            NIOUtilities.clean(buffer, useMemoryMappedBuffer);
        } finally {
            //if(shxReader != null)
            //    shxReader.close();
        }
        //shxReader = null;
        channel = null;
        header = null;
    }

    public boolean supportsRandomAccess() {
        return randomAccessEnabled;
    }

    public boolean hasNext() throws IOException {
        return this.hasNext(true);
    }

    private boolean hasNext(boolean checkRecno) throws IOException {
        // don't read past the end of the file (provided currentShape accurately
        // represents the current position)
        if(currentShape > UNKNOWN )
            return true;

        // mark current position
        int position = buffer.position();

        // ensure the proper position, regardless of read or handler behavior
        //buffer.position(getNextOffset());

        // no more data left
        if (buffer.remaining() < 8)
            return false;

        // looks good
        boolean hasNext = true;
        if (checkRecno) {
            // record headers in big endian
            buffer.order(ByteOrder.BIG_ENDIAN);
            int declaredRecNo = buffer.getInt();
            hasNext = declaredRecNo == record.number + 1;
        }

        // reset things to as they were
        buffer.position(position);

        return hasNext;
    }

    /*private int getNextOffset() throws IOException {
        if(currentShape >= 0) {
            return this.toBufferOffset(shxReader.getOffsetInBytes(currentShape));
        } else {
            return this.toBufferOffset(record.end);
        }
    }
    */

    /**
     * Fetch the next record information.
     * 
     * @throws IOException
     * @return The record instance associated with this reader.
     */
    public Record nextRecord() throws IOException {

        // need to update position
        //buffer.position(getNextOffset());
        if(currentShape != UNKNOWN)
            currentShape++;

        // record header is big endian
        buffer.order(ByteOrder.BIG_ENDIAN);

        // read shape record header
        int recordNumber = buffer.getInt();
        // silly ESRI say contentLength is in 2-byte words
        // and ByteByffer uses bytes.
        // track the record location
        int recordLength = buffer.getInt() * 2;

        if (!buffer.isReadOnly() && !useMemoryMappedBuffer) {
            // capacity is less than required for the record
            // copy the old into the newly allocated
            if (buffer.capacity() < recordLength + 8) {
                this.currentOffset += buffer.position();
                ByteBuffer old = buffer;
                // ensure enough capacity for one more record header
                buffer = ensureCapacity(buffer, recordLength + 8,
                        useMemoryMappedBuffer);
                buffer.put(old);
                NIOUtilities.clean(old, useMemoryMappedBuffer);
                fill(buffer, channel);
                buffer.position(0);
            } else
            // remaining is less than record length
            // compact the remaining data and read again,
            // allowing enough room for one more record header
            if (buffer.remaining() < recordLength + 8) {
                this.currentOffset += buffer.position();
                buffer.compact();
                fill(buffer, channel);
                buffer.position(0);
            }
        }

        // shape record is all little endian
        buffer.order(ByteOrder.LITTLE_ENDIAN);

        // read the type, handlers don't need it
        ShapeType recordType = ShapeType.forID(buffer.getInt());

        // this usually happens if the handler logic is bunk,
        // but bad files could exist as well...
        if (recordType != ShapeType.NULL && recordType != fileShapeType) {
            throw new IllegalStateException("ShapeType changed illegally from "
                    + fileShapeType + " to " + recordType);
        }

        // peek at bounds, then reset for handler
        // many handler's may ignore bounds reading, but we don't want to
        // second guess them...
        buffer.mark();
        if (recordType.isMultiPoint()) {
            record.minX = buffer.getDouble();
            record.minY = buffer.getDouble();
            record.maxX = buffer.getDouble();
            record.maxY = buffer.getDouble();
        } else if (recordType != ShapeType.NULL) {
            record.minX = record.maxX = buffer.getDouble();
            record.minY = record.maxY = buffer.getDouble();
        }
        buffer.reset();

        record.offset = record.end;
        // update all the record info.
        record.length = recordLength;
        record.type = recordType;
        record.number = recordNumber;
        // remember, we read one int already...
        record.end = this.toFileOffset(buffer.position()) + recordLength - 4;
        // mark this position for the reader
        record.start = buffer.position();
        // clear any cached shape
        // record.shape = null;

        return record;
    }

    /**
     * Parses the shpfile counting the records.
     * 
     * @return the number of non-null records in the shapefile
     */
    public int getCount(int count) throws Exception {
        try {
            if (channel == null)
                return -1;
            count = 0;
            long offset = this.currentOffset;
            try {
                goTo(100);
            } catch (UnsupportedOperationException e) {
                return -1;
            }
            while (hasNext()) {
                count++;
                nextRecord();
            }

            goTo((int) offset);

        } catch (IOException ioe) {
            count = -1;
            // What now? This seems arbitrarily appropriate !
            throw new Exception("Problem reading shapefile record",
                    ioe);
        }
        return count;
    }

    /**
     * Moves the reader to the specified byte offset in the file. Mind that:
     * <ul>
     * <li>it's your responsibility to ensure the offset corresponds to the
     * actual beginning of a shape struct</li>
     * <li>once you call this, reading with hasNext/next on sparse shapefiles
     * will be broken (we don't know anymore at which shape we are)</li>
     * </ul>
     * 
     * @param offset
     * @throws IOException
     * @throws UnsupportedOperationException
     */
    public void goTo(int offset) throws IOException,
            UnsupportedOperationException {
        if (randomAccessEnabled) {
            if (this.useMemoryMappedBuffer) {
                buffer.position(offset);
            } else {
                /*
                 * Check to see if requested offset is already loaded; ensure
                 * that record header is in the buffer
                 */
                if (this.currentOffset <= offset
                        && this.currentOffset + buffer.limit() >= offset + 8) {
                    buffer.position(this.toBufferOffset(offset));
                } else {
                    FileChannel fc = (FileChannel) this.channel;
                    fc.position(offset);
                    this.currentOffset = offset;
                    buffer.position(0);
                    buffer.limit(buffer.capacity());
                    fill(buffer, fc);
                    buffer.position(0);
                }
            }

            int oldRecordOffset = record.end;
            record.end = offset;
            try {
                hasNext(true); // don't check for next logical record equality
            } catch (IOException ioe) {
                record.end = oldRecordOffset;
                throw ioe;
            }
        } else {
            throw new UnsupportedOperationException("Random Access not enabled");
        }
    }

    /**
     * Converts file offset to buffer offset
     * 
     * @param offset
     *                The offset relative to the whole file
     * @return The offset relative to the current loaded portion of the file
     */
    private int toBufferOffset(int offset) {
        return (int) (offset - this.currentOffset);
    }

    /**
     * Converts buffer offset to file offset
     * 
     * @param offset
     *                The offset relative to the buffer
     * @return The offset relative to the whole file
     */
    private int toFileOffset(int offset) {
        return (int) (this.currentOffset + offset);
    }

    public String id() {
        return getClass().getName();
    }

    public void setFlatGeometry(boolean flatGeometry) {
        this.flatGeometry = flatGeometry;        
    }
}

1 个答案:

答案 0 :(得分:1)

我会使用支持良好的库来满足此要求。您可以使用Shapelib获取NDK(GDAL / OGR的组件)。

使用shapelib可以访问DBFGetRecordCount,它返回xBase文件中存在的记录数。

我创建了一个demo shapefile编写器应用程序,其中包含以下行:

const int recordCount = DBFGetRecordCount(hDBF);