/*
* 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 java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFactorySpi;
import org.geotools.data.Parameter;
import org.geotools.data.shapefile.indexed.IndexType;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
import org.geotools.util.KVP;
import org.geotools.util.SimpleInternationalString;
import com.vividsolutions.jts.geom.Geometry;
/**
* Implementation of the DataStore service provider interface for Shapefiles.
* <p>
* The specific implementation of ShapefileDataStore created by this class is
* not specified. For more information on the connection parameters please
* review the following public Param constants.
* <ul>
* <li>{@link URLP}
* <li>{@link NAMESPACEP}
* <li>{@link CREATE_SPATIAL_INDEX}
* <li>{@link MEMORY_MAPPED}
* <li>{@link DBFCHARSET}
* </ul>
*
* @author Chris Holmes, TOPP
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/ShapefileDataStoreFactory.java $
* @version $Id: ShapefileDataStoreFactory.java 27856 2007-11-12 17:23:35Z
* desruisseaux $
*/
public class ShapefileDataStoreFactory implements FileDataStoreFactorySpi {
public static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.data.shapefile");
/**
* url to the .shp file.
*/
public static final Param URLP = new Param("url", URL.class,
"url to a .shp file",true, null,
new KVP(Param.EXT,"shp"));
/**
* Optional - uri of the FeatureType's namespace
*/
public static final Param NAMESPACEP = new Param("namespace", URI.class,
"uri to a the namespace", false, null, // not required
new KVP(Param.LEVEL,"advanced") );
/**
* Optional - enable/disable the use of memory-mapped io
*/
public static final Param MEMORY_MAPPED = new Param("memory mapped buffer",
Boolean.class, "enable/disable the use of memory-mapped io", false, true,
new KVP(Param.LEVEL,"advanced") );
/**
* Optional - Enable/disable the automatic creation of spatial index
*/
public static final Param CREATE_SPATIAL_INDEX = new Param(
"create spatial index", Boolean.class,
"enable/disable the automatic creation of spatial index", false, true,
new KVP(Param.LEVEL,"advanced") );
/**
* Optional - character used to decode strings from the DBF file
*/
public static final Param DBFCHARSET = new Param("charset", Charset.class,
"character used to decode strings from the DBF file", false,
Charset.forName("ISO-8859-1"),
new KVP(Param.LEVEL,"advanced")) {
/*
* This is an example of a non simple Param type where a custom parse
* method is required.
*
* @see org.geotools.data.DataStoreFactorySpi.Param#parse(java.lang.String)
*/
public Object parse(String text) throws IOException {
return Charset.forName(text);
}
public String text(Object value) {
return ((Charset) value).name();
}
};
/**
* Takes a map of parameters which describes how to access a DataStore and
* determines if it can be read by the ShapefileDataStore or
* IndexedShapefileDataStore implementations.
*
* @param params
* A map of parameters describing the location of a
* datastore. Files should be pointed to by a 'url' param.
*
* @return true iff params contains a url param which points to a file
* ending in shp
*/
public boolean canProcess(Map params) {
boolean accept = false;
if (params.containsKey(URLP.key)) {
try {
URL url = (URL) URLP.lookUp(params);
accept = canProcess(url);
} catch (IOException ioe) {
// yes, I am eating this - since it is my job to return a
// true/false
}
}
return accept;
}
/**
* Returns an instance of DataStore iff the resource pointed to the Map of
* paramers can be handled as a shapefile.
* <p>
* The specific implementation of ShapefileDataStore returned is not
* specified, and depends on the parameters given. For more information
* please review the public static Param instances available for this class.
* </p>
* <ul>
* <li>{@link URLP}
* <li>{@link NAMESPACEP}
* <li>{@link CREATE_SPATIAL_INDEX}
* <li>{@link MEMORY_MAPPED}
* <li>{@link DBFCHARSET}
* </ul>
*
* @param params
* A param list with information on the location of a
* restore. For shapefiles this should contain a 'url' param
* which points to a file which ends in shp.
*
* @return DataStore A ShapefileDatastore
* @throws IOException
* If a connection error (such as the file not existing
* occurs)
* @throws DataSourceException
* Thrown if the datastore which is created cannot be
* attached to the restore specified in params.
*/
public ShapefileDataStore createDataStore(Map params) throws IOException {
URL url = (URL) URLP.lookUp(params);
Boolean isMemoryMapped = (Boolean) MEMORY_MAPPED.lookUp(params);
URI namespace = (URI) NAMESPACEP.lookUp(params);
Charset dbfCharset = (Charset) DBFCHARSET.lookUp(params);
Boolean isCreateSpatialIndex = (Boolean) CREATE_SPATIAL_INDEX
.lookUp(params);
if (isCreateSpatialIndex == null) {
// should not be needed as default is TRUE
isCreateSpatialIndex = Boolean.TRUE;
}
if (dbfCharset == null) {
// this should not happen as Charset.forName("ISO-8859-1") was used
// as the param default?
dbfCharset = Charset.forName("ISO-8859-1");
}
if (isMemoryMapped == null) {
isMemoryMapped = Boolean.FALSE;
}
ShpFiles shpFiles = new ShpFiles(url);
boolean isLocal = shpFiles.isLocal();
if (isLocal && !shpFiles.exists(ShpFileType.SHP)) {
throw new FileNotFoundException("Shapefile not found:"
+ shpFiles.get(ShpFileType.SHP));
}
boolean useMemoryMappedBuffer = isLocal
&& shpFiles.exists(ShpFileType.SHP)
&& isMemoryMapped.booleanValue();
boolean createIndex = isCreateSpatialIndex.booleanValue() && isLocal;
IndexType treeIndex = IndexType.NONE;
if (isLocal) {
if (createIndex) {
treeIndex = IndexType.QIX; // default
} else {
// lets check and see if any index file is avaialble
if (shpFiles.exists(ShpFileType.QIX)) {
treeIndex = IndexType.QIX;
}
// else if (shpFiles.exists(ShpFileType.GRX)) {
// treeIndex = IndexType.GRX;
// }
}
}
try {
if (createIndex) {
return new IndexedShapefileDataStore(url, namespace,
useMemoryMappedBuffer, createIndex, IndexType.QIX,
dbfCharset);
} else if (treeIndex != IndexType.NONE) {
return new IndexedShapefileDataStore(url, namespace,
useMemoryMappedBuffer, false, treeIndex, dbfCharset);
} else {
return new ShapefileDataStore(url, namespace,
useMemoryMappedBuffer, dbfCharset);
}
} catch (MalformedURLException mue) {
throw new DataSourceException(
"Url for shapefile malformed: " + url, mue);
}
}
/**
* Creates a new DataStore - for a file that does not exist yet.
* <p>
* This method has different logic than createDataStore. It is willing to be
* memory mapped, and generate an index for a local file that does not exist
* yet.
*
*/
public FileDataStore createNewDataStore(Map params) throws IOException {
URL url = (URL) URLP.lookUp(params);
Boolean isMemoryMapped = (Boolean) MEMORY_MAPPED.lookUp(params);
URI namespace = (URI) NAMESPACEP.lookUp(params);
Charset dbfCharset = (Charset) DBFCHARSET.lookUp(params);
Boolean isCreateSpatialIndex = (Boolean) CREATE_SPATIAL_INDEX
.lookUp(params);
if (isCreateSpatialIndex == null) {
// should not be needed as default is TRUE
assert (true);
isCreateSpatialIndex = Boolean.TRUE;
}
if (dbfCharset == null) {
assert (true);
// this should not happen as Charset.forName("ISO-8859-1") was used
// as the param default?
dbfCharset = Charset.forName("ISO-8859-1");
}
if (isMemoryMapped == null) {
assert (true);
// this should not happen as false was the default
isMemoryMapped = Boolean.FALSE;
}
ShpFiles shpFiles = new ShpFiles(url);
boolean isLocal = shpFiles.isLocal();
if (!isLocal || shpFiles.exists(ShpFileType.SHP)) {
LOGGER.warning("File already exists: "
+ shpFiles.get(ShpFileType.SHP));
}
boolean useMemoryMappedBuffer = isLocal
&& isMemoryMapped.booleanValue();
boolean createIndex = isCreateSpatialIndex.booleanValue() && isLocal;
try {
if (createIndex) {
return new IndexedShapefileDataStore(url, namespace,
useMemoryMappedBuffer, true, IndexType.QIX, dbfCharset);
} else {
return new ShapefileDataStore(url, namespace,
useMemoryMappedBuffer, dbfCharset);
}
} catch (MalformedURLException mue) {
throw new DataSourceException(
"Url for shapefile malformed: " + url, mue);
}
}
public String getDisplayName() {
return "Shapefile";
}
/**
* Describes the type of data the datastore returned by this factory works
* with.
*
* @return String a human readable description of the type of restore
* supported by this datastore.
*/
public String getDescription() {
return "ESRI(tm) Shapefiles (*.shp)";
}
// public DataSourceMetadataEnity createMetadata( Map params )
// throws IOException {
//
// URL url = (URL) URLP.lookUp(params);
// Boolean mm = (Boolean) MEMORY_MAPPED.lookUp(params);
//
// String server;
// String name;
// if( url.getProtocol().equals("file")){
// server = "localhost";
// name = url.getPath();
// }
// else {
// server = url.getHost()+":"+url.getPort();
// name = url.getFile();
// }
// return new DataSourceMetadataEnity( server, name, "Shapefile access for
// "+url );
// }
/**
* Test to see if this datastore is available, if it has all the appropriate
* libraries to construct a datastore.
*
* This datastore just checks for the ShapefileDataStore,
* IndexedShapefileDataStore and Geometry implementations.
*
* @return <tt>true</tt> if and only if this factory is available to
* create DataStores.
*/
public boolean isAvailable() {
try {
ShapefileDataStore.class.getName();
IndexedShapefileDataStore.class.getName();
Geometry.class.getName();
} catch (Exception e) {
return false;
}
return true;
}
/**
* Describe parameters.
*
*
* @see org.geotools.data.DataStoreFactorySpi#getParametersInfo()
*/
public Param[] getParametersInfo() {
return new Param[] { URLP, NAMESPACEP, CREATE_SPATIAL_INDEX,
DBFCHARSET, MEMORY_MAPPED };
}
/**
* @see org.geotools.data.dir.FileDataStoreFactorySpi#getFileExtensions()
*/
public String[] getFileExtensions() {
return new String[] { ".shp", };
}
/**
* @see org.geotools.data.dir.FileDataStoreFactorySpi#canProcess(java.net.URL)
*/
public boolean canProcess(URL f) {
return f.getFile().toUpperCase().endsWith("SHP");
}
/**
* We may need to create a new datastore if the provided file does not
* exist.
*
* @see org.geotools.data.dir.FileDataStoreFactorySpi#createDataStore(java.net.URL)
*/
public FileDataStore createDataStore(URL url) throws IOException {
Map params = new HashMap();
params.put(URLP.key, url);
boolean isLocal = url.getProtocol().equalsIgnoreCase("file");
if (isLocal && !DataUtilities.urlToFile(url).exists()) {
return createNewDataStore(params);
} else {
return createDataStore(params);
}
}
/**
* @see org.geotools.data.dir.FileDataStoreFactorySpi#createDataStore(java.net.URL)
*/
public DataStore createDataStore(URL url, boolean memorymapped)
throws IOException {
Map params = new HashMap();
params.put(URLP.key, url);
params.put(MEMORY_MAPPED.key, new Boolean(memorymapped));
return createDataStore(params);
}
/**
* @see org.geotools.data.dir.FileDataStoreFactorySpi#getTypeName(java.net.URL)
*/
public String getTypeName(URL url) throws IOException {
DataStore ds = createDataStore(url);
String[] names = ds.getTypeNames(); // should be exactly one
return ((names == null || names.length == 0) ? null : names[0]);
}
/**
* Returns the implementation hints. The default implementation returns an
* empty map.
* <p>
* When we have FeatureFactory, GeometryFactory and so on hooked up this map
* will return Hints we paid attention too when we were constructed.
*
* @return An empty map.
*/
public Map getImplementationHints() {
return Collections.EMPTY_MAP;
}
}