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