001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/spatialschema/GMLGeometryAdapter.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.model.spatialschema;
037    
038    import static org.deegree.model.spatialschema.GeometryFactory.createCurveSegment;
039    import static org.deegree.ogcwebservices.wfs.configuration.WFSDeegreeParams.getSwitchAxes;
040    
041    import java.io.ByteArrayOutputStream;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.io.OutputStream;
045    import java.io.PrintWriter;
046    import java.io.StringReader;
047    import java.util.ArrayList;
048    import java.util.HashMap;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Map;
052    import java.util.Properties;
053    import java.util.StringTokenizer;
054    
055    import org.deegree.framework.log.ILogger;
056    import org.deegree.framework.log.LoggerFactory;
057    import org.deegree.framework.util.StringTools;
058    import org.deegree.framework.xml.ElementList;
059    import org.deegree.framework.xml.NamespaceContext;
060    import org.deegree.framework.xml.XMLFragment;
061    import org.deegree.framework.xml.XMLParsingException;
062    import org.deegree.framework.xml.XMLTools;
063    import org.deegree.i18n.Messages;
064    import org.deegree.model.crs.CRSFactory;
065    import org.deegree.model.crs.CoordinateSystem;
066    import org.deegree.model.crs.UnknownCRSException;
067    import org.deegree.model.filterencoding.Function;
068    import org.deegree.ogcbase.CommonNamespaces;
069    import org.deegree.ogcbase.InvalidGMLException;
070    import org.w3c.dom.Document;
071    import org.w3c.dom.Element;
072    import org.w3c.dom.Node;
073    
074    /**
075     * Adapter class for converting GML geometries to deegree geometries and vice versa. Some logical problems result from
076     * the fact that an envelope isn't a geometry according to ISO 19107 (where the deegree geometry model is based on) but
077     * according to GML2/3 specification it is.<br>
078     * So if the wrap(..) method is called with an envelope a <tt>Surface</tt> will be returned representing the envelops
079     * shape. To export an <tt>Envelope</tt> to a GML box/envelope two specialized export methods are available.<BR>
080     * The export method(s) doesn't return a DOM element as one may expect but a <tt>StringBuffer</tt>. This is done because
081     * the transformation from deegree geometries to GML mainly is required when a GML representation of a geometry shall be
082     * serialized to a file or to a network connection. For both cases the string representation is required and it is
083     * simply faster to create the string directly instead of first creating a DOM tree that after this must be serialized
084     * to a string.<BR>
085     * In future version geometries will be serialized to a stream.
086     *
087     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
088     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
089     * @author last edited by: $Author: mschneider $
090     *
091     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
092     */
093    public class GMLGeometryAdapter {
094    
095        private static final ILogger LOG = LoggerFactory.getLogger( GMLGeometryAdapter.class );
096    
097        private static final NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
098    
099        private static Map<String, CoordinateSystem> crsMap = new HashMap<String, CoordinateSystem>();
100    
101        private static final String COORD = CommonNamespaces.GML_PREFIX + ":coord";
102    
103        private static final String COORDINATES = CommonNamespaces.GML_PREFIX + ":coordinates";
104    
105        private static final String POS = CommonNamespaces.GML_PREFIX + ":pos";
106    
107        private static final String POSLIST = CommonNamespaces.GML_PREFIX + ":posList";
108    
109        private static Properties arcProperties;
110    
111        /**
112         * This differs from GeographiCRS.WGS84 in the identifiers (equals fails for WGS84.equals(EPSG4326)).
113         */
114        public static CoordinateSystem EPSG4326;
115    
116        static {
117            try {
118                EPSG4326 = CRSFactory.create( "EPSG:4326" );
119            } catch ( UnknownCRSException e ) {
120                LOG.logError( "Unknown error", e );
121            }
122        }
123    
124        static {
125            if ( GMLGeometryAdapter.arcProperties == null ) {
126                GMLGeometryAdapter.initialize();
127            }
128        }
129    
130        private static void initialize() {
131    
132            arcProperties = new Properties();
133    
134            try {
135                // initialize mappings with mappings from "arc.properties" file in this package
136                InputStream is = GMLGeometryAdapter.class.getResourceAsStream( "arc.properties" );
137                try {
138                    arcProperties.load( is );
139                } catch ( IOException e ) {
140                    LOG.logError( e.getMessage(), e );
141                }
142    
143                // override mappings with mappings from "arc.properties" file in root package
144                is = Function.class.getResourceAsStream( "/arc.properties" );
145                if ( is != null ) {
146                    Properties props = new Properties();
147                    props.load( is );
148                    Iterator<?> iter = props.keySet().iterator();
149                    while ( iter.hasNext() ) {
150                        String key = (String) iter.next();
151                        String value = props.getProperty( key );
152                        arcProperties.put( key, value );
153                    }
154                }
155            } catch ( Exception e ) {
156                LOG.logError( e.getMessage(), e );
157            }
158        }
159    
160        /**
161         * Returns true if the given localName != <code>null</code> or empty and matches one of the following:
162         * <ul>
163         * <li>Box (alternative name: Envelope)</li>
164         * <li>CompositeSurface</li>
165         * <li>Curve</li>
166         * <li>LinearRing</li>
167         * <li>LineString</li>
168         * <li>MultiCurve</li>
169         * <li>MultiGeometry</li>
170         * <li>MultiLineString</li>
171         * <li>MultiPoint</li>
172         * <li>MultiPolygon</li>
173         * <li>MultiSurface</li>
174         * <li>Point (alternative name: Center)</li>
175         * <li>Polygon</li>
176         * <li>Ring</li>
177         * <li>Surface</li>
178         * </ul>
179         *
180         * @param localName
181         *            name to check
182         * @return true if localName equals (ignore-case) one of the above strings
183         */
184        public static boolean isGeometrieSupported( String localName ) {
185            if ( localName == null || "".equals( localName.trim() ) ) {
186                return false;
187            }
188            return "Point".equalsIgnoreCase( localName ) || "Center".equalsIgnoreCase( localName )
189                   || "LineString".equalsIgnoreCase( localName ) || "Polygon".equalsIgnoreCase( localName )
190                   || "MultiPoint".equalsIgnoreCase( localName ) || "MultiLineString".equalsIgnoreCase( localName )
191                   || "MultiPolygon".equalsIgnoreCase( localName ) || "Box".equalsIgnoreCase( localName )
192                   || "Envelope".equalsIgnoreCase( localName ) || "Curve".equalsIgnoreCase( localName )
193                   || "Surface".equalsIgnoreCase( localName ) || "MultiCurve".equalsIgnoreCase( localName )
194                   || "MultiSurface".equalsIgnoreCase( localName ) || "CompositeSurface".equalsIgnoreCase( localName )
195                   || "MultiGeometry".equalsIgnoreCase( localName ) || "LinearRing".equalsIgnoreCase( localName )
196                   || "Ring".equalsIgnoreCase( localName );
197        }
198    
199        /**
200         * Parses the given DOM element of a GML geometry and returns a corresponding deegree {@link Geometry} object.
201         * <p>
202         * Notice that GML boxes will be converted to surfaces because in ISO 19107 envelopes are no geometries. Rings are
203         * returned as {@link Curve} objects.
204         * <p>
205         * Currently, the following geometry elements are supported:
206         * <table border="1">
207         * <tr>
208         * <th>Geometry element name<br>
209         * (in gml namespace)</th>
210         * <th>Return type<br>
211         * (deegree {@link Geometry})</th>
212         * </tr>
213         * <tr>
214         * <td align="center">Box/Envelope</td>
215         * <td align="center">{@link Envelope}</td>
216         * </tr>
217         * <tr>
218         * <td align="center">CompositeSurface</td>
219         * <td align="center">{@link CompositeSurface}</td>
220         * </tr>
221         * <tr>
222         * <td align="center">Curve</td>
223         * <td align="center">{@link Curve}</td>
224         * </tr>
225         * <tr>
226         * <td align="center">LinearRing</td>
227         * <td align="center">{@link Curve}</td>
228         * </tr>
229         * <tr>
230         * <td align="center">LineString</td>
231         * <td align="center">{@link Curve}</td>
232         * </tr>
233         * <tr>
234         * <td align="center">MultiCurve</td>
235         * <td align="center">{@link MultiCurve}</td>
236         * </tr>
237         * <tr>
238         * <td align="center">MultiGeometry</td>
239         * <td align="center">{@link MultiGeometry}</td>
240         * </tr>
241         * <tr>
242         * <td align="center">MultiLineString</td>
243         * <td align="center">{@link MultiCurve}</td>
244         * </tr>
245         * <tr>
246         * <td align="center">MultiPoint</td>
247         * <td align="center">{@link MultiPoint}</td>
248         * </tr>
249         * <tr>
250         * <td align="center">MultiPolygon</td>
251         * <td align="center">{@link MultiSurface}</td>
252         * </tr>
253         * <tr>
254         * <td align="center">MultiSurface</td>
255         * <td align="center">{@link MultiSurface}</td>
256         * </tr>
257         * <tr>
258         * <td align="center">Point/Center</td>
259         * <td align="center">{@link Point}</td>
260         * </tr>
261         * <tr>
262         * <td align="center">Polygon</td>
263         * <td align="center">{@link Surface}</td>
264         * </tr>
265         * <tr>
266         * <td align="center">Ring</td>
267         * <td align="center">{@link Curve}</td>
268         * </tr>
269         * <tr>
270         * <td align="center">Surface</td>
271         * <td align="center">{@link Surface}</td>
272         * </tr>
273         * </table>
274         *
275         * @param element
276         *            gml geometry element
277         * @param srsName
278         *            default SRS for the geometry
279         * @return corresponding {@link Geometry} instance
280         * @throws GeometryException
281         *             if type unsupported or parsing / conversion failed
282         */
283        public static Geometry wrap( Element element, String srsName )
284                                throws GeometryException {
285    
286            Geometry geometry = null;
287            try {
288                String name = element.getLocalName();
289                if ( ( name.equals( "Point" ) ) || ( name.equals( "Center" ) ) ) {
290                    geometry = wrapPoint( element, srsName );
291                } else if ( name.equals( "LineString" ) ) {
292                    geometry = wrapLineString( element, srsName );
293                } else if ( name.equals( "Polygon" ) ) {
294                    geometry = wrapPolygon( element, srsName );
295                } else if ( name.equals( "MultiPoint" ) ) {
296                    geometry = wrapMultiPoint( element, srsName );
297                } else if ( name.equals( "MultiLineString" ) ) {
298                    geometry = wrapMultiLineString( element, srsName );
299                } else if ( name.equals( "MultiPolygon" ) ) {
300                    geometry = wrapMultiPolygon( element, srsName );
301                } else if ( name.equals( "Box" ) || name.equals( "Envelope" ) ) {
302                    geometry = wrapBoxAsSurface( element, srsName );
303                } else if ( name.equals( "Curve" ) ) {
304                    geometry = wrapCurveAsCurve( element, srsName );
305                } else if ( name.equals( "Surface" ) ) {
306                    geometry = wrapSurfaceAsSurface( element, srsName );
307                } else if ( name.equals( "MultiCurve" ) ) {
308                    geometry = wrapMultiCurveAsMultiCurve( element, srsName );
309                } else if ( name.equals( "MultiSurface" ) ) {
310                    geometry = wrapMultiSurfaceAsMultiSurface( element, srsName );
311                } else if ( name.equals( "CompositeSurface" ) ) {
312                    geometry = wrapCompositeSurface( element, srsName );
313                } else if ( name.equals( "MultiGeometry" ) ) {
314                    geometry = wrapMultiGeometry( element, srsName );
315                } else if ( name.equals( "LinearRing" ) ) {
316                    geometry = wrapLinearRing( element, srsName );
317                } else if ( name.equals( "Ring" ) ) {
318                    geometry = wrapRing( element, srsName );
319                } else {
320                    throw new GeometryException( "Not a supported geometry type: " + name );
321                }
322            } catch ( Exception e ) {
323                LOG.logError( e.getMessage(), e );
324                throw new GeometryException( StringTools.stackTraceToString( e ) );
325            }
326    
327            return geometry;
328        }
329    
330        /**
331         * Converts the given string representation of a GML geometry object to a corresponding deegree {@link Geometry}.
332         * Notice that GML Boxes will be converted to Surfaces because in ISO 19107 Envelopes are no geometries.
333         *
334         * @see #wrap(Element, String)
335         *
336         * @param gml
337         * @param srsName
338         *            default SRS for the geometry (may be overwritten in geometry elements)
339         * @return corresponding geometry object
340         * @throws GeometryException
341         * @throws XMLParsingException
342         */
343        public static Geometry wrap( String gml, String srsName )
344                                throws GeometryException, XMLParsingException {
345            StringReader sr = new StringReader( gml );
346            Document doc = null;
347            try {
348                doc = XMLTools.parse( sr );
349            } catch ( Exception e ) {
350                LOG.logError( "could not parse: '" + gml + "' as GML/XML", e );
351                throw new XMLParsingException( "could not parse: '" + gml + "' as GML/XML: " + e.getMessage() );
352            }
353            return wrap( doc.getDocumentElement(), srsName );
354        }
355    
356        /**
357         * Returns an instance of {@link Envelope} created from the passed <code>gml:Box</code> or <code>gml:Envelope</code>
358         * element.
359         *
360         * @param element
361         *            <code>gml:Box</code> or <code>gml:Envelope</code> element
362         * @param srsName
363         *            default SRS for the geometry
364         * @return instance of <code>Envelope</code>
365         * @throws XMLParsingException
366         * @throws InvalidGMLException
367         * @throws UnknownCRSException
368         */
369        public static Envelope wrapBox( Element element, String srsName )
370                                throws XMLParsingException, InvalidGMLException, UnknownCRSException {
371    
372            srsName = findSrsName( element, srsName );
373    
374            boolean swap = swap( srsName );
375    
376            Position[] bb = null;
377            List<Element> nl = XMLTools.getElements( element, COORD, nsContext );
378            if ( nl != null && nl.size() > 0 ) {
379                bb = new Position[2];
380                bb[0] = createPositionFromCoord( nl.get( 0 ), swap );
381                bb[1] = createPositionFromCoord( nl.get( 1 ), swap );
382            } else {
383                nl = XMLTools.getElements( element, COORDINATES, nsContext );
384                if ( nl != null && nl.size() > 0 ) {
385                    bb = createPositionFromCoordinates( nl.get( 0 ), swap );
386                } else {
387                    nl = XMLTools.getElements( element, POS, nsContext );
388                    if ( nl != null && nl.size() > 0 ) {
389                        bb = new Position[2];
390                        bb[0] = createPositionFromPos( nl.get( 0 ), swap );
391                        bb[1] = createPositionFromPos( nl.get( 1 ), swap );
392                    } else {
393                        Element lowerCorner = (Element) XMLTools.getRequiredNode( element, "gml:lowerCorner", nsContext );
394                        Element upperCorner = (Element) XMLTools.getRequiredNode( element, "gml:upperCorner", nsContext );
395                        bb = new Position[2];
396                        bb[0] = createPositionFromCorner( lowerCorner, swap );
397                        bb[1] = createPositionFromCorner( upperCorner, swap );
398                    }
399                }
400            }
401            Envelope box = GeometryFactory.createEnvelope( bb[0], bb[1], getCRS( srsName ) );
402            return box;
403        }
404    
405        /**
406         * Returns an instance of {@link Curve} created from the passed element.
407         * <p>
408         * The element must be substitutable for <code>gml:_Curve</code>. Currently, the following concrete elements are
409         * supported:
410         * <ul>
411         * <li><code>gml:Curve</code></li>
412         * <li><code>gml:LineString</code>
413         * <li>
414         * </ul>
415         *
416         * @param element
417         *            must be substitutable for the abstract element <code>gml:_Curve</code>
418         * @param srsName
419         * @return curve instance
420         * @throws UnknownCRSException
421         * @throws GeometryException
422         * @throws XMLParsingException
423         * @throws InvalidGMLException
424         */
425        private static Curve wrapAbstractCurve( Element element, String srsName )
426                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
427            Curve curve = null;
428            String localName = element.getLocalName();
429            if ( "Curve".equals( localName ) ) {
430                curve = wrapCurveAsCurve( element, srsName );
431            } else if ( "LineString".equals( localName ) ) {
432                curve = wrapLineString( element, srsName );
433            } else {
434                String msg = "'" + localName + "' is not a valid or supported substitution for 'gml:_Curve'.";
435                throw new XMLParsingException( msg );
436            }
437            return curve;
438        }
439    
440        /**
441         * Returns an instance of {@link Curve} created from the passed <code>gml:Curve</code> element.
442         *
443         * @param element
444         *            <code>gml:Curve</code> element
445         * @param srsName
446         *            default SRS for the geometry
447         * @return corresponding Curve instance
448         * @throws XMLParsingException
449         * @throws GeometryException
450         * @throws UnknownCRSException
451         */
452        public static Curve wrapCurveAsCurve( Element element, String srsName )
453                                throws XMLParsingException, GeometryException, UnknownCRSException {
454    
455            srsName = findSrsName( element, srsName );
456            Element segmentsElement = (Element) XMLTools.getRequiredNode( element, "gml:segments", nsContext );
457            ElementList childElements = XMLTools.getChildElements( segmentsElement );
458            CurveSegment[] segments = new CurveSegment[childElements.getLength()];
459            for ( int i = 0; i < childElements.getLength(); i++ ) {
460                segments[i] = parseAbstractCurveSegment( childElements.item( i ), srsName );
461            }
462            return GeometryFactory.createCurve( segments, getCRS( srsName ) );
463        }
464    
465        /**
466         * Parses the given element as a {@link CurveSegment}.
467         * <p>
468         * The element must be substitutable for <code>gml:_CurveSegment</code>. Currently, the following concrete elements
469         * are supported:
470         * <ul>
471         * <li><code>gml:Arc</code></li>
472         * <li><code>gml:Circle</code>
473         * <li><code>gml:LineStringSegment</code>
474         * <li>
475         * </ul>
476         *
477         * @param element
478         *            must be substitutable for the abstract element <code>gml:_CurveSegment</code>
479         * @param srsName
480         * @return curve instance
481         * @throws GeometryException
482         * @throws UnknownCRSException
483         * @throws XMLParsingException
484         */
485        private static CurveSegment parseAbstractCurveSegment( Element element, String srsName )
486                                throws GeometryException, XMLParsingException, UnknownCRSException {
487            CurveSegment segment = null;
488            String localName = element.getLocalName();
489            if ( "Arc".equals( localName ) ) {
490                segment = parseArc( element, srsName );
491            } else if ( "Circle".equals( localName ) ) {
492                segment = parseCircle( element, srsName );
493            } else if ( "LineStringSegment".equals( localName ) ) {
494                segment = parseLineStringSegment( element, srsName );
495            } else {
496                String msg = "'" + localName + "' is not a valid or supported substitution for 'gml:_CurveSegment'.";
497                throw new GeometryException( msg );
498            }
499            return segment;
500        }
501    
502        /**
503         * Parses the given <code>gml:Arc</code> element as a {@link CurveSegment}.
504         *
505         * @param element
506         * @param srsName
507         * @return the CurveSegment created from the given element
508         * @throws GeometryException
509         * @throws XMLParsingException
510         * @throws UnknownCRSException
511         */
512        private static CurveSegment parseArc( Element element, String srsName )
513                                throws GeometryException, XMLParsingException, UnknownCRSException {
514    
515            srsName = findSrsName( element, srsName );
516            Position[] pos = null;
517            try {
518                pos = createPositions( element, srsName );
519            } catch ( Exception e ) {
520                throw new GeometryException( "Error parsing gml:Arc: " + e.getMessage() );
521            }
522            if ( pos.length != 3 ) {
523                throw new GeometryException( "A gml:Arc must be described by exactly three points" );
524            }
525            int count = Integer.parseInt( arcProperties.getProperty( "defaultNumPoints" ) );
526            Position[] linearizedPos = LinearizationUtil.linearizeArc( pos[0], pos[1], pos[2], count );
527            return GeometryFactory.createCurveSegment( linearizedPos, getCRS( srsName ) );
528        }
529    
530        /**
531         * Parses the given <code>gml:Circle</code> element as a {@link CurveSegment}.
532         *
533         * @param element
534         * @param srsName
535         * @return the CurveSegment created from the given element
536         * @throws GeometryException
537         * @throws XMLParsingException
538         * @throws UnknownCRSException
539         */
540        private static CurveSegment parseCircle( Element element, String srsName )
541                                throws GeometryException, XMLParsingException, UnknownCRSException {
542    
543            srsName = findSrsName( element, srsName );
544            Position[] pos;
545            try {
546                pos = createPositions( element, srsName );
547            } catch ( Exception e ) {
548                throw new GeometryException( "Error parsing gml:Circle: " + e.getMessage() );
549            }
550            if ( pos.length != 3 ) {
551                throw new GeometryException( "A gml:Circle element must be described by exactly three points." );
552            }
553            int count = Integer.parseInt( arcProperties.getProperty( "defaultNumPoints" ) );
554            Position[] linearizedPos = LinearizationUtil.linearizeCircle( pos[0], pos[1], pos[2], count );
555            return GeometryFactory.createCurveSegment( linearizedPos, getCRS( srsName ) );
556        }
557    
558        /**
559         * Parses the given <code>gml:LineStringSegment</code> element as a {@link CurveSegment}.
560         *
561         * @param element
562         * @param srsName
563         * @return the curve segment created from the given element
564         * @throws GeometryException
565         * @throws XMLParsingException
566         */
567        private static CurveSegment parseLineStringSegment( Element element, String srsName )
568                                throws GeometryException, XMLParsingException {
569    
570            srsName = findSrsName( element, srsName );
571            CurveSegment segment = null;
572            try {
573                Position[] pos = createPositions( element, srsName );
574                segment = createCurveSegment( pos, getCRS( srsName ) );
575            } catch ( Exception e ) {
576                LOG.logError( "Real error:", e );
577                throw new GeometryException( "Error parsing LineStringSegment: " + e.getMessage() );
578            }
579            return segment;
580        }
581    
582        /**
583         * Parses the given element as a {@link Surface}.
584         * <p>
585         * The element must be substitutable for <code>gml:_Surface</code>. Currently, the following concrete elements are
586         * supported:
587         * <ul>
588         * <li><code>gml:Surface</code></li>
589         * <li><code>gml:Polygon</code>
590         * <li>
591         * </ul>
592         *
593         * @param element
594         *            must be substitutable for the abstract element <code>gml:_Surface</code>
595         * @param srsName
596         * @return corresponding surface instance
597         * @throws UnknownCRSException
598         * @throws GeometryException
599         * @throws XMLParsingException
600         * @throws InvalidGMLException
601         */
602        private static Surface wrapAbstractSurface( Element element, String srsName )
603                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
604            Surface surface = null;
605            String localName = element.getLocalName();
606            if ( "Polygon".equals( localName ) ) {
607                surface = wrapPolygon( element, srsName );
608            } else if ( "Surface".equals( localName ) ) {
609                surface = wrapSurfaceAsSurface( element, srsName );
610            } else {
611                String msg = "'" + localName + "' is not a valid or supported substitution for 'gml:_Surface'.";
612                throw new XMLParsingException( msg );
613            }
614            return surface;
615        }
616    
617        /**
618         * Returns an instance of {@link Surface} created from the passed <code>gml:Surface</code> element.
619         *
620         * @param element
621         * @param srsName
622         *            default SRS for the geometry
623         * @return Surface
624         * @throws XMLParsingException
625         * @throws GeometryException
626         * @throws UnknownCRSException
627         */
628        public static Surface wrapSurfaceAsSurface( Element element, String srsName )
629                                throws XMLParsingException, GeometryException, UnknownCRSException {
630    
631            srsName = findSrsName( element, srsName );
632    
633            Element patches = extractPatches( element );
634            List<Element> polygonList = XMLTools.getRequiredElements( patches, "gml:Polygon | gml:PolygonPatch", nsContext );
635    
636            SurfacePatch[] surfacePatches = new SurfacePatch[polygonList.size()];
637    
638            for ( int i = 0; i < polygonList.size(); i++ ) {
639                Curve exteriorRing = null;
640                Element polygon = polygonList.get( i );
641                try {
642                    Element exterior = (Element) XMLTools.getNode( polygon, "gml:exterior | gml:outerBounderyIs", nsContext );
643                    if ( exterior != null ) {
644                        exteriorRing = parseRing( srsName, exterior );
645                    } else {
646                        String msg = Messages.getMessage( "GEOM_SURFACE_NO_EXTERIOR_RING" );
647                        throw new XMLParsingException( msg );
648                    }
649    
650                    List<Element> interiorList = XMLTools.getElements( polygon, "gml:interior | gml:outerBounderyIs",
651                                                                       nsContext );
652                    Curve[] interiorRings = null;
653                    if ( interiorList != null && interiorList.size() > 0 ) {
654    
655                        interiorRings = new Curve[interiorList.size()];
656    
657                        for ( int j = 0; j < interiorRings.length; j++ ) {
658                            Element interior = interiorList.get( j );
659                            interiorRings[j] = parseRing( srsName, interior );
660                        }
661                    }
662                    surfacePatches[i] = GeometryFactory.createSurfacePatch( exteriorRing, interiorRings, getCRS( srsName ) );
663                } catch ( InvalidGMLException e ) {
664                    LOG.logError( e.getMessage(), e );
665                    throw new XMLParsingException( "Error parsing the polygon element '" + polygon.getNodeName()
666                                                   + "' to create a surface geometry." );
667                }
668    
669            }
670            Surface surface = null;
671            try {
672                surface = GeometryFactory.createSurface( surfacePatches, getCRS( srsName ) );
673            } catch ( GeometryException e ) {
674                throw new GeometryException( "Error creating a surface from '" + surfacePatches.length + "' polygons." );
675            }
676            return surface;
677        }
678    
679        /**
680         * Determines the relevant srsName attribute for a geometry element.
681         * <p>
682         * Strategy:
683         * <nl>
684         * <li>Check if the geometry element has a srsName attribute, or any ancestor. If an srsName is found this way, it
685         * is used.</li>
686         * <li>If no srsName was found, but an srsName was given as parameter to the method, it is returned.</li>
687         * <li>If no srsName was found *and* no srsName was given as parameter to the method, the whole DOM tree is searched
688         * for for an srsName attribute.</li>
689         * </nl>
690         *
691         * @param element
692         *            geometry element
693         * @param srsName
694         *            default value that is used when the element (or an ancestor element) does not have an own srsName
695         *            attribute
696         * @return the srs name
697         * @throws XMLParsingException
698         */
699        private static String findSrsName( Element element, String srsName )
700                                throws XMLParsingException {
701    
702            Node elem = element;
703            String tmp = null;
704            while ( tmp == null && elem != null ) {
705                tmp = XMLTools.getNodeAsString( elem, "@srsName", nsContext, null );
706                elem = elem.getParentNode();
707            }
708    
709            if ( tmp != null ) {
710                srsName = tmp;
711            } else if ( srsName == null ) {
712                // TODO do this is a better way!!!
713                srsName = XMLTools.getNodeAsString( element, "//@srsName", nsContext, null );
714            }
715            return srsName;
716        }
717    
718        /**
719         * Returns the {@link CoordinateSystem} corresponding to the passed crs name.
720         *
721         * @param name
722         *            name of the crs or null (not specified)
723         * @return CoordinateSystem corresponding to the given crs name or null if name is null
724         * @throws UnknownCRSException
725         */
726        private static CoordinateSystem getCRS( String name )
727                                throws UnknownCRSException {
728    
729            if ( name == null ) {
730                return null;
731            }
732    
733            if ( name.length() > 2 ) {
734                if ( name.startsWith( "http://www.opengis.net/gml/srs/" ) ) {
735                    // as declared in the GML 2.1.1 specification
736                    // http://www.opengis.net/gml/srs/epsg.xml#4326
737                    int p = name.lastIndexOf( "/" );
738    
739                    if ( p >= 0 ) {
740                        name = name.substring( p, name.length() );
741                        p = name.indexOf( "." );
742    
743                        String s1 = name.substring( 1, p ).toUpperCase();
744                        p = name.indexOf( "#" );
745    
746                        String s2 = name.substring( p + 1, name.length() );
747                        name = s1 + ":" + s2;
748                    }
749                }
750            }
751    
752            CoordinateSystem crs = crsMap.get( name );
753            if ( crs == null ) {
754                crs = CRSFactory.create( name );
755                crsMap.put( name, crs );
756            }
757            return crs;
758        }
759    
760        /**
761         * Parses a ring element; this may be a gml:LinearRing or a gml:Ring.
762         *
763         * @param srsName
764         * @param parent
765         *            parent of a gml:LinearRing or gml:Ring
766         * @return curves of a ring
767         * @throws XMLParsingException
768         * @throws InvalidGMLException
769         * @throws GeometryException
770         * @throws UnknownCRSException
771         */
772        private static Curve parseRing( String srsName, Element parent )
773                                throws XMLParsingException, InvalidGMLException, GeometryException, UnknownCRSException {
774    
775            List<CurveSegment> curveMembers = null;
776            Element ring = (Element) XMLTools.getNode( parent, "gml:LinearRing", nsContext );
777            if ( ring != null ) {
778                Position[] exteriorRing = createPositions( ring, srsName );
779                curveMembers = new ArrayList<CurveSegment>();
780                curveMembers.add( GeometryFactory.createCurveSegment( exteriorRing, getCRS( srsName ) ) );
781            } else {
782                List<Node> members = XMLTools.getRequiredNodes( parent, "gml:Ring/gml:curveMember/child::*", nsContext );
783                curveMembers = new ArrayList<CurveSegment>( members.size() );
784                for ( Node node : members ) {
785                    Curve curve = (Curve) wrap( (Element) node, srsName );
786                    CurveSegment[] tmp = curve.getCurveSegments();
787                    for ( int i = 0; i < tmp.length; i++ ) {
788                        curveMembers.add( tmp[i] );
789                    }
790                }
791            }
792            CurveSegment[] cs = curveMembers.toArray( new CurveSegment[curveMembers.size()] );
793            return GeometryFactory.createCurve( cs );
794        }
795    
796        /**
797         * Returns a {@link Point} instance created from the passed <code>gml:Point</code> element.
798         *
799         * @param element
800         *            <code>gml:Point</code> element
801         * @param srsName
802         *            default SRS for the geometry
803         * @return instance of Point
804         * @throws XMLParsingException
805         * @throws InvalidGMLException
806         * @throws UnknownCRSException
807         */
808        public static Point wrapPoint( Element element, String srsName )
809                                throws XMLParsingException, InvalidGMLException, UnknownCRSException {
810    
811            srsName = findSrsName( element, srsName );
812    
813            boolean swap = swap( srsName );
814    
815            Position[] bb = null;
816            List<Element> nl = XMLTools.getElements( element, COORD, nsContext );
817            if ( nl != null && nl.size() > 0 ) {
818                bb = new Position[1];
819                bb[0] = createPositionFromCoord( nl.get( 0 ), swap );
820            } else {
821                nl = XMLTools.getElements( element, COORDINATES, nsContext );
822                if ( nl != null && nl.size() > 0 ) {
823                    bb = createPositionFromCoordinates( nl.get( 0 ), swap );
824                } else {
825                    nl = XMLTools.getElements( element, POS, nsContext );
826                    bb = new Position[1];
827                    bb[0] = createPositionFromPos( nl.get( 0 ), swap );
828                }
829            }
830            return GeometryFactory.createPoint( bb[0], getCRS( srsName ) );
831        }
832    
833        /**
834         * Returns a {@link Curve} instance created from the passed <code>gml:LineString</code> element.
835         *
836         * @param element
837         *            <code>gml:LineString</code> element
838         * @param srsName
839         *            default SRS for the geometry
840         * @return instance of Curve
841         * @throws XMLParsingException
842         * @throws GeometryException
843         * @throws InvalidGMLException
844         * @throws UnknownCRSException
845         */
846        public static Curve wrapLineString( Element element, String srsName )
847                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
848    
849            srsName = findSrsName( element, srsName );
850            Position[] pos = createPositions( element, srsName );
851            return GeometryFactory.createCurve( pos, getCRS( srsName ) );
852        }
853    
854        /**
855         * Parses the given <code>gml:Polygon</code> element as a {@link Surface}.
856         *
857         * @param element
858         *            <code>gml:Polygon</code> element
859         * @param srsName
860         *            default SRS for the geometry
861         * @return corresponding Surface instance
862         * @throws XMLParsingException
863         * @throws GeometryException
864         * @throws InvalidGMLException
865         * @throws UnknownCRSException
866         */
867        public static Surface wrapPolygon( Element element, String srsName )
868                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
869    
870            srsName = findSrsName( element, srsName );
871    
872            // get positions for outer boundary
873            Element exteriorRingElement = XMLTools.getRequiredElement( element, "gml:exterior/*|gml:outerBoundaryIs/*",
874                                                                       nsContext );
875            Curve exteriorRing = wrapAbstractRing( exteriorRingElement, srsName );
876            Position[] exteriorPositions = getCurvePositions( exteriorRing );
877    
878            // get positions for inner boundaries
879            List<Element> interiorRingElements = XMLTools.getElements( element, "gml:interior/*|gml:innerBoundaryIs/*",
880                                                                       nsContext );
881            Position[][] interiorPositions = new Position[interiorRingElements.size()][];
882            for ( int i = 0; i < interiorPositions.length; i++ ) {
883                Curve interiorRing = wrapAbstractRing( interiorRingElements.get( i ), srsName );
884                interiorPositions[i] = getCurvePositions( interiorRing );
885            }
886    
887            SurfaceInterpolation si = new SurfaceInterpolationImpl();
888            return GeometryFactory.createSurface( exteriorPositions, interiorPositions, si, getCRS( srsName ) );
889        }
890    
891        private static Position[] getCurvePositions( Curve curve )
892                                throws GeometryException {
893            Position[] positions = null;
894            CurveSegment[] segments = curve.getCurveSegments();
895            if ( segments.length == 1 ) {
896                positions = segments[0].getPositions();
897            } else {
898                int numPositions = 0;
899                for ( int i = 0; i < segments.length; i++ ) {
900                    numPositions += segments[i].getNumberOfPoints();
901                }
902                positions = new Position[numPositions];
903                int positionId = 0;
904                for ( int i = 0; i < segments.length; i++ ) {
905                    Position[] segmentPositions = segments[i].getPositions();
906                    for ( int j = 0; j < segmentPositions.length; j++ ) {
907                        positions[positionId++] = segmentPositions[j];
908                    }
909                }
910            }
911            return positions;
912        }
913    
914        /**
915         * Returns an instance of {@link Curve} created from the passed element.
916         * <p>
917         * The element must be substitutable for <code>gml:_Ring</code>. Currently, the following concrete elements are
918         * supported:
919         * <ul>
920         * <li><code>gml:LinearRing</code></li>
921         * <li><code>gml:Ring</code>
922         * <li>
923         * </ul>
924         *
925         * @param element
926         *            must be substitutable for the abstract element <code>gml:_Ring</code>
927         * @param srsName
928         * @return curve instance
929         * @throws UnknownCRSException
930         * @throws GeometryException
931         * @throws XMLParsingException
932         * @throws InvalidGMLException
933         */
934        private static Curve wrapAbstractRing( Element element, String srsName )
935                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
936            Curve curve = null;
937            String localName = element.getLocalName();
938            if ( "LinearRing".equals( localName ) ) {
939                curve = wrapLinearRing( element, srsName );
940            } else if ( "Ring".equals( localName ) ) {
941                curve = wrapRing( element, srsName );
942            } else {
943                String msg = "'" + localName + "' is not a valid or supported substitution for 'gml:_Ring'.";
944                throw new XMLParsingException( msg );
945            }
946            return curve;
947        }
948    
949        /**
950         * Parses the given <code>gml:LinearRing</code> element as a {@link Curve}.
951         *
952         * @param element
953         * @param srsName
954         * @return a polygon containing the ring
955         * @throws XMLParsingException
956         * @throws GeometryException
957         * @throws InvalidGMLException
958         * @throws UnknownCRSException
959         */
960        public static Curve wrapLinearRing( Element element, String srsName )
961                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
962    
963            srsName = findSrsName( element, srsName );
964            Position[] pos = createPositions( element, srsName );
965            return GeometryFactory.createCurve( pos, getCRS( srsName ) );
966        }
967    
968        /**
969         * Parses the given <code>gml:Ring</code> element as a {@link Curve}.
970         *
971         * @param element
972         *            <code>gml:Ring</code> element
973         * @param srsName
974         *            default SRS for the geometry
975         * @return corresponding Curve instance
976         * @throws XMLParsingException
977         * @throws GeometryException
978         * @throws UnknownCRSException
979         * @throws InvalidGMLException
980         */
981        public static Curve wrapRing( Element element, String srsName )
982                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
983    
984            srsName = findSrsName( element, srsName );
985            Element curveElement = (Element) XMLTools.getRequiredNode( element, "gml:curveMember/*", nsContext );
986            return wrapAbstractCurve( curveElement, srsName );
987        }
988    
989        /**
990         * Returns a {@link MultiPoint} instance created from the passed <code>gml:MultiPoint</code> element.
991         *
992         * @param element
993         *            <code>gml:MultiPoint</code> element
994         * @param srsName
995         *            default SRS for the geometry
996         * @return instance of MultiPoint
997         * @throws XMLParsingException
998         * @throws InvalidGMLException
999         * @throws UnknownCRSException
1000         */
1001        public static MultiPoint wrapMultiPoint( Element element, String srsName )
1002                                throws XMLParsingException, InvalidGMLException, UnknownCRSException {
1003    
1004            srsName = findSrsName( element, srsName );
1005    
1006            // gml:pointMember
1007            List<Point> pointList = new ArrayList<Point>();
1008            List<Element> listPointMember = XMLTools.getElements( element, "gml:pointMember", nsContext );
1009            if ( listPointMember != null ) {
1010                for ( int i = 0; i < listPointMember.size(); i++ ) {
1011                    Element pointMember = listPointMember.get( i );
1012                    Element point = XMLTools.getElement( pointMember, "gml:Point", nsContext );
1013                    pointList.add( wrapPoint( point, srsName ) );
1014                }
1015            }
1016    
1017            // gml:pointMembers
1018            Element pointMembers = XMLTools.getElement( element, "gml:pointMembers", nsContext );
1019            if ( pointMembers != null ) {
1020                List<Element> pointElems = XMLTools.getElements( pointMembers, "gml:Point", nsContext );
1021                for ( int j = 0; j < pointElems.size(); j++ ) {
1022                    pointList.add( wrapPoint( pointElems.get( j ), srsName ) );
1023                }
1024            }
1025    
1026            Point[] points = new Point[pointList.size()];
1027            return GeometryFactory.createMultiPoint( pointList.toArray( points ), getCRS( srsName ) );
1028        }
1029    
1030        /**
1031         * Returns a {@link MultiCurve} instance created from the passed <code>gml:MultiLineString</code> element.
1032         *
1033         * @param element
1034         *            <code>gml:MultiLineString</code> element
1035         * @param srsName
1036         *            default SRS for the geometry
1037         * @return instance of MultiCurve
1038         * @throws XMLParsingException
1039         * @throws GeometryException
1040         * @throws InvalidGMLException
1041         * @throws UnknownCRSException
1042         */
1043        public static MultiCurve wrapMultiLineString( Element element, String srsName )
1044                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
1045    
1046            srsName = findSrsName( element, srsName );
1047    
1048            // ElementList el = XMLTools.getChildElements( "lineStringMember", CommonNamespaces.GMLNS,
1049            // element );
1050            List<Element> el = XMLTools.getElements( element, CommonNamespaces.GML_PREFIX + ":lineStringMember", nsContext );
1051            Curve[] curves = new Curve[el.size()];
1052            for ( int i = 0; i < curves.length; i++ ) {
1053                curves[i] = wrapLineString( XMLTools.getFirstChildElement( el.get( i ) ), srsName );
1054            }
1055            return GeometryFactory.createMultiCurve( curves, getCRS( srsName ) );
1056        }
1057    
1058        /**
1059         * Parses the given <code>gml:MultiCurve</code> element as a {@link MultiCurve}.
1060         *
1061         * @param element
1062         *            <code>gml:MultiCurve</code> element
1063         * @param srsName
1064         *            default SRS for the geometry
1065         * @return corresponding <code>MultiCurve</code> instance
1066         * @throws XMLParsingException
1067         * @throws GeometryException
1068         * @throws UnknownCRSException
1069         * @throws InvalidGMLException
1070         */
1071        public static MultiCurve wrapMultiCurveAsMultiCurve( Element element, String srsName )
1072                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
1073    
1074            srsName = findSrsName( element, srsName );
1075    
1076            // gml:curveMember
1077            List<Element> curveMemberElements = XMLTools.getElements( element, "gml:curveMember", nsContext );
1078            List<Curve> curves = new ArrayList<Curve>();
1079            if ( curveMemberElements.size() > 0 ) {
1080                for ( int i = 0; i < curveMemberElements.size(); i++ ) {
1081                    Element curveMemberElement = curveMemberElements.get( i );
1082                    Element curveElement = XMLTools.getFirstChildElement( curveMemberElement );
1083                    Curve curve = wrapAbstractCurve( curveElement, srsName );
1084                    curves.add( curve );
1085                }
1086            }
1087            // gml:curveMembers
1088            Element curveMembersElement = (Element) XMLTools.getNode( element, "gml:curveMembers", nsContext );
1089            if ( curveMembersElement != null ) {
1090                ElementList curveElements = XMLTools.getChildElements( curveMembersElement );
1091                for ( int i = 0; i < curveElements.getLength(); i++ ) {
1092                    Curve curve = wrapAbstractCurve( curveElements.item( i ), srsName );
1093                    curves.add( curve );
1094                }
1095            }
1096            return GeometryFactory.createMultiCurve( curves.toArray( new Curve[curves.size()] ), getCRS( srsName ) );
1097        }
1098    
1099        /**
1100         * Parses the given <code>gml:MultiSurface</code> element as a {@link MultiSurface}.
1101         *
1102         * @param element
1103         *            <code>gml:MultiSurface</code> element
1104         * @param srsName
1105         *            default SRS for the geometry
1106         * @return corresponding <code>MultiSurface</code> instance
1107         * @throws XMLParsingException
1108         * @throws GeometryException
1109         * @throws UnknownCRSException
1110         * @throws InvalidGMLException
1111         */
1112        public static MultiSurface wrapMultiSurfaceAsMultiSurface( Element element, String srsName )
1113                                throws XMLParsingException, GeometryException, UnknownCRSException, InvalidGMLException {
1114    
1115            srsName = findSrsName( element, srsName );
1116    
1117            // gml:surfaceMember
1118            List<Element> surfaceMemberElements = XMLTools.getElements( element, "gml:surfaceMember", nsContext );
1119            List<Surface> surfaces = new ArrayList<Surface>();
1120            if ( surfaceMemberElements.size() > 0 ) {
1121                for ( int i = 0; i < surfaceMemberElements.size(); i++ ) {
1122                    Element surfaceMemberElement = surfaceMemberElements.get( i );
1123                    Element surfaceElement = XMLTools.getFirstChildElement( surfaceMemberElement );
1124                    Surface surface = wrapAbstractSurface( surfaceElement, srsName );
1125                    surfaces.add( surface );
1126                }
1127            }
1128            // gml:surfaceMembers
1129            Element surfaceMembersElement = (Element) XMLTools.getNode( element, "gml:surfaceMembers", nsContext );
1130            if ( surfaceMembersElement != null ) {
1131                ElementList surfaceElements = XMLTools.getChildElements( surfaceMembersElement );
1132                for ( int i = 0; i < surfaceElements.getLength(); i++ ) {
1133                    Surface surface = wrapAbstractSurface( surfaceElements.item( i ), srsName );
1134                    surfaces.add( surface );
1135                }
1136            }
1137            return GeometryFactory.createMultiSurface( surfaces.toArray( new Surface[surfaces.size()] ), getCRS( srsName ) );
1138        }
1139    
1140        /**
1141         * Returns a {@link MultiSurface} instance created from the passed <code>gml:MultiPolygon</code> element.
1142         *
1143         * @param element
1144         *            <code>gml:MultiPolygon</code> element
1145         * @param srsName
1146         *            default SRS for the geometry
1147         * @return instance of MultiCurve
1148         *
1149         * @throws XMLParsingException
1150         * @throws GeometryException
1151         * @throws InvalidGMLException
1152         * @throws UnknownCRSException
1153         */
1154        public static MultiSurface wrapMultiPolygon( Element element, String srsName )
1155                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
1156    
1157            srsName = findSrsName( element, srsName );
1158    
1159            // ElementList el = XMLTools.getChildElements( "polygonMember", CommonNamespaces.GMLNS,
1160            // element );
1161            List<Element> el = XMLTools.getElements( element, CommonNamespaces.GML_PREFIX + ":polygonMember", nsContext );
1162            Surface[] surfaces = new Surface[el.size()];
1163            for ( int i = 0; i < surfaces.length; i++ ) {
1164                surfaces[i] = wrapPolygon( XMLTools.getFirstChildElement( el.get( i ) ), srsName );
1165            }
1166            return GeometryFactory.createMultiSurface( surfaces, getCRS( srsName ) );
1167        }
1168    
1169        /**
1170         * Creates an instance of {@link MultiGeometry} from the passed <code>gml:MultiGeometry</code> element.
1171         *
1172         * @param element
1173         *            <code>gml:MultiGeometry</code> element
1174         * @param srsName
1175         *            default SRS for the geometry
1176         * @return MultiSurface
1177         * @throws XMLParsingException
1178         * @throws GeometryException
1179         * @throws UnknownCRSException
1180         * @throws GeometryException
1181         */
1182        public static MultiGeometry wrapMultiGeometry( Element element, String srsName )
1183                                throws XMLParsingException, UnknownCRSException, GeometryException {
1184    
1185            srsName = findSrsName( element, srsName );
1186            List<Geometry> memberGeometries = new ArrayList<Geometry>();
1187    
1188            // gml:geometryMember
1189            List<Element> geometryMemberElements = XMLTools.getElements( element, "gml:geometryMember", nsContext );
1190            if ( geometryMemberElements != null ) {
1191                for ( Element geometryMemberElement : geometryMemberElements ) {
1192                    Element geometryElement = XMLTools.getFirstChildElement( geometryMemberElement );
1193                    memberGeometries.add( wrap( geometryElement, srsName ) );
1194                }
1195            }
1196    
1197            // gml:geometryMembers
1198            Element surfaceMembers = (Element) XMLTools.getNode( element, "gml:geometryMembers", nsContext );
1199            if ( surfaceMembers != null ) {
1200                ElementList geometryElements = XMLTools.getChildElements( surfaceMembers );
1201                for ( int i = 0; i < geometryElements.getLength(); i++ ) {
1202                    Element geometryElement = geometryElements.item( i );
1203                    memberGeometries.add( wrap( geometryElement, srsName ) );
1204                }
1205            }
1206            return GeometryFactory.createMultiGeometry( memberGeometries.toArray( new Geometry[memberGeometries.size()] ),
1207                                                        getCRS( srsName ) );
1208        }
1209    
1210        /**
1211         * Returns a <code>Surface</code> created from the given <code>gml:Box</code> element. This method is useful because
1212         * an Envelope that would normally be created from a Box isn't a geometry in context of ISO 19107.
1213         *
1214         * @param element
1215         *            <code>gml:Box</code> element
1216         * @param srsName
1217         * @return instance of <code>Surface</code>
1218         * @throws XMLParsingException
1219         * @throws GeometryException
1220         * @throws InvalidGMLException
1221         * @throws UnknownCRSException
1222         */
1223        public static Surface wrapBoxAsSurface( Element element, String srsName )
1224                                throws XMLParsingException, GeometryException, InvalidGMLException, UnknownCRSException {
1225            Envelope env = wrapBox( element, srsName );
1226            return GeometryFactory.createSurface( env, env.getCoordinateSystem() );
1227        }
1228    
1229        /**
1230         * Returns an instance of {@link CompositeSurface} created from the passed <code>gml:CompositeSurface</code>
1231         * element.
1232         *
1233         * TODO
1234         *
1235         * @param element
1236         * @param srsName
1237         *            default SRS for the geometry
1238         * @return CompositeSurface
1239         */
1240        public static CompositeSurface wrapCompositeSurface( Element element, String srsName ) {
1241            throw new UnsupportedOperationException(
1242                                                     "#wrapCompositeSurface(Element) is not implemented as yet. Work in Progress." );
1243        }
1244    
1245        /**
1246         * Extract the <gml:patches> node from a <gml:Surface> element.
1247         *
1248         * @param surface
1249         * @return Element
1250         * @throws XMLParsingException
1251         */
1252        private static Element extractPatches( Element surface )
1253                                throws XMLParsingException {
1254            Element patches = null;
1255            try {
1256                patches = (Element) XMLTools.getRequiredNode( surface, "gml:patches", nsContext );
1257            } catch ( XMLParsingException e ) {
1258                throw new XMLParsingException( "Error retrieving the patches element from the surface element." );
1259            }
1260            return patches;
1261        }
1262    
1263        private static Position createPositionFromCorner( Element corner, boolean swap )
1264                                throws InvalidGMLException {
1265    
1266            String tmp = XMLTools.getAttrValue( corner, null, "dimension", null );
1267            int dim = 0;
1268            if ( tmp != null ) {
1269                dim = Integer.parseInt( tmp );
1270            }
1271            tmp = XMLTools.getStringValue( corner );
1272            double[] vals = StringTools.toArrayDouble( tmp, ", " );
1273            if ( dim != 0 ) {
1274                if ( vals.length != dim ) {
1275                    throw new InvalidGMLException( "dimension must be equal to the number of coordinate values defined "
1276                                                   + "in pos element." );
1277                }
1278            } else {
1279                dim = vals.length;
1280            }
1281    
1282            Position pos = null;
1283            if ( dim == 3 ) {
1284                pos = GeometryFactory.createPosition( vals[0], vals[1], vals[2] );
1285            } else {
1286                pos = GeometryFactory.createPosition( vals[swap ? 1 : 0], vals[swap ? 0 : 1] );
1287            }
1288    
1289            return pos;
1290    
1291        }
1292    
1293        /**
1294         * returns an instance of Position created from the passed coord
1295         *
1296         * @param element
1297         *            &lt;coord&gt;
1298         *
1299         * @return instance of <tt>Position</tt>
1300         *
1301         * @throws XMLParsingException
1302         */
1303        private static Position createPositionFromCoord( Element element, boolean swap )
1304                                throws XMLParsingException {
1305    
1306            Position pos = null;
1307            // Element elem = XMLTools.getRequiredChildElement( "X", CommonNamespaces.GMLNS, element );
1308            Element elem = XMLTools.getRequiredElement( element, CommonNamespaces.GML_PREFIX + ":X", nsContext );
1309            double x = Double.parseDouble( XMLTools.getStringValue( elem ) );
1310            // elem = XMLTools.getRequiredChildElement( "Y", CommonNamespaces.GMLNS, element );
1311            elem = XMLTools.getRequiredElement( element, CommonNamespaces.GML_PREFIX + ":Y", nsContext );
1312            double y = Double.parseDouble( XMLTools.getStringValue( elem ) );
1313            // elem = XMLTools.getChildElement( "Z", CommonNamespaces.GMLNS, element );
1314            elem = XMLTools.getElement( element, CommonNamespaces.GML_PREFIX + ":Z", nsContext );
1315    
1316            if ( elem != null ) {
1317                double z = Double.parseDouble( XMLTools.getStringValue( elem ) );
1318                // no swapping for 3D
1319                pos = GeometryFactory.createPosition( new double[] { x, y, z } );
1320            } else {
1321                pos = GeometryFactory.createPosition( new double[] { swap ? y : x, swap ? x : y } );
1322            }
1323            return pos;
1324        }
1325    
1326        /**
1327         * returns an array of Positions created from the passed coordinates
1328         *
1329         * @param element
1330         *            <coordinates>
1331         *
1332         * @return instance of <tt>Position[]</tt>
1333         */
1334        private static Position[] createPositionFromCoordinates( Element element, boolean swap ) {
1335    
1336            Position[] points = null;
1337            // fixing the failure coming from the usage of the xmltools.getAttrib method
1338            String ts = XMLTools.getAttrValue( element, null, "ts", " " );
1339    
1340            // not used because javas current decimal seperator will be used
1341            // String ds = XMLTools.getAttrValue( element, null, "decimal", "." );
1342            String cs = XMLTools.getAttrValue( element, null, "cs", "," );
1343    
1344            String value = XMLTools.getStringValue( element ).trim();
1345    
1346            // first tokenizer, tokens the tuples
1347            StringTokenizer tuple = new StringTokenizer( value, ts );
1348            points = new Position[tuple.countTokens()];
1349            int i = 0;
1350            while ( tuple.hasMoreTokens() ) {
1351                String s = tuple.nextToken();
1352                // second tokenizer, tokens the coordinates
1353                StringTokenizer coort = new StringTokenizer( s, cs );
1354                double[] p = new double[coort.countTokens()];
1355    
1356                int idx;
1357                for ( int k = 0; k < p.length; k++ ) {
1358                    idx = swap ? ( k % 2 == 0 ? k + 1 : k - 1 ) : k;
1359                    s = coort.nextToken();
1360                    p[idx] = Double.parseDouble( s );
1361                }
1362    
1363                points[i++] = GeometryFactory.createPosition( p );
1364            }
1365    
1366            return points;
1367        }
1368    
1369        /**
1370         * creates a <tt>Point</tt> from the passed <pos> element containing a GML pos.
1371         *
1372         * @param element
1373         * @return created <tt>Point</tt>
1374         * @throws InvalidGMLException
1375         */
1376        private static Position createPositionFromPos( Element element, boolean swap )
1377                                throws InvalidGMLException {
1378    
1379            String tmp = XMLTools.getAttrValue( element, null, "dimension", null );
1380            int dim = 0;
1381            if ( tmp != null ) {
1382                dim = Integer.parseInt( tmp );
1383            } else {
1384                tmp = element.getAttribute( "srsDimension" );
1385                if ( tmp != null && !"".equals( tmp.trim() ) ) {
1386                    dim = Integer.parseInt( tmp );
1387                }
1388            }
1389            tmp = XMLTools.getStringValue( element );
1390            double[] vals = StringTools.toArrayDouble( tmp, "\t\n\r\f ," );
1391            if ( vals != null ) {
1392                if ( dim != 0 ) {
1393                    if ( vals.length != dim ) {
1394                        throw new InvalidGMLException(
1395                                                       "The dimension of a position must be equal to the number of coordinate values defined in the pos element." );
1396                    }
1397                } else {
1398                    dim = vals.length;
1399                }
1400            } else {
1401                throw new InvalidGMLException( "The given element {" + element.getNamespaceURI() + "}"
1402                                               + element.getLocalName()
1403                                               + " does not contain any coordinates, this may not be!" );
1404            }
1405    
1406            Position pos = null;
1407            if ( dim == 3 ) {
1408                // TODO again I guess no swapping for 3D
1409                pos = GeometryFactory.createPosition( vals[0], vals[1], vals[2] );
1410            } else {
1411                pos = GeometryFactory.createPosition( vals[swap ? 1 : 0], vals[swap ? 0 : 1] );
1412            }
1413    
1414            return pos;
1415        }
1416    
1417        /**
1418         *
1419         * @param element
1420         * @return Position
1421         * @throws InvalidGMLException
1422         * @throws XMLParsingException
1423         */
1424        private static Position[] createPositionFromPosList( Element element, String srsName )
1425                                throws InvalidGMLException, XMLParsingException {
1426    
1427            srsName = findSrsName( element, srsName );
1428            String srsDimension = XMLTools.getAttrValue( element, null, "srsDimension", null );
1429            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
1430                XMLFragment doc = new XMLFragment( element );
1431                LOG.logDebug( doc.getAsPrettyString() );
1432            }
1433    
1434            boolean swap = swap( srsName );
1435    
1436            int dim = 0;
1437            if ( srsDimension != null ) {
1438                dim = Integer.parseInt( srsDimension );
1439            }
1440    
1441            if ( dim == 0 ) {
1442                if ( srsName == null ) {
1443                    dim = 2;
1444                } else {
1445                    try {
1446                        dim = CRSFactory.create( srsName ).getCRS().getDimension();
1447                    } catch ( UnknownCRSException e ) {
1448                        LOG.logError( e.getMessage(), e );
1449                        dim = 2;
1450                    }
1451                }
1452            }
1453    
1454            String axisLabels = XMLTools.getAttrValue( element, null, "gml:axisAbbrev", null );
1455            String uomLabels = XMLTools.getAttrValue( element, null, "uomLabels", null );
1456    
1457            if ( srsName == null ) {
1458                if ( srsDimension != null ) {
1459                    String msg = "Attribute srsDimension cannot be defined unless attribute srsName has been defined.";
1460                    throw new InvalidGMLException( msg );
1461                }
1462                if ( axisLabels != null ) {
1463                    String msg = "Attribute axisLabels cannot be defined unless attribute srsName has been defined.";
1464                    throw new InvalidGMLException( msg );
1465                }
1466            }
1467            if ( axisLabels == null ) {
1468                if ( uomLabels != null ) {
1469                    String msg = "Attribute uomLabels cannot be defined unless attribute axisLabels has been defined.";
1470                    throw new InvalidGMLException( msg );
1471                }
1472            }
1473            String tmp = XMLTools.getStringValue( element );
1474            double[] values = StringTools.toArrayDouble( tmp, "\t\n\r\f ," );
1475            int size = values.length / dim;
1476            LOG.logDebug( "Number of points = ", size );
1477            LOG.logDebug( "Size of the original array: ", values.length );
1478            LOG.logDebug( "Dimension: ", dim );
1479    
1480            if ( values.length < 4 ) {
1481                if ( values.length == 2 ) {
1482                    values = new double[] { values[swap ? 1 : 0], values[swap ? 0 : 1], values[swap ? 1 : 0],
1483                                           values[swap ? 0 : 1] };
1484                    size = 2;
1485                } else {
1486                    for ( double d : values ) {
1487                        System.out.println( "value: " + d );
1488                    }
1489                    throw new InvalidGMLException( "A point list must have minimum 2 coordinate tuples. Here only '" + size
1490                                                   + "' are defined." );
1491                }
1492            }
1493            double positions[][] = new double[size][dim];
1494            int a = 0, b = 0;
1495            for ( int i = 0; i < values.length; i++ ) {
1496                if ( b == dim ) {
1497                    a++;
1498                    b = 0;
1499                }
1500                positions[a][b] = values[i];
1501                b++;
1502            }
1503    
1504            Position[] position = new Position[positions.length];
1505            for ( int i = 0; i < positions.length; i++ ) {
1506                double[] vals = positions[i];
1507                if ( dim == 3 ) {
1508                    // TODO no swapping for 3D I guess?
1509                    position[i] = GeometryFactory.createPosition( vals[0], vals[1], vals[2] );
1510                } else {
1511                    position[i] = GeometryFactory.createPosition( vals[swap ? 1 : 0], vals[swap ? 0 : 1] );
1512                }
1513            }
1514            return position;
1515        }
1516    
1517        /**
1518         * creates an array of <tt>Position</tt>s from the <coordinates> or <pos> Elements located as children under the
1519         * passed parent element.
1520         * <p>
1521         * example:<br>
1522         *
1523         * <pre>
1524         *
1525         * &lt;gml:Box&gt; &lt;gml:coordinates cs=&quot;,&quot; decimal=&quot;.&quot; ts=&quot; &quot;&gt;0,0
1526         * 4000,4000&lt;/gml:coordinates&gt; &lt;/gml:Box&gt;
1527         *
1528         * </pre>
1529         *
1530         * </p>
1531         *
1532         * @param parent
1533         * @param srsName
1534         * @return the positions of the given element.
1535         * @throws XMLParsingException
1536         * @throws InvalidGMLException
1537         */
1538        private static Position[] createPositions( Element parent, String srsName )
1539                                throws XMLParsingException, InvalidGMLException {
1540    
1541            srsName = findSrsName( parent, srsName );
1542    
1543            boolean swap = swap( srsName );
1544    
1545            List<Element> nl = XMLTools.getElements( parent, COORDINATES, nsContext );
1546            Position[] pos = null;
1547            if ( nl != null && nl.size() > 0 ) {
1548                pos = createPositionFromCoordinates( nl.get( 0 ), swap );
1549            } else {
1550                nl = XMLTools.getElements( parent, POS, nsContext );
1551                if ( nl != null && nl.size() > 0 ) {
1552                    pos = new Position[nl.size()];
1553                    for ( int i = 0; i < pos.length; i++ ) {
1554                        pos[i] = createPositionFromPos( nl.get( i ), swap );
1555                    }
1556                } else {
1557                    Element posList = (Element) XMLTools.getRequiredNode( parent, POSLIST, nsContext );
1558                    if ( posList != null ) {
1559                        pos = createPositionFromPosList( posList, srsName );
1560                    }
1561                }
1562            }
1563            return pos;
1564        }
1565    
1566        /**
1567         * Writes the GML representation of the given {@link Geometry} to the given {@link OutputStream}.
1568         * <p>
1569         * Currently, the {@link Geometry} realizations are handled:
1570         * <ul>
1571         * <li>SurfacePatch
1572         * <li>LineString
1573         * <li>Point
1574         * <li>Curve
1575         * <li>Surface
1576         * <li>MultiPoint
1577         * <li>MultiCurve
1578         * <li>MultiSurface
1579         * <li>MultiGeometry
1580         * </ul>
1581         *
1582         * @param geometry
1583         *            geometry to be exported
1584         * @param os
1585         *            target {@link OutputStream}
1586         * @return a {@link PrintWriter} created from the {@link OutputStream}
1587         * @throws GeometryException
1588         */
1589        public static PrintWriter export( Geometry geometry, OutputStream os )
1590                                throws GeometryException {
1591            return export( geometry, new PrintWriter( os ) );
1592        }
1593    
1594        /**
1595         * @param geometry
1596         * @param os
1597         * @param id
1598         * @return a {@link PrintWriter} created from the {@link OutputStream}
1599         * @throws GeometryException
1600         */
1601        public static PrintWriter export( Geometry geometry, OutputStream os, String id )
1602                                throws GeometryException {
1603            return export( geometry, new PrintWriter( os ), id );
1604        }
1605    
1606        /**
1607         * Writes the GML representation of the given {@link Geometry} to the given {@link PrintWriter}.
1608         * <p>
1609         * Currently, the {@link Geometry} realizations are handled:
1610         * <ul>
1611         * <li>SurfacePatch
1612         * <li>LineString
1613         * <li>Point
1614         * <li>Curve
1615         * <li>Surface
1616         * <li>MultiPoint
1617         * <li>MultiCurve
1618         * <li>MultiSurface
1619         * <li>MultiGeometry
1620         * </ul>
1621         *
1622         * @param geometry
1623         *            geometry to be exported
1624         * @param pw
1625         *            target {@link PrintWriter}
1626         * @return same as input {@link PrintWriter}
1627         * @throws GeometryException
1628         */
1629        public static PrintWriter export( Geometry geometry, PrintWriter pw )
1630                                throws GeometryException {
1631            return export( geometry, pw, null );
1632        }
1633    
1634        /**
1635         * @param geometry
1636         * @param pw
1637         * @param id
1638         * @return same as input {@link PrintWriter}
1639         * @throws GeometryException
1640         */
1641        public static PrintWriter export( Geometry geometry, PrintWriter pw, String id )
1642                                throws GeometryException {
1643            if ( geometry instanceof SurfacePatch ) {
1644                geometry = new SurfaceImpl( (SurfacePatch) geometry );
1645            } else if ( geometry instanceof LineString ) {
1646                geometry = new CurveImpl( (LineString) geometry );
1647            }
1648            if ( id == null ) {
1649                id = "";
1650            } else {
1651                id = "gml:id='" + id + "'";
1652            }
1653    
1654            if ( geometry instanceof Point ) {
1655                exportPoint( (Point) geometry, pw, id );
1656            } else if ( geometry instanceof Curve ) {
1657                exportCurve( (Curve) geometry, pw, id );
1658            } else if ( geometry instanceof Surface ) {
1659                exportSurface( (Surface) geometry, pw, id );
1660            } else if ( geometry instanceof MultiPoint ) {
1661                exportMultiPoint( (MultiPoint) geometry, pw, id );
1662            } else if ( geometry instanceof MultiCurve ) {
1663                exportMultiCurve( (MultiCurve) geometry, pw, id );
1664            } else if ( geometry instanceof MultiSurface ) {
1665                exportMultiSurface( (MultiSurface) geometry, pw, id );
1666            } else if ( geometry instanceof MultiGeometry ) {
1667                exportMultiGeometry( (MultiGeometry) geometry, pw, id );
1668            }
1669            pw.flush();
1670            return pw;
1671        }
1672    
1673        /**
1674         * Creates a GML representation from the passed <code>Geometry</code>.
1675         *
1676         * @param geometry
1677         * @return a string buffer containing the XML
1678         * @throws GeometryException
1679         */
1680        public static StringBuffer export( Geometry geometry )
1681                                throws GeometryException {
1682            return export( geometry, (String) null );
1683        }
1684    
1685        /**
1686         * @param geometry
1687         * @param id
1688         * @return a string buffer containing the XML
1689         * @throws GeometryException
1690         */
1691        public static StringBuffer export( Geometry geometry, String id )
1692                                throws GeometryException {
1693            ByteArrayOutputStream bos = new ByteArrayOutputStream( 5000 );
1694            export( geometry, bos, id );
1695            return new StringBuffer( new String( bos.toByteArray() ) );
1696        }
1697    
1698        /**
1699         * creates a GML representation from the passed <tt>Envelope</tt>. This method is required because in ISO 19107
1700         * Envelops are no geometries.
1701         *
1702         * @param envelope
1703         * @return the stringbuffer filled with the envelope.
1704         */
1705        public static StringBuffer exportAsBox( Envelope envelope ) {
1706    
1707            StringBuffer sb = new StringBuffer( "<gml:Box xmlns:gml='http://www.opengis.net/gml'" );
1708    
1709            if ( envelope.getCoordinateSystem() != null ) {
1710                sb.append( " srsName='" ).append( envelope.getCoordinateSystem().getIdentifier() ).append( "'" );
1711            }
1712            sb.append( ">" );
1713    
1714            boolean swap = swap( envelope );
1715    
1716            sb.append( "<gml:coordinates cs=\",\" decimal=\".\" ts=\" \">" );
1717            sb.append( swap ? envelope.getMin().getY() : envelope.getMin().getX() ).append( ',' );
1718            sb.append( swap ? envelope.getMin().getX() : envelope.getMin().getY() );
1719            int dim = envelope.getMax().getCoordinateDimension();
1720            if ( dim == 3 ) {
1721                sb.append( ',' ).append( envelope.getMin().getZ() );
1722            }
1723            sb.append( ' ' ).append( swap ? envelope.getMax().getY() : envelope.getMax().getX() );
1724            sb.append( ',' ).append( swap ? envelope.getMax().getX() : envelope.getMax().getY() );
1725            if ( dim == 3 ) {
1726                sb.append( ',' ).append( envelope.getMax().getZ() );
1727            }
1728            sb.append( "</gml:coordinates></gml:Box>" );
1729    
1730            return sb;
1731        }
1732    
1733        /**
1734         * creates a GML representation from the passed <tt>Envelope</tt>. This method is required because in ISO 19107
1735         * Envelops are no geometries.
1736         *
1737         * @param envelope
1738         * @return the String representation of the given envelope
1739         */
1740        public static StringBuffer exportAsEnvelope( Envelope envelope ) {
1741    
1742            StringBuffer sb = new StringBuffer( "<gml:Envelope " );
1743            if ( envelope.getCoordinateSystem() != null ) {
1744                sb.append( "srsName='" ).append( envelope.getCoordinateSystem().getIdentifier() ).append( "' " );
1745            }
1746    
1747            boolean swap = swap( envelope );
1748    
1749            sb.append( "xmlns:gml='http://www.opengis.net/gml'>" );
1750            sb.append( "<gml:coordinates cs=\",\" decimal=\".\" ts=\" \">" );
1751            sb.append( swap ? envelope.getMin().getY() : envelope.getMin().getX() ).append( ',' );
1752            sb.append( swap ? envelope.getMin().getX() : envelope.getMin().getY() );
1753            int dim = envelope.getMax().getCoordinateDimension();
1754            if ( dim == 3 ) {
1755                sb.append( ',' ).append( envelope.getMin().getZ() );
1756            }
1757            sb.append( ' ' ).append( swap ? envelope.getMax().getY() : envelope.getMax().getX() );
1758            sb.append( ',' ).append( swap ? envelope.getMax().getX() : envelope.getMax().getY() );
1759            if ( dim == 3 ) {
1760                sb.append( ',' ).append( envelope.getMax().getZ() );
1761            }
1762            sb.append( "</gml:coordinates></gml:Envelope>" );
1763    
1764            return sb;
1765        }
1766    
1767        /**
1768         * creates a GML expression of a point geometry
1769         *
1770         * @param point
1771         *            point geometry
1772         *
1773         */
1774        private static void exportPoint( Point point, PrintWriter pw, String id ) {
1775    
1776            String crs = null;
1777            if ( point.getCoordinateSystem() != null ) {
1778                crs = point.getCoordinateSystem().getIdentifier();
1779            }
1780    
1781            boolean swap = swap( point );
1782    
1783            String srs = null;
1784            if ( crs != null ) {
1785                srs = "<gml:Point srsName=\"" + crs + "\" " + id + ">";
1786            } else {
1787                srs = "<gml:Point " + id + ">";
1788            }
1789            pw.println( srs );
1790            pw.print( appendPos( point.getPosition(), point.getCoordinateSystem(), swap ) );
1791            pw.print( "</gml:Point>" );
1792    
1793        }
1794    
1795        /**
1796         * creates a GML expression of a curve geometry
1797         *
1798         * @param o
1799         *            curve geometry
1800         *
1801         *
1802         * @throws GeometryException
1803         */
1804        private static void exportCurve( Curve o, PrintWriter pw, String id )
1805                                throws GeometryException {
1806    
1807            String crs = null;
1808            if ( o.getCoordinateSystem() != null ) {
1809                crs = o.getCoordinateSystem().getIdentifier();
1810            }
1811    
1812            boolean swap = swap( o );
1813    
1814            String srs = null;
1815            if ( crs != null ) {
1816                srs = "<gml:Curve srsName=\"" + crs + "\" " + id + ">";
1817            } else {
1818                srs = "<gml:Curve " + id + ">";
1819            }
1820            pw.println( srs );
1821            pw.println( "<gml:segments>" );
1822    
1823            int curveSegments = o.getNumberOfCurveSegments();
1824            for ( int i = 0; i < curveSegments; i++ ) {
1825                pw.print( "<gml:LineStringSegment>" );
1826                CurveSegment segment = o.getCurveSegmentAt( i );
1827                Position[] p = segment.getAsLineString().getPositions();
1828                pw.print( appendPosList( p, o.getCoordinateDimension(), swap ) );
1829                pw.println( "</gml:LineStringSegment>" );
1830            }
1831            pw.println( "</gml:segments>" );
1832            pw.print( "</gml:Curve>" );
1833    
1834        }
1835    
1836        private static StringBuilder appendPosList( Position[] p, int coordinateDimension, boolean swap ) {
1837            StringBuilder sb = new StringBuilder();
1838            if ( p != null && p.length > 0 ) {
1839                sb.append( "<gml:posList" );
1840                if ( coordinateDimension > 0 ) {
1841                    sb.append( " srsDimension='" ).append( coordinateDimension );
1842                    sb.append( "' count='" ).append( p.length ).append( "'" );
1843                }
1844                sb.append( ">" );
1845                for ( int i = 0; i < p.length; ++i ) {
1846                    sb.append( swap ? ( p[i].getY() + " " + p[i].getX() ) : ( p[i].getX() + " " + p[i].getY() ) );
1847                    if ( coordinateDimension == 3 ) {
1848                        sb.append( " " ).append( p[i].getZ() );
1849                    }
1850                    if ( i < p.length - 1 ) {
1851                        sb.append( " " );
1852                    }
1853                }
1854                sb.append( "</gml:posList>" );
1855            }
1856            return sb;
1857        }
1858    
1859        private static StringBuilder appendPos( Position pos, CoordinateSystem crs, boolean swap ) {
1860            StringBuilder sb = new StringBuilder();
1861            if ( pos != null ) {
1862                sb.append( "<gml:pos" );
1863                int dimension = pos.getCoordinateDimension();
1864                if ( dimension > 0 ) {
1865                    if ( crs != null ) {
1866                        int tmp = crs.getDimension();
1867                        if ( dimension != tmp ) {
1868                            sb.append( " srsDimension='" ).append( dimension ).append( "'" );
1869                            sb.append( " srsName='" ).append( crs.getIdentifier() ).append( "'" );
1870                        }
1871                    }
1872                }
1873                sb.append( ">" );
1874                sb.append( swap ? ( pos.getY() + " " + pos.getX() ) : ( pos.getX() + " " + pos.getY() ) );
1875                if ( dimension == 3 ) {
1876                    sb.append( " " ).append( pos.getZ() );
1877                }
1878                sb.append( "</gml:pos>" );
1879            }
1880            return sb;
1881        }
1882    
1883        /**
1884         * @throws GeometryException
1885         */
1886        private static void exportSurface( Surface surface, PrintWriter pw, String id )
1887                                throws GeometryException {
1888    
1889            String crs = null;
1890            if ( surface.getCoordinateSystem() != null ) {
1891                crs = surface.getCoordinateSystem().getIdentifier().replace( ' ', ':' );
1892            }
1893    
1894            boolean swap = swap( surface );
1895    
1896            String srs = null;
1897            if ( crs != null ) {
1898                srs = "<gml:Surface srsName='" + crs + "\' " + id + ">";
1899            } else {
1900                srs = "<gml:Surface " + id + ">";
1901            }
1902            pw.println( srs );
1903            int patches = surface.getNumberOfSurfacePatches();
1904            pw.println( "<gml:patches>" );
1905            for ( int i = 0; i < patches; i++ ) {
1906                pw.println( "<gml:PolygonPatch>" );
1907                SurfacePatch patch = surface.getSurfacePatchAt( i );
1908                printExteriorRing( surface, pw, patch, swap );
1909                printInteriorRing( surface, pw, patch, swap );
1910                pw.println( "</gml:PolygonPatch>" );
1911            }
1912            pw.println( "</gml:patches>" );
1913            pw.print( "</gml:Surface>" );
1914    
1915        }
1916    
1917        /**
1918         * @param surface
1919         * @param pw
1920         * @param patch
1921         */
1922        private static void printInteriorRing( Surface surface, PrintWriter pw, SurfacePatch patch, boolean swap ) {
1923            // interior rings
1924            Position[][] ip = patch.getInteriorRings();
1925            if ( ip != null ) {
1926                for ( int j = 0; j < ip.length; j++ ) {
1927                    pw.println( "<gml:interior>" );
1928                    pw.println( "<gml:LinearRing>" );
1929                    if ( surface.getCoordinateSystem() != null ) {
1930                        pw.print( appendPosList( ip[j], surface.getCoordinateDimension(), swap ) );
1931                    } else {
1932                        pw.print( appendPosList( ip[j], 0, swap ) );
1933                    }
1934                    pw.println( "</gml:LinearRing>" );
1935                    pw.println( "</gml:interior>" );
1936                }
1937            }
1938        }
1939    
1940        /**
1941         * @param surface
1942         * @param pw
1943         * @param patch
1944         */
1945        private static void printExteriorRing( Surface surface, PrintWriter pw, SurfacePatch patch, boolean swap ) {
1946            // exterior ring
1947            pw.print( "<gml:exterior>" );
1948            pw.print( "<gml:LinearRing>" );
1949            if ( surface.getCoordinateSystem() != null ) {
1950                pw.print( appendPosList( patch.getExteriorRing(), surface.getCoordinateDimension(), swap ) );
1951            } else {
1952                pw.print( appendPosList( patch.getExteriorRing(), 0, swap ) );
1953            }
1954            pw.print( "</gml:LinearRing>" );
1955            pw.print( "</gml:exterior>" );
1956        }
1957    
1958        /**
1959         * @param mp
1960         */
1961        private static void exportMultiPoint( MultiPoint mp, PrintWriter pw, String id ) {
1962    
1963            String crs = null;
1964            if ( mp.getCoordinateSystem() != null ) {
1965                crs = mp.getCoordinateSystem().getIdentifier();
1966            }
1967    
1968            boolean swap = swap( mp );
1969    
1970            String srs = null;
1971            if ( crs != null ) {
1972                srs = "<gml:MultiPoint srsName=\"" + crs + "\" " + id + ">";
1973            } else {
1974                srs = "<gml:MultiPoint " + id + ">";
1975            }
1976            pw.println( srs );
1977            pw.println( "<gml:pointMembers>" );
1978            for ( int i = 0; i < mp.getSize(); i++ ) {
1979                pw.println( "<gml:Point>" );
1980                pw.print( appendPos( mp.getPointAt( i ).getPosition(), mp.getCoordinateSystem(), swap ) );
1981                pw.println( "</gml:Point>" );
1982            }
1983            pw.println( "</gml:pointMembers>" );
1984            pw.print( "</gml:MultiPoint>" );
1985    
1986        }
1987    
1988        /**
1989         * @param multiCurve
1990         * @throws GeometryException
1991         */
1992        private static void exportMultiCurve( MultiCurve multiCurve, PrintWriter pw, String id )
1993                                throws GeometryException {
1994    
1995            String crs = null;
1996            if ( multiCurve.getCoordinateSystem() != null ) {
1997                crs = multiCurve.getCoordinateSystem().getIdentifier().replace( ' ', ':' );
1998            }
1999    
2000            boolean swap = swap( multiCurve );
2001    
2002            String srs = null;
2003            if ( crs != null ) {
2004                srs = "<gml:MultiCurve srsName=\"" + crs + "\" " + id + ">";
2005            } else {
2006                srs = "<gml:MultiCurve " + id + ">";
2007            }
2008            pw.println( srs );
2009    
2010            Curve[] curves = multiCurve.getAllCurves();
2011            pw.println( "<gml:curveMembers>" );
2012            for ( int i = 0; i < curves.length; i++ ) {
2013                Curve curve = curves[i];
2014                pw.println( "<gml:Curve>" );
2015                pw.println( "<gml:segments>" );
2016                int numberCurveSegments = curve.getNumberOfCurveSegments();
2017                for ( int j = 0; j < numberCurveSegments; j++ ) {
2018                    pw.println( "<gml:LineStringSegment>" );
2019                    CurveSegment curveSegment = curve.getCurveSegmentAt( j );
2020                    Position[] p = curveSegment.getAsLineString().getPositions();
2021                    pw.print( appendPosList( p, curve.getCoordinateDimension(), swap ) );
2022                    pw.println( "</gml:LineStringSegment>" );
2023                }
2024                pw.println( "</gml:segments>" );
2025                pw.println( "</gml:Curve>" );
2026            }
2027            pw.println( "</gml:curveMembers>" );
2028            pw.print( "</gml:MultiCurve>" );
2029        }
2030    
2031        /**
2032         * @param multiSurface
2033         * @throws GeometryException
2034         */
2035        private static void exportMultiSurface( MultiSurface multiSurface, PrintWriter pw, String id )
2036                                throws GeometryException {
2037    
2038            String crs = null;
2039            if ( multiSurface.getCoordinateSystem() != null ) {
2040                crs = multiSurface.getCoordinateSystem().getIdentifier().replace( ' ', ':' );
2041            }
2042            String srs = null;
2043            if ( crs != null ) {
2044                srs = "<gml:MultiSurface srsName=\"" + crs + "\" " + id + ">";
2045            } else {
2046                srs = "<gml:MultiSurface " + id + ">";
2047            }
2048            pw.println( srs );
2049    
2050            Surface[] surfaces = multiSurface.getAllSurfaces();
2051    
2052            pw.println( "<gml:surfaceMembers>" );
2053            for ( int i = 0; i < surfaces.length; i++ ) {
2054                Surface surface = surfaces[i];
2055                exportSurface( surface, pw, "" );
2056            }
2057            pw.println( "</gml:surfaceMembers>" );
2058            // substitution as requested in issue
2059            // http://wald.intevation.org/tracker/index.php?func=detail&aid=477&group_id=27&atid=212
2060            // can be removed if it was inserted correctly
2061            // pw.println( "<gml:surfaceMembers>" );
2062            // for ( int i = 0; i < surfaces.length; i++ ) {
2063            // Surface surface = surfaces[i];
2064            // pw.println( "<gml:Surface>" );
2065            // pw.println( "<gml:patches>" );
2066            // pw.println( "<gml:Polygon>" );
2067            // int numberSurfaces = surface.getNumberOfSurfacePatches();
2068            // for ( int j = 0; j < numberSurfaces; j++ ) {
2069            // SurfacePatch surfacePatch = surface.getSurfacePatchAt( j );
2070            // printExteriorRing( surface, pw, surfacePatch );
2071            // printInteriorRing( surface, pw, surfacePatch );
2072            // }
2073            // pw.println( "</gml:Polygon>" );
2074            // pw.println( "</gml:patches>" );
2075            // pw.println( "</gml:Surface>" );
2076            // }
2077            // pw.println( "</gml:surfaceMembers>" );
2078            pw.print( "</gml:MultiSurface>" );
2079    
2080        }
2081    
2082        /**
2083         * Exports the given {@link MultiGeometry} as a <code>gml:MultiGeometry</code> element.
2084         *
2085         * @param multiGeometry
2086         * @param pw
2087         * @throws GeometryException
2088         */
2089        public static void exportMultiGeometry( MultiGeometry multiGeometry, PrintWriter pw )
2090                                throws GeometryException {
2091            exportMultiGeometry( multiGeometry, pw, null );
2092        }
2093    
2094        /**
2095         * Exports the given {@link MultiGeometry} as a <code>gml:MultiGeometry</code> element.
2096         *
2097         * @param multiGeometry
2098         * @param pw
2099         * @param id
2100         * @throws GeometryException
2101         */
2102        public static void exportMultiGeometry( MultiGeometry multiGeometry, PrintWriter pw, String id )
2103                                throws GeometryException {
2104    
2105            String crs = null;
2106            if ( multiGeometry.getCoordinateSystem() != null ) {
2107                crs = multiGeometry.getCoordinateSystem().getIdentifier().replace( ' ', ':' );
2108            }
2109    
2110            // opening tag
2111            if ( crs != null ) {
2112                pw.print( "<gml:MultiGeometry srsName=\"" + crs + "\" " + id + ">" );
2113            } else {
2114                pw.print( "<gml:MultiGeometry " + id + ">" );
2115            }
2116    
2117            Geometry[] memberGeometries = multiGeometry.getAll();
2118            if ( memberGeometries.length > 0 ) {
2119                pw.print( "<gml:geometryMembers>" );
2120                for ( Geometry geometry : memberGeometries ) {
2121                    export( geometry, pw );
2122                }
2123                pw.print( "</gml:geometryMembers>" );
2124            }
2125            pw.print( "</gml:MultiGeometry>" );
2126    
2127        }
2128    
2129        /**
2130         * Converts the string representation of a GML geometry object to a corresponding <code>Geometry</code>. Notice that
2131         * GML Boxes will be converted to Surfaces because in ISO 19107 Envelopes are no geometries.
2132         *
2133         * @param gml
2134         * @return corresponding geometry object
2135         * @throws GeometryException
2136         * @throws XMLParsingException
2137         * @deprecated this method cannot provide default SRS information, please use {@link #wrap(String,String)} instead
2138         */
2139        @Deprecated
2140        public static Geometry wrap( String gml )
2141                                throws GeometryException, XMLParsingException {
2142            return wrap( gml, null );
2143        }
2144    
2145        /**
2146         * Converts a GML geometry object to a corresponding <tt>Geometry</tt>. Notice that GML Boxes will be converted to
2147         * Surfaces because in ISO 19107 Envelops are no geometries.
2148         * <p>
2149         * Currently, the following conversions are supported:
2150         * <ul>
2151         * <li>GML Point -> Point
2152         * <li>GML MultiPoint -> MultiPoint
2153         * <li>GML LineString -> Curve
2154         * <li>GML MultiLineString -> MultiCurve
2155         * <li>GML Polygon -> Surface
2156         * <li>GML MultiPolygon -> MultiSurface
2157         * <li>GML Box -> Surface
2158         * <li>GML Curve -> Curve
2159         * <li>GML Surface -> Surface
2160         * <li>GML MultiCurve -> MultiCurve
2161         * <li>GML MultiSurface -> MultiSurface
2162         * </ul>
2163         * <p>
2164         *
2165         * @param gml
2166         * @return the corresponding <tt>Geometry</tt>
2167         * @throws GeometryException
2168         *             if type unsupported or conversion failed
2169         * @deprecated this method cannot provide default SRS information, please use {@link #wrap(Element,String)} instead
2170         */
2171        @Deprecated
2172        public static Geometry wrap( Element gml )
2173                                throws GeometryException {
2174            return wrap( gml, null );
2175        }
2176    
2177        /**
2178         * returns a Envelope created from Box element
2179         *
2180         * @param element
2181         *            <boundedBy>
2182         *
2183         * @return instance of <tt>Envelope</tt>
2184         *
2185         * @throws XMLParsingException
2186         * @throws InvalidGMLException
2187         * @throws UnknownCRSException
2188         * @deprecated this method cannot provide default SRS information, please use {@link #wrapBox(Element,String)}
2189         *             instead
2190         */
2191        @Deprecated
2192        public static Envelope wrapBox( Element element )
2193                                throws XMLParsingException, InvalidGMLException, UnknownCRSException {
2194            return wrapBox( element, null );
2195        }
2196    
2197        // helpers that determine whether to swap x/y coordinates
2198        private static boolean swap( Geometry geom ) {
2199            return ( geom.getCoordinateSystem() == null || geom.getCoordinateSystem().equals( EPSG4326 ) )
2200                   && getSwitchAxes();
2201        }
2202    
2203        /**
2204         * @param env
2205         * @return true, if configuration and environment say yes to swapping
2206         */
2207        public static boolean swap( Envelope env ) {
2208            return ( env.getCoordinateSystem() == null || env.getCoordinateSystem().equals( EPSG4326 ) ) && getSwitchAxes();
2209        }
2210    
2211        private static boolean swap( String srsName ) {
2212            // TODO use real crs instance just to verify the name? Too expensive?
2213            return ( srsName == null || srsName.contains( "4326" ) ) && getSwitchAxes();
2214        }
2215    
2216    }