📄 shapefilereader.java
字号:
/*
* GeoTools - OpenSource mapping toolkit
* http://geotools.org
* (C) 2002-2006, Geotools Project Managment Committee (PMC)
* (C) 2002, Centre for Computational Geography
*
* 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.
*/
package org.geotools.data.shapefile.shp;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import org.geotools.data.DataSourceException;
import org.geotools.data.shapefile.FileReader;
import org.geotools.data.shapefile.ShpFileType;
import org.geotools.data.shapefile.ShpFiles;
import org.geotools.data.shapefile.StreamLogging;
import org.geotools.resources.NIOUtilities;
/**
* The general use of this class is: <CODE><PRE>
*
* FileChannel in = new FileInputStream("thefile.dbf").getChannel();
* ShapefileReader r = new ShapefileReader( in ) while (r.hasNext()) { Geometry
* shape = (Geometry) r.nextRecord().shape() // do stuff } r.close();
*
* </PRE></CODE> You don't have to immediately ask for the shape from the record. The
* record will contain the bounds of the shape and will only read the shape when
* the shape() method is called. This ShapefileReader.Record is the same object
* every time, so if you need data from the Record, be sure to copy it.
*
* @author jamesm
* @author aaime
* @author Ian Schneider
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/shp/ShapefileReader.java $
*/
public class ShapefileReader implements FileReader {
/**
* The reader returns only one Record instance in its lifetime. The record
* contains the current record information.
*/
public final class Record {
int length;
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;
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);
shape = handler.read(buffer, type);
}
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;
private boolean useMemoryMappedBuffer;
private long currentOffset = 0L;
private StreamLogging streamLogger = new StreamLogging("Shapefile Reader");
/**
* Creates a new instance of ShapeFile.
*
* @param shapefileFiles
* The ReadableByteChannel this reader will use.
* @param strict
* True to make the header parsing throw Exceptions if the
* version or magic number are incorrect.
* @throws IOException
* If problems arise.
* @throws ShapefileException
* If for some reason the file contains invalid records.
*/
public ShapefileReader(ShpFiles shapefileFiles, boolean strict,
boolean useMemoryMapped) throws IOException, ShapefileException {
this.channel = shapefileFiles.getReadChannel(ShpFileType.SHP, this);
this.useMemoryMappedBuffer = useMemoryMapped;
streamLogger.open();
randomAccessEnabled = channel instanceof FileChannel;
init(strict);
}
// convenience to peak at a header
/**
* A short cut for reading the header from the given channel.
*
* @param channel
* The channel to read from.
* @param strict
* True to make the header parsing throw Exceptions if the
* version or magic number are incorrect.
* @throws IOException
* If problems arise.
* @return A ShapefileHeader object.
*/
public static ShapefileHeader readHeader(ReadableByteChannel channel,
boolean strict) throws IOException {
ByteBuffer buffer = ByteBuffer.allocateDirect(100);
if (fill(buffer, channel) == -1) {
throw new EOFException("Premature end of header");
}
buffer.flip();
ShapefileHeader header = new ShapefileHeader();
header.read(buffer, strict);
NIOUtilities.clean(buffer);
return header;
}
// 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...
public static 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()) {
// if (record.ready) {
buffer = ByteBuffer.allocateDirect(limit);
// }
// else {
// throw new IllegalArgumentException("next before hasNext");
// }
}
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);
}
if (r == -1) {
buffer.limit(buffer.position());
}
return r;
}
private void init(boolean strict) throws IOException, ShapefileException {
header = readHeader(channel, strict);
fileShapeType = header.getShapeType();
handler = fileShapeType.getShapeHandler();
// recordHeader = ByteBuffer.allocateDirect(8);
// recordHeader.order(ByteOrder.BIG_ENDIAN);
if (handler == null) {
throw new IOException("Unsuported shape type:" + fileShapeType);
}
if (channel instanceof FileChannel && useMemoryMappedBuffer) {
FileChannel fc = (FileChannel) channel;
buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
buffer.position(100);
this.currentOffset = 0;
} else {
// force useMemoryMappedBuffer to false
this.useMemoryMappedBuffer = false;
// start with 8K buffer
buffer = ByteBuffer.allocateDirect(8 * 1024);
fill(buffer, channel);
buffer.flip();
this.currentOffset = 100;
}
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 {
if (channel.isOpen()) {
channel.close();
streamLogger.close();
}
if (buffer instanceof MappedByteBuffer) {
NIOUtilities.clean(buffer);
}
channel = null;
header = null;
}
public boolean supportsRandomAccess() {
return randomAccessEnabled;
}
/**
* If there exists another record. Currently checks the stream for the
* presence of 8 more bytes, the length of a record. If this is true and the
* record indicates the next logical record number, there exists more
* records.
*
* @throws IOException
* @return True if has next record, false otherwise.
*/
public boolean hasNext() throws IOException {
return this.hasNext(true);
}
/**
* If there exists another record. Currently checks the stream for the
* presence of 8 more bytes, the length of a record. If this is true and the
* record indicates the next logical record number (if checkRecord == true),
* there exists more records.
*
* @param checkRecno
* If true then record number is checked
* @throws IOException
* @return True if has next record, false otherwise.
*/
private boolean hasNext(boolean checkRecno) throws IOException {
// mark current position
int position = buffer.position();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -