/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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;
import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;
import org.geotools.data.AbstractAttributeIO;
import org.geotools.data.AttributeReader;
import org.geotools.data.shapefile.dbf.DbaseFileHeader;
import org.geotools.data.shapefile.dbf.DbaseFileReader;
import org.geotools.data.shapefile.indexed.RecordNumberTracker;
import org.geotools.data.shapefile.shp.ShapefileReader;
import org.geotools.renderer.ScreenMap;
import org.opengis.feature.type.AttributeDescriptor;
import com.vividsolutions.jts.geom.Envelope;
/**
* An AttributeReader implementation for Shapefile. Pretty straightforward.
* <BR/>The default geometry is at position 0, and all dbf columns follow.
* <BR/>The dbf file may not be necessary, if not, just pass null as the
* DbaseFileReader
*
*
* @source $URL$
*/
public class ShapefileAttributeReader extends AbstractAttributeIO implements
AttributeReader, RecordNumberTracker {
protected ShapefileReader shp;
protected DbaseFileReader dbf;
protected DbaseFileReader.Row row;
protected ShapefileReader.Record record;
int cnt;
protected int[] dbfindexes;
protected Envelope targetBBox;
protected double simplificationDistance;
protected Object geometry;
protected ScreenMap screenMap;
protected boolean featureAvailable = false;
protected boolean flatFeature = false;
public ShapefileAttributeReader(List<AttributeDescriptor> atts,
ShapefileReader shp, DbaseFileReader dbf) {
this(atts.toArray(new AttributeDescriptor[0]), shp, dbf);
}
/**
* Sets a search area. If the geometry does not fall into it
* it won't be read and will return a null geometry instead
* @param envelope
*/
public void setTargetBBox(Envelope envelope) {
this.targetBBox = envelope;
}
public void setSimplificationDistance(double distance) {
this.simplificationDistance = distance;
}
public void setScreenMap(ScreenMap screenMap) {
this.screenMap = screenMap;
}
/**
* Create the shapefile reader
*
* @param atts -
* the attributes that we are going to read.
* @param shp -
* the shapefile reader, required
* @param dbf -
* the dbf file reader. May be null, in this case no
* attributes will be read from the dbf file
*/
public ShapefileAttributeReader(AttributeDescriptor[] atts,
ShapefileReader shp, DbaseFileReader dbf) {
super(atts);
this.shp = shp;
this.dbf = dbf;
if(dbf != null) {
dbfindexes = new int[atts.length];
DbaseFileHeader head = dbf.getHeader();
AT: for (int i = 0; i < atts.length; i++) {
String attName = atts[i].getLocalName();
int count = 0;
if (atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_NAME) != null){
attName = (String)atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_NAME);
count = (Integer)atts[i].getUserData().get(ShapefileDataStore.ORIGINAL_FIELD_DUPLICITY_COUNT);
}
for(int j = 0; j < head.getNumFields(); j++) {
if(head.getFieldName(j).equals(attName) && count-- <= 0){
dbfindexes[i] = j;
continue AT;
}
}
dbfindexes[i] = -1; // geometry
}
}
}
public void close() throws IOException {
try {
if (shp != null) {
shp.close();
}
if (dbf != null) {
dbf.close();
}
} finally {
row = null;
record = null;
shp = null;
dbf = null;
}
}
boolean internalReadersHaveNext() throws IOException {
int n = shp.hasNext() ? 1 : 0;
if (dbf != null) {
n += (dbf.hasNext() ? 2 : 0);
}
if ((n == 3) || ((n == 1) && (dbf == null))) {
return true;
}
if (n == 0) {
return false;
}
throw new IOException(((n == 1) ? "Shp" : "Dbf") + " has extra record");
}
public boolean hasNext() throws IOException {
while(!featureAvailable && internalReadersHaveNext()) {
record = shp.nextRecord();
// read the geometry, so that we can decide if this row is to be skipped or not
Envelope envelope = record.envelope();
boolean skip = false;
// ... if geometry is out of the target bbox, skip both geom and row
if (targetBBox != null && !targetBBox.isNull() && !targetBBox.intersects(envelope)) {
geometry = null;
skip = true;
// ... if the geometry is awfully small avoid reading it (unless it's a point)
} else if (simplificationDistance > 0 && envelope.getWidth() < simplificationDistance
&& envelope.getHeight() < simplificationDistance) {
try {
if(screenMap != null && screenMap.checkAndSet(envelope)) {
geometry = null;
skip = true;
} else {
// if we are using the screenmap better provide a slightly modified
// version of the geometry bounds or we'll end up with many holes
// in the rendering
geometry = record.getSimplifiedShape(screenMap);
}
} catch(Exception e) {
geometry = record.getSimplifiedShape();
}
// ... otherwise business as usual
} else {
geometry = record.shape();
}
// read the dbf only if the geometry was not skipped
if (dbf != null) {
if(skip) {
dbf.skip();
row = null;
} else {
row = dbf.readRow();
}
} else {
row = null;
}
featureAvailable = !skip;
}
return featureAvailable;
}
public void next() throws IOException {
if(!hasNext()) {
throw new NoSuchElementException("hasNext() returned false");
}
featureAvailable = false;
}
public Object read(int param) throws IOException,
java.lang.ArrayIndexOutOfBoundsException {
int index = dbfindexes != null ? dbfindexes[param] : -1;
switch (index) {
case -1:
return geometry; // geometry is considered dbf index -1
default:
if (row != null) {
return row.read( index );
}
return null;
}
}
public int getRecordNumber() {
return this.record.number;
}
}