001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2002, Centre for Computational Geography
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.data.shapefile.shp;
018:
019: import java.io.IOException;
020: import java.nio.ByteBuffer;
021: import java.nio.ByteOrder;
022: import java.nio.IntBuffer;
023: import java.nio.MappedByteBuffer;
024: import java.nio.channels.FileChannel;
025: import java.nio.channels.ReadableByteChannel;
026: import java.util.logging.Logger;
027:
028: import org.geotools.data.shapefile.StreamLogging;
029: import org.geotools.data.shapefile.shp.ShapefileHeader;
030: import org.geotools.resources.NIOUtilities;
031:
032: /** IndexFile parser for .shx files.<br>
033: * For now, the creation of index files is done in the ShapefileWriter. But this
034: * can be used to access the index.<br>
035: * For details on the index file, see <br>
036: * <a href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf"><b>"ESRI(r) Shapefile - A Technical Description"</b><br>
037: * * <i>'An ESRI White Paper . May 1997'</i></a>
038: *
039: * @author Ian Schneider
040: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/shp/IndexFile.java $
041: */
042: public class IndexFile {
043: private static final Logger LOGGER = org.geotools.util.logging.Logging
044: .getLogger("org.geotools.data.shapefile");
045:
046: private static final int RECS_IN_BUFFER = 2000;
047:
048: private boolean useMemoryMappedBuffer;
049: private FileChannel channel;
050: private int channelOffset;
051: private ByteBuffer buf = null;
052: private int lastIndex = -1;
053: private int recOffset;
054: private int recLen;
055: private ShapefileHeader header = null;
056: private int[] content;
057: private StreamLogging streamLogger = new StreamLogging("IndexFile");
058:
059: /** Load the index file from the given channel.
060: * @param channel The channel to read from.
061: * @throws IOException If an error occurs.
062: */
063: public IndexFile(ReadableByteChannel channel) throws IOException {
064: this (channel, false);
065: }
066:
067: /** Load the index file from the given channel.
068: * @param channel The channel to read from.
069: * @throws IOException If an error occurs.
070: */
071: public IndexFile(ReadableByteChannel channel,
072: boolean useMemoryMappedBuffer) throws IOException {
073: this .useMemoryMappedBuffer = useMemoryMappedBuffer;
074: streamLogger.open();
075: readHeader(channel);
076: if (channel instanceof FileChannel) {
077:
078: this .channel = (FileChannel) channel;
079: if (useMemoryMappedBuffer) {
080: LOGGER.finest("Memory mapping file...");
081: this .buf = this .channel.map(
082: FileChannel.MapMode.READ_ONLY, 0, this .channel
083: .size());
084:
085: this .channelOffset = 0;
086: } else {
087: LOGGER.finest("Reading from file...");
088: this .buf = ByteBuffer
089: .allocateDirect(8 * RECS_IN_BUFFER);
090: this .channelOffset = 100;
091: }
092:
093: } else {
094: LOGGER.finest("Loading all shx...");
095: readRecords(channel);
096: }
097: }
098:
099: /** Get the header of this index file.
100: * @return The header of the index file.
101: */
102: public ShapefileHeader getHeader() {
103: return header;
104: }
105:
106: private void readHeader(ReadableByteChannel channel)
107: throws IOException {
108: ByteBuffer buffer = ByteBuffer.allocateDirect(100);
109: while (buffer.remaining() > 0) {
110: channel.read(buffer);
111: }
112: buffer.flip();
113: header = new ShapefileHeader();
114: header.read(buffer, true);
115:
116: NIOUtilities.clean(buffer);
117: }
118:
119: private void readRecords(ReadableByteChannel channel)
120: throws IOException {
121: int remaining = (header.getFileLength() * 2) - 100;
122: ByteBuffer buffer = ByteBuffer.allocateDirect(remaining);
123: buffer.order(ByteOrder.BIG_ENDIAN);
124: while (buffer.remaining() > 0) {
125: channel.read(buffer);
126: }
127: buffer.flip();
128: int records = remaining / 4;
129: content = new int[records];
130: IntBuffer ints = buffer.asIntBuffer();
131: ints.get(content);
132: NIOUtilities.clean(buffer);
133: }
134:
135: private void readRecord(int index) throws IOException {
136: int pos = 100 + index * 8;
137: if (this .useMemoryMappedBuffer) {
138:
139: } else {
140: if (pos - this .channelOffset < 0
141: || this .channelOffset + buf.limit() <= pos
142: || this .lastIndex == -1) {
143: LOGGER.finest("Filling buffer...");
144: this .channelOffset = pos;
145: this .channel.position(pos);
146: buf.clear();
147: this .channel.read(buf);
148: buf.flip();
149: }
150: }
151:
152: buf.position(pos - this .channelOffset);
153: this .recOffset = buf.getInt();
154: this .recLen = buf.getInt();
155: this .lastIndex = index;
156: }
157:
158: public void close() throws IOException {
159: if (channel != null && channel.isOpen()) {
160: channel.close();
161: streamLogger.close();
162:
163: if (buf instanceof MappedByteBuffer) {
164: NIOUtilities.clean(buf);
165: } else {
166: buf.clear();
167: }
168: }
169: this .buf = null;
170: this .content = null;
171: }
172:
173: /**
174: * @see java.lang.Object#finalize()
175: */
176: protected void finalize() throws Throwable {
177: this .close();
178: super .finalize();
179: }
180:
181: /** Get the number of records in this index.
182: * @return The number of records.
183: */
184: public int getRecordCount() {
185: return (header.getFileLength() * 2 - 100) / 8;
186: }
187:
188: /** Get the offset of the record (in 16-bit words).
189: * @param index The index, from 0 to getRecordCount - 1
190: * @return The offset in 16-bit words.
191: * @throws IOException
192: */
193: public int getOffset(int index) throws IOException {
194: int ret = -1;
195:
196: if (this .channel != null) {
197: if (this .lastIndex != index) {
198: this .readRecord(index);
199: }
200:
201: ret = this .recOffset;
202: } else {
203: ret = content[2 * index];
204: }
205:
206: return ret;
207: }
208:
209: /** Get the offset of the record (in real bytes, not 16-bit words).
210: * @param index The index, from 0 to getRecordCount - 1
211: * @return The offset in bytes.
212: * @throws IOException
213: */
214: public int getOffsetInBytes(int index) throws IOException {
215: return this .getOffset(index) * 2;
216: }
217:
218: /** Get the content length of the given record in bytes, not 16 bit words.
219: * @param index The index, from 0 to getRecordCount - 1
220: * @return The lengh in bytes of the record.
221: * @throws IOException
222: */
223: public int getContentLength(int index) throws IOException {
224: int ret = -1;
225:
226: if (this .channel != null) {
227: if (this .lastIndex != index) {
228: this .readRecord(index);
229: }
230:
231: ret = this .recLen;
232: } else {
233: ret = content[2 * index + 1];
234: }
235:
236: return ret;
237: }
238:
239: }
|