/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008-2014, 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.wfs; import java.io.IOException; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import org.geotools.data.DataSourceException; import org.geotools.data.DataUtilities; import org.geotools.data.DiffFeatureReader; import org.geotools.data.EmptyFeatureReader; import org.geotools.data.FeatureReader; import org.geotools.data.FeatureSource; import org.geotools.data.FilteringFeatureReader; import org.geotools.data.Query; import org.geotools.data.ReTypeFeatureReader; import org.geotools.data.ResourceInfo; import org.geotools.data.Transaction; import org.geotools.data.Transaction.State; import org.geotools.data.crs.ForceCoordinateSystemFeatureReader; import org.geotools.data.crs.ReprojectFeatureReader; import org.geotools.data.store.ContentEntry; import org.geotools.data.store.ContentFeatureSource; import org.geotools.data.wfs.internal.GetFeatureParser; import org.geotools.data.wfs.internal.GetFeatureRequest; import org.geotools.data.wfs.internal.GetFeatureRequest.ResultType; import org.geotools.data.wfs.internal.GetFeatureResponse; import org.geotools.data.wfs.internal.WFSClient; import org.geotools.data.wfs.internal.WFSConfig; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Hints; import org.geotools.feature.SchemaException; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.gml2.bindings.GML2EncodingUtils; import org.geotools.util.logging.Logging; import org.opengis.feature.FeatureVisitor; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; class WFSFeatureSource extends ContentFeatureSource { private static final Logger LOGGER = Logging.getLogger(WFSFeatureSource.class); private final WFSClient client; public WFSFeatureSource(final ContentEntry entry, final WFSClient client) { super(entry, null); this.client = client; } WFSClient getWfs() { return client; } @Override protected boolean handleVisitor(Query query, FeatureVisitor visitor) throws IOException { return false; } @Override protected boolean canReproject() { return true; } @Override protected boolean canOffset() { return false;// TODO: check with the WFS client } /** * @return {@code true} * @see org.geotools.data.store.ContentFeatureSource#canSort() */ @Override protected boolean canSort() { return client.canSort(); } /** * @return {@code true} * @see org.geotools.data.store.ContentFeatureSource#canRetype() */ @Override protected boolean canRetype() { return client.canRetype(); } /** * @return {@code true} * @see org.geotools.data.store.ContentFeatureSource#canFilter() */ @Override protected boolean canFilter() { return client.canFilter(); } /** * @return {@code true} * @see org.geotools.data.store.ContentFeatureSource#canLimit() */ @Override protected boolean canLimit() { return client.canLimit(); } @Override public WFSDataStore getDataStore() { return (WFSDataStore) super.getDataStore(); } /** * @return the WFS advertised bounds of the feature type if * {@code Filter.INCLUDE == query.getFilter()}, reprojected to the Query's crs, or * {@code null} otherwise as it would be too expensive to calculate. * @see FeatureSource#getBounds(Query) * @see org.geotools.data.store.ContentFeatureSource#getBoundsInternal(org.geotools.data.Query) */ @Override protected ReferencedEnvelope getBoundsInternal(Query query) throws IOException { if (!Filter.INCLUDE.equals(query.getFilter())) { return null; } final QName remoteTypeName = getRemoteTypeName(); final CoordinateReferenceSystem targetCrs; if (null == query.getCoordinateSystem()) { targetCrs = client.getDefaultCRS(remoteTypeName); } else { targetCrs = query.getCoordinateSystem(); } ReferencedEnvelope bounds = client.getBounds(remoteTypeName, targetCrs); return bounds; } /** * @return the remote WFS advertised number of features for the given query only if the query * filter is fully supported AND the wfs returns that information in as an attribute of * the FeatureCollection (since the request is performed with resultType=hits), * otherwise {@code -1} as it would be too expensive to calculate. * @see FeatureSource#getCount(Query) * @see org.geotools.data.store.ContentFeatureSource#getCountInternal(org.geotools.data.Query) */ @Override protected int getCountInternal(Query query) throws IOException { if (!client.canCount()) { return -1; } GetFeatureRequest request = createGetFeature(query, ResultType.HITS); GetFeatureResponse response = client.issueRequest(request); GetFeatureParser featureParser = response.getFeatures(null); int resultCount = featureParser.getNumberOfFeatures(); return resultCount; } /** * Invert axis order in the given query filter, if needed. * * @param query */ private void invertAxisInFilterIfNeeded(Query query) { boolean invertXY = WFSConfig.invertAxisNeeded(client.getAxisOrderFilter(), query.getCoordinateSystem()); if (invertXY) { Filter filter = query.getFilter(); FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); InvertAxisFilterVisitor visitor = new InvertAxisFilterVisitor(ff, new GeometryFactory()); filter = (Filter) filter.accept(visitor, null); query.setFilter(filter); } } protected GetFeatureRequest createGetFeature(Query query, ResultType resultType) throws IOException { GetFeatureRequest request = client.createGetFeatureRequest(); final WFSDataStore dataStore = getDataStore(); final QName remoteTypeName = dataStore.getRemoteTypeName(getEntry().getName()); final SimpleFeatureType remoteSimpleFeatureType; remoteSimpleFeatureType = dataStore.getRemoteSimpleFeatureType(remoteTypeName); request.setTypeName(remoteTypeName); request.setFullType(remoteSimpleFeatureType); invertAxisInFilterIfNeeded(query); request.setFilter(query.getFilter()); request.setResultType(resultType); request.setHints(query.getHints()); int maxFeatures = query.getMaxFeatures(); if (Integer.MAX_VALUE > maxFeatures) { request.setMaxFeatures(maxFeatures); } // let the request decide request.setOutputFormat(outputFormat); request.setPropertyNames(query.getPropertyNames()); request.setSortBy(query.getSortBy()); String srsName = getSupportedSrsName(request, query); request.setSrsName(srsName); return request; } /** * @see FeatureSource#getFeatures(Query) * @see org.geotools.data.store.ContentFeatureSource#getReaderInternal(org.geotools.data.Query) */ @Override protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(Query localQuery) throws IOException { if (Filter.EXCLUDE.equals(localQuery.getFilter())) { return new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>(getSchema()); } GetFeatureRequest request = createGetFeature(localQuery, ResultType.RESULTS); final SimpleFeatureType destType = getQueryType(localQuery, getSchema()); final SimpleFeatureType contentType = getQueryType(localQuery, (SimpleFeatureType) request.getFullType()); request.setQueryType(contentType); GetFeatureResponse response = client.issueRequest(request); GeometryFactory geometryFactory = findGeometryFactory(localQuery.getHints()); GetFeatureParser features = response.getSimpleFeatures(geometryFactory); FeatureReader<SimpleFeatureType, SimpleFeature> reader; reader = new WFSFeatureReader(features); if (request.getUnsupportedFilter() != null && request.getUnsupportedFilter() != Filter.INCLUDE) { reader = new FilteringFeatureReader<SimpleFeatureType, SimpleFeature>(reader, request.getUnsupportedFilter()); } if (!reader.hasNext()) { return new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>(contentType); } final SimpleFeatureType readerType = reader.getFeatureType(); if (!destType.equals(readerType)) { final boolean cloneContents = false; reader = new ReTypeFeatureReader(reader, destType, cloneContents); } reader = applyReprojectionDecorator(reader, localQuery, request); Transaction transaction = getTransaction(); if (!Transaction.AUTO_COMMIT.equals(transaction)) { ContentEntry entry = getEntry(); State state = transaction.getState(entry); WFSLocalTransactionState wfsState = (WFSLocalTransactionState) state; if (wfsState != null) { WFSDiff diff = wfsState.getDiff(); reader = new DiffFeatureReader<SimpleFeatureType, SimpleFeature>(reader, diff, localQuery.getFilter()); } } return reader; } protected String getSupportedSrsName(GetFeatureRequest request, Query query) { String epsgCode = GML2EncodingUtils.epsgCode(query.getCoordinateSystem()); Set<String> supported = request.getStrategy().getSupportedCRSIdentifiers(request.getTypeName()); for (String supportedSrs : supported) { if (supportedSrs.endsWith(":" + epsgCode)) { return supportedSrs; } } return null; } protected FeatureReader<SimpleFeatureType, SimpleFeature> applyReprojectionDecorator(FeatureReader <SimpleFeatureType, SimpleFeature> reader, Query query, GetFeatureRequest request) { FeatureReader<SimpleFeatureType, SimpleFeature> tmp = reader; if (query.getCoordinateSystem() != null && !query.getCoordinateSystem().equals(reader.getFeatureType().getCoordinateReferenceSystem())) { if (request.getSrsName() != null) { try { reader = new ForceCoordinateSystemFeatureReader(reader, query.getCoordinateSystem()); } catch (SchemaException e) { LOGGER.warning(e.toString()); reader = tmp; } } else { try { reader = new ReprojectFeatureReader(reader, query.getCoordinateSystem()); } catch (Exception e) { LOGGER.warning(e.toString()); reader = tmp; } } } return reader; } private GeometryFactory findGeometryFactory(Hints hints) { GeometryFactory geomFactory = (GeometryFactory) hints.get(Hints.JTS_GEOMETRY_FACTORY); if (geomFactory == null) { CoordinateSequenceFactory seqFac; seqFac = (CoordinateSequenceFactory) hints.get(Hints.JTS_COORDINATE_SEQUENCE_FACTORY); if (seqFac == null) { seqFac = PackedCoordinateSequenceFactory.DOUBLE_FACTORY; } geomFactory = new GeometryFactory(seqFac); } return geomFactory; } @Override protected SimpleFeatureType buildFeatureType() throws IOException { final WFSDataStore dataStore = getDataStore(); final Name localTypeName = getEntry().getName(); final QName remoteTypeName = dataStore.getRemoteTypeName(localTypeName); final SimpleFeatureType remoteSimpleFeatureType; remoteSimpleFeatureType = dataStore.getRemoteSimpleFeatureType(remoteTypeName); // adapt the feature type name SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); builder.init(remoteSimpleFeatureType); builder.setName(localTypeName); String namespaceOverride = entry.getName().getNamespaceURI(); if (namespaceOverride != null) { builder.setNamespaceURI(namespaceOverride); } GeometryDescriptor defaultGeometry = remoteSimpleFeatureType.getGeometryDescriptor(); if (defaultGeometry != null) { builder.setDefaultGeometry(defaultGeometry.getLocalName()); builder.setCRS(defaultGeometry.getCoordinateReferenceSystem()); } final SimpleFeatureType adaptedFeatureType = builder.buildFeatureType(); return adaptedFeatureType; } public QName getRemoteTypeName() throws IOException { Name localTypeName = getEntry().getName(); QName remoteTypeName = getDataStore().getRemoteTypeName(localTypeName); return remoteTypeName; } /** * Returns the feature type that shall result of issueing the given request, adapting the * original feature type for the request's type name in terms of the query CRS and requested * attributes. * * @param query * @return * @throws IOException */ SimpleFeatureType getQueryType(final Query query, SimpleFeatureType featureType) throws IOException { final CoordinateReferenceSystem coordinateSystemReproject = query .getCoordinateSystemReproject(); String[] propertyNames = query.getPropertyNames(); SimpleFeatureType queryType = featureType; if (propertyNames != null && propertyNames.length > 0) { try { queryType = DataUtilities.createSubType(queryType, propertyNames); } catch (SchemaException e) { throw new DataSourceException(e); } } else { propertyNames = DataUtilities.attributeNames(featureType); } if (coordinateSystemReproject != null) { try { queryType = DataUtilities.createSubType(queryType, propertyNames, coordinateSystemReproject); } catch (SchemaException e) { throw new DataSourceException(e); } } return queryType; } @Override public ResourceInfo getInfo() { try { return client.getInfo(getRemoteTypeName()); } catch (IOException e) { LOGGER.log(Level.WARNING, "Unexpected error getting ResourceInfo: ", e); return super.getInfo(); } } }