Skip to content

Commit

Permalink
[GEOS-2749] Reproject geometries in WMS GetFeatureInfo responses when…
Browse files Browse the repository at this point in the history
… info_format is GML
  • Loading branch information
Nuno Oliveira committed May 20, 2017
1 parent 825a5ca commit a20d5da
Show file tree
Hide file tree
Showing 22 changed files with 601 additions and 52 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 8 additions & 4 deletions doc/en/user/source/services/wms/webadmin.rst
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -126,8 +126,12 @@ The usage of dynamic styling can be restricted on a global or per virtual servic
.. figure:: img/service_WMS_disableDynamicStyling.png .. figure:: img/service_WMS_disableDynamicStyling.png


When the flag is checked, a GetMap/GetFeatureInfo request with a dynamic style will result in a service exception reporting the error. When the flag is checked, a GetMap/GetFeatureInfo request with a dynamic style will result in a service exception reporting the error.

Disabling GetFeatureInfo requests results reprojection
------------------------------------------------------

By default GetFeatureInfo results are reproject to the map coordinate reference system. This behavior can be deactivated on a global or per virtual service basis in the **GetFeatureInfo results reprojection** section.

.. figure:: img/service_WMS_disableFeaturesReprojection.png



When the flag is checked, GetFeatureInfo requests results will not be reprojected and will instead used the layer coordinate reference system.



Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@


import static org.junit.Assert.*; import static org.junit.Assert.*;


import org.geoserver.wms.WMSInfo;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;


import org.geoserver.test.NamespaceTestData; import org.geoserver.test.NamespaceTestData;
Expand All @@ -18,6 +20,14 @@ public WmsGetFeatureInfoTest() throws Exception {
super(); super();
} }


@Before
public void setupAdvancedProjectionHandling() {
// make sure GetFeatureInfo is not deactivated (this will only update the global service)
WMSInfo wms = getGeoServer().getService(WMSInfo.class);
wms.setFeaturesReprojectionDisabled(false);
getGeoServer().save(wms);
}

@Override @Override
protected WmsSupportMockData createTestData() { protected WmsSupportMockData createTestData() {
WmsSupportMockData mockData = new WmsSupportMockData(); WmsSupportMockData mockData = new WmsSupportMockData();
Expand Down Expand Up @@ -69,7 +79,21 @@ public void testGetFeatureInfoGMLReprojection() throws Exception {
"gu.25678", "gu.25678",
"/wfs:FeatureCollection/gml:featureMember/gsml:MappedFeature/gsml:specification/gsml:GeologicUnit/@gml:id", "/wfs:FeatureCollection/gml:featureMember/gsml:MappedFeature/gsml:specification/gsml:GeologicUnit/@gml:id",
doc); doc);
// check that features coordinates where reprojected to EPSG:3857
assertXpathMatches(".*3857",
"/wfs:FeatureCollection/gml:featureMember/gsml:MappedFeature[@gml:id='mf2']/gsml:shape/gml:Polygon/@srsName",
doc);
validateGet(request); validateGet(request);
// disable features reprojection
WMSInfo wms = getGeoServer().getService(WMSInfo.class);
wms.setFeaturesReprojectionDisabled(true);
getGeoServer().save(wms);
// execute the request
doc = getAsDOM(request);
// check that features were not reprojected and still in EPSG:4326
assertXpathMatches(".*4326",
"/wfs:FeatureCollection/gml:featureMember/gsml:MappedFeature[@gml:id='mf2']/gsml:shape/gml:Polygon/@srsName",
doc);
} }


@Test @Test
Expand Down
15 changes: 15 additions & 0 deletions src/web/wms/src/main/java/org/geoserver/wms/web/WMSAdminPage.html
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@
</ul> </ul>
</fieldset> </fieldset>
</li> </li>
<li>
<fieldset>
<legend><span><wicket:message key="disableFeaturesReprojectTitle"></wicket:message></span></legend>
<ul class="choiceList">
<li>
<input type="checkbox" wicket:id="disableFeaturesReproject"></input>
<label for="disableFeaturesReproject">
<wicket:message key="disableFeaturesReproject">
Reproject GetFeatureInfo request results to the map coordinate reference system
</wicket:message>
</label>
</li>
</ul>
</fieldset>
</li>
<div wicket:id="modal"></div> <div wicket:id="modal"></div>
</wicket:extend> </wicket:extend>
</body> </body>
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ public void onClick(AjaxRequestTarget target) {


//dynamicStylingDisabled //dynamicStylingDisabled
form.add(new CheckBox("dynamicStyling.disabled",new PropertyModel<Boolean>(info, WMS.DYNAMIC_STYLING_DISABLED))); form.add(new CheckBox("dynamicStyling.disabled",new PropertyModel<Boolean>(info, WMS.DYNAMIC_STYLING_DISABLED)));


// disable the reprojection of GetFeatureInfo results
form.add(new CheckBox("disableFeaturesReproject", new PropertyModel<>(info, WMS.FEATURES_REPROJECTION_DISABLED)));
} }


@Override @Override
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ WMSAdminPage.aph.enabled = Enable advanced projection handling
WMSAdminPage.aph.wrap = Enable continuous map wrapping WMSAdminPage.aph.wrap = Enable continuous map wrapping
WMSAdminPage.dynamicStylingDisabledTitle = Dynamic styling WMSAdminPage.dynamicStylingDisabledTitle = Dynamic styling
WMSAdminPage.dynamicStylingDisabled = Disable usage of SLD and SLD_BODY parameters in GET requests and user styles in POST requests WMSAdminPage.dynamicStylingDisabled = Disable usage of SLD and SLD_BODY parameters in GET requests and user styles in POST requests
WMSAdminPage.disableFeaturesReprojectTitle = GetFeatureInfo results reprojection
WMSAdminPage.disableFeaturesReproject = Disable the reprojection of GetFeatureInfo results


MimeTypesFormComponent.selectedHeader =Allowed MIME types MimeTypesFormComponent.selectedHeader =Allowed MIME types
MimeTypesFormComponent.availableHeader =Available MIME types MimeTypesFormComponent.availableHeader =Available MIME types
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ WMSAdminPage.scalehintOptions = Scalehint (WMS 1.1.1 seulement)
WMSAdminPage.scalehintUnitsPixel = Présenter le Scalehint en unités par diagonale de pixel dans la réponse GetCapabilities WMSAdminPage.scalehintUnitsPixel = Présenter le Scalehint en unités par diagonale de pixel dans la réponse GetCapabilities
WMSAdminPage.getFeatureInfoMimeTypes= Types MIME autorisés pour une requête GetFeatureInfo WMSAdminPage.getFeatureInfoMimeTypes= Types MIME autorisés pour une requête GetFeatureInfo
WMSAdminPage.getMapMimeTypes = Types MIME autorisés pour une requête GetMap WMSAdminPage.getMapMimeTypes = Types MIME autorisés pour une requête GetMap
WMSAdminPage.disableFeaturesReprojectTitle = Reprojection des résultats des requêtes GetFeatureInfo
WMSAdminPage.disableFeaturesReproject = Désactiver la reprojection des résultats des requêtes GetFeatureInfo


MimeTypesFormComponent.selectedHeader =Types MIME autorisés MimeTypesFormComponent.selectedHeader =Types MIME autorisés
MimeTypesFormComponent.availableHeader =Types MIME disponibles MimeTypesFormComponent.availableHeader =Types MIME disponibles
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ WMSAdminPage.scalehintUnitsPixel = Show the Scalehint as units per diagonal pixe
# MimeTypesFormComponent.selectedHeader =Allowed MIME types # MimeTypesFormComponent.selectedHeader =Allowed MIME types
# MimeTypesFormComponent.availableHeader =Available MIME types # MimeTypesFormComponent.availableHeader =Available MIME types
# MimeTypesFormComponent.mimeTypeCheckingEnabled =Enable MIME type checking # MimeTypesFormComponent.mimeTypeCheckingEnabled =Enable MIME type checking
WMSAdminPage.disableFeaturesReprojectTitle = Reprojecção dos resultados de pedidos GetFeatureInfo
WMSAdminPage.disableFeaturesReproject = Desativa a reprojecção dos resultados de pedidos GetFeatureInfo


WMSLayerConfig.additionalStyles = Estilos adicionais WMSLayerConfig.additionalStyles = Estilos adicionais
WMSLayerConfig.defaultStyle = Estilo padrão WMSLayerConfig.defaultStyle = Estilo padrão
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.apache.wicket.util.tester.FormTester; import org.apache.wicket.util.tester.FormTester;
import org.geoserver.web.GeoServerHomePage; import org.geoserver.web.GeoServerHomePage;
import org.geoserver.web.GeoServerWicketTestSupport; import org.geoserver.web.GeoServerWicketTestSupport;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSInfo; import org.geoserver.wms.WMSInfo;
import org.geoserver.wms.web.WMSAdminPage; import org.geoserver.wms.web.WMSAdminPage;
import org.junit.Before; import org.junit.Before;
Expand Down Expand Up @@ -89,4 +90,14 @@ public void testDynamicStylingDisabled() throws Exception {
ft.submit("submit"); ft.submit("submit");
assertTrue(wms.isDynamicStylingDisabled()); assertTrue(wms.isDynamicStylingDisabled());
} }

@Test
public void testFeaturesReprojectionDisabled() throws Exception {
assertFalse(wms.isFeaturesReprojectionDisabled());
tester.startPage(WMSAdminPage.class);
FormTester ft = tester.newFormTester("form");
ft.setValue("disableFeaturesReproject", true);
ft.submit("submit");
assertTrue(wms.isFeaturesReprojectionDisabled());
}
} }
1 change: 1 addition & 0 deletions src/wms/src/main/java/applicationContext.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@
</bean> </bean>
<bean id="wmsLayerIdentifier" class="org.geoserver.wms.featureinfo.WMSLayerIdentifier"> <bean id="wmsLayerIdentifier" class="org.geoserver.wms.featureinfo.WMSLayerIdentifier">
<constructor-arg ref="entityResolverProvider" /> <constructor-arg ref="entityResolverProvider" />
<constructor-arg ref="wms" />
</bean> </bean>




Expand Down
12 changes: 12 additions & 0 deletions src/wms/src/main/java/org/geoserver/wms/WMS.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public class WMS implements ApplicationContextAware {


public static final String DYNAMIC_STYLING_DISABLED = "dynamicStylingDisabled"; public static final String DYNAMIC_STYLING_DISABLED = "dynamicStylingDisabled";


public static final String FEATURES_REPROJECTION_DISABLED = "featuresReprojectionDisabled";

static final Logger LOGGER = Logging.getLogger(WMS.class); static final Logger LOGGER = Logging.getLogger(WMS.class);


public static final String WEB_CONTAINER_KEY = "WMS"; public static final String WEB_CONTAINER_KEY = "WMS";
Expand Down Expand Up @@ -364,6 +366,16 @@ public boolean isDynamicStylingDisabled() {
return getServiceInfo().isDynamicStylingDisabled(); return getServiceInfo().isDynamicStylingDisabled();
} }


/**
* If TRUE is returned GetFeatureInfo results should NOT be reproject
* to the map coordinate reference system.
*
* @return GetFeatureInfo results reprojection allowance
*/
public boolean isFeaturesReprojectionDisabled() {
return getServiceInfo().isFeaturesReprojectionDisabled();
}

public JAIInfo.PngEncoderType getPNGEncoderType() { public JAIInfo.PngEncoderType getPNGEncoderType() {
JAIInfo jaiInfo = getJaiInfo(); JAIInfo jaiInfo = getJaiInfo();
return jaiInfo.getPngEncoderType(); return jaiInfo.getPngEncoderType();
Expand Down
20 changes: 20 additions & 0 deletions src/wms/src/main/java/org/geoserver/wms/WMSInfo.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -158,4 +158,24 @@ enum WMSInterpolation {
* @return the status of dynamic styling (SLD and SLD_BODY params) allowance * @return the status of dynamic styling (SLD and SLD_BODY params) allowance
*/ */
Boolean isDynamicStylingDisabled(); Boolean isDynamicStylingDisabled();

/**
* If set to TRUE GetFeatureInfo results will NOT be reprojected.
*
* @param featuresReprojectionDisabled features reprojection allowance
*/
default void setFeaturesReprojectionDisabled(boolean featuresReprojectionDisabled) {
// if not implemented nothing is done
}

/**
* Flag that controls if GetFeatureInfo results should NOT be reprojected to the map
* coordinate reference system.
*
* @return GetFeatureInfo features reprojection allowance
*/
default boolean isFeaturesReprojectionDisabled() {
// deactivate features reprojection by default
return true;
}
} }
14 changes: 13 additions & 1 deletion src/wms/src/main/java/org/geoserver/wms/WMSInfoImpl.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public class WMSInfoImpl extends ServiceInfoImpl implements WMSInfo {
Set<String> getMapMimeTypes = new HashSet<String>(); Set<String> getMapMimeTypes = new HashSet<String>();


boolean dynamicStylingDisabled; boolean dynamicStylingDisabled;


// GetFeatureInfo result are reprojected by default
private boolean featuresReprojectionDisabled = false;


/** /**
* This property is transient in 2.1.x series and stored under the metadata map with key * This property is transient in 2.1.x series and stored under the metadata map with key
Expand Down Expand Up @@ -203,4 +205,14 @@ public void setDynamicStylingDisabled(Boolean dynamicStylingDisabled) {
public Boolean isDynamicStylingDisabled() { public Boolean isDynamicStylingDisabled() {
return dynamicStylingDisabled; return dynamicStylingDisabled;
} }

@Override
public boolean isFeaturesReprojectionDisabled() {
return featuresReprojectionDisabled;
}

@Override
public void setFeaturesReprojectionDisabled(boolean featuresReprojectionDisabled) {
this.featuresReprojectionDisabled = featuresReprojectionDisabled;
}
} }
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,151 @@
/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/

package org.geoserver.wms.featureinfo;

import com.vividsolutions.jts.geom.Geometry;
import org.geotools.data.crs.ReprojectFeatureResults;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.CRS;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
* Contain helpers methods needed by layers identifiers.
*/
public final class LayerIdentifierUtils {

private static final Logger LOGGER = Logging.getLogger(LayerIdentifierUtils.class);

private LayerIdentifierUtils() {
}

/**
* Helper method that tries to reproject each feature collection to the target CRS.
* Complex features collections will not be reprojected.
*
* @param featureCollections feature collections to reprojected, should NOT be NULL
* @param targetCrs reprojection target CRS, can be NULL
* @return feature collections, some may not have been reprojected
*/
@SuppressWarnings("unchecked")
public static List<FeatureCollection> reproject(List<FeatureCollection> featureCollections,
CoordinateReferenceSystem targetCrs) {
if (targetCrs == null) {
// nothing to do
return featureCollections;
}
// try to reproject features collections to the target CRS
return featureCollections.stream()
.map(featureCollection -> reproject(featureCollection, targetCrs))
.collect(Collectors.toList());
}

/**
* Helper method that reprojects a feature collection to the target CRS. If the provided
* feature collection doesn't contain simple features or if the source CRS is equal to
* the target CRS nothing will be done.
*
* @param featureCollection feature collection to be reprojected, should NOT be NULL
* @param targetCrs reprojection target CRS, can be NULL
* @return feature collection, it may be reprojected or not
*/
@SuppressWarnings("unchecked")
public static FeatureCollection reproject(FeatureCollection featureCollection,
CoordinateReferenceSystem targetCrs) {
if (targetCrs == null) {
// nothing to do
return featureCollection;
}
if (!(featureCollection instanceof SimpleFeatureCollection)) {
// not able to reproject complex features collection
LOGGER.warning("Complex feature collection will not be reprojected.");
return featureCollection;
}
// get feature collection CRS
CoordinateReferenceSystem sourceCrs = featureCollection.getSchema().getCoordinateReferenceSystem();
if (sourceCrs == null) {
// reprojector requires the source CRS to be defined
return featureCollection;
}
if (!CRS.equalsIgnoreMetadata(sourceCrs, targetCrs)) {
try {
// reproject to to the target CRS
return new ReprojectFeatureResults(featureCollection, targetCrs);
} catch (Exception exception) {
throw new RuntimeException(String.format(
"Error reproject feature collection from SRS '%s' to SRS '%s'.",
CRS.toSRS(sourceCrs), CRS.toSRS(targetCrs)), exception);
}
}
// the target CRS and the source CRS are the same, so nothing to do
return featureCollection;
}

/**
* Helper method that tries to find feature collection CRS. First we try to use schema
* defined CRS then we try to find a common CRS among simple features default geometries.
* If this is not a simple feature collection or if no common CRS can be found (i.e. we
* have geometries with different CRS) NULL will be returned.
*
* @param featureCollection feature collection, should NOT be NULL
* @return the found CRS, may be NULL
*/
public static CoordinateReferenceSystem getCrs(FeatureCollection featureCollection) {
CoordinateReferenceSystem crs = featureCollection.getSchema().getCoordinateReferenceSystem();
if (crs != null || featureCollection.isEmpty()) {
// the feature collection has a defined CRS or the feature collection is empty
return crs;
}
// try to extract the CRS from the geometry descriptor (normally it should be NULL too)
GeometryDescriptor geometryDescriptor = featureCollection.getSchema().getGeometryDescriptor();
crs = geometryDescriptor == null ? null : geometryDescriptor.getCoordinateReferenceSystem();
if (crs != null) {
// the geometry descriptor has a defined CRS
return crs;
}
// iterate over features and find the common CRS
try (FeatureIterator iterator = featureCollection.features()) {
while (iterator.hasNext()) {
Feature feature = iterator.next();
if (!(feature instanceof SimpleFeature)) {
// not a simple feature, we are done
return null;
}
SimpleFeature simpleFeature = (SimpleFeature) feature;
Object object = simpleFeature.getDefaultGeometry();
if (!(object instanceof Geometry)) {
// current feature doesn't have a geometry, move to the next one
continue;
}
// the user data may contain the coordinate reference system
Geometry geometry = (Geometry) object;
Object userData = geometry.getUserData();
if (!(userData instanceof CoordinateReferenceSystem)) {
// no user data available or doesn't contain a coordinate reference system
return null;
}
CoordinateReferenceSystem geometryCrs = (CoordinateReferenceSystem) userData;
if (crs != null && !CRS.equalsIgnoreMetadata(crs, geometryCrs)) {
// this geometry CRS is different from the other ones, we are done
return null;
}
// store the found CRS
crs = geometryCrs;
}
}
// the found common CRS among geometries, or NULL if no geometries are defined
return crs;
}
}
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ public List<FeatureCollection> identify(FeatureInfoRequestParameters params, int


FeatureCollection match; FeatureCollection match;
LOGGER.log(Level.FINE, q.toString()); LOGGER.log(Level.FINE, q.toString());
// let's see if we need to reproject
if (!wms.isFeaturesReprojectionDisabled()) {
// reproject the features to the request CRS, this way complex feature will also be reprojected
q.setCoordinateSystemReproject(requestedCRS);
}
match = featureSource.getFeatures(q); match = featureSource.getFeatures(q);


// if we could not include the rules filter into the query, post process in // if we could not include the rules filter into the query, post process in
Expand Down

0 comments on commit a20d5da

Please sign in to comment.