/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-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.ogr;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.geotools.data.DataSourceException;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.store.ContentDataStore;
import org.geotools.data.store.ContentEntry;
import org.geotools.data.store.ContentFeatureSource;
import org.geotools.feature.NameImpl;
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.Name;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
/**
* A data store based on the OGR native library, bound to it via <a
* href="http://code.google.com/p/bridj/">BridJ</a>
*
* @author Andrea Aime - GeoSolutions
*/
@SuppressWarnings("rawtypes")
public class OGRDataStore extends ContentDataStore {
OGR ogr;
String ogrSourceName;
String ogrDriver;
public OGRDataStore(String ogrName, String ogrDriver, URI namespace, OGR ogr) {
if (namespace != null) {
setNamespaceURI(namespace.toString());
}
this.ogrSourceName = ogrName;
this.ogrDriver = ogrDriver;
this.ogr = ogr;
}
@Override
protected List<Name> createTypeNames() throws IOException {
Object dataSource = null;
Object layer = null;
try {
dataSource = openOGRDataSource(false);
List<Name> result = new ArrayList<Name>();
int count = ogr.DataSourceGetLayerCount(dataSource);
for (int i = 0; i < count; i++) {
layer = ogr.DataSourceGetLayer(dataSource, i);
String name = ogr.LayerGetName(layer);
if (name != null) {
result.add(new NameImpl(getNamespaceURI(), name));
}
ogr.LayerRelease(layer);
}
return result;
} catch (IOException e) {
LOGGER.log(Level.FINE, "Error looking up type names", e);
return Collections.emptyList();
} finally {
ogr.DataSourceRelease(dataSource);
ogr.LayerRelease(layer);
}
}
Object openOGRDataSource(boolean update) throws IOException {
Object ds = null;
int mode = update ? 1 : 0;
if (ogrDriver != null) {
Object driver = ogr.GetDriverByName(ogrDriver);
if (driver == null) {
throw new IOException("Could not find a driver named " + driver);
}
ds = ogr.DriverOpen(driver, ogrSourceName, mode);
if (ds == null) {
throw new IOException("OGR could not open '" + ogrSourceName + "' in "
+ (update ? "read-write" : "read-only") + " mode with driver " + ogrDriver);
}
} else {
ds = ogr.OpenShared(ogrSourceName, mode);
if (ds == null) {
throw new IOException("OGR could not open '" + ogrSourceName + "' in "
+ (update ? "read-write" : "read-only") + " mode");
}
}
return ds;
}
Object openOGRLayer(Object dataSource, String layerName) throws IOException {
Object layer = ogr.DataSourceGetLayerByName(dataSource, layerName);
if (layer == null) {
throw new IOException("OGR could not find layer '" + layerName + "'");
}
return layer;
}
@Override
protected ContentFeatureSource createFeatureSource(ContentEntry entry) throws IOException {
if (supportsInPlaceWrite(entry.getTypeName())) {
return new OGRFeatureStore(entry, Query.ALL, ogr);
} else {
return new OGRFeatureSource(entry, Query.ALL, ogr);
}
}
public boolean supportsInPlaceWrite(String typeName) throws IOException {
Object ds = null;
Object l = null;
try {
// try opening in update mode
ds = ogr.Open(ogrSourceName, 1);
if (ds == null) {
return false;
}
l = openOGRLayer(ds, typeName);
// for the moment we support working only with random writers
boolean canDelete = ogr.LayerCanDeleteFeature(l);
boolean canWriteRandom = ogr.LayerCanWriteRandom(l);
boolean canWriteSequential = ogr.LayerCanWriteSequential(l);
return canDelete && canWriteRandom && canWriteSequential;
} finally {
if (l != null) ogr.LayerRelease(l);
if (ds != null) ogr.DataSourceRelease(ds);
}
}
public void createSchema(SimpleFeatureType schema) throws IOException {
// TODO: add a field to allow approximate definitions
createSchema(schema, false, null);
}
/**
* Creates a new OGR layer with provided schema and options
*
* @param schema the geotools schema
* @param approximateFields if true, OGR will try to create fields that are approximations of
* the required ones when an exact match cannt be provided
* @param options OGR data source/layer creation options
* @throws IOException
*/
public void createSchema(SimpleFeatureType schema, boolean approximateFields, String[] options)
throws IOException {
Object dataSource = null;
Object layer = null;
try {
// either open datasource, or try creating one
dataSource = openOrCreateDataSource(options, dataSource);
FeatureTypeMapper mapper = new FeatureTypeMapper(ogr);
layer = createNewLayer(schema, dataSource, options, mapper);
// check the ability to create fields
Object driver = ogr.DataSourceGetDriver(dataSource);
String driverName = ogr.DriverGetName(driver);
ogr.DriverRelease(driver);
if (!driverName.equalsIgnoreCase("georss") &&
!driverName.equalsIgnoreCase("gpx") &&
!driverName.equalsIgnoreCase("sosi") &&
!ogr.LayerCanCreateField(layer)) {
throw new DataSourceException(
"OGR reports it's not possible to create fields on this layer");
}
// create fields
for (int i = 0; i < schema.getAttributeCount(); i++) {
AttributeDescriptor ad = schema.getDescriptor(i);
if (ad == schema.getGeometryDescriptor())
continue;
Object fieldDefinition = mapper.getOGRFieldDefinition(ad);
ogr.LayerCreateField(layer, fieldDefinition, approximateFields ? 1 : 0);
}
ogr.LayerSyncToDisk(layer);
} finally {
ogr.LayerRelease(layer);
ogr.DataSourceRelease(dataSource);
}
}
/**
* Creates a new OGR layer with provided data and options. This call is specifically made
* available for the OGC store since for some data source types, such as GML or KML, it is not
* possible to call createSchema() independently from a write, as the result will not contain
* the schema definition without having data too. Also, in those formats, the output is writable
* only so as long as it's empty, it's not possible to write against an existing GML file for
* example.
*
* @param schema the geotools schema
* @param approximateFields if true, OGR will try to create fields that are approximations of
* the required ones when an exact match cannt be provided
* @param options OGR data source/layer creation options
* @throws IOException
*/
public void createSchema(SimpleFeatureCollection data, boolean approximateFields,
String[] options) throws IOException {
Object dataSource = null;
Object layer = null;
SimpleFeatureType schema = data.getSchema();
SimpleFeatureIterator features;
try {
// either open datasource, or try creating one
dataSource = openOrCreateDataSource(options, dataSource);
FeatureTypeMapper mapper = new FeatureTypeMapper(ogr);
//layer = createNewLayer(schema, dataSource, optionsPointer, mapper);
layer = createNewLayer(schema, dataSource, options, mapper);
// check the ability to create fields
Object driver = ogr.DataSourceGetDriver(dataSource);
String driverName = ogr.DriverGetName(driver);
ogr.DriverRelease(driver);
if (!driverName.equalsIgnoreCase("georss") &&
!driverName.equalsIgnoreCase("gpx") &&
!driverName.equalsIgnoreCase("sosi") &&
!ogr.LayerCanCreateField(layer)) {
throw new DataSourceException(
"OGR reports it's not possible to create fields on this layer");
}
// create fields
Map<String, String> nameMap = new HashMap<String, String>();
for (int i = 0, j = 0; i < schema.getAttributeCount(); i++) {
AttributeDescriptor ad = schema.getDescriptor(i);
if (ad == schema.getGeometryDescriptor()) {
continue;
}
Object fieldDefinition = mapper.getOGRFieldDefinition(ad);
ogr.LayerCreateField(layer, fieldDefinition, approximateFields ? 1 : 0);
// the data source might have changed the name of the field, map them
String newName = ogr.FieldGetName(fieldDefinition);
nameMap.put(newName, ad.getLocalName());
j++;
}
// get back the feature definition
Object layerDefinition = ogr.LayerGetLayerDefn(layer);
// remap positions, as the store might add extra attributes (and the field api is
// positional)
Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
int count = ogr.LayerGetFieldCount(layerDefinition);
for (int i = 0; i < count; i++) {
Object fd = ogr.LayerGetFieldDefn(layerDefinition, i);
String newName = ogr.FieldGetName(fd);
if (newName != null) {
String oldName = nameMap.get(newName);
// Check case insensitive because sqlite can convert names to lowercase
if (oldName == null) {
oldName = nameMap.get(newName.toLowerCase());
}
if (oldName == null) {
oldName = nameMap.get(newName.toUpperCase());
}
for (int j = 0; j < schema.getAttributeCount(); j++) {
if (schema.getDescriptor(j).getLocalName().equals(oldName)) {
indexMap.put(j, i);
}
}
}
}
// iterate and write out without going throught the ContentDataStore api, which
// assumes it's possible to let go of it later
GeometryMapper geomMapper = new GeometryMapper.WKB(new GeometryFactory(), ogr);
features = data.features();
while (features.hasNext()) {
SimpleFeature feature = features.next();
// create the equivalent ogr feature
Object ogrFeature = ogr.LayerNewFeature(layerDefinition);
for (int i = 0; i < schema.getAttributeCount(); i++) {
Object value = feature.getAttribute(i);
if (value instanceof Geometry) {
// using setGeoemtryDirectly the feature becomes the owner of the generated
// OGR geometry and we don't have to .delete() it (it's faster, too)
Object geometry = geomMapper.parseGTGeometry((Geometry) value);
ogr.FeatureSetGeometryDirectly(ogrFeature, geometry);
} else {
// remap index
int ogrIndex = indexMap.get(i);
FeatureMapper.setFieldValue(layerDefinition, ogrFeature, ogrIndex, value, ogr);
}
}
// write it out
ogr.CheckError(ogr.LayerCreateFeature(layer, ogrFeature));
ogr.FeatureDestroy(ogrFeature);
}
ogr.LayerSyncToDisk(layer);
} finally {
ogr.LayerRelease(layer);
ogr.DataSourceRelease(dataSource);
}
}
private Object createNewLayer(SimpleFeatureType schema, Object dataSource, String[] options,
FeatureTypeMapper mapper) throws IOException, DataSourceException {
Object layer;
// get the spatial reference corresponding to the default geometry
GeometryDescriptor geomType = schema.getGeometryDescriptor();
long ogrGeomType = mapper.getOGRGeometryType(geomType);
Object spatialReference = mapper.getSpatialReference(geomType
.getCoordinateReferenceSystem());
// create the layer
layer = ogr.DataSourceCreateLayer(dataSource, schema.getTypeName(), spatialReference, ogrGeomType, options);
if (layer == null) {
throw new DataSourceException("Could not create the OGR layer: "+ogr.GetLastErrorMsg());
}
return layer;
}
private Object openOrCreateDataSource(String[] options, Object dataSource)
throws IOException, DataSourceException {
try {
dataSource = openOGRDataSource(true);
} catch (IOException e) {
if (ogrDriver != null) {
Object driver = ogr.GetDriverByName(ogrDriver);
dataSource = ogr.DriverCreateDataSource(driver, ogrSourceName, options);
ogr.DriverRelease(driver);
if (dataSource == null)
throw new IOException("Could not create OGR data source with driver "
+ ogrDriver + " and options " + options);
} else {
throw new DataSourceException("Driver not provided, and could not "
+ "open data source neither");
}
}
return dataSource;
}
}