/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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 static org.geotools.data.shapefile.ShpFileType.DBF;
import static org.geotools.data.shapefile.ShpFileType.PRJ;
import static org.geotools.data.shapefile.ShpFileType.SHP;
import static org.geotools.data.shapefile.ShpFileType.SHP_XML;
import static org.geotools.data.shapefile.ShpFileType.SHX;
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.FeatureSource;
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.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.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.geometry.jts.ReferencedEnvelope;
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;
/**
* A DataStore implementation which allows reading and writing from Shapefiles.
*
* @author Ian Schneider
*
* @todo fix file creation bug
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/ShapefileDataStore.java $
*/
public class ShapefileDataStore extends AbstractFileDataStore {
// This is the default character as specified by the DBF specification
public static final Charset DEFAULT_STRING_CHARSET = Charset
.forName("ISO-8859-1");
/**
* The query hints we do support
*/
private static final Set HINTS = Collections.unmodifiableSet(new HashSet(
Arrays.asList(new Object[] { Hints.FEATURE_DETACHED })));
protected final ShpFiles shpFiles;
protected URI namespace = null; // namespace provided by the constructor's
// map
protected SimpleFeatureType schema; // read only
protected boolean useMemoryMappedBuffer = true;
protected Charset dbfCharset;
private ServiceInfo info;
private ResourceInfo resourceInfo;
/**
* Creates a new instance of ShapefileDataStore.
*
* @param url
* The URL of the shp file to use for this DataSource.
*
* @throws NullPointerException
* DOCUMENT ME!
* @throws .
* If computation of related URLs (dbf,shx) fails.
*/
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);
}
/**
* this sets the datastore's namespace during construction (so the schema -
* FeatureType - will have the correct value) You can call this with
* namespace = null, but I suggest you give it an actual namespace.
*
* @param url
* @param namespace
*/
public ShapefileDataStore(URL url, URI namespace)
throws java.net.MalformedURLException {
this(url, namespace, false, DEFAULT_STRING_CHARSET);
}
/**
* this sets the datastore's namespace during construction (so the schema -
* FeatureType - will have the correct value) You can call this with
* namespace = null, but I suggest you give it an actual namespace.
*
* @param url
* @param namespace
* @param useMemoryMapped
* @param dbfCharset
*/
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;
}
/**
* this sets the datastore's namespace during construction (so the schema -
* FeatureType - will have the correct value) You can call this with
* namespace = null, but I suggest you give it an actual namespace.
*
* @param url
* @param namespace
* @param useMemoryMapped
*/
public ShapefileDataStore(URL url, URI namespace, boolean useMemoryMapped)
throws java.net.MalformedURLException {
this(url, namespace, useMemoryMapped, DEFAULT_STRING_CHARSET);
}
/**
* Access a ServiceInfo object for this shapefile.
*
* @return ShapefileServiceInfo describing service.
*/
public synchronized ServiceInfo getInfo(){
if( info == null ){
if( isLocal() ){
info = new ShapefileFileServiceInfo( this );
}
else {
info = new ShapefileURLServiceInfo( this );
}
}
return info;
}
/**
* Used by FeatureSource<SimpleFeatureType, SimpleFeature> / FeatureStore / FeatureLocking to
* access a single ResourceInfo.
*
* @param typeName
* @return ResourceInfo
*/
synchronized ResourceInfo getInfo( String typeName ) {
if( resourceInfo == null ){
if( isLocal() ){
// we may be able to make this instance writable
// allowing users to store values into shapefile metadata?
resourceInfo = new ShapefileFileResourceInfo( this );
}
else {
resourceInfo = new ShapefileURLResourceInfo( this );
}
}
return resourceInfo;
}
/**
* Set this if you need BDF strings to be decoded in a {@link Charset} other
* than ISO-8859-1
*
* @param stringCharset
* @since 2.3.3
*/
public void setStringCharset(Charset stringCharset) {
this.dbfCharset = stringCharset;
}
/**
* Returns the {@link Charset} used to decode strings in the DBF file
*
* @return
*/
public Charset getStringCharset() {
return dbfCharset;
}
/**
* Latch onto xmlURL if it is there, we may be able to get out of
* calculating the bounding box!
*
* <p>
* This method is called by the createTypeEntry anonymous inner class
* DefaultTypeEntry.
* </p>
*
* @param typeName
* DOCUMENT ME!
*
* @return Map with xmlURL parsed, or an EMPTY_MAP.
*/
protected Map createMetadata(String typeName) {
String urlString = shpFiles.get(SHP_XML);
if (urlString == null) {
return Collections.EMPTY_MAP;
}
try {
// System.out.println("found metadata = " + xmlURL);
ShpXmlFileReader reader = new ShpXmlFileReader(shpFiles);
Map map = new HashMap();
map.put("shp.xml", reader.parse());
// System.out.println("parsed ..." + xmlURL);
return map;
} catch (Throwable t) {
LOGGER.warning("Could not parse " + urlString + ":"
+ t.getLocalizedMessage());
return Collections.EMPTY_MAP;
}
}
/**
* Determine if the location of this shapefile is local or remote.
*
* @return true if local, false if remote
*/
public boolean isLocal() {
return shpFiles.isLocal();
}
/**
* Create a FeatureReader<SimpleFeatureType, SimpleFeature> for the provided type name.
*
* @param typeName
* The name of the FeatureType to create a reader for.
*
* @return A new FeatureReader.
*
* @throws IOException
* If an error occurs during creation
*/
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, new GeometryFactory()), schema);
} catch (SchemaException se) {
throw new DataSourceException("Error creating schema", se);
}
}
/**
* Just like the basic version, but adds a small optimization: if no
* attributes are going to be read, don't uselessly open and read the dbf
* file. Makes sure to consider also attributes in the query.
*
* @see org.geotools.data.AbstractDataStore#getFeatureReader(java.lang.String,
* org.geotools.data.Query)
*/
protected FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(String typeName, Query query)
throws IOException {
String[] propertyNames = query.getPropertyNames();
String defaultGeomName = schema.getGeometryDescriptor().getLocalName();
GeometryFactory geometryFactory = getGeometryFactory(query.getHints());
// gather attributes needed by the query tool, they will be used by the
// query filter
FilterAttributeExtractor extractor = new FilterAttributeExtractor(schema);
Filter filter = query.getFilter();
filter.accept(extractor, null);
String[] filterAttnames = extractor.getAttributeNames();
// check if the geometry is the one and only attribute needed
// to return attribute _and_ to run the query filter
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, geometryFactory), newSchema);
} catch (SchemaException se) {
throw new DataSourceException("Error creating schema", se);
}
}
return super.getFeatureReader(typeName, query);
}
/**
* Builds the most appropriate geometry factory depending on the available query hints
* @param query
* @return
*/
protected GeometryFactory getGeometryFactory(Hints hints) {
// if no hints, use the default geometry factory
if(hints == null)
return new GeometryFactory();
// grab a geometry factory... check for a special hint
GeometryFactory geometryFactory = (GeometryFactory) hints.get(Hints.JTS_GEOMETRY_FACTORY);
if (geometryFactory == null) {
// look for a coordinate sequence factory
CoordinateSequenceFactory csFactory =
(CoordinateSequenceFactory) hints.get(Hints.JTS_COORDINATE_SEQUENCE_FACTORY);
if (csFactory != null) {
geometryFactory = new GeometryFactory(csFactory);
}
}
if (geometryFactory == null) {
// fall back on the default one
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 DefaultFIDReader(typeName), readerSchema);
}
/**
* Returns the attribute reader, allowing for a pure shapefile reader, or a
* combined dbf/shp reader.
*
* @param readDbf -
* if true, the dbf fill will be opened and read
*
*
* @throws IOException
*/
protected ShapefileAttributeReader getAttributesReader(boolean readDbf, GeometryFactory gf)
throws IOException {
List<AttributeDescriptor> atts = (schema == null) ? readAttributes()
: schema.getAttributeDescriptors();
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());
return new ShapefileAttributeReader(atts, openShapeReader(gf), null);
}
return new ShapefileAttributeReader(atts, openShapeReader(gf),
openDbfReader());
}
/**
* Convenience method for opening a ShapefileReader.
*
* @return A new ShapefileReader.
*
* @throws IOException
* If an error occurs during creation.
*/
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);
}
}
/**
* Convenience method for opening a DbaseFileReader.
*
* @return A new DbaseFileReader
*
* @throws IOException
* If an error occurs during creation.
*/
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) {
// could happen if dbf file does not exist
return null;
}
}
/**
* Convenience method for opening an index file.
*
* @return An IndexFile
*
* @throws IOException
*/
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) {
// could happen if shx file does not exist remotely
return null;
}
}
/**
* Convenience method for opening a DbaseFileReader.
*
* @return A new DbaseFileReader
*
* @throws IOException
* If an error occurs during creation.
* @throws FactoryException
* DOCUMENT ME!
*/
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) {
// could happen if prj file does not exist remotely
return null;
}
}
/**
* Get an array of type names this DataStore holds.<BR/>ShapefileDataStore
* will always return a single name.
*
* @return An array of length one containing the single type held.
*/
public String[] getTypeNames() {
return new String[] { getCurrentTypeName(), };
}
/**
* Create the type name of the single FeatureType this DataStore represents.<BR/>
* For example, if the urls path is file:///home/billy/mytheme.shp, the type
* name will be mytheme.
*
* @return A name based upon the last path component of the url minus the
* extension.
*/
protected String createFeatureTypeName() {
return shpFiles.getTypeName();
}
protected String getCurrentTypeName() {
return (schema == null) ? createFeatureTypeName() : schema
.getTypeName();
}
/**
* A convenience method to check if a type name is correct.
*
* @param requested
* The type name requested.
*
* @throws IOException
* If the type name is not available
*/
protected void typeCheck(String requested) throws IOException {
if (!getCurrentTypeName().equals(requested)) {
throw new IOException("No such type : " + requested);
}
}
/**
* Create a FeatureWriter for the given type name.
*
* @param typeName
* The typeName of the FeatureType to write
* @param transaction
* DOCUMENT ME!
*
* @return A new FeatureWriter.
*
* @throws IOException
* If the typeName is not available or some other error
* occurs.
*/
protected FeatureWriter<SimpleFeatureType, SimpleFeature> createFeatureWriter(String typeName,
Transaction transaction) throws IOException {
typeCheck(typeName);
FeatureReader<SimpleFeatureType, SimpleFeature> featureReader;
ShapefileAttributeReader attReader = getAttributesReader(true, new GeometryFactory());
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);
}
/**
* Obtain the FeatureType of the given name. ShapefileDataStore contains
* only one FeatureType.
*
* @param typeName
* The name of the FeatureType.
*
* @return The FeatureType that this DataStore contains.
*
* @throws IOException
* If a type by the requested name is not present.
*/
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;
}
/**
* Create the AttributeDescriptor contained within this DataStore.
*
* @return List of new AttributeDescriptor
* @throws IOException
* If AttributeType reading fails
*/
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>(); // record names in
// case of
// duplicates
usedNames.add(BasicFeatureTypes.GEOMETRY_ATTRIBUTE_NAME);
// take care of the case where no dbf and query wants all =>
// geometry only
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) {
// do nothing
}
try {
if (dbf != null) {
dbf.close();
}
} catch (IOException ioe) {
// do nothing
}
try {
if (shp != null) {
shp.close();
}
} catch (IOException ioe) {
// do nothing
}
}
}
/**
* This method is used to force the creation of a .prj file.
* <p>
* The internally cached FeatureType will be removed, so the next call to
* getSchema() will read in the created file. This method is not thread safe
* and will have dire consequences for any other thread making use of the
* shapefile.
* <p>
*
* @param crs
*/
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;
}
/**
* Set the FeatureType of this DataStore. This method will delete any
* existing local resources or throw an IOException if the DataStore is
* remote.
*
* @param featureType
* The desired FeatureType.
*
* @throws IOException
* If the DataStore is remote.
*/
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 {
// by spec, if the file is empty, the shape envelope should be ignored
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();
// .prj files should have no carriage returns in them, this
// messes up
// ESRI's ArcXXX software, so we'll be compatible
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);
}
/**
* Gets the bounding box of the file represented by this data store as a
* whole (that is, off all of the features in the shapefile)
*
* @return The bounding box of the datasource or null if unknown and too
* expensive for the method to calculate.
*
* @throws DataSourceException
* DOCUMENT ME!
*/
protected ReferencedEnvelope getBounds() throws DataSourceException {
// This is way quick!!!
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) {
// What now? This seems arbitrarily appropriate !
throw new DataSourceException("Problem getting Bbox", ioe);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ioe) {
// do nothing
}
}
}
protected ReferencedEnvelope getBounds(Query query) throws IOException {
if (query.getFilter().equals(Filter.INCLUDE)) {
return getBounds();
}
return null; // too expensive
// TODO should we just return the layer? matches the javadocs
}
/**
* @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
*/
public FeatureSource<SimpleFeatureType, SimpleFeature> 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);
}
/**
* @see org.geotools.data.AbstractDataStore#getCount(org.geotools.data.Query)
*/
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();
}
}
// no Index file so use the number of shapefile records
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);
}
/**
* Attempt to create a DbaseFileHeader for the FeatureType. Note, we cannot
* set the number of records until the write has completed.
*
* @param featureType
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IOException
* DOCUMENT ME!
* @throws DbaseFileException
* DOCUMENT ME!
*/
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);
// This check has to come before the Date one or it is never reached
// also, this field is only activated with the following system property:
// org.geotools.shapefile.datetime=true
} 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)) {
// Possible fix for GEOT-42 : ArcExplorer doesn't like 0 length
// ensure that maxLength is at least 1
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() {
return HINTS;
}
}