我正在尝试获取形状文件中的记录数。该文件是多边形形状文件。我能够获取第一条记录的详细信息,但是当计数增加到 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;
}
}