001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/portal/standard/wfs/control/GeometryValidator.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 037 package org.deegree.portal.standard.wfs.control; 038 039 import javax.servlet.http.HttpServletRequest; 040 041 import org.deegree.enterprise.control.AbstractListener; 042 import org.deegree.enterprise.control.FormEvent; 043 import org.deegree.enterprise.control.RPCParameter; 044 import org.deegree.enterprise.control.RPCStruct; 045 import org.deegree.enterprise.control.RPCUtils; 046 import org.deegree.enterprise.control.RPCWebEvent; 047 import org.deegree.framework.log.ILogger; 048 import org.deegree.framework.log.LoggerFactory; 049 import org.deegree.i18n.Messages; 050 import org.deegree.model.spatialschema.Geometry; 051 import org.deegree.model.spatialschema.JTSAdapter; 052 import org.deegree.model.spatialschema.WKTAdapter; 053 import org.deegree.portal.PortalException; 054 055 import com.vividsolutions.jts.algorithm.CGAlgorithms; 056 057 /** 058 * This listener validates simple geometries. The geometries to be validated are passed in as the value of the 059 * <code>'GEOMETRY'</code> parameter of such an RPC request: 060 * 061 * <pre> 062 * <methodCall> 063 * <methodName>dig:checkGeometry</methodName> 064 * <params> 065 * <param><value><struct><member> 066 * <name>GEOMETRY</name> 067 * <value><string>POLYGON(( 7.38 49.64, 12.03 48.7, 11.34 51.66, 11.34 51.66, 7.38 49.64 ))</string></value> 068 * </member></struct></value></param> 069 * </params> 070 * </methodCall> 071 * </pre> 072 * 073 * Subclasses may override {@link #validateRequest( RPCWebEvent rpcEvent )}, 074 * {@link #createGeometry( RPCWebEvent rpcEvent )} and/or validateGeometry() if they want to change the behaviour 075 * regarding request validation, geometry creation and/or geometry validation, respectively. Especially the last method 076 * may be different according to use-cases. The result of the validation is put in the session under the key 077 * <code>GeometryValidator.GEOMETRY_STATE</code>. 078 * 079 * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a> 080 * @author last edited by: $Author: mschneider $ 081 * 082 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 083 */ 084 public class GeometryValidator extends AbstractListener { 085 086 private static final ILogger LOG = LoggerFactory.getLogger( GeometryValidator.class ); 087 088 /** Attribute name for the geometry validity state */ 089 public static final String GEOMETRY_STATE = "GEOMETRY_STATE"; 090 091 /** Indicates that the geometry is perfectly valid */ 092 public static final String VALID_GEOMETRY = "VALID_GEOMETRY"; 093 094 /** Indicates that the geometry is invalid (self intersection, missing points, etc) */ 095 public static final String INVALID_GEOMETRY = "INVALID_GEOMETRY"; 096 097 /** Indicates that the geometry is OK, although it may not be considered formally correct in most GIS contexts. */ 098 public static final String INFORMALLY_VALID_GEOMETRY = "INFORMALLY_VALID_GEOMETRY"; 099 100 protected Geometry geometry; 101 102 /** 103 * This method validates the RPC-encoded request ({@link #validateRequest(RPCWebEvent)}), create some geometry 104 * from the request parameters ({@link #createGeometry(RPCWebEvent)} and then validate this geometry ({@link #validateGeometry()}. 105 * The result of this validate is made available to the request object as an attribute under the key 106 * <code>GeometryValidator.GEOMETRY_STATE</code>. 107 * 108 * @param event 109 * the event containing the RPC-emcoded request. 110 */ 111 @Override 112 public void actionPerformed( FormEvent event ) { 113 114 RPCWebEvent rpcEvent = (RPCWebEvent) event; 115 116 try { 117 validateRequest( rpcEvent ); 118 } catch ( Exception e ) { 119 LOG.logError( e.getMessage(), e ); 120 gotoErrorPage( Messages.getMessage( "IGEO_STD_INVALID_RPC", e.getLocalizedMessage() ) ); 121 return; 122 } 123 124 Object validationResult = INVALID_GEOMETRY; 125 126 try { 127 geometry = createGeometry( rpcEvent ); 128 } catch ( Exception e ) { 129 // do not consider this as an exception - and thus return the error 130 // mesg back to the page -, but as a failure to create a valid geometry 131 LOG.logDebug( e.getMessage(), e ); 132 } 133 134 try { 135 validationResult = validateGeometry(); 136 } catch ( Exception e ) { 137 LOG.logError( e.getMessage(), e ); 138 // TODO i18nize 139 gotoErrorPage( e.getLocalizedMessage() ); 140 return; 141 } 142 143 HttpServletRequest req = (HttpServletRequest) getRequest(); 144 req.setAttribute( GEOMETRY_STATE, validationResult ); 145 } 146 147 /** 148 * Validates the incoming RPc request. This method looks for an RPC parameter (struct) named <code>'GEOMETRY'</code>, 149 * checks if the valeu is empty and looks no further. The expected value is a well-known geometry representation. 150 * 151 * @param rpcEvent 152 * the event containing at least a struct with a string type parameter named <code>'GEOMETRY'</code>. 153 * @throws Exception 154 * If no such parameter is found or if the value is empty. This is interpreted as an exception and the 155 * resulting message is sent back to the client. 156 */ 157 protected void validateRequest( RPCWebEvent rpcEvent ) 158 throws Exception { 159 160 RPCParameter[] pars = rpcEvent.getRPCMethodCall().getParameters(); 161 if ( pars == null || pars.length != 1 ) { 162 throw new PortalException( "The RPC request must contain a 'GEOMETRY' parameter." ); 163 } 164 RPCStruct struct = (RPCStruct) pars[0].getValue(); 165 166 String geomString = RPCUtils.getRpcPropertyAsString( struct, "GEOMETRY" ); 167 if ( geomString == null || geomString.length() == 0 ) { 168 // TODO i18nize 169 throw new PortalException( "The RPC request does not contains a well-known " 170 + " text representation of a geometry." ); 171 } 172 } 173 174 /** 175 * Creates the geometry from the data provided it the request (rpcEvent). 176 * 177 * @see #validateRequest(RPCWebEvent) 178 * @param rpcEvent 179 * the <code>RPCWebEvent</code> conatining the request 180 * @return a new geometry. This method uses {@link WKTAdapter#wrap(String, org.deegree.model.crs.CoordinateSystem)} 181 * to create the new <code>Geometry</code>. 182 * @throws Exception 183 * if the <code>Geometry</code> creation failed. 184 */ 185 protected Geometry createGeometry( RPCWebEvent rpcEvent ) 186 throws Exception { 187 RPCParameter[] pars = rpcEvent.getRPCMethodCall().getParameters(); 188 RPCStruct struct = (RPCStruct) pars[0].getValue(); 189 String geomString = RPCUtils.getRpcPropertyAsString( struct, "GEOMETRY" ); 190 LOG.logDebug( "Digitized geometry: \n" + geomString ); 191 return WKTAdapter.wrap( geomString, null ); 192 } 193 194 /** 195 * Validates this object's <code>Geometry</code> and return an object, preferably a code, indicating the validity 196 * state of the geometry. There are three type of geometry validity states. 197 * <ul> 198 * <li><code>GeometryValidator.VALID_GEOMETRY</code>: The geometry is perfecly OK,</li> 199 * <li><code>GeometryValidator.INVALID_GEOMETRY</code>: The geometry is not OK (self intersection, missing 200 * points, etc),</li> 201 * <li><code>GeometryValidator.INFORMALLY_VALID_GEOMETRY</code>: The geometry is OK, although it may not be 202 * considered formally correct in most GIS contexts. For example, polygons are supposed to have counter-clockwise 203 * outter rings, lines may no self-intersect, etc.</li> 204 * </ul> 205 * <br/> The meaning of those may also vary according to the use-case, e.g. a polygon that is counter-clockwise but 206 * is required to have at least 4 vertices. <br/> This method employs the Java Topology Suite to check for geometry 207 * validity ({@link com.vividsolutions.jts.geom.Geometry#isValid()}) and, in the case of polygons, for the order 208 * of the coordinates (in other words, whether they are counter-clockwise}. <br/> The result of the validation is 209 * available in the request under the key <code>GeometryValidator.GEOMETRY_STATE</code>. 210 * 211 * @return the geometry's validity state 212 */ 213 protected Object validateGeometry() { 214 215 String result = INVALID_GEOMETRY; 216 217 try { 218 com.vividsolutions.jts.geom.Geometry jtsGeom = JTSAdapter.export( geometry ); 219 if ( jtsGeom.isValid() ) { // means is OK, really 220 result = VALID_GEOMETRY; 221 if ( jtsGeom instanceof com.vividsolutions.jts.geom.Polygon ) { 222 if ( !CGAlgorithms.isCCW( jtsGeom.getCoordinates() ) ) { 223 result = INFORMALLY_VALID_GEOMETRY; 224 } 225 } 226 } 227 } catch ( Exception e ) { 228 LOG.logError( e.getLocalizedMessage(), e ); 229 // just in case 230 result = INVALID_GEOMETRY; 231 } 232 233 LOG.logInfo( "Geometry validation result: " + result ); 234 235 return result; 236 } 237 }