    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }