/* * 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.simple.SimpleFeatureBuilder; 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 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; /** * 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 Andrea Aime - OpenGeo * */ class FeatureMapper { SimpleFeatureBuilder builder; SimpleFeatureType schema; GeometryMapper geomMapper; GeometryFactory geomFactory; /** * 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(SimpleFeatureType schema, GeometryFactory geomFactory) { this.schema = schema; this.builder = new SimpleFeatureBuilder(schema); this.geomMapper = new GeometryMapper(geomFactory); this.geomFactory = geomFactory; } /** * Converts an OGR feature into a GeoTools one * * @param schema * @param ogrFeature * @return * @throws IOException */ SimpleFeature convertOgrFeature(org.gdal.ogr.Feature ogrFeature) throws IOException { // 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++) { AttributeDescriptor at = schema.getDescriptor(i); if (at instanceof GeometryDescriptor) { org.gdal.ogr.Geometry ogrGeometry = ogrFeature.GetGeometryRef(); try { builder.add(fixGeometryType(geomMapper.parseOgrGeometry(ogrGeometry), at)); } finally { ogrGeometry.delete(); } } else { builder.add(getOgrField(at, ogrFeature)); } } // .. gather the FID String fid = convertOGRFID(schema, ogrFeature); // .. finally create the feature return builder.buildFeature(fid); } /** * Turns a GeoTools feature into an OGR one * * @param feature * @return * @throws DataSourceException */ org.gdal.ogr.Feature convertGTFeature(FeatureDefn ogrSchema, SimpleFeature 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 SimpleFeatureType schema = feature.getFeatureType(); for (int i = 0, j = 0; i < schema.getAttributeCount(); i++) { AttributeDescriptor at = schema.getDescriptor(i); Object attribute = feature.getAttribute(i); if (at instanceof GeometryDescriptor) { // 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(geomMapper.parseGTGeometry((Geometry) attribute)); continue; } if (attribute == 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) attribute).intValue()); else if (ogrType == ogr.OFTReal) result.SetField(j, ((Number) attribute).doubleValue()); else if (ogrType == ogr.OFTDateTime) result.SetField(j, dateTimeFormat.format((java.util.Date) attribute)); else if (ogrType == ogr.OFTDate) result.SetField(j, dateFormat.format((java.util.Date) attribute)); else if (ogrType == ogr.OFTTime) result.SetField(j, timeFormat.format((java.util.Date) attribute)); else result.SetField(j, attribute.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 ad * @return */ Geometry fixGeometryType(Geometry ogrGeometry, AttributeDescriptor ad) { if (MultiPolygon.class.equals(ad.getType())) { if (ogrGeometry instanceof MultiPolygon) return ogrGeometry; else return geomFactory.createMultiPolygon(new Polygon[] { (Polygon) ogrGeometry }); } else if (MultiLineString.class.equals(ad.getType())) { if (ogrGeometry instanceof MultiLineString) return ogrGeometry; else return geomFactory .createMultiLineString(new LineString[] { (LineString) ogrGeometry }); } return ogrGeometry; } /** * Reads the current feature's specified field using the most appropriate OGR field extraction * method * * @param ad * @return */ Object getOgrField(AttributeDescriptor ad, org.gdal.ogr.Feature ogrFeature) throws IOException { String name = ad.getLocalName(); Class clazz = ad.getType().getBinding(); // 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(SimpleFeatureType 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(SimpleFeature feature) { String id = feature.getID(); return Integer.parseInt(id.substring(id.indexOf(".") + 1)); } }