/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2015, Geomatys
*
* 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.geotoolkit.data.shapefile;
import org.geotoolkit.data.shapefile.lock.ShpFiles;
import org.geotoolkit.data.shapefile.lock.ShpFileType;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.FileFeatureStoreFactory;
import org.geotoolkit.data.shapefile.indexed.IndexedShapefileFeatureStore;
import org.apache.sis.metadata.iso.quality.DefaultConformanceResult;
import org.geotoolkit.data.AbstractFileFeatureStoreFactory;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.data.shapefile.indexed.IndexType;
import com.vividsolutions.jts.geom.Geometry;
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 java.util.Collections;
import org.apache.sis.metadata.iso.DefaultIdentifier;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.identification.DefaultServiceIdentification;
import org.apache.sis.parameter.ParameterBuilder;
import org.geotoolkit.storage.DataType;
import org.geotoolkit.storage.DefaultFactoryMetadata;
import org.geotoolkit.storage.FactoryMetadata;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.identification.Identification;
import org.opengis.metadata.quality.ConformanceResult;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
/**
* Implementation of the featurestore service provider interface for Shapefiles.
* <p>
* The specific implementation of ShapefileFeatureStore created by this class is
* not specified. For more information on the connection parameters please
* review the following public Param constants.
* <ul>
* <li>{@link #PATH}
* <li>{@link #NAMESPACEP}
* <li>{@link #CREATE_SPATIAL_INDEX}
* <li>{@link #MEMORY_MAPPED}
* <li>{@link #DBFCHARSET}
* </ul>
*
* @author Chris Holmes, TOPP
* @author Johann Sorel (Geomatys)
* @module
*/
public class ShapefileFeatureStoreFactory extends AbstractFileFeatureStoreFactory implements FileFeatureStoreFactory {
/** factory identification **/
public static final String NAME = "shapefile";
public static final DefaultServiceIdentification IDENTIFICATION;
static {
IDENTIFICATION = new DefaultServiceIdentification();
final Identifier id = new DefaultIdentifier(NAME);
final DefaultCitation citation = new DefaultCitation(NAME);
citation.setIdentifiers(Collections.singleton(id));
IDENTIFICATION.setCitation(citation);
}
public static final ParameterDescriptor<String> IDENTIFIER = createFixedIdentifier(NAME);
public static final String ENCODING = "UTF-8";
public static final Logger LOGGER = Logging.getLogger("org.geotoolkit.data.shapefile");
/**
* Optional - enable/disable the use of memory-mapped io
*/
public static final ParameterDescriptor<Boolean> MEMORY_MAPPED = new ParameterBuilder()
.addName("memory mapped buffer")
.addName(Bundle.formatInternational(Bundle.Keys.memory_mapped_buffer))
.setRemarks(Bundle.formatInternational(Bundle.Keys.memory_mapped_buffer_remarks))
.setRequired(false)
.create(Boolean.class, Boolean.FALSE);
/**
* Optional - Enable/disable the automatic creation of spatial index
*/
public static final ParameterDescriptor<Boolean> CREATE_SPATIAL_INDEX = new ParameterBuilder()
.addName("create spatial index")
.addName(Bundle.formatInternational(Bundle.Keys.create_spatial_index))
.setRemarks(Bundle.formatInternational(Bundle.Keys.create_spatial_index_remarks))
.setRequired(false)
.create(Boolean.class, Boolean.TRUE);
/**
* Optional - character used to decode strings from the DBF file
*/
public static final ParameterDescriptor<Charset> DBFCHARSET = new ParameterBuilder()
.addName("charset")
.addName(Bundle.formatInternational(Bundle.Keys.charset))
.setRemarks(Bundle.formatInternational(Bundle.Keys.charset_remarks))
.setRequired(false)
.create(Charset.class, null);
/**
* Optional - load in memory the quadtree if exist.
*/
public static final ParameterDescriptor<Boolean> LOAD_QIX = new ParameterBuilder()
.addName("load qix")
.addName(Bundle.formatInternational(Bundle.Keys.load_qix))
.setRemarks(Bundle.formatInternational(Bundle.Keys.load_qix_remarks))
.setRequired(false)
.create(Boolean.class, Boolean.FALSE);
public static final ParameterDescriptorGroup PARAMETERS_DESCRIPTOR =
new ParameterBuilder().addName("ShapefileParameters").createGroup(
IDENTIFIER, PATH,NAMESPACE,MEMORY_MAPPED,CREATE_SPATIAL_INDEX,DBFCHARSET,LOAD_QIX);
@Override
public Identification getIdentification() {
return IDENTIFICATION;
}
/**
* {@inheritDoc }
*/
@Override
public ParameterDescriptorGroup getParametersDescriptor() {
return PARAMETERS_DESCRIPTOR;
}
/**
* {@inheritDoc }
*/
@Override
public CharSequence getDisplayName() {
return Bundle.formatInternational(Bundle.Keys.datastoreTitle);
}
/**
* Describes the type of data the featurestore returned by this factory works
* with.
*
* @return String a human readable description of the type of restore
* supported by this datastore.
*/
@Override
public CharSequence getDescription() {
return Bundle.formatInternational(Bundle.Keys.datastoreDescription);
}
/**
* Test to see if this featurestore is available, if it has all the appropriate
* libraries to construct a datastore.
*
* This featurestore just checks for the ShapefileDataStore,
* IndexedShapefileFeatureStore and Geometry implementations.
*
* @return <tt>true</tt> if and only if this factory is available to
* open DataStores.
*/
@Override
public ConformanceResult availability() {
final DefaultConformanceResult result = new DefaultConformanceResult();
try {
ShapefileFeatureStore.class.getName();
IndexedShapefileFeatureStore.class.getName();
Geometry.class.getName();
result.setPass(true);
} catch (Exception e) {
result.setPass(false);
}
return result;
}
/**
* {@inheritDoc }
*/
@Override
public String[] getFileExtensions() {
return new String[] {".shp"};
}
/**
* {@inheritDoc }
*/
@Override
public ShapefileFeatureStore open(final ParameterValueGroup params) throws DataStoreException {
ensureCanProcess(params);
final URI uri = (URI) params.parameter(PATH.getName().toString()).getValue();
Boolean isMemoryMapped = (Boolean) params.parameter(MEMORY_MAPPED.getName().toString()).getValue();
final String namespace = (String) params.parameter(NAMESPACE.getName().toString()).getValue();
Charset dbfCharset = (Charset) params.parameter(DBFCHARSET.getName().toString()).getValue();
Boolean isCreateSpatialIndex = (Boolean) params.parameter(CREATE_SPATIAL_INDEX.getName().toString()).getValue();
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;
}
//index loading hints
final Boolean loadQix = (Boolean) params.parameter(LOAD_QIX.getName().toString()).getValue();
final ShpFiles shpFiles;
try{
shpFiles = new ShpFiles(uri, (loadQix == null) ? false : loadQix );
}catch(IllegalArgumentException ex){
throw new DataStoreException(ex.getMessage(),ex);
}
if (!shpFiles.exists(ShpFileType.SHP)) {
throw new DataStoreException("Shapefile not found:" + shpFiles.get(ShpFileType.SHP));
}
final boolean isWritable = shpFiles.isWritable();
final boolean useMemoryMappedBuffer = shpFiles.exists(ShpFileType.SHP) && isMemoryMapped;
final boolean createIndex = isCreateSpatialIndex && isWritable;
IndexType treeIndex = IndexType.NONE;
if (isWritable) {
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;
}
}
}
try {
if (createIndex) {
return new IndexedShapefileFeatureStore(uri, namespace, useMemoryMappedBuffer, createIndex, IndexType.QIX, dbfCharset);
} else if (treeIndex != IndexType.NONE) {
return new IndexedShapefileFeatureStore(uri, namespace, useMemoryMappedBuffer, false, treeIndex, dbfCharset);
} else {
return new ShapefileFeatureStore(uri, namespace, useMemoryMappedBuffer, dbfCharset);
}
} catch (MalformedURLException mue) {
throw new DataStoreException("Url for shapefile malformed: " + uri, mue);
}
}
/**
* {@inheritDoc }
*/
@Override
public ShapefileFeatureStore create(final ParameterValueGroup params) throws DataStoreException {
final URI uri = (URI) params.parameter(PATH.getName().toString()).getValue();
Boolean isMemoryMapped = (Boolean) params.parameter(MEMORY_MAPPED.getName().toString()).getValue();
final String namespace = (String) params.parameter(NAMESPACE.getName().toString()).getValue();
Charset dbfCharset = (Charset) params.parameter(DBFCHARSET.getName().toString()).getValue();
Boolean isCreateSpatialIndex = (Boolean) params.parameter(CREATE_SPATIAL_INDEX.getName().toString()).getValue();
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;
}
final ShpFiles shpFiles = new ShpFiles(uri);
final boolean isLocal = shpFiles.isWritable();
if (!isLocal || shpFiles.exists(ShpFileType.SHP)) {
LOGGER.fine("File already exists: " + shpFiles.get(ShpFileType.SHP));
}
final boolean useMemoryMappedBuffer = isLocal && isMemoryMapped.booleanValue();
final boolean createIndex = isCreateSpatialIndex.booleanValue() && isLocal;
try {
if (createIndex) {
return new IndexedShapefileFeatureStore(uri, namespace, useMemoryMappedBuffer, true, IndexType.QIX, dbfCharset);
} else {
return new ShapefileFeatureStore(uri, namespace, useMemoryMappedBuffer, dbfCharset);
}
} catch (MalformedURLException mue) {
throw new DataStoreException("Uri for shapefile malformed: " + uri, mue);
}
}
@Override
public FactoryMetadata getMetadata() {
return new DefaultFactoryMetadata(DataType.VECTOR, true, true, true, false, new Class[]{
Point.class, MultiPoint.class, MultiLineString.class, MultiPolygon.class
});
}
}