/* * 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 static org.bridj.Pointer.*; import static org.geotools.data.ogr.bridj.OgrLibrary.*; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DateFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.TimeZone; import org.bridj.Pointer; import org.geotools.data.DataSourceException; import org.geotools.data.ogr.bridj.OgrLibrary.OGRFieldType; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.util.Converters; 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"); HashMap<String, Integer> attributeIndexes; /** * TODO: this is subscepitble to changes to the Locale in Java that might not affect * the C code... we should probably figure out a way to get the OS level locale? */ static final DecimalFormatSymbols DECIMAL_SYMBOLS = new DecimalFormatSymbols(); public FeatureMapper(SimpleFeatureType targetSchema, Pointer layer, GeometryFactory geomFactory) { this.schema = targetSchema; this.builder = new SimpleFeatureBuilder(schema); this.geomMapper = new GeometryMapper.WKB(geomFactory); this.geomFactory = geomFactory; attributeIndexes = new HashMap<String, Integer>(); Pointer layerDefinition = OGR_L_GetLayerDefn(layer); int size = OGR_FD_GetFieldCount(layerDefinition); for(int i = 0; i < size; i++) { Pointer field = OGR_FD_GetFieldDefn(layerDefinition, i); Pointer<Byte> namePtr = OGR_Fld_GetNameRef(field); String name = namePtr.getCString(); if(targetSchema.getDescriptor(name) != null) { attributeIndexes.put(name, i); } } } /** * Converts an OGR feature into a GeoTools one * * @param schema * @param ogrFeature * @return * @throws IOException */ SimpleFeature convertOgrFeature(Pointer<?> 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); 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 */ Pointer convertGTFeature(Pointer featureDefinition, SimpleFeature feature) throws IOException { // create a new empty OGR feature Pointer ogrFeature = OGR_F_Create(featureDefinition); // go thru GeoTools feature attributes, and convert SimpleFeatureType schema = feature.getFeatureType(); for (int i = 0, j = 0; i < schema.getAttributeCount(); i++) { Object attribute = feature.getAttribute(i); if (attribute 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) Pointer geometry = geomMapper.parseGTGeometry((Geometry) attribute); OGR_F_SetGeometryDirectly(ogrFeature, geometry); } else { setFieldValue(featureDefinition, ogrFeature, j, attribute); j++; } } return ogrFeature; } static void setFieldValue(Pointer featureDefinition, Pointer ogrFeature, int fieldIdx, Object value) throws IOException { if (value == null) { OGR_F_UnsetField(ogrFeature, fieldIdx); } else { Pointer fieldDefinition = OGR_FD_GetFieldDefn(featureDefinition, fieldIdx); long ogrType = OGR_Fld_GetType(fieldDefinition).value(); if (ogrType == OGRFieldType.OFTInteger.value()) { OGR_F_SetFieldInteger(ogrFeature, fieldIdx, ((Number) value).intValue()); } else if (ogrType == OGRFieldType.OFTReal.value()) { OGR_F_SetFieldDouble(ogrFeature, fieldIdx, ((Number) value).doubleValue()); } else if (ogrType == OGRFieldType.OFTBinary.value()) { byte[] attValue = (byte[]) value; OGR_F_SetFieldBinary(ogrFeature, fieldIdx, attValue.length, pointerToBytes(attValue)); } else if (ogrType == OGRFieldType.OFTDate.value()) { Calendar cal = Calendar.getInstance(); cal.setTime((Date) value); int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH); int day = cal.get(Calendar.DAY_OF_MONTH); OGR_F_SetFieldDateTime(ogrFeature, fieldIdx, year, month, day, 0, 0, 0, 0); } else if (ogrType == OGRFieldType.OFTTime.value()) { Calendar cal = Calendar.getInstance(); cal.setTime((Date) value); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND); OGR_F_SetFieldDateTime(ogrFeature, fieldIdx, 0, 0, 0, hour, minute, second, 0); } else if (ogrType == OGRFieldType.OFTDateTime.value()) { Calendar cal = Calendar.getInstance(); cal.setTime((Date) value); int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH); int day = cal.get(Calendar.DAY_OF_MONTH); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND); OGR_F_SetFieldDateTime(ogrFeature, fieldIdx, year, month, day, hour, minute, second, 0); } else { // anything else we treat as a string String str = Converters.convert(value, String.class); OGR_F_SetFieldString(ogrFeature, fieldIdx, pointerToCString(str)); } } } /** * 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, Pointer<?> ogrFeature) throws IOException { if(ad instanceof GeometryDescriptor) { // gets the geometry as a reference, we don't own it, we should not deallocate it Pointer<?> ogrGeometry = OGR_F_GetGeometryRef(ogrFeature); return fixGeometryType(geomMapper.parseOgrGeometry(ogrGeometry), ad); } Integer idx = attributeIndexes.get(ad.getLocalName()); // check for null fields if (idx == null || OGR_F_IsFieldSet(ogrFeature, idx) == 0) { return null; } // hum, ok try and parse it Class clazz = ad.getType().getBinding(); if (clazz.equals(String.class)) { return OGR_F_GetFieldAsString(ogrFeature, idx).getCString(); } else if (clazz.equals(Byte.class)) { return (byte) OGR_F_GetFieldAsInteger(ogrFeature, idx); } else if (clazz.equals(Short.class)) { return (short) OGR_F_GetFieldAsInteger(ogrFeature, idx); } else if (clazz.equals(Integer.class)) { return OGR_F_GetFieldAsInteger(ogrFeature, idx); } else if (clazz.equals(Long.class)) { String value = OGR_F_GetFieldAsString(ogrFeature, idx).getCString(); return new Long(value); } else if (clazz.equals(BigInteger.class)) { String value = OGR_F_GetFieldAsString(ogrFeature, idx).getCString(); return new BigInteger(value); } else if (clazz.equals(Double.class)) { return OGR_F_GetFieldAsDouble(ogrFeature, idx); } else if (clazz.equals(Float.class)) { return (float) OGR_F_GetFieldAsDouble(ogrFeature, idx); } else if (clazz.equals(BigDecimal.class)) { String value = OGR_F_GetFieldAsString(ogrFeature, idx).getCString().trim(); char separator = DECIMAL_SYMBOLS.getDecimalSeparator(); if(separator != '.') { value = value.replace(separator, '.'); } return new BigDecimal(value); } else if (clazz.equals(java.sql.Date.class)) { Calendar cal = getDateField(ogrFeature, idx); cal.clear(Calendar.HOUR_OF_DAY); cal.clear(Calendar.MINUTE); cal.clear(Calendar.SECOND); return new java.sql.Date(cal.getTimeInMillis()); } else if (clazz.equals(java.sql.Time.class)) { Calendar cal = getDateField(ogrFeature, idx); cal.clear(Calendar.YEAR); cal.clear(Calendar.MONTH); cal.clear(Calendar.DAY_OF_MONTH); return new java.sql.Time(cal.getTimeInMillis()); } else if (clazz.equals(java.sql.Timestamp.class)) { Calendar cal = getDateField(ogrFeature, idx); return new java.sql.Time(cal.getTimeInMillis()); } else if (clazz.equals(java.util.Date.class)) { Calendar cal = getDateField(ogrFeature, idx); return cal.getTime(); } else { throw new IllegalArgumentException("Don't know how to read " + clazz.getName() + " fields"); } } /** * Reads a date field from the OGR api * @param ogrFeature * @param idx * @return */ private Calendar getDateField(Pointer<?> ogrFeature, Integer idx) { Pointer<Integer> year = allocateInt(); Pointer<Integer> month = allocateInt(); Pointer<Integer> day = allocateInt(); Pointer<Integer> hour = allocateInt(); Pointer<Integer> minute = allocateInt(); Pointer<Integer> second = allocateInt(); Pointer<Integer> timeZone = allocateInt(); OGR_F_GetFieldAsDateTime(ogrFeature, idx, year, month, day, hour, minute, second, timeZone); Calendar cal = Calendar.getInstance(); // from ogr_core.h // 0=unknown, 1=localtime(ambiguous), 100=GMT, 104=GMT+1, 80=GMT-5, etc int tz = timeZone.getInt(); if(tz != 0 && tz != 1) { int offset = tz - 100 / 4; if(offset < 0) { cal.setTimeZone(TimeZone.getTimeZone("GMT" + offset)); } else if(offset == 0) { cal.setTimeZone(TimeZone.getTimeZone("GMT")); } else { cal.setTimeZone(TimeZone.getTimeZone("GMT+" + offset)); } } cal.clear(); cal.set(Calendar.YEAR, year.getInt()); cal.set(Calendar.MONTH, month.getInt()); cal.set(Calendar.DAY_OF_MONTH, day.getInt()); cal.set(Calendar.HOUR_OF_DAY, hour.getInt()); cal.set(Calendar.MINUTE, minute.getInt()); cal.set(Calendar.SECOND, second.getInt()); return cal; } /** * Generates a GT2 feature id given its feature type and an OGR feature * * @param schema * @param ogrFeature * @return */ String convertOGRFID(SimpleFeatureType schema, Pointer<?> ogrFeature) { long id = OGR_F_GetFID(ogrFeature); return schema.getTypeName() + "." + id; } /** * Decodes a GT2 feature id into an OGR one * * @param feature * @return */ long convertGTFID(SimpleFeature feature) { String id = feature.getID(); return Long.parseLong(id.substring(id.indexOf(".") + 1)); } }