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 }