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