/*
* 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.text.DateFormat;
import java.text.SimpleDateFormat;
import org.gdal.ogr.FeatureDefn;
import org.gdal.ogr.FieldDefn;
import org.gdal.ogr.ogr;
import org.geotools.data.DataSourceException;
import org.geotools.feature.AttributeType;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureType;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.type.GeometricAttributeType;
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.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;
/**
* Maps OGR features into Geotools ones, and vice versa. Chances are that if you
* need to update a decode method a simmetric modification will be needed in the
* encode method. This class is not thread safe, so each thread should create
* its own instance.
*
* @author aaime
*
*/
class FeatureMapper {
/**
* From ogr_core.h, the byte order constants
*/
static final int WKB_XDR = 1;
/**
* Enables usage of WKB encoding for OGR/Java Geometry conversion. At the
* time of writing, it cannot be used because it'll bring the virtual
* machine down (yes, a real crash...)
*/
static final boolean USE_WKB = true;
GeometryFactory geomFactory;
WKBReader wkbReader;
WKTReader wktReader;
WKBWriter wkbWriter;
WKTWriter wktWriter;
/**
* The date time format used by OGR when getting/setting times using strings
*/
DateFormat dateTimeFormat = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
DateFormat timeFormat = new SimpleDateFormat("hh:mm:ss");
public FeatureMapper(GeometryFactory geomFactory) {
this.geomFactory = geomFactory;
if (USE_WKB) {
this.wkbReader = new WKBReader(geomFactory);
this.wkbWriter = new WKBWriter();
} else {
this.wktReader = new WKTReader(geomFactory);
this.wktWriter = new WKTWriter();
}
}
/**
* Converts an OGR feature into a GeoTools one
*
* @param schema
* @param ogrFeature
* @return
* @throws IOException
* @throws IllegalAttributeException
*/
Feature convertOgrFeature(FeatureType schema, org.gdal.ogr.Feature ogrFeature)
throws IOException, IllegalAttributeException {
// Extract all attributes (do not assume any specific order, the feature
// type may have been re-ordered by the Query)
Object[] attributes = new Object[schema.getAttributeCount()];
// .. then extract each attribute using the attribute type to determine
// which extraction method to call
for (int i = 0; i < attributes.length; i++) {
AttributeType at = schema.getAttributeType(i);
if (at instanceof GeometricAttributeType) {
org.gdal.ogr.Geometry ogrGeometry = ogrFeature.GetGeometryRef();
try {
attributes[i] = fixGeometryType(parseOgrGeometry(ogrGeometry), at);
} finally {
ogrGeometry.delete();
}
} else {
attributes[i] = getOgrField(at, ogrFeature);
}
}
// .. gather the FID
String fid = convertOGRFID(schema, ogrFeature);
// .. finally create the feature
return schema.create(attributes, fid);
}
/**
* Turns a GeoTools feature into an OGR one
*
* @param feature
* @return
* @throws DataSourceException
*/
org.gdal.ogr.Feature convertGTFeature(FeatureDefn ogrSchema, Feature feature)
throws IOException {
// create a new empty OGR feature
org.gdal.ogr.Feature result = new org.gdal.ogr.Feature(ogrSchema);
// go thru GeoTools feature attributes, and convert
FeatureType schema = feature.getFeatureType();
Object[] attributes = feature.getAttributes(new Object[schema.getAttributeCount()]);
for (int i = 0, j = 0; i < attributes.length; i++) {
AttributeType at = schema.getAttributeType(i);
if (at instanceof GeometricAttributeType) {
// using setGeoemtryDirectly the feature becomes the owner of the generated
// OGR geometry and we don't have to .delete() it (it's faster, too)
result.SetGeometryDirectly(parseGTGeometry((Geometry) attributes[i]));
continue;
}
if (attributes[i] == null) {
result.UnsetField(j);
} else {
final FieldDefn ogrField = ogrSchema.GetFieldDefn(j);
final int ogrType = ogrField.GetFieldType();
ogrField.delete();
if(ogrType == ogr.OFTInteger)
result.SetField(j, ((Number) attributes[i]).intValue());
else if(ogrType == ogr.OFTReal)
result.SetField(j, ((Number) attributes[i]).doubleValue());
else if (ogrType == ogr.OFTDateTime)
result.SetField(j, dateTimeFormat.format((java.util.Date) attributes[i]));
else if (ogrType == ogr.OFTDate)
result.SetField(j, dateFormat.format((java.util.Date) attributes[i]));
else if (ogrType == ogr.OFTTime)
result.SetField(j, timeFormat.format((java.util.Date) attributes[i]));
else
result.SetField(j, attributes[i].toString());
}
j++;
}
return result;
}
/**
* Turns line and polygon into multiline and multipolygon. This is a
* stop-gap measure to make things works against shapefiles, I've asked the
* GDAL mailing list on how to properly handle this in the meantime
*
* @param ogrGeometry
* @param at
* @return
*/
Geometry fixGeometryType(Geometry ogrGeometry, AttributeType at) {
if (MultiPolygon.class.equals(at.getType())) {
if (ogrGeometry instanceof MultiPolygon)
return ogrGeometry;
else
return geomFactory.createMultiPolygon(new Polygon[] { (Polygon) ogrGeometry });
} else if (MultiLineString.class.equals(at.getType())) {
if (ogrGeometry instanceof MultiLineString)
return ogrGeometry;
else
return geomFactory
.createMultiLineString(new LineString[] { (LineString) ogrGeometry });
}
return ogrGeometry;
}
/**
* Reads the current feature's geometry using wkb encoding. A wkbReader
* should be provided since it's not thread safe by design.
*
* @throws IOException
*/
Geometry parseOgrGeometry(org.gdal.ogr.Geometry geom) throws IOException {
// Extract the geometry using either WKT or WKB. Rationale: the SWIG
// bindings do not provide subclasses. Even if they did, going thru the
// JNI barrier often is expensive, so it's better to gather the geometry
// is a single call
if (USE_WKB) {
int wkbSize = geom.WkbSize();
// the gdal interface uses a char* type, maybe because in C it's
// unsigned and has
// the same size as a byte, unfortunately this means we have to
// unpack it
// to byte format by doing bit masking and shifting
byte[] byteBuffer = new byte[wkbSize];
geom.ExportToWkb(byteBuffer, WKB_XDR);
try {
Geometry g = wkbReader.read(byteBuffer);
return g;
} catch (ParseException pe) {
throw new DataSourceException(
"Could not parse the current Geometry in WKB format.", pe);
}
} else {
String[] stringArray = new String[1];
geom.ExportToWkt(stringArray);
try {
return wktReader.read(stringArray[0]);
} catch (ParseException pe) {
throw new DataSourceException(
"Could not parse the current Geometry in WKB format.", pe);
}
}
}
org.gdal.ogr.Geometry parseGTGeometry(Geometry geometry) throws DataSourceException {
final org.gdal.ogr.Geometry ogrGeom;
if (USE_WKB) {
byte[] wkb = wkbWriter.write(geometry);
ogrGeom = ogr.CreateGeometryFromWkb(wkb, null);
if (ogrGeom == null)
throw new DataSourceException(
"Could not turn JTS geometry into an OGR one thought WKB");
} else {
String wkt = wktWriter.write(geometry);
ogrGeom = ogr.CreateGeometryFromWkt(wkt, null);
if (ogrGeom == null)
throw new DataSourceException(
"Could not turn JTS geometry into an OGR one thought WKT");
}
return ogrGeom;
}
/**
* Reads the current feature's specified field using the most appropriate
* OGR field extraction method
*
* @param at
* @return
*/
Object getOgrField(AttributeType at, org.gdal.ogr.Feature ogrFeature) throws IOException {
String name = at.getName();
Class clazz = at.getType();
// check for null fields
if (!ogrFeature.IsFieldSet(name))
return null;
// hum, ok try and parse it
if (clazz.equals(String.class)) {
return ogrFeature.GetFieldAsString(name);
} else if (clazz.equals(Integer.class)) {
return new Integer(ogrFeature.GetFieldAsInteger(name));
} else if (clazz.equals(Double.class)) {
return new Double(ogrFeature.GetFieldAsDouble(name));
} else if (clazz.equals(Float.class)) {
return new Float(ogrFeature.GetFieldAsDouble(name));
} else if (clazz.equals(Integer.class)) {
return new Integer(ogrFeature.GetFieldAsInteger(name));
} else if (clazz.equals(java.util.Date.class)) {
String date = ogrFeature.GetFieldAsString(name);
if (date == null || date.trim().equals(""))
return null;
int ogrType = ogrFeature.GetFieldType(name);
try {
if (ogrType == ogr.OFTDateTime)
return dateTimeFormat.parse(date);
else if (ogrType == ogr.OFTDate)
return dateFormat.parse(date);
else if (ogrType == ogr.OFTTime)
return timeFormat.parse(date);
} catch (java.text.ParseException e) {
throw new DataSourceException("Could not parse date value", e);
}
throw new IOException("Date attribute, but field type is not compatible: " + ogrType);
} else {
throw new IllegalArgumentException("Don't know how to read " + clazz.getName()
+ " fields");
}
}
/**
* Generates a GT2 feature id given its feature type and an OGR feature
*
* @param schema
* @param ogrFeature
* @return
*/
String convertOGRFID(FeatureType schema, org.gdal.ogr.Feature ogrFeature) {
return schema.getTypeName() + "." + ogrFeature.GetFID();
}
/**
* Decodes a GT2 feature id into an OGR one
*
* @param feature
* @return
*/
int convertGTFID(Feature feature) {
String id = feature.getID();
return Integer.parseInt(id.substring(id.indexOf(".") + 1));
}
}