DbfReader.java
package nl.tudelft.simulation.dsol.animation.gis.esri;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import nl.tudelft.simulation.dsol.animation.gis.io.Endianness;
import nl.tudelft.simulation.dsol.animation.gis.io.ObjectEndianInputStream;
/**
* This class reads a dbf file (in dBase III format), as used in ESRI ShapeFiles.
* <p>
* Copyright (c) 2020-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
* </p>
* <p>
* The dsol-animation-gis project is based on the gisbeans project that has been part of DSOL since 2002, originally by Peter
* Jacobs and Paul Jacobs.
* </p>
* @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
*/
public class DbfReader implements Serializable
{
/** */
private static final long serialVersionUID = 20201223L;
/** the FBFFile. */
private URL dbfFile;
/** the numberofColumns. */
private int[] columnLength;
/** the names of the columns. */
private String[] columnNames;
/** the headerLength. */
private int headerLength = 0;
/** the number of columns. */
private int numColumns = 0;
/** the number of records. */
private int numRecords = 0;
/** the length of the records. */
private int recordLength = 0;
/**
* Construct a DbfReader.
* @param dbfFile URL; the URL of the dbfFile
* @throws IOException whenever url does not occur to exist.
*/
public DbfReader(final URL dbfFile) throws IOException
{
this.dbfFile = dbfFile;
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(dbfFile.openStream());
if (dbfInput.readByte() != 3)
{
throw new IOException("dbf file does not seem to be a Dbase III file");
}
dbfInput.skipBytes(3);
dbfInput.setEndianness(Endianness.LITTLE_ENDIAN);
this.numRecords = dbfInput.readInt();
this.headerLength = dbfInput.readShort();
this.numColumns = (this.headerLength - 33) / 32;
this.recordLength = dbfInput.readShort();
this.columnLength = new int[this.numColumns];
this.columnNames = new String[this.numColumns];
dbfInput.skipBytes(20);
for (int i = 0; i < this.numColumns; i++)
{
StringBuffer buffer = new StringBuffer();
for (int j = 0; j < 10; j++)
{
byte b = dbfInput.readByte();
if (b > 31)
{
buffer.append((char) b);
}
}
this.columnNames[i] = buffer.toString();
dbfInput.setEndianness(Endianness.BIG_ENDIAN);
dbfInput.readChar();
dbfInput.skipBytes(4);
this.columnLength[i] = dbfInput.readByte();
if (this.columnLength[i] < 0)
{
this.columnLength[i] += 256;
}
dbfInput.skipBytes(15);
}
dbfInput.close();
}
/**
* returns the columnNames.
* @return String[] the columnNames
*/
public String[] getColumnNames()
{
return this.columnNames;
}
/**
* returns the row.
* @param rowNumber int; the rowNumber
* @return String[] the attributes of the row
* @throws IOException on read failure
* @throws IndexOutOfBoundsException whenever the rowNumber > numRecords
*/
public String[] getRow(final int rowNumber) throws IOException, IndexOutOfBoundsException
{
if (rowNumber > this.numRecords)
{
throw new IndexOutOfBoundsException("dbfFile : rowNumber > numRecords");
}
// Either we may not cache of the cache is still empty
String[] row = new String[this.numColumns];
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(this.dbfFile.openStream());
dbfInput.skipBytes(this.headerLength + 1);
dbfInput.skipBytes(rowNumber * this.recordLength);
for (int i = 0; i < this.numColumns; i++)
{
byte[] bytes = new byte[this.columnLength[i]];
dbfInput.read(bytes);
row[i] = new String(bytes);
}
dbfInput.close();
return row;
}
/**
* returns a table of all attributes stored for the particular dbf-file.
* @return String[][] a table of attributes
* @throws IOException an IOException
*/
public String[][] getRows() throws IOException
{
String[][] result = new String[this.numRecords][this.numColumns];
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(this.dbfFile.openStream());
dbfInput.skipBytes(this.headerLength + 1);
for (int row = 0; row < this.numRecords; row++)
{
for (int col = 0; col < this.numColumns; col++)
{
byte[] bytes = new byte[this.columnLength[col]];
dbfInput.read(bytes);
result[row][col] = new String(bytes);
if (col == this.numColumns - 1)
{
dbfInput.skipBytes(1);
}
}
}
dbfInput.close();
return result;
}
/**
* returns the array of rowNumbers belonging to a attribute/column pair.
* @param attribute String; the attribute value
* @param columnName String; the name of the column
* @return int[] the array of shape numbers.
* @throws IOException on read failure
*/
public int[] getRowNumbers(final String attribute, final String columnName) throws IOException
{
ArrayList<Integer> result = new ArrayList<>();
String[][] rows = this.getRows();
for (int col = 0; col < this.numColumns; col++)
{
if (this.columnNames[col].equals(columnName))
{
for (int row = 0; row < this.numRecords; row++)
{
if (rows[row][col].equals(attribute))
{
result.add(Integer.valueOf(row));
}
}
}
}
int[] array = new int[result.size()];
for (int i = 0; i < array.length; i++)
{
array[i] = result.get(i).intValue();
}
return array;
}
}