001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/framework/xml/GeometryUtils.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    package org.deegree.framework.xml;
037    
038    import java.util.List;
039    import java.util.StringTokenizer;
040    
041    import org.deegree.framework.log.ILogger;
042    import org.deegree.framework.log.LoggerFactory;
043    import org.deegree.framework.util.StringTools;
044    import org.deegree.model.crs.CRSTransformationException;
045    import org.deegree.model.crs.GeoTransformer;
046    import org.deegree.model.crs.UnknownCRSException;
047    import org.deegree.model.spatialschema.Envelope;
048    import org.deegree.model.spatialschema.GMLGeometryAdapter;
049    import org.deegree.model.spatialschema.Geometry;
050    import org.deegree.model.spatialschema.GeometryException;
051    import org.deegree.model.spatialschema.MultiSurface;
052    import org.deegree.model.spatialschema.Point;
053    import org.deegree.model.spatialschema.Position;
054    import org.deegree.model.spatialschema.Ring;
055    import org.deegree.model.spatialschema.Surface;
056    import org.deegree.ogcbase.CommonNamespaces;
057    import org.deegree.ogcbase.InvalidGMLException;
058    import org.w3c.dom.Element;
059    import org.w3c.dom.Node;
060    
061    /**
062     * Utility methods for handling geometries within XSLT transformations
063     * 
064     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
065     * @author last edited by: $Author: mschneider $
066     * 
067     * @version $Revision: 19257 $, $Date: 2009-08-19 16:07:10 +0200 (Mi, 19. Aug 2009) $
068     */
069    public class GeometryUtils {
070    
071        private static ILogger LOG = LoggerFactory.getLogger( GeometryUtils.class );
072    
073        private static NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
074    
075        /**
076         * @param node
077         *            (an Element) describing an envelope to get the coordinates from.
078         * @return the coordinates from the envelope separated by ','. Or the empty String if an error occurred.
079         */
080        public static String getPolygonCoordinatesFromEnvelope( Node node ) {
081            StringBuilder sb = new StringBuilder( 500 );
082            if ( node != null ) {
083                Envelope env = null;
084                try {
085                    env = GMLGeometryAdapter.wrapBox( (Element) node, null );
086                } catch ( InvalidGMLException e ) {
087                    LOG.logError( "Could not get Polygon coordinates of given envelope because: " + e.getMessage(), e );
088                } catch ( XMLParsingException e ) {
089                    LOG.logError( "Could not get Polygon coordinates of given envelope because: " + e.getMessage(), e );
090                } catch ( UnknownCRSException e ) {
091                    LOG.logError( "Could not get Polygon coordinates of given envelope because: " + e.getMessage(), e );
092                }
093                if ( env != null ) {
094                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMin().getY() ).append( ' ' );
095                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMax().getY() ).append( ' ' );
096                    sb.append( env.getMax().getX() ).append( ',' ).append( env.getMax().getY() ).append( ' ' );
097                    sb.append( env.getMax().getX() ).append( ',' ).append( env.getMin().getY() ).append( ' ' );
098                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMin().getY() );
099                }
100            } else {
101                LOG.logWarning( "Could not get Polygon coordinates of given envelope because the given node was null." );
102            }
103    
104            return sb.toString();
105        }
106    
107        /**
108         * 
109         * @param node
110         *            (an Element) describing a geometry from which the bbox (envelope) will be returned.
111         * @return the coordinates from the bbox of the geometry separated by ','. Or the empty String if an error occurred.
112         */
113        public static String getEnvelopeFromGeometry( Node node ) {
114            StringBuilder sb = new StringBuilder( 500 );
115            if ( node != null ) {
116                Envelope env = null;
117                try {
118                    env = GMLGeometryAdapter.wrap( (Element) node, null ).getEnvelope();
119                } catch ( GeometryException e ) {
120                    LOG.logError( "Could not get envelope of geometry because: " + e.getMessage(), e );
121                }
122                if ( env != null ) {
123                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMin().getY() ).append( ' ' );
124                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMax().getY() ).append( ' ' );
125                    sb.append( env.getMax().getX() ).append( ',' ).append( env.getMax().getY() ).append( ' ' );
126                    sb.append( env.getMax().getX() ).append( ',' ).append( env.getMin().getY() ).append( ' ' );
127                    sb.append( env.getMin().getX() ).append( ',' ).append( env.getMin().getY() );
128                }
129            } else {
130                LOG.logWarning( "Could not get envelope of geometry because the given node was null." );
131            }
132    
133            return sb.toString();
134        }
135    
136        /**
137         * returns the coordinates of the out ring of a polygon as comma separated list. The coordinate tuples are separated
138         * by a blank. If required the polygon will first transformed to the target CRS
139         * 
140         * @param node
141         * @param sourceCRS
142         * @param targetCRS
143         * @return the coordinates of the out ring of a polygon as comma separated list, or the empty String if an exception
144         *         occurred in the extracting/transforming process.
145         */
146        public static String getPolygonOuterRing( Node node, String sourceCRS, String targetCRS ) {
147            StringBuilder coords = new StringBuilder( 10000 );
148            if ( node != null ) {
149                Surface surface = null;
150                try {
151                    surface = (Surface) GMLGeometryAdapter.wrap( (Element) node, sourceCRS );
152                    if ( !targetCRS.equals( sourceCRS ) ) {
153                        GeoTransformer gt = new GeoTransformer( targetCRS );
154                        surface = (Surface) gt.transform( surface );
155                    }
156                } catch ( GeometryException e ) {
157                    LOG.logError( "Could not extract outer ring of polygon because: " + e.getMessage(), e );
158                } catch ( IllegalArgumentException e ) {
159                    LOG.logError( "Could not extract outer ring of polygon because: " + e.getMessage(), e );
160                } catch ( CRSTransformationException e ) {
161                    LOG.logError( "Could not transform outer ring of polygon because: " + e.getMessage(), e );
162                    surface = null;
163                } catch ( UnknownCRSException e ) {
164                    LOG.logError( "Could not transform outer ring of polygon because: " + e.getMessage(), e );
165                    surface = null;
166                }
167                if ( surface != null ) {
168                    Position[] pos = surface.getSurfaceBoundary().getExteriorRing().getPositions();
169                    int dim = pos[0].getCoordinateDimension();
170                    for ( int i = 0; i < pos.length; i++ ) {
171                        coords.append( pos[i].getX() ).append( ',' ).append( pos[i].getY() );
172                        if ( dim == 3 ) {
173                            coords.append( ',' ).append( pos[i].getZ() );
174                        }
175                        coords.append( ' ' );
176                    }
177                }
178            } else {
179                LOG.logWarning( "Could not extract outer ring of polygon because the given node was null." );
180            }
181    
182            return coords.toString();
183        }
184    
185        /**
186         * 
187         * @param node
188         * @param index
189         * @param sourceCRS
190         * @param targetCRS
191         * @return the inner ring of the given polyong / surface found in the node or the empty String otherwise.
192         */
193        public static String getPolygonInnerRing( Node node, int index, String sourceCRS, String targetCRS ) {
194            StringBuilder coords = new StringBuilder( 10000 );
195    
196            if ( node != null ) {
197                if ( "Polygon".equals( node.getLocalName() ) || "Surface".equals( node.getLocalName() ) ) {
198                    try {
199                        Surface surface = (Surface) GMLGeometryAdapter.wrap( (Element) node, sourceCRS );
200                        if ( !targetCRS.equals( sourceCRS ) ) {
201                            GeoTransformer gt = new GeoTransformer( targetCRS );
202                            surface = (Surface) gt.transform( surface );
203                        }
204                        Position[] pos = surface.getSurfaceBoundary().getInteriorRings()[index - 1].getPositions();
205                        int dim = pos[0].getCoordinateDimension();
206                        for ( int i = 0; i < pos.length; i++ ) {
207                            coords.append( pos[i].getX() ).append( ',' ).append( pos[i].getY() );
208                            if ( dim == 3 ) {
209                                coords.append( ',' ).append( pos[i].getZ() );
210                            }
211                            coords.append( ' ' );
212                        }
213                    } catch ( Exception e ) {
214                        LOG.logError( "Could not extract Innerring because: " + e.getMessage(), e );
215                    }
216                } else {
217                    LOG.logError( "The given node '" + node.getLocalName()
218                                  + "' does not contain a Polygon or a Surface, their could not extract inner-ring" );
219                }
220            } else {
221                LOG.logWarning( "Could not extract inner-ring because the given node was null." );
222            }
223            return coords.toString();
224        }
225    
226        /**
227         * 
228         * @param node
229         *            to extract the geometry from
230         * @return the area of the geometry inside the node or -1 if the given geometry node does not contain a surface or
231         *         multi surface, or an exception occurred while extracting the geometry.
232         */
233        public static double calcArea( Node node ) {
234            double area = -1;
235            if ( node != null ) {
236                Geometry geom = null;
237                try {
238                    geom = GMLGeometryAdapter.wrap( (Element) node, null );
239                } catch ( GeometryException e ) {
240                    LOG.logError( "Could not calculate the area of node with localname: '" + node.getLocalName()
241                                  + "' because: " + e.getLocalizedMessage(), e );
242                }
243                if ( geom != null ) {
244                    if ( geom instanceof Surface ) {
245                        area = ( (Surface) geom ).getArea();
246                    } else if ( geom instanceof MultiSurface ) {
247                        area = ( (MultiSurface) geom ).getArea();
248                    }
249                }
250            } else {
251                LOG.logWarning( "Could not calculate the area because the given node was null." );
252            }
253    
254            return area;
255        }
256    
257        /**
258         * @param node
259         *            to extract the geometry from from which the length of the outerboundary will be calculated
260         * @return the length outerboundary of the geometry inside the node or 0 if the given geometry node does not contain
261         *         a surface or multi surface, or an exception occurred while extracting the geometry.
262         */
263        public static double calcOuterBoundaryLength( Node node ) {
264            double length = 0;
265            if ( node != null ) {
266                try {
267                    Geometry geom = GMLGeometryAdapter.wrap( (Element) node, null );
268                    if ( geom instanceof Surface ) {
269                        Ring ring = ( (Surface) geom ).getSurfaceBoundary().getExteriorRing();
270                        length = ring.getAsCurveSegment().getLength();
271                    } else if ( geom instanceof MultiSurface ) {
272                        MultiSurface ms = ( (MultiSurface) geom );
273                        for ( int i = 0; i < ms.getSize(); i++ ) {
274                            Ring ring = ms.getSurfaceAt( i ).getSurfaceBoundary().getExteriorRing();
275                            length += ring.getAsCurveSegment().getLength();
276                        }
277                    }
278                } catch ( GeometryException e ) {
279                    LOG.logError( "Could not calculate length of the outer boundary because: " + e.getMessage(), e );
280                }
281    
282            } else {
283                LOG.logWarning( "Could not calculate length of the outer boundary because the given node was null." );
284            }
285            return length;
286        }
287    
288        /**
289         * Calculates the centroid of the given node.
290         * 
291         * @param node
292         *            to extract the given node from
293         * @param targetCRS
294         *            to transform the given centroid to.
295         * @return the centroid of the given geometry transformed to the targetCRS. or <code>null</code> if it could not be
296         *         calculated.
297         */
298        private static Point calcCentroid( Node node, String targetCRS ) {
299            Point point = null;
300            if ( node != null ) {
301                try {
302                    if ( "Envelope".equals( node.getLocalName() ) ) {
303                        Envelope env = GMLGeometryAdapter.wrapBox( (Element) node, null );
304                        point = env.getCentroid();
305                    } else {
306                        Geometry geom = GMLGeometryAdapter.wrap( (Element) node, null );
307                        point = geom.getCentroid();
308                    }
309                    if ( targetCRS != null && !"".equals( targetCRS ) && point.getCoordinateSystem() != null ) {
310                        GeoTransformer gt = new GeoTransformer( targetCRS );
311                        point = (Point) gt.transform( point );
312                    }
313                } catch ( InvalidGMLException e ) {
314                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
315                } catch ( XMLParsingException e ) {
316                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
317                } catch ( UnknownCRSException e ) {
318                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
319                } catch ( GeometryException e ) {
320                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
321                } catch ( IllegalArgumentException e ) {
322                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
323                } catch ( CRSTransformationException e ) {
324                    LOG.logError( "Could not calculate centroid because: " + e.getMessage(), e );
325                }
326    
327            } else {
328                LOG.logWarning( "Could not calculate centroid because the given node was null." );
329            }
330            return point;
331        }
332    
333        /**
334         * returns the X coordinate of the centroid of the geometry represented by the passed Node
335         * 
336         * @param node
337         *            to calculate the centroid from.
338         * @param targetCRS
339         *            of resulting centroid, may be null
340         * @return the x coordinate of the (transformed) centroid or -1 if it could not be calculated.
341         */
342        public static double getCentroidX( Node node, String targetCRS ) {
343            Point p = calcCentroid( node, targetCRS );
344            return ( p == null ) ? -1 : p.getX();
345        }
346    
347        /**
348         * returns the y coordinate of the centroid of the geometry represented by the passed Node
349         * 
350         * @param node
351         *            to calculate the centroid from.
352         * @param targetCRS
353         *            of resulting centroid, may be null
354         * @return the y coordinate of the (transformed) centroid or -1 if it could not be calculated.
355         */
356        public static double getCentroidY( Node node, String targetCRS ) {
357            Point p = calcCentroid( node, targetCRS );
358            return ( p == null ) ? -1 : p.getY();
359        }
360    
361        /**
362         * Searches for a gml:poslist, gml:pos or gml:coordinates and creates a String from them.
363         * 
364         * @param node
365         *            to get the coordinates from
366         * @return A string representation of the coordinates found, or the emtpy string if not.
367         */
368        public static String getCurveCoordinates( Node node ) {
369            StringBuilder sb = new StringBuilder( 10000 );
370            if ( node != null ) {
371                try {
372                    List<Node> list = XMLTools.getNodes( node, ".//gml:posList | gml:pos | gml:coordinates", nsc );
373                    for ( Node node2 : list ) {
374                        String s = XMLTools.getStringValue( node2 ).trim();
375                        if ( node2.getLocalName().equals( "posList" ) ) {
376                            String[] sl = StringTools.toArray( s, " ", false );
377                            int dim = XMLTools.getNodeAsInt( node2, "./@srsDimension", nsc, 2 );
378                            for ( int i = 0; i < sl.length; i++ ) {
379                                sb.append( sl[i] );
380                                if ( ( i + 1 ) % dim == 0 ) {
381                                    sb.append( ' ' );
382                                } else {
383                                    sb.append( ',' );
384                                }
385                            }
386                        } else if ( node2.getLocalName().equals( "pos" ) ) {
387                            String[] sl = StringTools.toArray( s, "\t\n\r\f ,", false );
388                            for ( int i = 0; i < sl.length; i++ ) {
389                                sb.append( sl[i] );
390                                if ( i < sl.length - 1 ) {
391                                    sb.append( ',' );
392                                } else {
393                                    sb.append( ' ' );
394                                }
395                            }
396                        } else if ( node2.getLocalName().equals( "coordinates" ) ) {
397                            sb.append( s );
398                        }
399                    }
400                } catch ( XMLParsingException e ) {
401                    LOG.logError( "Could not get the Curve coordinates because: " + e.getMessage(), e );
402                }
403            } else {
404                LOG.logWarning( "Could not get the Curve coordinates because the given node was null." );
405            }
406            return sb.toString();
407        }
408    
409        /**
410         * Transforms the string contents of a GML 3.1.1-style <code>gml:posList</code> into a GML 2.1-style
411         * <code>gml:coordinates</code> element.
412         * 
413         * @param input
414         *            coordinates string that separates all number using withspace characters
415         * @param dimension
416         * @return translated coordinates string, with a "," between each coordinate and a " " between each ordinate of a
417         *         coordinate
418         */
419        public static String toGML2Coordinates( String input, int dimension ) {
420    
421            StringBuffer sb = new StringBuffer();
422            StringTokenizer st = new StringTokenizer( input );
423            if ( st.hasMoreTokens() ) {
424                sb.append( st.nextToken() );
425            }
426            int i = 0;
427            while ( st.hasMoreTokens() ) {
428                if ( i++ % dimension == 0 ) {
429                    sb.append( ',' );
430                } else {
431                    sb.append( ' ' );
432                }
433                String token = st.nextToken();
434                sb.append( token );
435            }
436            return sb.toString();
437        }
438    }