001: package org.geotools.data.ogr;
002:
003: import java.io.IOException;
004: import java.math.BigDecimal;
005: import java.math.BigInteger;
006: import java.net.URI;
007: import java.util.Arrays;
008: import java.util.Vector;
009:
010: import org.gdal.gdal.gdal;
011: import org.gdal.ogr.DataSource;
012: import org.gdal.ogr.Driver;
013: import org.gdal.ogr.FeatureDefn;
014: import org.gdal.ogr.FieldDefn;
015: import org.gdal.ogr.Layer;
016: import org.gdal.ogr.ogr;
017: import org.gdal.osr.SpatialReference;
018: import org.geotools.data.AbstractDataStore;
019: import org.geotools.data.DataSourceException;
020: import org.geotools.data.FeatureReader;
021: import org.geotools.data.FeatureWriter;
022: import org.geotools.data.Query;
023: import org.geotools.data.Transaction;
024: import org.geotools.factory.FactoryConfigurationError;
025: import org.geotools.feature.AttributeType;
026: import org.geotools.feature.AttributeTypeFactory;
027: import org.geotools.feature.FeatureType;
028: import org.geotools.feature.FeatureTypes;
029: import org.geotools.feature.GeometryAttributeType;
030: import org.geotools.feature.SchemaException;
031: import org.geotools.feature.type.BasicFeatureTypes;
032: import org.geotools.referencing.CRS;
033: import org.opengis.filter.Filter;
034: import org.opengis.referencing.FactoryException;
035: import org.opengis.referencing.crs.CoordinateReferenceSystem;
036:
037: import com.vividsolutions.jts.geom.Envelope;
038: import com.vividsolutions.jts.geom.Geometry;
039: import com.vividsolutions.jts.geom.GeometryCollection;
040: import com.vividsolutions.jts.geom.LineString;
041: import com.vividsolutions.jts.geom.MultiLineString;
042: import com.vividsolutions.jts.geom.MultiPoint;
043: import com.vividsolutions.jts.geom.MultiPolygon;
044: import com.vividsolutions.jts.geom.Point;
045: import com.vividsolutions.jts.geom.Polygon;
046:
047: /**
048: * A datastore based on the the <a href="http://www.gdal.org/ogr">OGR</a>
049: * spatial data abstraction library
050: *
051: * @author aaime
052: */
053: public class OGRDataStore extends AbstractDataStore {
054: /** C compatible FALSE */
055: static final int FALSE = 0;
056:
057: /** C compatible TRUE */
058: static final int TRUE = 1;
059:
060: /**
061: * The OGRwkbGeometryType enum from ogr_core.h, reduced to represent only 2D
062: * classes (I hope the 2.5D will be handled transparently by JTS)
063: */
064: static final Class[] OGR_GEOM_TYPES = new Class[] { //
065: Geometry.class, // wkbUnknown = 0
066: Point.class, // wkbPoint = 1
067: MultiLineString.class, // wkbLineString = 2,
068: MultiPolygon.class, // wkbPolygon = 3,
069: MultiPoint.class, // wkbMultiPoint = 4,
070: MultiLineString.class, // wkbMultiLineString = 5,
071: MultiPolygon.class, // wkbMultiPolygon = 6,
072: GeometryCollection.class, // wkbGeometryCollection = 7
073: };
074:
075: /**
076: * The source name that OGR should open and handle
077: */
078: private String ogrSourceName;
079:
080: /**
081: * Datastore namespace
082: */
083: private URI namespace;
084:
085: /**
086: * OGR driver to be used for the creation of new data sources
087: */
088: private String ogrDriverName;
089:
090: static {
091: // perform OGR format registration once
092: if (ogr.GetDriverCount() == 0)
093: ogr.RegisterAll();
094: }
095:
096: /**
097: * Creates a new OGRDataStore
098: *
099: * @param ogrSourceName
100: * a references to the source that needs to be opened. May be a
101: * file system path, or a database reference. See the OGR driver
102: * documentation for valid formats of this string.
103: */
104:
105: public OGRDataStore(String ogrSourceName, String ogrDriverName,
106: URI namespace) throws IOException {
107: this .ogrSourceName = ogrSourceName;
108: this .ogrDriverName = ogrDriverName;
109: this .namespace = (namespace != null) ? namespace
110: : FeatureTypes.DEFAULT_NAMESPACE;
111: int update = FALSE;
112: if (ogrDriverName == null) {
113: DataSource ds = getOGRDataSource(update);
114: ds.delete();
115: }
116: }
117:
118: protected FeatureReader getFeatureReader(String typeName)
119: throws IOException {
120: return getFeatureReader(typeName, false);
121: }
122:
123: protected FeatureReader getFeatureReader(String typeName,
124: boolean openForUpdate) throws IOException {
125: DataSource ds = getOGRDataSource(openForUpdate ? TRUE : FALSE);
126: Layer layer = getOGRLayer(ds, typeName);
127: FeatureType schema = getSchema(typeName);
128: return new OGRFeatureReader(ds, layer, schema);
129: }
130:
131: public FeatureType getSchema(String typeName) throws IOException {
132: DataSource ds = getOGRDataSource(FALSE);
133: Layer layer = getOGRLayer(ds, typeName);
134: if (layer == null) {
135: ds.delete();
136: throw new IOException("No such type : " + typeName);
137: }
138: FeatureDefn featureDef = null;
139: try {
140: featureDef = layer.GetLayerDefn();
141: AttributeType[] types = new AttributeType[featureDef
142: .GetFieldCount() + 1];
143:
144: // handle the geometry
145: CoordinateReferenceSystem crs = getCRS(layer);
146: Class geomClass = getGeometryClass(featureDef.GetGeomType());
147: types[0] = AttributeTypeFactory.newAttributeType(
148: "the_geom", geomClass, true, 0, null, crs);
149:
150: // compute a default parent feature type
151: Class geomType = types[0].getType();
152: FeatureType parent = null;
153: if ((geomType == Point.class)
154: || (geomType == MultiPoint.class)) {
155: parent = BasicFeatureTypes.POINT;
156: } else if ((geomType == Polygon.class)
157: || (geomType == MultiPolygon.class)) {
158: parent = BasicFeatureTypes.POLYGON;
159: } else if ((geomType == LineString.class)
160: || (geomType == MultiLineString.class)) {
161: parent = BasicFeatureTypes.LINE;
162: }
163:
164: // handle the other fields
165: for (int i = 1; i < types.length; i++) {
166: FieldDefn fd = featureDef.GetFieldDefn(i - 1);
167: types[i] = AttributeTypeFactory.newAttributeType(fd
168: .GetNameRef(), getFieldClass(fd), true, fd
169: .GetWidth());
170: fd.delete();
171: }
172:
173: // finally build the geometry type
174: return FeatureTypes.newFeatureType(types, layer.GetName(),
175: namespace, false,
176: parent != null ? new FeatureType[] { parent }
177: : null);
178: } catch (FactoryException e) {
179: throw new DataSourceException(
180: "Could not determine geometry SRS", e);
181: } catch (FactoryConfigurationError e) {
182: throw new DataSourceException(
183: "Could not create feature type", e);
184: } catch (SchemaException e) {
185: throw new DataSourceException(
186: "Could not create feature type", e);
187: } finally {
188: if (featureDef != null)
189: featureDef.delete();
190: if (layer != null)
191: layer.delete();
192: if (ds != null)
193: ds.delete();
194: }
195:
196: }
197:
198: public String[] getTypeNames() throws IOException {
199: DataSource ds;
200: try {
201: ds = getOGRDataSource(FALSE);
202: } catch (IOException e) {
203: return new String[0];
204: }
205: String[] typeNames = new String[ds.GetLayerCount()];
206: for (int i = 0; i < typeNames.length; i++) {
207: Layer l = ds.GetLayerByIndex(i);
208: typeNames[i] = l.GetName();
209: l.delete();
210: }
211: ds.delete();
212: return typeNames;
213: }
214:
215: protected int getCount(Query query) throws IOException {
216: if (!Filter.INCLUDE.equals(query.getFilter()))
217: return -1;
218:
219: DataSource ds = getOGRDataSource(FALSE);
220: Layer l = getOGRLayer(ds, query.getTypeName());
221: if (l == null)
222: throw new IOException("Unknown feature type: "
223: + query.getTypeName());
224:
225: // go for the quick computation, return -1 otherwise
226: int count = l.GetFeatureCount(FALSE);
227: l.delete();
228: ds.delete();
229: return count;
230: }
231:
232: protected FeatureWriter createFeatureWriter(String typeName,
233: Transaction transaction) throws IOException {
234: if (supportsInPlaceWrite(typeName)) {
235: OGRFeatureReader reader = (OGRFeatureReader) getFeatureReader(
236: typeName, true);
237: return new OGRDirectFeatureWriter(reader);
238: } else {
239: throw new UnsupportedOperationException(
240: "This file format does not support in place write, "
241: + "can't perform updates/deletes");
242: }
243: }
244:
245: public boolean supportsInPlaceWrite(String typeName)
246: throws IOException {
247: // if it's not able to open the datasource in update mode, there's no
248: // change it'll be able to support in place write (no need to throw an
249: // exception in
250: // this case, thus the direct call instead of getOGRDataSource())
251: DataSource ds = ogr.OpenShared(ogrSourceName, TRUE);
252: if (ds == null)
253: return false;
254: Layer l = getOGRLayer(ds, typeName);
255: boolean retval = l.TestCapability(ogr.OLCDeleteFeature)
256: && l.TestCapability(ogr.OLCRandomWrite)
257: && l.TestCapability(ogr.OLCSequentialWrite);
258: l.delete();
259: ds.delete();
260: return retval;
261: }
262:
263: // given up on this one. Some drivers tell you that you can write on a layer
264: // only if you opened in update mode. We could try to create a new data
265: // source,
266: // but unfortunately there is no way to have a generic ogr name that works
267: // for every data store. Finally, not all drivers that do write do support
268: // datastore deletion. So, in the end, either we can update directly an OGR
269: // datasource content, or we have to create a new one and roll our own
270: // mechanism to have it replace the old one (this includes moving and
271: // deleting
272: // the elements that make up a data store).
273: public boolean supportsWriteNewLayer(String typeName)
274: throws IOException {
275: DataSource ds = getOGRDataSource(FALSE);
276: Layer l = getOGRLayer(ds, typeName);
277: boolean retval = ds.TestCapability(ogr.ODsCCreateLayer)
278: && l.TestCapability(ogr.OLCSequentialWrite);
279: l.delete();
280: ds.delete();
281: return retval;
282: }
283:
284: public Envelope getBounds(Query q) throws IOException {
285: if (!q.getFilter().equals(Filter.INCLUDE))
286: return null;
287:
288: DataSource ds = getOGRDataSource(OGRDataStore.FALSE);
289: Layer l = getOGRLayer(ds, q.getTypeName());
290: if (l.TestCapability(ogr.OLCFastGetExtent)) {
291: double[] bbox = new double[4];
292: // TODO: hum... going thru this forcing the computation is anyways
293: // faster than loading all the features just for the sake of
294: // getting out the bbox.... but we don't have this
295: // explicit middle ground in Geotools
296: l.GetExtent(bbox, OGRDataStore.FALSE);
297: // TODO: return a ReferencedEnvelope?
298: return new Envelope(bbox[0], bbox[1], bbox[2], bbox[3]);
299: } else {
300: return null;
301: }
302: }
303:
304: public void createSchema(FeatureType schema) throws IOException {
305: // TODO: add a field to allow approximate definitions
306: createSchema(schema, false, null);
307: }
308:
309: /**
310: * Creates a new OGR layer with provided schema and options
311: *
312: * @param schema
313: * the geotools schema
314: * @param approximateFields
315: * if true, OGR will try to create fields that are approximations
316: * of the required ones when an exact match cannt be provided
317: * @param options
318: * OGR data source/layer creation options
319: * @throws IOException
320: */
321: public void createSchema(FeatureType schema,
322: boolean approximateFields, String[] options)
323: throws IOException {
324: DataSource ds = null;
325: Layer l = null;
326:
327: try {
328: // either open datasource, or try creating one
329: Vector optVector = options != null ? new Vector(Arrays
330: .asList(options)) : null;
331: try {
332: ds = getOGRDataSource(TRUE);
333: } catch (IOException e) {
334: if (ogrDriverName != null) {
335: Driver d = ogr.GetDriverByName(ogrDriverName);
336: ds = d.CreateDataSource(ogrSourceName, optVector);
337: d.delete();
338:
339: if (ds == null)
340: throw new IOException(
341: "Could not create OGR data source with driver "
342: + ogrDriverName
343: + " and options " + optVector);
344: } else {
345: throw new DataSourceException(
346: "Driver not provided, and could not "
347: + "open data source neither");
348: }
349: }
350:
351: // get the spatial reference corresponding to the default geometry
352: GeometryAttributeType geomType = schema
353: .getDefaultGeometry();
354: int ogrGeomType = getOGRGeometryType(geomType);
355: SpatialReference sr = null;
356: if (geomType.getCoordinateSystem() != null) {
357: String wkt = geomType.getCoordinateSystem().toString();
358: sr = new SpatialReference(null);
359: if (sr.ImportFromWkt(wkt) != 0) {
360: sr = null;
361: LOGGER
362: .warning("OGR could not parse the geometry WKT,"
363: + " detailed error is: "
364: + gdal.GetLastErrorMsg()
365: + "\n"
366: + "WKT was: " + wkt);
367: }
368: }
369:
370: // create the layer
371: l = ds.CreateLayer(schema.getTypeName(), sr, ogrGeomType,
372: optVector);
373: if (l == null) {
374: throw new DataSourceException(
375: "Could not create the OGR layer: "
376: + gdal.GetLastErrorMsg());
377: }
378:
379: // create fields
380: for (int i = 0; i < schema.getAttributeCount(); i++) {
381: AttributeType at = schema.getAttributeType(i);
382: if (at == schema.getDefaultGeometry())
383: continue;
384:
385: FieldDefn definition = getOGRFieldDefinition(at);
386: l.CreateField(definition, approximateFields ? TRUE
387: : FALSE);
388: }
389: } finally {
390: if (l != null) {
391: l.delete();
392: }
393: if (ds != null)
394: ds.delete();
395: }
396: }
397:
398: // ---------------------------------------------------------------------------------------
399: // PRIVATE SUPPORT METHODS
400: // ---------------------------------------------------------------------------------------
401:
402: private FieldDefn getOGRFieldDefinition(AttributeType at)
403: throws IOException {
404: final Class type = at.getType();
405: final FieldDefn def;
406: // set type, width, precision and justification where:
407: // * width is the number of chars needed to format the strings
408: // equivalent of
409: // the number
410: // * precision is the number of chars after decimal pont
411: // * justification: right or left (in outputs)
412: // TODO: steal code from Shapefile data store to guess eventual size
413: // limitations
414: if (Boolean.class.equals(type)) {
415: def = new FieldDefn(at.getName(), ogr.OFTString);
416: def.SetWidth(5);
417: } else if (Byte.class.equals(type)) {
418: def = new FieldDefn(at.getName(), ogr.OFTInteger);
419: def.SetWidth(3);
420: def.SetJustify(ogr.OJRight);
421: } else if (Short.class.equals(type)) {
422: def = new FieldDefn(at.getName(), ogr.OFTInteger);
423: def.SetWidth(5);
424: def.SetJustify(ogr.OJRight);
425: } else if (Integer.class.equals(type)) {
426: def = new FieldDefn(at.getName(), ogr.OFTInteger);
427: def.SetWidth(9);
428: def.SetJustify(ogr.OJRight);
429: } else if (Long.class.equals(type)) {
430: def = new FieldDefn(at.getName(), ogr.OFTInteger);
431: def.SetWidth(19);
432: def.SetJustify(ogr.OJRight);
433: } else if (BigInteger.class.equals(type)) {
434: def = new FieldDefn(at.getName(), ogr.OFTInteger);
435: def.SetWidth(32);
436: def.SetJustify(ogr.OJRight);
437: } else if (BigDecimal.class.equals(type)) {
438: def = new FieldDefn(at.getName(), ogr.OFTReal);
439: def.SetWidth(32);
440: def.SetPrecision(15);
441: def.SetJustify(ogr.OJRight);
442: } else if (Float.class.equals(type)) {
443: def = new FieldDefn(at.getName(), ogr.OFTReal);
444: def.SetWidth(12);
445: def.SetPrecision(7);
446: def.SetJustify(ogr.OJRight);
447: } else if (Double.class.equals(type)
448: || Number.class.isAssignableFrom(type)) {
449: def = new FieldDefn(at.getName(), ogr.OFTReal);
450: def.SetWidth(22);
451: def.SetPrecision(16);
452: def.SetJustify(ogr.OJRight);
453: } else if (String.class.equals(type)) {
454: def = new FieldDefn(at.getName(), ogr.OFTString);
455: def.SetWidth(255);
456: // TODO: do a serious attempt to cover blob and clob too
457: } else if (byte[].class.equals(type)) {
458: def = new FieldDefn(at.getName(), ogr.OFTBinary);
459: // } else if (java.sql.Date.class.isAssignableFrom(type)) {
460: // def = new FieldDefn(at.getName(), ogr.OFTDate);
461: // } else if (java.sql.Time.class.isAssignableFrom(type)) {
462: // def = new FieldDefn(at.getName(), ogr.OFTTime);
463: } else if (java.util.Date.class.isAssignableFrom(type)) {
464: def = new FieldDefn(at.getName(), ogr.OFTDateTime);
465: } else {
466: throw new IOException("Cannot map " + type
467: + " to an OGR type");
468: }
469:
470: return def;
471: }
472:
473: /**
474: * Tries to open the specified source in either read only or read/write mode
475: *
476: * @param update
477: * open read/write if TRUE, otherwise open read only
478: *
479: * @return
480: * @throws IOException
481: */
482: DataSource getOGRDataSource(int update) throws IOException {
483: DataSource ds = ogr.OpenShared(ogrSourceName, update);
484: if (ds == null)
485: throw new IOException("OGR could not open '"
486: + ogrSourceName + "'");
487: return ds;
488: }
489:
490: /**
491: * Internal utility method, returns a layer if available, otherwise closes
492: * the provided datasource and throws and exception stating the feature type
493: * is not available
494: *
495: * @param ds
496: * @param typeName
497: * @return
498: * @throws IOException
499: */
500: Layer getOGRLayer(DataSource ds, String typeName)
501: throws IOException {
502: Layer l = ds.GetLayerByName(typeName);
503: if (l == null) {
504: ds.delete();
505: throw new IOException("No feature type " + typeName);
506: }
507: return l;
508: }
509:
510: /**
511: * Turns an ogrFieldType into the best corresponding class we can figure out
512: *
513: * @param ogrFieldType
514: * @return
515: */
516: private Class getFieldClass(FieldDefn field) {
517: final int ogrFieldType = field.GetFieldType();
518: final int width = field.GetWidth();
519: if (ogrFieldType < 0)
520: throw new IllegalArgumentException(
521: "Can't have a negative type");
522: if (ogrFieldType == ogr.OFTInteger) {
523: if (width <= 9)
524: return Integer.class;
525: else if (width <= 19)
526: return Long.class;
527: else
528: return BigDecimal.class;
529: } else if (ogrFieldType == ogr.OFTReal) {
530: if (width < 13)
531: return Float.class;
532: else
533: return Double.class;
534: } else if (ogrFieldType == ogr.OFTDate
535: || ogrFieldType == ogr.OFTTime
536: || ogrFieldType == ogr.OFTDateTime) {
537: // return java.sql.Date.class;
538: // } else if (ogrFieldType == ogr.OFTTime) {
539: // return java.sql.Time.class;
540: // } else if (ogrFieldType == ogr.OFTDateTime) {
541: return java.util.Date.class;
542: } else {
543: return String.class;
544: }
545: }
546:
547: /**
548: * Turns an OGR {@link SpatialReference} object into a
549: * {@link CoordinateReferenceSystem} one (using WKT parsing)
550: *
551: * @param reference
552: * @return
553: * @throws FactoryException
554: */
555: private CoordinateReferenceSystem getCRS(Layer layer)
556: throws FactoryException {
557: SpatialReference reference = layer.GetSpatialRef();
558: if (reference == null)
559: return null;
560:
561: String[] wkt = new String[1];
562: reference.ExportToWkt(wkt);
563: reference.delete();
564: return CRS.parseWKT(wkt[0]);
565: }
566:
567: /**
568: * Turns a wkbGeometryType into the best corresponding geometry class we can
569: * figure out
570: *
571: * @param wkbGeometryType
572: * @return
573: */
574: private Class getGeometryClass(int wkbGeometryType) {
575: if (wkbGeometryType < 0)
576: throw new IllegalArgumentException(
577: "Can't have a negative type");
578: int mask25d = 0x80000000;
579: int unmaskedType = wkbGeometryType & ~mask25d;
580: if (wkbGeometryType >= OGR_GEOM_TYPES.length) {
581: LOGGER
582: .warning("Could not recognize geometry type "
583: + wkbGeometryType
584: + ". Assuming it's a new type and handling as a string");
585: return Geometry.class;
586: }
587: return OGR_GEOM_TYPES[unmaskedType];
588: }
589:
590: /**
591: * Returns the OGR geometry type constant ginve a geometry attribute type
592: *
593: * @param type
594: * @return
595: * @throws IOException
596: */
597: private int getOGRGeometryType(GeometryAttributeType type)
598: throws IOException {
599: for (int i = 0; i < OGR_GEOM_TYPES.length; i++) {
600: if (type.getType().equals(OGR_GEOM_TYPES[i]))
601: return i;
602: }
603: throw new IOException("Could not map " + type.getType()
604: + " to an OGR geometry type");
605: }
606:
607: }
|