001: package org.geotools.data.ogr;
002:
003: import java.io.IOException;
004: import java.text.DateFormat;
005: import java.text.SimpleDateFormat;
006:
007: import org.gdal.ogr.FeatureDefn;
008: import org.gdal.ogr.FieldDefn;
009: import org.gdal.ogr.ogr;
010: import org.geotools.data.DataSourceException;
011: import org.geotools.feature.AttributeType;
012: import org.geotools.feature.Feature;
013: import org.geotools.feature.FeatureType;
014: import org.geotools.feature.IllegalAttributeException;
015: import org.geotools.feature.type.GeometricAttributeType;
016:
017: import com.vividsolutions.jts.geom.Geometry;
018: import com.vividsolutions.jts.geom.GeometryFactory;
019: import com.vividsolutions.jts.geom.LineString;
020: import com.vividsolutions.jts.geom.MultiLineString;
021: import com.vividsolutions.jts.geom.MultiPolygon;
022: import com.vividsolutions.jts.geom.Polygon;
023: import com.vividsolutions.jts.io.ParseException;
024: import com.vividsolutions.jts.io.WKBReader;
025: import com.vividsolutions.jts.io.WKBWriter;
026: import com.vividsolutions.jts.io.WKTReader;
027: import com.vividsolutions.jts.io.WKTWriter;
028:
029: /**
030: * Maps OGR features into Geotools ones, and vice versa. Chances are that if you
031: * need to update a decode method a simmetric modification will be needed in the
032: * encode method. This class is not thread safe, so each thread should create
033: * its own instance.
034: *
035: * @author aaime
036: *
037: */
038: class FeatureMapper {
039:
040: /**
041: * From ogr_core.h, the byte order constants
042: */
043: static final int WKB_XDR = 1;
044:
045: /**
046: * Enables usage of WKB encoding for OGR/Java Geometry conversion. At the
047: * time of writing, it cannot be used because it'll bring the virtual
048: * machine down (yes, a real crash...)
049: */
050: static final boolean USE_WKB = true;
051:
052: GeometryFactory geomFactory;
053:
054: WKBReader wkbReader;
055:
056: WKTReader wktReader;
057:
058: WKBWriter wkbWriter;
059:
060: WKTWriter wktWriter;
061:
062: /**
063: * The date time format used by OGR when getting/setting times using strings
064: */
065: DateFormat dateTimeFormat = new SimpleDateFormat(
066: "yyyy/MM/dd hh:mm:ss");
067:
068: DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
069:
070: DateFormat timeFormat = new SimpleDateFormat("hh:mm:ss");
071:
072: public FeatureMapper(GeometryFactory geomFactory) {
073: this .geomFactory = geomFactory;
074: if (USE_WKB) {
075: this .wkbReader = new WKBReader(geomFactory);
076: this .wkbWriter = new WKBWriter();
077: } else {
078: this .wktReader = new WKTReader(geomFactory);
079: this .wktWriter = new WKTWriter();
080: }
081: }
082:
083: /**
084: * Converts an OGR feature into a GeoTools one
085: *
086: * @param schema
087: * @param ogrFeature
088: * @return
089: * @throws IOException
090: * @throws IllegalAttributeException
091: */
092: Feature convertOgrFeature(FeatureType schema,
093: org.gdal.ogr.Feature ogrFeature) throws IOException,
094: IllegalAttributeException {
095: // Extract all attributes (do not assume any specific order, the feature
096: // type may have been re-ordered by the Query)
097: Object[] attributes = new Object[schema.getAttributeCount()];
098:
099: // .. then extract each attribute using the attribute type to determine
100: // which extraction method to call
101: for (int i = 0; i < attributes.length; i++) {
102: AttributeType at = schema.getAttributeType(i);
103: if (at instanceof GeometricAttributeType) {
104: org.gdal.ogr.Geometry ogrGeometry = ogrFeature
105: .GetGeometryRef();
106: try {
107: attributes[i] = fixGeometryType(
108: parseOgrGeometry(ogrGeometry), at);
109: } finally {
110: ogrGeometry.delete();
111: }
112: } else {
113: attributes[i] = getOgrField(at, ogrFeature);
114: }
115: }
116:
117: // .. gather the FID
118: String fid = convertOGRFID(schema, ogrFeature);
119:
120: // .. finally create the feature
121: return schema.create(attributes, fid);
122: }
123:
124: /**
125: * Turns a GeoTools feature into an OGR one
126: *
127: * @param feature
128: * @return
129: * @throws DataSourceException
130: */
131: org.gdal.ogr.Feature convertGTFeature(FeatureDefn ogrSchema,
132: Feature feature) throws IOException {
133: // create a new empty OGR feature
134: org.gdal.ogr.Feature result = new org.gdal.ogr.Feature(
135: ogrSchema);
136:
137: // go thru GeoTools feature attributes, and convert
138: FeatureType schema = feature.getFeatureType();
139: Object[] attributes = feature.getAttributes(new Object[schema
140: .getAttributeCount()]);
141: for (int i = 0, j = 0; i < attributes.length; i++) {
142: AttributeType at = schema.getAttributeType(i);
143: if (at instanceof GeometricAttributeType) {
144: // using setGeoemtryDirectly the feature becomes the owner of the generated
145: // OGR geometry and we don't have to .delete() it (it's faster, too)
146: result
147: .SetGeometryDirectly(parseGTGeometry((Geometry) attributes[i]));
148: continue;
149: }
150:
151: if (attributes[i] == null) {
152: result.UnsetField(j);
153: } else {
154: final FieldDefn ogrField = ogrSchema.GetFieldDefn(j);
155: final int ogrType = ogrField.GetFieldType();
156: ogrField.delete();
157: if (ogrType == ogr.OFTInteger)
158: result.SetField(j, ((Number) attributes[i])
159: .intValue());
160: else if (ogrType == ogr.OFTReal)
161: result.SetField(j, ((Number) attributes[i])
162: .doubleValue());
163: else if (ogrType == ogr.OFTDateTime)
164: result.SetField(j, dateTimeFormat
165: .format((java.util.Date) attributes[i]));
166: else if (ogrType == ogr.OFTDate)
167: result.SetField(j, dateFormat
168: .format((java.util.Date) attributes[i]));
169: else if (ogrType == ogr.OFTTime)
170: result.SetField(j, timeFormat
171: .format((java.util.Date) attributes[i]));
172: else
173: result.SetField(j, attributes[i].toString());
174: }
175: j++;
176: }
177:
178: return result;
179: }
180:
181: /**
182: * Turns line and polygon into multiline and multipolygon. This is a
183: * stop-gap measure to make things works against shapefiles, I've asked the
184: * GDAL mailing list on how to properly handle this in the meantime
185: *
186: * @param ogrGeometry
187: * @param at
188: * @return
189: */
190: Geometry fixGeometryType(Geometry ogrGeometry, AttributeType at) {
191: if (MultiPolygon.class.equals(at.getType())) {
192: if (ogrGeometry instanceof MultiPolygon)
193: return ogrGeometry;
194: else
195: return geomFactory
196: .createMultiPolygon(new Polygon[] { (Polygon) ogrGeometry });
197: } else if (MultiLineString.class.equals(at.getType())) {
198: if (ogrGeometry instanceof MultiLineString)
199: return ogrGeometry;
200: else
201: return geomFactory
202: .createMultiLineString(new LineString[] { (LineString) ogrGeometry });
203: }
204: return ogrGeometry;
205:
206: }
207:
208: /**
209: * Reads the current feature's geometry using wkb encoding. A wkbReader
210: * should be provided since it's not thread safe by design.
211: *
212: * @throws IOException
213: */
214: Geometry parseOgrGeometry(org.gdal.ogr.Geometry geom)
215: throws IOException {
216: // Extract the geometry using either WKT or WKB. Rationale: the SWIG
217: // bindings do not provide subclasses. Even if they did, going thru the
218: // JNI barrier often is expensive, so it's better to gather the geometry
219: // is a single call
220: if (USE_WKB) {
221: int wkbSize = geom.WkbSize();
222: // the gdal interface uses a char* type, maybe because in C it's
223: // unsigned and has
224: // the same size as a byte, unfortunately this means we have to
225: // unpack it
226: // to byte format by doing bit masking and shifting
227: byte[] byteBuffer = new byte[wkbSize];
228: geom.ExportToWkb(byteBuffer, WKB_XDR);
229: try {
230: Geometry g = wkbReader.read(byteBuffer);
231: return g;
232: } catch (ParseException pe) {
233: throw new DataSourceException(
234: "Could not parse the current Geometry in WKB format.",
235: pe);
236: }
237: } else {
238: String[] stringArray = new String[1];
239: geom.ExportToWkt(stringArray);
240: try {
241: return wktReader.read(stringArray[0]);
242: } catch (ParseException pe) {
243: throw new DataSourceException(
244: "Could not parse the current Geometry in WKB format.",
245: pe);
246: }
247: }
248: }
249:
250: org.gdal.ogr.Geometry parseGTGeometry(Geometry geometry)
251: throws DataSourceException {
252: final org.gdal.ogr.Geometry ogrGeom;
253: if (USE_WKB) {
254: byte[] wkb = wkbWriter.write(geometry);
255: ogrGeom = ogr.CreateGeometryFromWkb(wkb, null);
256: if (ogrGeom == null)
257: throw new DataSourceException(
258: "Could not turn JTS geometry into an OGR one thought WKB");
259: } else {
260: String wkt = wktWriter.write(geometry);
261: ogrGeom = ogr.CreateGeometryFromWkt(wkt, null);
262: if (ogrGeom == null)
263: throw new DataSourceException(
264: "Could not turn JTS geometry into an OGR one thought WKT");
265: }
266: return ogrGeom;
267: }
268:
269: /**
270: * Reads the current feature's specified field using the most appropriate
271: * OGR field extraction method
272: *
273: * @param at
274: * @return
275: */
276: Object getOgrField(AttributeType at, org.gdal.ogr.Feature ogrFeature)
277: throws IOException {
278: String name = at.getName();
279: Class clazz = at.getType();
280:
281: // check for null fields
282: if (!ogrFeature.IsFieldSet(name))
283: return null;
284:
285: // hum, ok try and parse it
286: if (clazz.equals(String.class)) {
287: return ogrFeature.GetFieldAsString(name);
288: } else if (clazz.equals(Integer.class)) {
289: return new Integer(ogrFeature.GetFieldAsInteger(name));
290: } else if (clazz.equals(Double.class)) {
291: return new Double(ogrFeature.GetFieldAsDouble(name));
292: } else if (clazz.equals(Float.class)) {
293: return new Float(ogrFeature.GetFieldAsDouble(name));
294: } else if (clazz.equals(Integer.class)) {
295: return new Integer(ogrFeature.GetFieldAsInteger(name));
296: } else if (clazz.equals(java.util.Date.class)) {
297: String date = ogrFeature.GetFieldAsString(name);
298: if (date == null || date.trim().equals(""))
299: return null;
300: int ogrType = ogrFeature.GetFieldType(name);
301: try {
302: if (ogrType == ogr.OFTDateTime)
303: return dateTimeFormat.parse(date);
304: else if (ogrType == ogr.OFTDate)
305: return dateFormat.parse(date);
306: else if (ogrType == ogr.OFTTime)
307: return timeFormat.parse(date);
308: } catch (java.text.ParseException e) {
309: throw new DataSourceException(
310: "Could not parse date value", e);
311: }
312: throw new IOException(
313: "Date attribute, but field type is not compatible: "
314: + ogrType);
315: } else {
316: throw new IllegalArgumentException(
317: "Don't know how to read " + clazz.getName()
318: + " fields");
319: }
320: }
321:
322: /**
323: * Generates a GT2 feature id given its feature type and an OGR feature
324: *
325: * @param schema
326: * @param ogrFeature
327: * @return
328: */
329: String convertOGRFID(FeatureType schema,
330: org.gdal.ogr.Feature ogrFeature) {
331: return schema.getTypeName() + "." + ogrFeature.GetFID();
332: }
333:
334: /**
335: * Decodes a GT2 feature id into an OGR one
336: *
337: * @param feature
338: * @return
339: */
340: int convertGTFID(Feature feature) {
341: String id = feature.getID();
342: return Integer.parseInt(id.substring(id.indexOf(".") + 1));
343: }
344:
345: }
|