package org.geotools.data.shapefile;
import static org.geotools.data.shapefile.ShpFileType.*;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.geotools.data.AbstractFileDataStore;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultFIDReader;
import org.geotools.data.EmptyFeatureReader;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Query;
import org.geotools.data.ResourceInfo;
import org.geotools.data.ServiceInfo;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.dbf.DbaseFileException;
import org.geotools.data.shapefile.dbf.DbaseFileHeader;
import org.geotools.data.shapefile.dbf.DbaseFileReader;
import org.geotools.data.shapefile.indexed.ShapeFIDReader;
import org.geotools.data.shapefile.prj.PrjFileReader;
import org.geotools.data.shapefile.shp.IndexFile;
import org.geotools.data.shapefile.shp.JTSUtilities;
import org.geotools.data.shapefile.shp.ShapeType;
import org.geotools.data.shapefile.shp.ShapefileException;
import org.geotools.data.shapefile.shp.ShapefileHeader;
import org.geotools.data.shapefile.shp.ShapefileReader;
import org.geotools.data.shapefile.shp.ShapefileWriter;
import org.geotools.data.shapefile.shp.xml.ShpXmlFileReader;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.Hints;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.FeatureTypes;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.type.BasicFeatureTypes;
import org.geotools.filter.FilterAttributeExtractor;
import org.geotools.filter.visitor.ExtractBoundsFilterVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.renderer.ScreenMap;
import org.geotools.resources.Classes;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
public class ShapefileDataStore extends AbstractFileDataStore {
public static final Charset DEFAULT_STRING_CHARSET = Charset
.forName("ISO-8859-1");
private static final Set HINTS = Collections.unmodifiableSet(new HashSet(
Arrays.asList(new Object[] { Hints.FEATURE_DETACHED, Hints.SCREENMAP })));
protected final ShpFiles shpFiles;
protected URI namespace = null; protected SimpleFeatureType schema; protected boolean useMemoryMappedBuffer = true;
protected Charset dbfCharset;
private ServiceInfo info;
private ResourceInfo resourceInfo;
public ShapefileDataStore(URL url) throws java.net.MalformedURLException {
this(url, false, DEFAULT_STRING_CHARSET);
}
public ShapefileDataStore(URL url, boolean useMemoryMappedBuffer)
throws java.net.MalformedURLException {
this(url, useMemoryMappedBuffer, DEFAULT_STRING_CHARSET);
}
public ShapefileDataStore(URL url, boolean useMemoryMappedBuffer,
Charset dbfCharset) throws java.net.MalformedURLException {
this(url, null, false, dbfCharset);
}
public ShapefileDataStore(URL url, URI namespace)
throws java.net.MalformedURLException {
this(url, namespace, false, DEFAULT_STRING_CHARSET);
}
public ShapefileDataStore(URL url, URI namespace, boolean useMemoryMapped,
Charset dbfCharset) throws java.net.MalformedURLException {
shpFiles = new ShpFiles(url);
this.namespace = namespace;
if (!isLocal() || !shpFiles.exists(SHP)) {
this.useMemoryMappedBuffer = false;
} else {
this.useMemoryMappedBuffer = useMemoryMapped;
}
this.useMemoryMappedBuffer = useMemoryMapped;
this.dbfCharset = dbfCharset;
}
public ShapefileDataStore(URL url, URI namespace, boolean useMemoryMapped)
throws java.net.MalformedURLException {
this(url, namespace, useMemoryMapped, DEFAULT_STRING_CHARSET);
}
public synchronized ServiceInfo getInfo(){
if( info == null ){
if( isLocal() ){
info = new ShapefileFileServiceInfo( this );
}
else {
info = new ShapefileURLServiceInfo( this );
}
}
return info;
}
synchronized ResourceInfo getInfo( String typeName ) {
if( resourceInfo == null ){
if( isLocal() ){
resourceInfo = new ShapefileFileResourceInfo( this );
}
else {
resourceInfo = new ShapefileURLResourceInfo( this );
}
}
return resourceInfo;
}
public void setStringCharset(Charset stringCharset) {
this.dbfCharset = stringCharset;
}
public Charset getStringCharset() {
return dbfCharset;
}
protected Map createMetadata(String typeName) {
String urlString = shpFiles.get(SHP_XML);
if (urlString == null) {
return Collections.EMPTY_MAP;
}
try {
ShpXmlFileReader reader = new ShpXmlFileReader(shpFiles);
Map map = new HashMap();
map.put("shp.xml", reader.parse());
return map;
} catch (Throwable t) {
LOGGER.warning("Could not parse " + urlString + ":"
+ t.getLocalizedMessage());
return Collections.EMPTY_MAP;
}
}
public boolean isLocal() {
return shpFiles.isLocal();
}
protected FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(String typeName)
throws IOException {
typeCheck(typeName);
return getFeatureReader();
}
public FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader() throws IOException {
try {
return createFeatureReader(getSchema().getTypeName(),
getAttributesReader(true, null), schema);
} catch (SchemaException se) {
throw new DataSourceException("Error creating schema", se);
}
}
protected FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(String typeName, Query query)
throws IOException {
String[] propertyNames = query.getPropertyNames();
String defaultGeomName = schema.getGeometryDescriptor().getLocalName();
FilterAttributeExtractor extractor = new FilterAttributeExtractor(schema);
Filter filter = query.getFilter();
filter.accept(extractor, null);
String[] filterAttnames = extractor.getAttributeNames();
if ((propertyNames != null)
&& (propertyNames.length == 1)
&& propertyNames[0].equals(defaultGeomName)
&& (filterAttnames.length == 0 || (filterAttnames.length == 1 && filterAttnames[0]
.equals(defaultGeomName)))) {
try {
SimpleFeatureType newSchema = DataUtilities.createSubType(
schema, propertyNames);
return createFeatureReader(typeName,
getAttributesReader(false, query), newSchema);
} catch (SchemaException se) {
throw new DataSourceException("Error creating schema", se);
}
}
try {
return createFeatureReader(getSchema().getTypeName(),
getAttributesReader(true, query), schema);
} catch (SchemaException se) {
throw new DataSourceException("Error creating schema", se);
}
}
protected GeometryFactory getGeometryFactory(Hints hints) {
if(hints == null)
return new GeometryFactory();
GeometryFactory geometryFactory = (GeometryFactory) hints.get(Hints.JTS_GEOMETRY_FACTORY);
if (geometryFactory == null) {
CoordinateSequenceFactory csFactory =
(CoordinateSequenceFactory) hints.get(Hints.JTS_COORDINATE_SEQUENCE_FACTORY);
if (csFactory != null) {
geometryFactory = new GeometryFactory(csFactory);
}
}
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}
protected org.geotools.data.FIDFeatureReader createFeatureReader(String typeName,
ShapefileAttributeReader reader, SimpleFeatureType readerSchema)
throws SchemaException {
return new org.geotools.data.FIDFeatureReader(reader,
new ShapeFIDReader(readerSchema, reader), readerSchema);
}
protected ShapefileAttributeReader getAttributesReader(boolean readDbf, Query q)
throws IOException {
List<AttributeDescriptor> atts = (schema == null) ? readAttributes()
: schema.getAttributeDescriptors();
GeometryFactory geometryFactory;
if(q != null) {
geometryFactory = getGeometryFactory(q.getHints());
} else {
geometryFactory = new GeometryFactory();
}
ShapefileAttributeReader result;
ShapefileReader shapeReader = openShapeReader(geometryFactory);
if (!readDbf) {
LOGGER.fine("The DBF file won't be opened since no attributes will be read from it");
atts = new ArrayList(1);
atts.add(schema.getGeometryDescriptor());
result =new ShapefileAttributeReader(atts, shapeReader, null);
} else {
result = new ShapefileAttributeReader(atts, shapeReader, openDbfReader());
}
if(q != null) {
Envelope bbox = new ReferencedEnvelope();
bbox = (Envelope) q.getFilter().accept(
ExtractBoundsFilterVisitor.BOUNDS_VISITOR, bbox);
if(bbox != null && !bbox.isNull()) {
result.setTargetBBox(bbox);
}
Hints hints = q.getHints();
if(hints != null) {
Number simplificationDistance = (Number) hints.get(Hints.GEOMETRY_DISTANCE);
if(simplificationDistance != null) {
result.setSimplificationDistance(simplificationDistance.doubleValue());
}
result.setScreenMap((ScreenMap) hints.get(Hints.SCREENMAP));
if(Boolean.TRUE.equals(hints.get(Hints.FEATURE_2D))) {
shapeReader.setFlatGeometry(true);
}
}
}
return result;
}
protected ShapefileReader openShapeReader(GeometryFactory gf) throws IOException {
try {
return new ShapefileReader(shpFiles, true, useMemoryMappedBuffer, gf);
} catch (ShapefileException se) {
throw new DataSourceException("Error creating ShapefileReader", se);
}
}
protected DbaseFileReader openDbfReader() throws IOException {
if (shpFiles.get(ShpFileType.DBF) == null) {
return null;
}
if (isLocal() && !shpFiles.exists(DBF)) {
return null;
}
try {
return new DbaseFileReader(shpFiles, useMemoryMappedBuffer,
dbfCharset);
} catch (IOException e) {
return null;
}
}
protected IndexFile openIndexFile() throws IOException {
if (shpFiles.get(SHX) == null) {
return null;
}
if (isLocal() && !shpFiles.exists(SHX)) {
return null;
}
try {
return new IndexFile(shpFiles, this.useMemoryMappedBuffer);
} catch (IOException e) {
return null;
}
}
protected PrjFileReader openPrjReader() throws IOException,
FactoryException {
if (shpFiles.get(PRJ) == null) {
return null;
}
if (isLocal() && !shpFiles.exists(PRJ)) {
return null;
}
try {
return new PrjFileReader(shpFiles);
} catch (IOException e) {
return null;
}
}
public String[] getTypeNames() {
return new String[] { getCurrentTypeName(), };
}
protected String createFeatureTypeName() {
return shpFiles.getTypeName();
}
protected String getCurrentTypeName() {
return (schema == null) ? createFeatureTypeName() : schema
.getTypeName();
}
protected void typeCheck(String requested) throws IOException {
if (!getCurrentTypeName().equals(requested)) {
throw new IOException("No such type : " + requested);
}
}
protected FeatureWriter<SimpleFeatureType, SimpleFeature> createFeatureWriter(String typeName,
Transaction transaction) throws IOException {
typeCheck(typeName);
FeatureReader<SimpleFeatureType, SimpleFeature> featureReader;
ShapefileAttributeReader attReader = getAttributesReader(true, null);
try {
SimpleFeatureType schema = getSchema();
if (schema == null) {
throw new IOException(
"To create a shapefile, you must first call createSchema()");
}
featureReader = createFeatureReader(typeName, attReader, schema);
} catch (Exception e) {
featureReader = new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>(schema);
}
return new ShapefileFeatureWriter(typeName, shpFiles, attReader,
featureReader, dbfCharset);
}
public SimpleFeatureType getSchema(String typeName) throws IOException {
typeCheck(typeName);
return getSchema();
}
public SimpleFeatureType getSchema() throws IOException {
if (schema == null) {
List<AttributeDescriptor> types = readAttributes();
SimpleFeatureType parent = null;
GeometryDescriptor geomDescriptor = (GeometryDescriptor) types.get(0);
Class<?> geomBinding = geomDescriptor.getType().getBinding();
if ((geomBinding == Point.class) || (geomBinding == MultiPoint.class)) {
parent = BasicFeatureTypes.POINT;
} else if ((geomBinding == Polygon.class)
|| (geomBinding == MultiPolygon.class)) {
parent = BasicFeatureTypes.POLYGON;
} else if ((geomBinding == LineString.class)
|| (geomBinding == MultiLineString.class)) {
parent = BasicFeatureTypes.LINE;
}
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setDefaultGeometry( geomDescriptor.getLocalName() );
builder.addAll(types);
builder.setName(createFeatureTypeName());
if (namespace != null) {
builder.setNamespaceURI(namespace);
} else {
builder.setNamespaceURI(BasicFeatureTypes.DEFAULT_NAMESPACE);
}
builder.setAbstract(false);
if (parent != null) {
builder.setSuperType(parent);
}
schema = builder.buildFeatureType();
}
return schema;
}
protected List<AttributeDescriptor> readAttributes() throws IOException {
ShapefileReader shp = openShapeReader(new GeometryFactory());
DbaseFileReader dbf = openDbfReader();
CoordinateReferenceSystem crs = null;
PrjFileReader prj = null;
try {
prj = openPrjReader();
if (prj != null) {
crs = prj.getCoodinateSystem();
}
} catch (FactoryException fe) {
crs = null;
}
AttributeTypeBuilder build = new AttributeTypeBuilder();
List<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
try {
Class<?> geometryClass = JTSUtilities.findBestGeometryClass(shp
.getHeader().getShapeType());
build.setName(Classes.getShortName( geometryClass ));
build.setNillable(true);
build.setCRS(crs);
build.setBinding(geometryClass);
GeometryType geometryType = build.buildGeometryType();
attributes.add(build.buildDescriptor(
BasicFeatureTypes.GEOMETRY_ATTRIBUTE_NAME, geometryType));
Set<String> usedNames = new HashSet<String>(); usedNames.add(BasicFeatureTypes.GEOMETRY_ATTRIBUTE_NAME);
if (dbf != null) {
DbaseFileHeader header = dbf.getHeader();
for (int i = 0, ii = header.getNumFields(); i < ii; i++) {
Class attributeClass = header.getFieldClass(i);
String name = header.getFieldName(i);
if (usedNames.contains(name)) {
String origional = name;
int count = 1;
name = name + count;
while (usedNames.contains(name)) {
count++;
name = origional + count;
}
}
usedNames.add(name);
int length = header.getFieldLength(i);
build.setNillable(true);
build.setLength(length);
build.setBinding(attributeClass);
attributes.add(build.buildDescriptor(name));
}
}
return attributes;
} finally {
try {
if (prj != null) {
prj.close();
}
} catch (IOException ioe) {
}
try {
if (dbf != null) {
dbf.close();
}
} catch (IOException ioe) {
}
try {
if (shp != null) {
shp.close();
}
} catch (IOException ioe) {
}
}
}
public void forceSchemaCRS(CoordinateReferenceSystem crs)
throws IOException {
if (crs == null)
throw new NullPointerException("CRS required for .prj file");
String s = crs.toWKT();
s = s.replaceAll("\n", "").replaceAll(" ", "");
StorageFile storageFile = shpFiles.getStorageFile(PRJ);
FileWriter out = new FileWriter(storageFile.getFile());
try {
out.write(s);
} finally {
out.close();
}
storageFile.replaceOriginal();
schema = null;
}
public void createSchema(SimpleFeatureType featureType) throws IOException {
if (!isLocal()) {
throw new IOException(
"Cannot create FeatureType on remote shapefile");
}
shpFiles.delete();
schema = featureType;
CoordinateReferenceSystem crs = featureType.getGeometryDescriptor()
.getCoordinateReferenceSystem();
final Class<?> geomType = featureType.getGeometryDescriptor().getType()
.getBinding();
final ShapeType shapeType;
if (Point.class.isAssignableFrom(geomType)) {
shapeType = ShapeType.POINT;
} else if (MultiPoint.class.isAssignableFrom(geomType)) {
shapeType = ShapeType.MULTIPOINT;
} else if (LineString.class.isAssignableFrom(geomType)
|| MultiLineString.class.isAssignableFrom(geomType)) {
shapeType = ShapeType.ARC;
} else if (Polygon.class.isAssignableFrom(geomType)
|| MultiPolygon.class.isAssignableFrom(geomType)) {
shapeType = ShapeType.POLYGON;
} else {
throw new DataSourceException(
"Cannot create a shapefile whose geometry type is "
+ geomType);
}
StorageFile shpStoragefile = shpFiles.getStorageFile(SHP);
StorageFile shxStoragefile = shpFiles.getStorageFile(SHX);
StorageFile dbfStoragefile = shpFiles.getStorageFile(DBF);
StorageFile prjStoragefile = shpFiles.getStorageFile(PRJ);
FileChannel shpChannel = shpStoragefile.getWriteChannel();
FileChannel shxChannel = shxStoragefile.getWriteChannel();
ShapefileWriter writer = new ShapefileWriter(shpChannel, shxChannel);
try {
writer.writeHeaders(new Envelope(), shapeType, 0, 100);
} finally {
writer.close();
assert !shpChannel.isOpen();
assert !shxChannel.isOpen();
}
DbaseFileHeader dbfheader = createDbaseHeader(featureType);
dbfheader.setNumRecords(0);
WritableByteChannel dbfChannel = dbfStoragefile.getWriteChannel();
try {
dbfheader.writeHeader(dbfChannel);
} finally {
dbfChannel.close();
}
if (crs != null) {
String s = crs.toWKT();
s = s.replaceAll("\n", "").replaceAll(" ", "");
FileWriter prjWriter = new FileWriter(prjStoragefile.getFile());
try {
prjWriter.write(s);
} finally {
prjWriter.close();
}
}
else {
LOGGER.warning("PRJ file not generated for null CoordinateReferenceSystem");
}
StorageFile.replaceOriginals(shpStoragefile, shxStoragefile,
dbfStoragefile, prjStoragefile);
}
protected ReferencedEnvelope getBounds() throws DataSourceException {
ReadableByteChannel in = null;
try {
ByteBuffer buffer = ByteBuffer.allocate(100);
FileReader reader = new FileReader() {
public String id() {
return "Shapefile Datastore's getBounds Method";
}
};
in = shpFiles.getReadChannel(SHP, reader);
try {
in.read(buffer);
buffer.flip();
ShapefileHeader header = new ShapefileHeader();
header.read(buffer, true);
ReferencedEnvelope bounds = new ReferencedEnvelope(schema
.getCoordinateReferenceSystem());
bounds.include(header.minX(), header.minY());
bounds.include(header.minX(), header.minY());
Envelope env = new Envelope(header.minX(), header.maxX(),
header.minY(), header.maxY());
if (schema != null) {
return new ReferencedEnvelope(env, schema.getCoordinateReferenceSystem());
}
return new ReferencedEnvelope(env, null);
} finally {
in.close();
}
} catch (IOException ioe) {
throw new DataSourceException("Problem getting Bbox", ioe);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ioe) {
}
}
}
protected ReferencedEnvelope getBounds(Query query) throws IOException {
if (query.getFilter().equals(Filter.INCLUDE)) {
return getBounds();
}
return null;
}
public SimpleFeatureSource getFeatureSource(final String typeName)
throws IOException {
final SimpleFeatureType featureType = getSchema(typeName);
if (isWriteable) {
if (getLockingManager() != null) {
return new ShapefileFeatureLocking(this, getSupportedHints(), featureType);
}
else {
return new ShapefileFeatureStore(this, getSupportedHints(), featureType);
}
}
return new ShapefileFeatureSource(this, getSupportedHints(), featureType);
}
public int getCount(Query query) throws IOException {
if (query.getFilter() == Filter.INCLUDE) {
IndexFile file = openIndexFile();
if (file != null) {
try {
return file.getRecordCount();
} finally {
file.close();
}
}
ShapefileReader reader = openShapeReader(new GeometryFactory());
int count = -1;
try {
count = reader.getCount(count);
} catch (IOException e) {
throw e;
} finally {
reader.close();
}
return count;
}
return super.getCount(query);
}
protected static DbaseFileHeader createDbaseHeader(
SimpleFeatureType featureType) throws IOException,
DbaseFileException {
DbaseFileHeader header = new DbaseFileHeader();
for (int i = 0, ii = featureType.getAttributeCount(); i < ii; i++) {
AttributeDescriptor type = featureType.getDescriptor(i);
Class<?> colType = type.getType().getBinding();
String colName = type.getLocalName();
int fieldLen = FeatureTypes.getFieldLength(type);
if (fieldLen == FeatureTypes.ANY_LENGTH)
fieldLen = 255;
if ((colType == Integer.class) || (colType == Short.class)
|| (colType == Byte.class)) {
header.addColumn(colName, 'N', Math.min(fieldLen, 9), 0);
} else if (colType == Long.class) {
header.addColumn(colName, 'N', Math.min(fieldLen, 19), 0);
} else if (colType == BigInteger.class) {
header.addColumn(colName, 'N', Math.min(fieldLen, 33), 0);
} else if (Number.class.isAssignableFrom(colType)) {
int l = Math.min(fieldLen, 33);
int d = Math.max(l - 2, 0);
header.addColumn(colName, 'N', l, d);
} else if (java.util.Date.class.isAssignableFrom(colType)
&& Boolean.getBoolean("org.geotools.shapefile.datetime"))
{
header.addColumn(colName, '@', fieldLen, 0);
} else if (java.util.Date.class.isAssignableFrom(colType)) {
header.addColumn(colName, 'D', fieldLen, 0);
} else if (colType == Boolean.class) {
header.addColumn(colName, 'L', 1, 0);
} else if (CharSequence.class.isAssignableFrom(colType)) {
header.addColumn(colName, 'C', Math.min(254, fieldLen), 0);
} else if (Geometry.class.isAssignableFrom(colType)) {
continue;
} else {
throw new IOException("Unable to write : " + colType.getName());
}
}
return header;
}
@Override
public String toString() {
return "Shapefile datastore for :" + shpFiles.get(SHP);
}
@Override
public void dispose() {
super.dispose();
shpFiles.dispose();
}
@Override
protected Set getSupportedHints() {
Set<Hints.Key> hints = new HashSet<Hints.Key>();
hints.add( Hints.FEATURE_DETACHED );
hints.add( Hints.JTS_GEOMETRY_FACTORY );
hints.add( Hints.JTS_COORDINATE_SEQUENCE_FACTORY );
hints.add( Hints.GEOMETRY_DISTANCE);
hints.add( Hints.SCREENMAP);
return hints;
}
}