/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.gwt2.plugin.wfs.server.command;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.geomajas.command.CommandHasRequest;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.service.BboxService;
import org.geomajas.geometry.service.GeometryService;
import org.geomajas.global.GeomajasException;
import org.geomajas.gwt2.client.map.attribute.AttributeDescriptor;
import org.geomajas.gwt2.client.map.feature.query.Criterion;
import org.geomajas.gwt2.plugin.wfs.server.command.converter.FeatureConverter;
import org.geomajas.gwt2.plugin.wfs.server.command.dto.WfsGetFeatureRequest;
import org.geomajas.gwt2.plugin.wfs.server.command.dto.WfsGetFeatureResponse;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.CriterionToFilterConverter;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.CriterionToFilterConverterFactory;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.URLBuilder;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.WfsDataStoreFactory;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.WfsHttpClientFactory;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.impl.DefaultCriterionFilterConverter;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.impl.DefaultWfsDataStoreFactory;
import org.geomajas.gwt2.plugin.wfs.server.command.factory.impl.DefaultWfsHttpClientFactory;
import org.geomajas.gwt2.plugin.wfs.server.dto.WfsFeatureCollectionDto;
import org.geomajas.gwt2.plugin.wfs.server.dto.WfsVersionDto;
import org.geomajas.layer.feature.Feature;
import org.geomajas.service.DtoConverterService;
import org.geomajas.service.FilterService;
import org.geotools.data.DataStore;
import org.geotools.data.Query;
import org.geotools.data.ows.HTTPClient;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.wfs.WFSDataStoreFactory;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.CRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.GeographicCRS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
/**
* Command that executes a WFS GetFeatures request.
* <p/>
* This command is not part of the API and shouldn't be used directly.
*
* @author Jan De Moerloose
*
*/
@Component(WfsGetFeatureRequest.COMMAND_NAME)
public class WfsGetFeatureCommand implements CommandHasRequest<WfsGetFeatureRequest, WfsGetFeatureResponse> {
private final Logger log = LoggerFactory.getLogger(WfsGetFeatureCommand.class);
@Autowired
private FilterService filterService;
@Autowired
private DtoConverterService converterService;
private WfsDataStoreFactory dataStoreFactory;
private CriterionToFilterConverterFactory criterionToFilterFactory;
private WfsHttpClientFactory httpClientFactory;
public WfsGetFeatureCommand() {
dataStoreFactory = new DefaultWfsDataStoreFactory();
criterionToFilterFactory = new CriterionToFilterConverterFactory() {
@Override
public CriterionToFilterConverter createConverter() {
return new DefaultCriterionFilterConverter(filterService, converterService);
}
};
httpClientFactory = new DefaultWfsHttpClientFactory();
}
public void setDataStoreFactory(WfsDataStoreFactory dataStoreFactory) {
this.dataStoreFactory = dataStoreFactory;
}
public void setCriterionToFilterFactory(CriterionToFilterConverterFactory criterionToFilterFactory) {
this.criterionToFilterFactory = criterionToFilterFactory;
}
public void setHttpClientFactory(WfsHttpClientFactory httpClientFactory) {
this.httpClientFactory = httpClientFactory;
}
@Override
public void execute(WfsGetFeatureRequest request, WfsGetFeatureResponse response) throws Exception {
FeatureCollection<SimpleFeatureType, SimpleFeature> features = performWfsQuery(request);
int maxCoordinates = request.getMaxCoordsPerFeature();
double distance = 10;
// Note: features.getSchema() can be null when no features have been found
if (null != features.getSchema()
&& features.getSchema().getCoordinateReferenceSystem() instanceof GeographicCRS) {
distance = 0.0001;
}
WfsFeatureCollectionDto featureCollectionDto = new WfsFeatureCollectionDto();
featureCollectionDto.setBaseUrl(request.getBaseUrl());
featureCollectionDto.setTypeName(request.getTypeName());
List<Feature> dtoFeatures = convertFeatures(features, maxCoordinates, distance);
featureCollectionDto.setFeatures(dtoFeatures);
featureCollectionDto.setBoundingBox(getTotalBounds(dtoFeatures));
response.setFeatureCollection(featureCollectionDto);
log.info("Found " + response.getFeatureCollection().size() + " features for layer " + request.getTypeName());
}
protected FeatureCollection<SimpleFeatureType, SimpleFeature> performWfsQuery(WfsGetFeatureRequest request)
throws IOException {
try {
// create Geotools query
Query query = createQuery(request.getTypeName(), request.getSchema(), request.getCriterion(),
request.getMaxFeatures(), request.getStartIndex(), request.getRequestedAttributeNames(),
request.getCrs());
String sourceUrl = request.getBaseUrl();
URL targetUrl = httpClientFactory.getTargetUrl(sourceUrl);
HTTPClient client = httpClientFactory.create(sourceUrl);
// run it
return getFeatures(targetUrl, client, request.getTypeName(), query, request.getVersion(),
request.getStrategy());
} catch (SAXException e) {
throw new IOException(e);
} catch (ParserConfigurationException e) {
throw new IOException(e);
} catch (URISyntaxException e) {
throw new IOException(e);
}
}
/**
* Query a geotools source for features using the criterion as a filter.
*
* @param source
* @param criterion
* @param attributeNames
* @param crs
* @return
* @throws IOException
* @throws GeomajasException
*/
protected Query createQuery(String typeName, List<AttributeDescriptor> schema, Criterion criterion,
int maxFeatures, int startIndex, List<String> attributeNames, String crs) throws IOException {
CriterionToFilterConverter converter = criterionToFilterFactory.createConverter();
Filter filter = converter.convert(criterion, schema);
Query query = null;
if (attributeNames == null) {
query = new Query(typeName, filter, maxFeatures > 0 ? maxFeatures : Integer.MAX_VALUE, Query.ALL_NAMES,
null);
} else {
query = new Query(typeName, filter, maxFeatures > 0 ? maxFeatures : Integer.MAX_VALUE,
attributeNames.toArray(new String[attributeNames.size()]), null);
}
if (startIndex > 0) {
query.setStartIndex(startIndex);
}
if (null != crs) {
try {
// always use the urn version as otherwise servers disagree on axis order
if (crs.equalsIgnoreCase("EPSG:4326")) {
crs = "urn:x-ogc:def:crs:EPSG:4326";
}
query.setCoordinateSystem(CRS.decode(crs));
} catch (NoSuchAuthorityCodeException e) { // assume non-fatal
log.warn("Problem getting CRS for id " + crs + ": " + e.getMessage());
} catch (FactoryException e) {
// assume non-fatal
log.warn("Problem getting CRS for id " + crs + ": " + e.getMessage());
}
}
return query;
}
protected FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatures(URL baseUrl, HTTPClient client,
String typeName, Query query, WfsVersionDto version, String strategy) throws IOException, SAXException,
ParserConfigurationException, URISyntaxException {
URL url = URLBuilder.createWfsURL(baseUrl, version, "GetCapabilities");
String capa = url.toExternalForm();
Map<String, Serializable> connectionParameters = new HashMap<String, Serializable>();
connectionParameters.put(WFSDataStoreFactory.MAXFEATURES.key, query.getMaxFeatures());
connectionParameters.put(WFSDataStoreFactory.URL.key, capa);
connectionParameters.put(WFSDataStoreFactory.PROTOCOL.key, Boolean.TRUE);
if (strategy != null) {
connectionParameters.put(WFSDataStoreFactory.WFS_STRATEGY.key, strategy);
}
DataStore data = dataStoreFactory.createDataStore(connectionParameters, client);
SimpleFeatureSource features = data.getFeatureSource(typeName.replace(":", "_"));
return features.getFeatures(query);
}
/**
* Convert a feature collection to its dto equivalent.
*
* @param features
* @return
* @throws IOException
* @throws GeomajasException
*/
protected List<Feature> convertFeatures(FeatureCollection<SimpleFeatureType, SimpleFeature> features,
int maxCoordinates, double startDistance) throws IOException, GeomajasException {
FeatureIterator<SimpleFeature> iterator = features.features();
List<Feature> dtoFeatures = new ArrayList<Feature>();
FeatureConverter converter = new FeatureConverter();
while (iterator.hasNext()) {
try {
SimpleFeature feature = iterator.next();
dtoFeatures.add(converter.toDto(feature, maxCoordinates));
} catch (Exception e) {
continue;
}
}
iterator.close();
return dtoFeatures;
}
/**
* Get the total bounds of all features in the collection.
*
* @param features
* @return bounds or null if no features
*/
protected Bbox getTotalBounds(List<Feature> features) {
Bbox total = null;
for (Feature featureDto : features) {
org.geomajas.geometry.Geometry geom = featureDto.getGeometry();
if (geom != null) {
Bbox b = GeometryService.getBounds(geom);
if (total == null) {
total = b;
} else {
total = BboxService.union(total, b);
}
}
}
return total;
}
@Override
public WfsGetFeatureRequest getEmptyCommandRequest() {
return new WfsGetFeatureRequest();
}
@Override
public WfsGetFeatureResponse getEmptyCommandResponse() {
return new WfsGetFeatureResponse();
}
}