/* * 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.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.bridj.Pointer; import org.geotools.data.EmptyFeatureReader; import org.geotools.data.FeatureReader; import org.geotools.data.FilteringFeatureReader; import org.geotools.data.Query; import org.geotools.data.ReTypeFeatureReader; import org.geotools.data.ogr.bridj.OGREnvelope; import org.geotools.data.store.ContentEntry; import org.geotools.data.store.ContentFeatureSource; import org.geotools.data.store.ContentFeatureStore; import org.geotools.factory.Hints; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.filter.FilterAttributeExtractor; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.FeatureVisitor; 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 org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; /** * FeatureSource for the OGR store, based on the {@link ContentFeatureStore} framework * * @author Andrea Aime - GeoSolutions * @source $URL$ */ @SuppressWarnings("rawtypes") class OGRFeatureSource extends ContentFeatureSource { public OGRFeatureSource(ContentEntry entry, Query query) { super(entry, query); } @Override public OGRDataStore getDataStore() { return (OGRDataStore) super.getDataStore(); } @Override protected ReferencedEnvelope getBoundsInternal(Query query) throws IOException { CoordinateReferenceSystem crs = getSchema().getCoordinateReferenceSystem(); // we need to know how much we can translate of the filter (the translator will also // simplify the filter) OGRFilterTranslator filterTx = new OGRFilterTranslator(getSchema(), query.getFilter()); if (Filter.EXCLUDE.equals(filterTx.getFilter())) { // empty results return new ReferencedEnvelope(crs); } else if (!filterTx.isFilterFullySupported()) { return null; } else { // encodable, we then encode and get the bounds Pointer dataSource = null; Pointer layer = null; try { // grab the layer String typeName = getEntry().getTypeName(); dataSource = getDataStore().openOGRDataSource(false); layer = getDataStore().openOGRLayer(dataSource, typeName); // filter it setLayerFilters(layer, filterTx); Pointer<OGREnvelope> boundsPtr = allocate(OGREnvelope.class); int code = OGR_L_GetExtent(layer, boundsPtr, 0); if (code == OGRERR_FAILURE) { return null; } else { OGREnvelope bounds = boundsPtr.get(); return new ReferencedEnvelope(bounds.MinX(), bounds.MaxX(), bounds.MinY(), bounds.MaxY(), crs); } } finally { OGRUtils.releaseLayer(layer); OGRUtils.releaseDataSource(dataSource); } } } /** * Sets the spatial filter and attribute filter on the specified layer * * @param layer * @param filterTx * @throws IOException */ private void setLayerFilters(Pointer layer, OGRFilterTranslator filterTx) throws IOException { Geometry spatialFilter = filterTx.getSpatialFilter(); if (spatialFilter != null) { Pointer ogrGeometry = new GeometryMapper.WKB(new GeometryFactory()) .parseGTGeometry(spatialFilter); OGR_L_SetSpatialFilter(layer, ogrGeometry); } String attFilter = filterTx.getAttributeFilter(); if (attFilter != null) { OGR_L_SetAttributeFilter(layer, pointerToCString(attFilter)); } } @Override protected int getCountInternal(Query query) throws IOException { // check how much we can encode OGRFilterTranslator filterTx = new OGRFilterTranslator(getSchema(), query.getFilter()); if (Filter.EXCLUDE.equals(filterTx.getFilter())) { return 0; } else if (!filterTx.isFilterFullySupported()) { // too expensive then return -1; } else { // encode and count Pointer dataSource = null; Pointer layer = null; try { // grab the layer String typeName = getEntry().getTypeName(); dataSource = getDataStore().openOGRDataSource(false); layer = getDataStore().openOGRLayer(dataSource, typeName); // filter it setLayerFilters(layer, filterTx); return OGR_L_GetFeatureCount(layer, 0); } finally { OGRUtils.releaseLayer(layer); OGRUtils.releaseDataSource(dataSource); } } } @Override protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(Query query) throws IOException { return getReaderInternal(null, null, query); } protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(Pointer dataSource, Pointer layer, Query query) throws IOException { // check how much we can encode OGRFilterTranslator filterTx = new OGRFilterTranslator(getSchema(), query.getFilter()); if (Filter.EXCLUDE.equals(filterTx.getFilter())) { return new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>(getSchema()); } // encode and count boolean cleanup = false; try { // grab the data source String typeName = getEntry().getTypeName(); if (dataSource == null) { dataSource = getDataStore().openOGRDataSource(false); cleanup = true; } // extract the post filter Filter postFilter = null; if (!filterTx.isFilterFullySupported()) { postFilter = filterTx.getPostFilter(); } // prepare the target schema SimpleFeatureType sourceSchema = getSchema(); SimpleFeatureType querySchema = sourceSchema; SimpleFeatureType targetSchema = sourceSchema; String[] properties = query.getPropertyNames(); if (properties != null && properties.length > 0) { targetSchema = SimpleFeatureTypeBuilder.retype(sourceSchema, properties); querySchema = targetSchema; // if we have a post filter we have to include in the queried features also the // attribute needed to evaluate the post-filter if (postFilter != null) { Set<String> queriedAttributes = new HashSet<String>(Arrays.asList(properties)); FilterAttributeExtractor extraAttributeExtractor = new FilterAttributeExtractor(); postFilter.accept(extraAttributeExtractor, null); Set<String> extraAttributeSet = new HashSet<String>( extraAttributeExtractor.getAttributeNameSet()); extraAttributeSet.removeAll(queriedAttributes); if (extraAttributeSet.size() > 0) { String[] queryProperties = new String[properties.length + extraAttributeSet.size()]; System.arraycopy(properties, 0, queryProperties, 0, properties.length); String[] extraAttributes = (String[]) extraAttributeSet .toArray(new String[extraAttributeSet.size()]); System.arraycopy(extraAttributes, 0, queryProperties, properties.length, extraAttributes.length); querySchema = SimpleFeatureTypeBuilder .retype(sourceSchema, queryProperties); } } } // build the layer query and execute it if (layer == null) { String sql = getLayerSql(querySchema == sourceSchema ? null : querySchema, filterTx.getAttributeFilter(), query.getSortBy()); Pointer spatialFilterPtr = null; Geometry spatialFilter = filterTx.getSpatialFilter(); if (spatialFilter != null) { spatialFilterPtr = new GeometryMapper.WKB(new GeometryFactory()) .parseGTGeometry(spatialFilter); } layer = OGR_DS_ExecuteSQL(dataSource, pointerToCString(sql), spatialFilterPtr, null); if (layer == null) { throw new IOException("Failed to query the source layer with SQL: " + sql); } } else { setLayerFilters(layer, filterTx); // would be nice, but it's not really working... // setIgnoredFields(layer, querySchema, sourceSchema); } // see if we have a geometry factory to use GeometryFactory gf = getGeometryFactory(query); // build the reader FeatureReader<SimpleFeatureType, SimpleFeature> reader = new OGRFeatureReader( dataSource, layer, querySchema, sourceSchema, gf); cleanup = false; // do we have to post-filter? if (!filterTx.isFilterFullySupported()) { reader = new FilteringFeatureReader<SimpleFeatureType, SimpleFeature>(reader, filterTx.getPostFilter()); if (targetSchema != querySchema) { reader = new ReTypeFeatureReader(reader, targetSchema); } } return reader; } finally { if (cleanup) { OGRUtils.releaseLayer(layer); OGRUtils.releaseDataSource(dataSource); } } } void setIgnoredFields(Pointer layer, SimpleFeatureType querySchema, SimpleFeatureType sourceSchema) throws IOException { if (OGR_L_TestCapability(layer, pointerToCString(OLCIgnoreFields)) != 0) { List<String> ignoredFields = new ArrayList<String>(); ignoredFields.add("OGR_STYLE"); // if no geometry, skip it if (querySchema.getGeometryDescriptor() == null) { ignoredFields.add("OGR_GEOMETRY"); } // process all other attributes for (AttributeDescriptor ad : sourceSchema.getAttributeDescriptors()) { if (!(ad instanceof GeometryDescriptor)) { String name = ad.getLocalName(); if (querySchema.getDescriptor(name) == null) { ignoredFields.add(name); } } } if (ignoredFields.size() > 0) { // the list should be NULL terminated ignoredFields.add(null); String[] ignoredFieldsArr = (String[]) ignoredFields .toArray(new String[ignoredFields.size()]); Pointer<Pointer<Byte>> ifPtr = pointerToCStrings(ignoredFieldsArr); OGRUtils.checkError(OGR_L_SetIgnoredFields(layer, ifPtr)); } } } private String getLayerSql(SimpleFeatureType targetSchema, String attributeFilter, SortBy[] sortBy) { StringBuilder sb = new StringBuilder(); // list only the non geometry attributes // select attributes sb.append("SELECT FID, "); if (targetSchema == null) { sb.append("* "); } else { for (AttributeDescriptor attribute : targetSchema.getAttributeDescriptors()) { if(attribute instanceof GeometryDescriptor) { continue; } else { sb.append(attribute.getLocalName()).append(", "); } } sb.setLength(sb.length() - 2); sb.append(" "); } sb.append("FROM ").append("'").append(getSchema().getTypeName()).append("' "); // attribute filter if (attributeFilter != null) { sb.append("WHERE ").append(attributeFilter); } // order by if (sortBy != null && sortBy.length > 0) { sb.append("ORDER BY "); for (SortBy sort : sortBy) { if (sort == SortBy.NATURAL_ORDER) { sb.append("FID, "); } else if (sort == SortBy.REVERSE_ORDER) { sb.append("FID DESC, "); } else { sb.append(sort.getPropertyName().getPropertyName()); if (sort.getSortOrder() == SortOrder.DESCENDING) { sb.append(" DESC"); } sb.append(", "); } } sb.setLength(sb.length() - 2); } return sb.toString(); } GeometryFactory getGeometryFactory(Query query) { Hints hints = query.getHints(); GeometryFactory gf = null; if (hints != null) { gf = (GeometryFactory) hints.get(Hints.JTS_GEOMETRY_FACTORY); if (gf == null) { // look for a coordinate sequence factory CoordinateSequenceFactory csFactory = (CoordinateSequenceFactory) hints .get(Hints.JTS_COORDINATE_SEQUENCE_FACTORY); if (csFactory != null) { gf = new GeometryFactory(csFactory); } } } if (gf == null) { gf = new GeometryFactory(); } return gf; } @Override protected SimpleFeatureType buildFeatureType() throws IOException { String typeName = getEntry().getTypeName(); String namespaceURI = getDataStore().getNamespaceURI(); Pointer dataSource = null; Pointer layer = null; try { // grab the layer definition dataSource = getDataStore().openOGRDataSource(false); layer = getDataStore().openOGRLayer(dataSource, typeName); // map to geotools feature type return new FeatureTypeMapper().getFeatureType(layer, typeName, namespaceURI); } finally { OGRUtils.releaseLayer(layer); OGRUtils.releaseDataSource(dataSource); } } @Override protected boolean canFilter() { return true; } @Override protected boolean canRetype() { return true; } @Override protected boolean canSort() { return true; } @Override protected boolean handleVisitor(Query query, FeatureVisitor visitor) throws IOException { return super.handleVisitor(query, visitor); } }