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 }