package com.ximple.io.dgn7;
|
|
//~--- JDK imports ------------------------------------------------------------
|
|
import javax.swing.*;
|
import java.io.EOFException;
|
import java.io.File;
|
import java.io.FileInputStream;
|
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.apache.log4j.LogManager;
|
import org.apache.log4j.Logger;
|
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
|
|
/**
|
* Dgn7fileReader
|
*
|
* @author Ulysses
|
* @version 0.1
|
* @since 2006/5/17 下午 01:24:10
|
*/
|
public class Dgn7fileReader
|
{
|
private static final Logger logger = LogManager.getLogger("com.isimple.glyphjump.io.dgn7");
|
private Dgn7fileHeader header;
|
private ReadableByteChannel channel;
|
ByteBuffer buffer;
|
private ElementType fileElementType = ElementType.UNDEFINED;
|
private ByteBuffer headerTransfer;
|
private final Record record = new Record();
|
private final boolean randomAccessEnabled;
|
private Lock lock;
|
private boolean useMemoryMappedBuffer;
|
private long currentOffset = 0L;
|
private StreamLogging streamLogger = new StreamLogging("Shapefile Reader");
|
private int maxElementId = 0;
|
|
public Dgn7fileReader(ReadableByteChannel channel, boolean strict, boolean useMemoryMapped, Lock lock)
|
throws IOException, Dgn7fileException
|
{
|
this.channel = channel;
|
this.useMemoryMappedBuffer = useMemoryMapped;
|
streamLogger.open();
|
randomAccessEnabled = channel instanceof FileChannel;
|
this.lock = lock;
|
lock.lockRead();
|
init(strict);
|
}
|
|
public Dgn7fileReader(ReadableByteChannel channel, Lock lock) throws IOException, Dgn7fileException
|
{
|
this(channel, true, true, lock);
|
}
|
|
// 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;
|
}
|
|
public static Dgn7fileHeader readHeader(ReadableByteChannel channel, boolean strict) throws IOException
|
{
|
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
|
|
if (fill(buffer, channel) == -1)
|
{
|
throw new EOFException("Premature end of header");
|
}
|
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
int length = buffer.getShort(2) * 2;
|
ByteBuffer old = buffer;
|
|
old.position(0);
|
|
// ensure enough capacity for one more record header
|
buffer = ByteBuffer.allocateDirect(length + 4);
|
buffer.put(old);
|
|
if (fill(buffer, channel) == -1)
|
{
|
throw new EOFException("Premature end of header");
|
}
|
|
buffer.position(0);
|
|
Dgn7fileHeader header = new Dgn7fileHeader();
|
|
header.read(buffer, strict);
|
|
return header;
|
}
|
|
public Dgn7fileHeader getHeader()
|
{
|
return header;
|
}
|
|
public void close() throws IOException
|
{
|
lock.unlockRead();
|
|
if (channel.isOpen())
|
{
|
channel.close();
|
streamLogger.close();
|
}
|
|
if (buffer instanceof MappedByteBuffer)
|
{
|
NIOUtilities.clean(buffer);
|
}
|
|
channel = null;
|
header = null;
|
}
|
|
public boolean supportsRandomAccess()
|
{
|
return randomAccessEnabled;
|
}
|
|
public Record nextElement() throws IOException, Dgn7fileException
|
{
|
// need to update position
|
buffer.position(this.toBufferOffset(record.end));
|
|
// record header is big endian
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
// read shape record header
|
int recordNumber = ++maxElementId;
|
short signature = buffer.getShort();
|
|
// byte type = (byte) (buffer.get() & 0x7f);
|
byte type = (byte) ((signature >>> 8) & 0x007f);
|
|
// silly Bentley say contentLength is in 2-byte words
|
// and ByteByffer uses bytes.
|
// track the record location
|
int elementLength = (buffer.getShort() * 2) + 4;
|
|
if (!buffer.isReadOnly() &&!useMemoryMappedBuffer)
|
{
|
// capacity is less than required for the record
|
// copy the old into the newly allocated
|
if (buffer.capacity() < elementLength)
|
{
|
this.currentOffset += buffer.position();
|
|
ByteBuffer old = buffer;
|
|
// ensure enough capacity for one more record header
|
buffer = ensureCapacity(buffer, elementLength, useMemoryMappedBuffer);
|
buffer.put(old);
|
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() < elementLength)
|
{
|
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
|
ElementType recordType = ElementType.forID(type);
|
|
logger.debug("nextElement at " + this.toBufferOffset(record.end) + ":type=" + type);
|
|
// this usually happens if the handler logic is bunk,
|
// but bad files could exist as well...
|
|
/*
|
* if (recordType != ElementType.NULL && recordType != fileElementType)
|
* {
|
* throw new IllegalStateException("ShapeType changed illegally from " + fileElementType + " 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())
|
{
|
int lowCoorX = buffer.getInt();
|
|
lowCoorX = Utility.convertFromDGN(lowCoorX);
|
record.minX = Utility.converUnitToCoord(lowCoorX);
|
|
int lowCoorY = buffer.getInt();
|
|
lowCoorY = Utility.convertFromDGN(lowCoorY);
|
record.minY = Utility.converUnitToCoord(lowCoorY);
|
|
int lowCoorZ = buffer.getInt();
|
|
lowCoorZ = Utility.convertFromDGN(lowCoorZ);
|
record.minZ = Utility.converUnitToCoord(lowCoorZ);
|
|
int highCoorX = buffer.getInt();
|
|
highCoorX = Utility.convertFromDGN(highCoorX);
|
record.maxX = Utility.converUnitToCoord(highCoorX);
|
|
int highCoorY = buffer.getInt();
|
|
highCoorY = Utility.convertFromDGN(highCoorY);
|
record.maxY = Utility.converUnitToCoord(highCoorY);
|
|
int highCoorZ = buffer.getInt();
|
|
highCoorZ = Utility.convertFromDGN(highCoorZ);
|
record.maxZ = Utility.converUnitToCoord(highCoorZ);
|
}
|
|
buffer.reset();
|
record.offset = record.end;
|
|
// update all the record info.
|
record.length = elementLength;
|
record.signature = signature;
|
record.number = recordNumber;
|
|
// remember, we read one int already...
|
record.end = this.toFileOffset(buffer.position()) + elementLength - 4;
|
// record.end = this.toFileOffset(buffer.position()) + elementLength;
|
|
// mark this position for the reader
|
record.start = buffer.position();
|
|
// clear any cached record
|
record.handler = recordType.getElementHandler();
|
record.element = null;
|
|
return record;
|
}
|
|
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 + 4))
|
{
|
buffer.position(this.toBufferOffset(offset));
|
} else
|
{
|
FileChannel fc = (FileChannel) this.channel;
|
|
fc.position(offset);
|
this.currentOffset = offset;
|
buffer.position(0);
|
fill(buffer, fc);
|
buffer.position(0);
|
}
|
}
|
|
int oldRecordOffset = record.end;
|
|
record.end = offset;
|
|
try
|
{
|
hasNext();
|
} catch (IOException ioe)
|
{
|
record.end = oldRecordOffset;
|
|
throw ioe;
|
}
|
} else
|
{
|
throw new UnsupportedOperationException("Random Access not enabled");
|
}
|
}
|
|
public Record elementAt(int offset) throws IOException, UnsupportedOperationException, Dgn7fileException
|
{
|
if (randomAccessEnabled)
|
{
|
this.goTo(offset);
|
|
return nextElement();
|
}
|
|
throw new UnsupportedOperationException("Random Access not enabled");
|
}
|
|
public boolean hasNext() throws IOException
|
{
|
// mark current position
|
int position = buffer.position();
|
|
// ensure the proper position, regardless of read or handler behavior
|
try
|
{
|
buffer.position(this.toBufferOffset(record.end));
|
} catch (IllegalArgumentException e)
|
{
|
logger.warn("position=" + this.toBufferOffset(record.end), e);
|
|
return false;
|
}
|
|
// no more data left
|
if (buffer.remaining() < 4)
|
{
|
return false;
|
}
|
|
// looks good
|
boolean hasNext = true;
|
short type = buffer.getShort();
|
|
if (type == -1)
|
{
|
hasNext = false;
|
}
|
|
// reset things to as they were
|
buffer.position(position);
|
|
return hasNext;
|
}
|
|
private void init(boolean strict) throws IOException, Dgn7fileException
|
{
|
header = readHeader(channel, strict);
|
|
// fileElementType = header.getElementType();
|
// handler = fileElementType.getElementHandler();
|
|
// recordHeader = ByteBuffer.allocateDirect(4);
|
// recordHeader.order(ByteOrder.BIG_ENDIAN);
|
// if (handler == null)
|
// {
|
// throw new IOException("Unsuported shape type:" + fileElementType);
|
// }
|
if ((channel instanceof FileChannel) && useMemoryMappedBuffer)
|
{
|
FileChannel fc = (FileChannel) channel;
|
|
buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
|
|
// buffer.position(100);
|
buffer.position(header.size());
|
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 = header.size();
|
}
|
|
headerTransfer = ByteBuffer.allocate(4);
|
headerTransfer.order(ByteOrder.LITTLE_ENDIAN);
|
|
// make sure the record end is set now...
|
record.end = toFileOffset(buffer.position());
|
}
|
|
private int toBufferOffset(int offset)
|
{
|
return (int) (offset - currentOffset);
|
}
|
|
private int toFileOffset(int offset)
|
{
|
return (int) (currentOffset + offset);
|
}
|
|
public int getCount(int count) throws Dgn7fileException
|
{
|
try
|
{
|
if (channel == null)
|
{
|
return -1;
|
}
|
|
count = 0;
|
|
for (int tmp = readElement(); tmp != -1; tmp = readElement())
|
{
|
count += tmp;
|
}
|
} catch (IOException ioe)
|
{
|
count = -1;
|
|
// What now? This seems arbitrarily appropriate !
|
throw new Dgn7fileException("Problem reading dgnfile record", ioe);
|
}
|
|
return count;
|
}
|
|
public int getCount() throws Dgn7fileException
|
{
|
return getCount(0);
|
}
|
|
private int readElement() throws IOException
|
{
|
if (!fillBuffer())
|
{
|
return -1;
|
}
|
|
// burn the record number
|
buffer.getInt();
|
|
if (!fillBuffer())
|
{
|
return -1;
|
}
|
|
int recordlength = buffer.getInt() * 2;
|
|
// Going to read the first 4 bytes of the record so
|
// subtract that from the record length
|
recordlength -= 4;
|
|
if (!fillBuffer())
|
{
|
return -1;
|
}
|
|
// read record type (used to determine if record is a null record)
|
int type = buffer.getInt();
|
|
// go to end of record
|
while (buffer.limit() < buffer.position() + recordlength)
|
{
|
recordlength -= buffer.limit() - buffer.position();
|
buffer.clear();
|
|
if (channel.read(buffer) < 1)
|
{
|
return -1;
|
}
|
}
|
|
buffer.position(buffer.position() + recordlength);
|
|
// return 0 if record is null. Null records should be counted.
|
if (type == 0)
|
{
|
// this is a null feature
|
return 0;
|
}
|
|
return 1;
|
}
|
|
private boolean fillBuffer() throws IOException
|
{
|
int result = 1;
|
|
if (buffer.limit() <= buffer.position() + 4)
|
{
|
result = fill(buffer, channel);
|
}
|
|
return result > 0;
|
}
|
|
public static void main(String[] args)
|
{
|
JFileChooser jfc = new JFileChooser("D:/TEMP");
|
File f = null;
|
int r = jfc.showOpenDialog(new JFrame());
|
|
if (r == JFileChooser.APPROVE_OPTION)
|
{
|
try
|
{
|
f = jfc.getSelectedFile();
|
|
FileChannel channel = new FileInputStream(f).getChannel();
|
Dgn7fileReader reader = new Dgn7fileReader(channel, new Lock());
|
|
System.out.println(reader.getHeader().toString());
|
|
GeometryFactory factory = new GeometryFactory();
|
int count, size;
|
|
count = 0;
|
size = 0;
|
|
try
|
{
|
Element lastComplex = null;
|
|
while (reader.hasNext())
|
{
|
size++;
|
|
Dgn7fileReader.Record record = reader.nextElement();
|
|
if (record.element() != null)
|
{
|
Element element = (Element) record.element();
|
ElementType type = element.getElementType();
|
|
if ((!type.isComplexElement()) && (!element.isComponentElement()))
|
{
|
if (lastComplex != null)
|
{
|
// @todo add process in here
|
count++;
|
lastComplex = null;
|
}
|
|
// @todo add process in here
|
count++;
|
} else if (element.isComponentElement())
|
{
|
if (lastComplex != null)
|
{
|
((ComplexElement) lastComplex).add(element);
|
}
|
} else if (type.isComplexElement())
|
{
|
if (lastComplex == null)
|
{
|
lastComplex = element;
|
} else
|
{
|
// @todo add process in here
|
count++;
|
lastComplex = element;
|
}
|
}
|
}
|
}
|
} catch (IOException e)
|
{
|
logger.warn("Stop read dgn file", e);
|
} catch (Dgn7fileException e)
|
{
|
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
|
} finally
|
{
|
reader.close();
|
}
|
|
System.out.println("count=" + count + " size=" + size);
|
// reader.close();
|
} catch (IOException ioe)
|
{
|
System.out.println(ioe);
|
ioe.printStackTrace();
|
} catch (Dgn7fileException e)
|
{
|
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
|
}
|
}
|
|
System.exit(0);
|
}
|
|
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
|
short signature = 0;
|
|
/**
|
* The minimum X value.
|
*/
|
public double minX;
|
|
/**
|
* The minimum Y value.
|
*/
|
public double minY;
|
|
/**
|
* The minimum Z value.
|
*/
|
public double minZ;
|
|
/**
|
* The maximum X value.
|
*/
|
public double maxX;
|
|
/**
|
* The maximum Y value.
|
*/
|
public double maxY;
|
|
/**
|
* The maximum Z value.
|
*/
|
public double maxZ;
|
|
// ElementType type;
|
int end = 0; // Relative to the whole file
|
Object element = null;
|
IElementHandler handler;
|
|
public Object element()
|
{
|
if (element == null)
|
{
|
buffer.position(start);
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
if (handler == null)
|
{
|
return null;
|
}
|
|
element = handler.read(buffer, signature, length);
|
}
|
|
return element;
|
}
|
|
public int offset()
|
{
|
return offset;
|
}
|
|
/**
|
* A summary of the record.
|
*/
|
public String toString()
|
{
|
return "Record " + number + " length " + length + " bounds " + minX + "," + minY + " " + maxX + "," + maxY;
|
}
|
}
|
}
|