001    //$HeadURL: $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014     This library is distributed in the hope that it will be useful,
015     but WITHOUT ANY WARRANTY; without even the implied warranty of
016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     Lesser General Public License for more details.
018     You should have received a copy of the GNU Lesser General Public
019     License along with this library; if not, write to the Free Software
020     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021     Contact:
022    
023     Andreas Poth
024     lat/lon GmbH
025     Aennchenstr. 19
026     53177 Bonn
027     Germany
028     E-Mail: poth@lat-lon.de
029    
030     Prof. Dr. Klaus Greve
031     Department of Geography
032     University of Bonn
033     Meckenheimer Allee 166
034     53115 Bonn
035     Germany
036     E-Mail: greve@giub.uni-bonn.de
037     ---------------------------------------------------------------------------*/
038    
039    package org.deegree.tools.app3d;
040    
041    import static org.deegree.ogcbase.CommonNamespaces.CITYGMLNS;
042    import static org.deegree.ogcbase.CommonNamespaces.CITYGML_PREFIX;
043    import static org.deegree.ogcbase.CommonNamespaces.GMLNS;
044    import static org.deegree.ogcbase.CommonNamespaces.GML_PREFIX;
045    
046    import java.awt.image.BufferedImage;
047    import java.io.BufferedReader;
048    import java.io.BufferedWriter;
049    import java.io.File;
050    import java.io.FileNotFoundException;
051    import java.io.FileWriter;
052    import java.io.IOException;
053    import java.io.InputStreamReader;
054    import java.util.Calendar;
055    import java.util.Enumeration;
056    import java.util.GregorianCalendar;
057    import java.util.HashMap;
058    import java.util.Map;
059    
060    import javax.imageio.ImageIO;
061    import javax.media.j3d.Appearance;
062    import javax.media.j3d.Background;
063    import javax.media.j3d.BoundingBox;
064    import javax.media.j3d.Bounds;
065    import javax.media.j3d.BranchGroup;
066    import javax.media.j3d.Geometry;
067    import javax.media.j3d.GeometryArray;
068    import javax.media.j3d.Group;
069    import javax.media.j3d.ImageComponent;
070    import javax.media.j3d.ImageComponent2D;
071    import javax.media.j3d.Leaf;
072    import javax.media.j3d.Material;
073    import javax.media.j3d.Node;
074    import javax.media.j3d.Shape3D;
075    import javax.media.j3d.Texture;
076    import javax.media.j3d.TriangleArray;
077    import javax.vecmath.Color3f;
078    import javax.vecmath.Point3d;
079    import javax.vecmath.TexCoord2f;
080    
081    import org.deegree.framework.log.ILogger;
082    import org.deegree.framework.log.LoggerFactory;
083    import org.deegree.framework.xml.XMLFragment;
084    import org.deegree.framework.xml.XMLTools;
085    import org.deegree.ogcbase.CommonNamespaces;
086    import org.jdesktop.j3d.loaders.vrml97.VrmlLoader;
087    import org.w3c.dom.Document;
088    import org.w3c.dom.Element;
089    
090    import com.sun.j3d.loaders.IncorrectFormatException;
091    import com.sun.j3d.loaders.ParsingErrorException;
092    import com.sun.j3d.loaders.Scene;
093    import com.sun.j3d.utils.geometry.GeometryInfo;
094    
095    /**
096     * The <code>J3DToCityGMLExporter</code> exports a J3D scene to citygml level 1.
097     * 
098     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
099     * 
100     * @author last edited by: $Author:$
101     * 
102     * @version $Revision:$, $Date:$
103     * 
104     */
105    
106    public class J3DToCityGMLExporter implements J3DExporter {
107        private static ILogger LOG = LoggerFactory.getLogger( J3DToCityGMLExporter.class );
108    
109        private final static String PRE_C = CITYGML_PREFIX + ":";
110    
111        private final static String PRE_G = GML_PREFIX + ":";
112    
113        private static int textureCount = 0;
114    
115        // Instance variables.
116        private String name = null;
117    
118        private String crsName = "EPSG:31466";
119    
120        private String cityGMLFunction = "1001";
121    
122        private String textureOutputDir;
123    
124        private double translationX;
125    
126        private double translationY;
127    
128        private boolean asWFSTransaction = false;
129    
130        // Will hold the texture file names if two different geometries use a single texture.
131        //private Map<String, String> savedTextures = new HashMap<String, String>( 200 );
132    
133        private double translationZ;
134        
135        private static int numberOfSurfaces = 0;
136    
137        /**
138         * @param citygmlName
139         *            of the exported branchgroup.
140         * @param crsName
141         *            of the crs to set the srsName of gmlNodes to, if <code>null</code> 'epsg:31466' will be used.
142         * @param cityGMLFunction
143         *            to insert om the gml
144         * @param textureOutputDirectory
145         *            the directory to output the textures to, if any.
146         * @param translationX
147         *            a value to add up to the found x - coordinates. If NaN, 0 will be used.
148         * @param translationY
149         *            a value to add up to the found y - coordinates. If NaN, 0 will be used.
150         * @param translationZ
151         *            a value to add up to the found z - coordinates. If NaN, 0 will be used.
152         * @param asWFSTransaction
153         *            true if the scene should be exported as a wfs:Transaction document (the root node is wfs:Transaction)
154         *            or a cityGML document (the root node is citygml:Building).
155         * @throws IOException
156         *             if the something went wrong with wile creating, referring or adressing the given filePath.
157         */
158        public J3DToCityGMLExporter( String citygmlName, String crsName, String cityGMLFunction,
159                                     String textureOutputDirectory, double translationX, double translationY,
160                                     double translationZ, boolean asWFSTransaction ) throws IOException {
161            if ( citygmlName != null && !"".equals( citygmlName.trim() ) ) {
162                this.name = citygmlName;
163            }
164    
165            if ( crsName != null && !"".equals( crsName.trim() ) ) {
166                this.crsName = crsName;
167            }
168    
169            if ( cityGMLFunction != null && !"".equals( cityGMLFunction.trim() ) ) {
170                this.cityGMLFunction = cityGMLFunction;
171            }
172    
173            if ( textureOutputDirectory != null && !"".equals( textureOutputDirectory.trim() ) ) {
174                File tmp = new File( textureOutputDirectory );
175                if ( !tmp.exists() ) {
176                    System.out.print( "The directory: " + textureOutputDirectory + " does not exist, create?: " );
177                    BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );
178                    String answer = in.readLine();
179                    if ( answer != null && ( "yes".equalsIgnoreCase( answer ) || "y".equalsIgnoreCase( answer ) ) ) {
180                        if ( !tmp.mkdir() ) {
181                            throw new IOException( "Could not create given directory: " + tmp.getAbsoluteFile()
182                                                   + " please create it first!" );
183                        }
184                    } else {
185                        throw new IOException( "Given directory: " + tmp.getAbsoluteFile()
186                                               + " does not exist, please create it first!" );
187                    }
188                }
189                if ( !tmp.isDirectory() ) {
190                    throw new IOException( "Given file: " + tmp.getAbsoluteFile()
191                                           + " exists, but is not a directory, please specify a valid directory." );
192                }
193                if ( !tmp.canWrite() ) {
194                    throw new IOException( "Can not write to specified directory: " + tmp.getAbsoluteFile()
195                                           + ", please specify directory which can be written to." );
196                }
197                textureOutputDir = textureOutputDirectory;
198                if ( !textureOutputDirectory.endsWith( File.separator ) ) {
199                    // just make sure the given path ends with the separator.
200                    textureOutputDir += File.separator;
201                }
202    
203            } else {
204                throw new IOException( "No directory was given for the output textures." );
205            }
206    
207            if ( Double.isNaN( translationX ) ) {
208                translationX = 0;
209            }
210            if ( Double.isNaN( translationY ) ) {
211                translationY = 0;
212            }
213            if ( Double.isNaN( translationZ ) ) {
214                translationZ = 0;
215            }
216            this.translationX = translationX;
217            this.translationY = translationY;
218            this.translationZ = translationZ;
219    
220            this.asWFSTransaction = asWFSTransaction;
221        }
222    
223        public void export( StringBuilder result, Group j3dScene ) {
224            if ( j3dScene != null ) {
225                if ( j3dScene.getCapability( Group.ALLOW_CHILDREN_READ ) ) {
226                    Document doc = XMLTools.create();
227                    Element root = null;
228                    Element building = doc.createElementNS( CommonNamespaces.CITYGMLNS.toASCIIString(), PRE_C + "Building" );
229                    if ( asWFSTransaction ) {
230                        root = doc.createElementNS( CommonNamespaces.WFSNS.toASCIIString(),
231                                                    CommonNamespaces.WFS_PREFIX + ":Transaction" );
232                        root.setAttribute( "version", "1.1.0" );
233                        root.setAttribute( "service", "WFS" );
234                        Element insert = XMLTools.appendElement( root,
235                                                         CommonNamespaces.WFSNS,
236                                                         CommonNamespaces.WFS_PREFIX + ":Insert" );
237                        //Element featureCollection = XMLTools.appendElement( insert, CommonNamespaces.WFSNS, CommonNamespaces.WFS_PREFIX + ":FeatureCollection" );
238                        
239                        //building = (Element)featureCollection.appendChild( building );
240                        building = (Element)insert.appendChild( building );
241    
242                    } else {
243                        root = doc.createElementNS( CITYGMLNS.toASCIIString(), PRE_C + "CityModel" );
244                        Element member = XMLTools.appendElement( root, CITYGMLNS, PRE_C + "cityObjectMember" );
245                        member.appendChild( building );
246                    }
247    
248                    // set the name element
249                    if ( name == null ) {
250                        name = j3dScene.getName();
251                    }
252                    XMLTools.appendElement( building, CommonNamespaces.GMLNS, PRE_G + "name", name );
253                    boolean calcBounds = false;
254                    Point3d lower = new Point3d( Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE );
255                    Point3d upper = new Point3d( Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE );
256                    if ( j3dScene.getCapability( Node.ALLOW_BOUNDS_READ ) ) {
257                        Bounds b = j3dScene.getBounds();
258                        if ( b != null ) {
259                            BoundingBox bBox = new BoundingBox( b );
260                            bBox.getLower( lower );
261                            bBox.getUpper( upper );
262                            lower.x += translationX;
263                            lower.y += translationY;
264                            upper.x += translationX;
265                            upper.y += translationY;
266                            Element boundBy = XMLTools.appendElement( building, GMLNS, PRE_G + "boundedBy" );
267                            Element env = XMLTools.appendElement( boundBy, GMLNS, PRE_G + "Envelope" );
268                            env.setAttribute( "srsName", crsName );
269                            Element lowerPos = XMLTools.appendElement( env, GMLNS, PRE_G + "pos", lower.x + " "
270                                                                                                  + lower.y
271                                                                                                  + " "
272                                                                                                  + lower.z );
273                            lowerPos.setAttribute( "srsDimension", "" + 3 );
274                            Element upperPos = XMLTools.appendElement( env, GMLNS, PRE_G + "pos", upper.x + " "
275                                                                                                  + upper.y
276                                                                                                  + " "
277                                                                                                  + upper.z );
278                            upperPos.setAttribute( "srsDimension", "" + 3 );
279                        }
280                    } else {
281                        LOG.logInfo( "The gml bounded by may not be read, it will be calculated from Hand." );
282                        calcBounds = true;
283                    }
284                    // citygml creation date
285                    GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance();
286                    String timeStamp = cal.get( Calendar.YEAR ) + "-"
287                                       + ( cal.get( Calendar.MONTH ) + 1 )
288                                       + "-"
289                                       + cal.get( Calendar.DAY_OF_MONTH )
290                                       + "T"
291                                       + cal.get( Calendar.HOUR_OF_DAY )
292                                       + ":"
293                                       + cal.get( Calendar.MINUTE );
294                    XMLTools.appendElement( building, CITYGMLNS, PRE_C + "creationDate", timeStamp );
295                    XMLTools.appendElement( building, CITYGMLNS, PRE_C + "function", this.cityGMLFunction );
296                    Element multiSurfaceLod2 = XMLTools.appendElement( building, CITYGMLNS, PRE_C + "lod2MultiSurface" );
297                    Element multiSurfaceParent = XMLTools.appendElement( multiSurfaceLod2,
298                                                                         GMLNS,
299                                                                         PRE_G + "MultiSurface" );
300                    multiSurfaceParent.setAttribute( "srsName", crsName );
301                    outputGroup( j3dScene, multiSurfaceParent, calcBounds, lower, upper );
302                    root = (Element) doc.importNode( root, true );
303                    XMLFragment frag = new XMLFragment( root );
304                    result.append( frag.getAsPrettyString() );
305                    LOG.logDebug( "\n\nExported " + numberOfSurfaces + " surfaces." );
306                } else {
307                    LOG.logInfo( "The given branchgroup may not read it's children, nothing to export." );
308                }
309            } else {
310                LOG.logInfo( "The given branchgroup may not be null, nothing to export." );
311            }
312    
313        }
314    
315        /**
316         * Iterates over all children of the given branchgroup and appends their citygml representation to the rootNode.
317         * 
318         * @param j3dScene
319         *            to export
320         * @param rootNode
321         *            to append to.
322         * @param calcBounds
323         *            if true the bounds should be calculated and placed in lower and upper.
324         * @param upper
325         *            bound
326         * @param lower
327         *            bound
328         */
329        private void outputGroup( Group j3dScene, Element rootNode, boolean calcBounds, Point3d lower, Point3d upper ) {
330            Enumeration en = j3dScene.getAllChildren();
331            LOG.logDebug( "Outputting a group." );
332            while ( en.hasMoreElements() ) {
333                Node n = (Node) en.nextElement();
334                if ( n != null ) {
335                    if ( n instanceof Group ) {
336                        outputGroup( (Group) n, rootNode, calcBounds, lower, upper );
337                    } else if ( n instanceof Leaf ) {
338                        outputLeaf( (Leaf) n, rootNode, calcBounds, lower, upper );
339                    }
340                }
341            }
342    
343        }
344    
345        /**
346         * Checks if the given leaf is a shape3D or background leaf, and if so, their cityGML representation will be
347         * appended to the parent node.
348         * 
349         * @param l
350         *            a j3d leaf.
351         * @param parent
352         *            to append to.
353         * @param calcBounds
354         *            if true the bounds should be calculated and placed in lower and upper.
355         * @param upper
356         *            bound
357         * @param lower
358         *            bound
359         */
360        private void outputLeaf( Leaf l, Element parent, boolean calcBounds, Point3d lower, Point3d upper ) {
361            if ( l != null ) {
362                if ( l instanceof Shape3D ) {
363                    outputShape3D( (Shape3D) l, parent, calcBounds, lower, upper );
364                } else if ( l instanceof Background ) {
365                    outputBackground( (Background) l, parent );
366                } else {
367                    LOG.logInfo( "Don't know howto output object of instance: " + l.getClass().getName() );
368                }
369            }
370        }
371    
372        /**
373         * Appends the citygml representation to the given parent node.
374         * 
375         * @param shape
376         *            (of the j3dScene) to append
377         * @param parent
378         *            to append to.
379         * @param calcBounds
380         *            if true the bounds should be calculated and placed in lower and upper.
381         * @param upper
382         *            bound
383         * @param lower
384         *            bound
385         */
386        private void outputShape3D( Shape3D shape, Element parent, boolean calcBounds, Point3d lower, Point3d upper ) {
387            Enumeration geoms = shape.getAllGeometries();
388            String shapeName = shape.getName();
389            LOG.logDebug( "Outputting a Shape3d with name: " + shapeName );
390            Appearance app = shape.getAppearance();
391            float ambientIntensity = 0.5f;
392            Color3f ambient = new Color3f( 0.8f, 0.8f, 0.8f );
393            Color3f diffuse = new Color3f( 0.8f, 0.8f, 0.8f );
394            Color3f specular = new Color3f( 0.8f, 0.8f, 0.8f );
395            if ( app != null ) {
396                Material mat = app.getMaterial();
397                if ( mat != null ) {
398                    // ambientIntensity = mat.getShininess();
399                    mat.getAmbientColor( ambient );
400                    mat.getDiffuseColor( diffuse );
401                    mat.getSpecularColor( specular );
402                }
403            }
404            while ( geoms.hasMoreElements() ) {
405                Geometry geom = (Geometry) geoms.nextElement();
406                if ( geom != null ) {
407                    if ( geom instanceof GeometryArray ) {
408                        GeometryArray ga = (GeometryArray) geom;
409                        if ( !( ga instanceof TriangleArray ) ) {
410                            LOG.logDebug( "Not a triangle Array geometry -> convert to triangles, original type is: " + ga );
411                            GeometryInfo inf = new GeometryInfo( ga );
412                            inf.convertToIndexedTriangles();
413                            ga = inf.getGeometryArray();
414                            LOG.logDebug( "The converted type is: " + ga );
415                        }
416    
417                        int vertexCount = ga.getVertexCount();
418                        if ( vertexCount == 0 ) {
419                            LOG.logError( "No coordinates found in the geometryArray, this may not be." );
420                        } else {
421                            LOG.logDebug( "Number of vertices in shape3d: " + vertexCount );
422    
423                            Element appearance = parent.getOwnerDocument().createElementNS( CITYGMLNS.toASCIIString(),
424                                                                                            PRE_C + "appearance" );
425                            Element simpleTexture = parent.getOwnerDocument().createElementNS( CITYGMLNS.toASCIIString(),
426                                                                                               PRE_C + "SimpleTexture" );
427    
428                            int vertexFormat = ga.getVertexFormat();
429    
430                            /**
431                             * Textures
432                             */
433                            TexCoord2f[] texCoords = null;
434                            if ( ( GeometryArray.TEXTURE_COORDINATE_2 & vertexFormat ) == GeometryArray.TEXTURE_COORDINATE_2 ) {
435                                LOG.logDebug( "The Geometry has a texture attached to it." );
436    
437                                texCoords = new TexCoord2f[vertexCount];
438                                for ( int i = 0; i < texCoords.length; ++i ) {
439                                    texCoords[i] = new TexCoord2f( 0, 0 );
440                                }
441                                ga.getTextureCoordinates( 0, ga.getInitialTexCoordIndex( 0 ), texCoords );
442                                StringBuilder texCoordsAsString = new StringBuilder( texCoords.length * 2 );
443                                for ( TexCoord2f coord : texCoords ) {
444                                    texCoordsAsString.append( coord.x ).append( " " ).append( coord.y ).append( " " );
445                                }
446    
447                                // TODO try to get all textures of all texture units
448                                if ( app != null ) {
449                                    Texture tex = app.getTexture();
450                                    if ( tex != null ) {
451                                        String texName = tex.getName();
452                                        LOG.logDebug( "Texture name: " + texName );
453                                        ImageComponent ic = tex.getImage( 0 );
454                                        if ( ic != null ) {
455                                            if ( ic instanceof ImageComponent2D ) {
456                                                LOG.logDebug( "ImageComponent name: " + ( (ImageComponent2D) ic ).getName() );
457                                                BufferedImage bi = ( (ImageComponent2D) ic ).getImage();
458                                                if ( bi != null ) {
459                                                    if ( texName == null || "".equals( texName.trim() ) ) {
460                                                        texName = name + "_texture_" + textureCount++;
461                                                    }
462                                                    texName += ( ".jpg" );
463                                                    try {
464                                                        File f = new File( textureOutputDir, texName );
465                                                        ImageIO.write( bi, "jpg", f );
466                                                        LOG.logDebug( "Wrote texture to: " + f.getAbsolutePath() );
467                                                    } catch ( IOException e ) {
468                                                        LOG.logError( "Failed to write texture: " + texName, e );
469                                                    }
470                                                    // add the texture to the appearance node
471    
472                                                    XMLTools.appendElement( simpleTexture,
473                                                                            CITYGMLNS,
474                                                                            PRE_C + "textureMap",
475                                                                            textureOutputDir + texName );
476                                                    XMLTools.appendElement( simpleTexture,
477                                                                            CITYGMLNS,
478                                                                            PRE_C + "textureType",
479                                                                            "specific" );
480                                                    XMLTools.appendElement( simpleTexture,
481                                                                            CITYGMLNS,
482                                                                            PRE_C + "repeat",
483                                                                            "0" );
484    
485                                                    
486                                                    
487                                                }
488                                            }
489                                        }
490                                    }
491                                }
492                            } else {
493    
494                                Element material = XMLTools.appendElement( appearance, CITYGMLNS, PRE_C + "Material" );
495                                XMLTools.appendElement( material,
496                                                        CITYGMLNS,
497                                                        PRE_C + "ambientIntensity",
498                                                        "" + ambientIntensity );
499                                // allthough the citgml spec does not contain it, we set the ambient color :(
500                                XMLTools.appendElement( material, CITYGMLNS, PRE_C + "ambientColor", ambient.x + " "
501                                                                                                     + ambient.y
502                                                                                                     + " "
503                                                                                                     + ambient.z );
504                                XMLTools.appendElement( material, CITYGMLNS, PRE_C + "specularColor", specular.x + " "
505                                                                                                      + specular.y
506                                                                                                      + " "
507                                                                                                      + specular.z );
508                                XMLTools.appendElement( material, CITYGMLNS, PRE_C + "diffuseColor", diffuse.x + " "
509                                                                                                     + diffuse.y
510                                                                                                     + " "
511                                                                                                     + diffuse.z );
512                            }
513    
514                            Point3d[] coords = new Point3d[vertexCount];
515                            for ( int i = 0; i < coords.length; ++i ) {
516                                coords[i] = new Point3d( 0, 0, 0 );
517                            }
518                            ga.getCoordinates( ga.getInitialVertexIndex(), coords );
519                            LOG.logDebug( "Number of coords in geometry: " + coords.length );
520    
521                            // remove double defined coords (not allowed in a polygon).
522                            // List<Point3d> resultingPoints = new ArrayList<Point3d>( coords.length );
523                            if ( coords != null && coords.length > 0 && coords[0] != null ) {
524    
525                                if ( ga instanceof TriangleArray ) {
526                                    // coords.length % 3 == 0 ) {
527                                    LOG.logInfo( "Using triangles for the coords." );
528                                    // Point3d firstCoord = coords[0];
529                                    // Point3d lastCoord = null;
530                                    int i = 0;
531                                    for ( ; i < coords.length; i += 3 ) {
532                                        Element surfaceMember = XMLTools.appendElement( parent,
533                                                                                        GMLNS,
534                                                                                        PRE_G + "surfaceMember" );
535    
536                                        Element texturedSurface = XMLTools.appendElement( surfaceMember,
537                                                                                          CITYGMLNS,
538                                                                                          PRE_C + "TexturedSurface" );
539                                        texturedSurface.setAttribute( "orientation", "+" );
540    
541                                        Element baseSurface = XMLTools.appendElement( texturedSurface,
542                                                                                      GMLNS,
543                                                                                      PRE_G + "baseSurface" );
544                                        Element polygon = XMLTools.appendElement( baseSurface, GMLNS, PRE_G + "Polygon" );
545                                        Element exterior = XMLTools.appendElement( polygon, GMLNS, PRE_G + "exterior" );
546                                        Element lRing = XMLTools.appendElement( exterior, GMLNS, PRE_G + "LinearRing" );
547                                        if ( ( i + 3 ) <= coords.length ) {
548                                            for ( int j = 0; j < 3; j++ ) {
549                                                Point3d coord = coords[i + j];
550    
551                                                // use point3d.equals method to see if allready present.
552                                                // if ( resultingPoints.contains( coord ) ) {
553                                                // resultingPoints.add( coord );
554                                                // check the bounds if set
555                                                if ( calcBounds ) {
556                                                    if ( coord.x < lower.x ) {
557                                                        lower.x = coord.x;
558                                                    }
559                                                    if ( coord.y < lower.y ) {
560                                                        lower.y = coord.y;
561                                                    }
562                                                    if ( coord.z < lower.z ) {
563                                                        lower.z = coord.z;
564                                                    }
565                                                    if ( coord.x > upper.x ) {
566                                                        upper.x = coord.x;
567                                                    }
568                                                    if ( coord.y > upper.y ) {
569                                                        upper.y = coord.y;
570                                                    }
571                                                    if ( coord.z > upper.z ) {
572                                                        upper.z = coord.z;
573                                                    }
574                                                }
575                                                XMLTools.appendElement( lRing,
576                                                                        GMLNS,
577                                                                        PRE_G + "pos",
578                                                                        ( translationX + coord.x ) + " "
579                                                                                                + ( translationY + coord.y )
580                                                                                                + " "
581                                                                                                + ( translationZ + coord.z ) );
582                                            }
583                                            XMLTools.appendElement( lRing,
584                                                                    GMLNS,
585                                                                    PRE_G + "pos",
586                                                                    ( translationX + coords[i].x ) + " "
587                                                                                            + ( translationY + coords[i].y )
588                                                                                            + " "
589                                                                                            + ( translationZ + coords[i].z ) );
590                                        } else {// no three points are left;
591                                            LOG.logInfo( "One of the geometries have an inconsistent number of points (" + ( coords.length - i )
592                                                         + ")to create triangles-> correcting!" );
593                                            Point3d current = coords[i];
594                                            Point3d previous = coords[i - 1];
595                                            Point3d tmp = null;
596                                            switch ( coords.length - i ) {
597                                            case 1: // take last two nodes and create a triangle.
598                                                tmp = coords[i - 2];
599                                                break;
600                                            default: // take last node and create a triangle.
601                                                tmp = coords[i + 1];
602                                                break;
603                                            }
604                                            XMLTools.appendElement( lRing,
605                                                                    GMLNS,
606                                                                    PRE_G + "pos",
607                                                                    ( translationX + current.x ) + " "
608                                                                                            + ( translationY + current.y )
609                                                                                            + " "
610                                                                                            + ( translationZ + current.z ) );
611                                            XMLTools.appendElement( lRing,
612                                                                    GMLNS,
613                                                                    PRE_G + "pos",
614                                                                    ( translationX + previous.x ) + " "
615                                                                                            + ( translationY + previous.y )
616                                                                                            + " "
617                                                                                            + ( translationZ + previous.z ) );
618                                            XMLTools.appendElement( lRing,
619                                                                    GMLNS,
620                                                                    PRE_G + "pos",
621                                                                    ( translationX + tmp.x ) + " "
622                                                                                            + ( translationY + tmp.y )
623                                                                                            + " "
624                                                                                            + ( translationZ + tmp.z ) );
625                                            XMLTools.appendElement( lRing,
626                                                                    GMLNS,
627                                                                    PRE_G + "pos",
628                                                                    ( translationX + current.x ) + " "
629                                                                                            + ( translationY + current.y )
630                                                                                            + " "
631                                                                                            + ( translationZ + current.z ) );
632    
633                                        }
634    
635                                        // add the appearance
636                                        Element tmp = (Element) appearance.cloneNode( true );
637                                        if ( texCoords != null && ( i + 2 ) < texCoords.length ) {
638                                            Element sTex = (Element) simpleTexture.cloneNode( true );
639                                            XMLTools.appendElement( sTex,
640                                                                    CITYGMLNS,
641                                                                    PRE_C + "textureCoordinates",
642                                                                    texCoords[i].x          + " "
643                                                                                            + texCoords[i].y
644                                                                                            + " "
645                                                                                            + texCoords[i + 1].x
646                                                                                            + " "
647                                                                                            + texCoords[i + 1].y
648                                                                                            + " "
649                                                                                            + texCoords[i + 2].x
650                                                                                            + " "
651                                                                                            + texCoords[i + 2].y
652                                                                                            + " "
653                                                                                            + texCoords[i].x
654                                                                                            + " "
655                                                                                            + texCoords[i].y );
656                                            tmp.appendChild( sTex );
657                                        }
658                                        texturedSurface.appendChild( tmp );
659                                        numberOfSurfaces++;
660                                    }
661                                } else { // try to set as polygon.
662                                    LOG.logInfo( "Setting points as polygon." );
663                                    Point3d firstCoord = coords[0];
664                                    Point3d lastCoord = null;
665                                    Element texturedSurface = XMLTools.appendElement( parent,
666                                                                                      CITYGMLNS,
667                                                                                      PRE_C + "TexturedSurface" );
668                                    texturedSurface.setAttribute( "orientation", "+" );
669    
670                                    Element baseSurface = XMLTools.appendElement( texturedSurface,
671                                                                                  GMLNS,
672                                                                                  PRE_G + "baseSurface" );
673                                    Element polygon = XMLTools.appendElement( baseSurface, GMLNS, PRE_G + "Polygon" );
674                                    Element exterior = XMLTools.appendElement( polygon, GMLNS, PRE_G + "exterior" );
675                                    Element lRing = XMLTools.appendElement( exterior, GMLNS, PRE_G + "LinearRing" );
676    
677                                    for ( int i = 0; i < coords.length; ++i ) {
678                                        Point3d coord = coords[i];
679                                        if ( calcBounds ) {
680                                            if ( coord.x < lower.x ) {
681                                                lower.x = coord.x;
682                                            }
683                                            if ( coord.y < lower.y ) {
684                                                lower.y = coord.y;
685                                            }
686                                            if ( coord.z < lower.z ) {
687                                                lower.z = coord.z;
688                                            }
689                                            if ( coord.x > upper.x ) {
690                                                upper.x = coord.x;
691                                            }
692                                            if ( coord.y > upper.y ) {
693                                                upper.y = coord.y;
694                                            }
695                                            if ( coord.z > upper.z ) {
696                                                upper.z = coord.z;
697                                            }
698                                        }
699                                        XMLTools.appendElement( lRing,
700                                                                GMLNS,
701                                                                PRE_G + "pos",
702                                                                ( translationX + coord.x ) + " "
703                                                                                        + ( translationY + coord.y )
704                                                                                        + " "
705                                                                                        + ( translationZ + coord.z ) );
706                                        lastCoord = coords[i];
707                                    }
708                                    if ( !firstCoord.equals( lastCoord ) ) {
709                                        XMLTools.appendElement( lRing,
710                                                                GMLNS,
711                                                                PRE_G + "pos",
712                                                                ( translationX + firstCoord.x ) + " "
713                                                                                        + ( translationY + firstCoord.y )
714                                                                                        + " "
715                                                                                        + ( translationZ + firstCoord.z ) );
716                                    }
717                                    // XMLTools.appendElement( lRing,
718                                    // GMLNS,
719                                    // PRE_G + "pos",
720                                    // ( translationX + coords[i].x ) + " "
721                                    // + ( translationY + coords[i].y )
722                                    // + " "
723                                    // + ( translationZ + coords[i].z ) );
724                                    // }
725    
726                                    // add the appearance
727                                    Element tmp = (Element) appearance.cloneNode( true );
728                                    texturedSurface.appendChild( tmp );
729                                }
730    
731                            }
732    
733                            /**
734                             * NORMALS
735                             */
736                            if ( ( GeometryArray.NORMALS & vertexFormat ) == GeometryArray.NORMALS ) {
737                                LOG.logDebug( "The Geometry has normals, but they are not exported (yet)." );
738                                // Vector3f[] normals = new Vector3f[vertexCount];
739                                // for ( int i = 0; i < normals.length; ++i ) {
740                                // normals[i] = new Vector3f( 0, 0, 0 );
741                                // }
742                                // ga.getNormals( 0, normals );
743                                // for ( Vector3f normal : normals ) {
744                                // parent.append( normal ).append( " " );
745                                // }
746                            }
747                            /**
748                             * Colors 3f
749                             */
750                            if ( ( GeometryArray.COLOR_3 & vertexFormat ) == GeometryArray.COLOR_3 ) {
751                                LOG.logDebug( "The Geometry has Colors per Vertex with 3 chanels, but they are not exported (yet)." );
752                                // Color3f[] colors = new Color3f[vertexCount];
753                                // for ( int i = 0; i < colors.length; ++i ) {
754                                // colors[i] = new Color3f( 0, 0, 0 );
755                                // }
756                                // ga.getColors( 0, colors );
757                                // parent.append( "\n - Colors3f (" ).append( numberOfGeom ).append( "): " );
758                                // for ( Color3f color : colors ) {
759                                // parent.append( color ).append( " " );
760                                // }
761                            }
762    
763                            /**
764                             * Colors 4f
765                             */
766                            if ( ( GeometryArray.COLOR_4 & vertexFormat ) == GeometryArray.COLOR_4 ) {
767                                LOG.logDebug( "The Geometry has Colors per Vertex with 4 chanels, but they are not exported (yet)." );
768                                // Color4f[] colors = new Color4f[vertexCount];
769                                // for ( int i = 0; i < colors.length; ++i ) {
770                                // colors[i] = new Color4f( 0, 0, 0, 0 );
771                                // }
772                                // ga.getColors( 0, colors );
773                                // parent.append( "\n - Colors4f (" ).append( numberOfGeom ).append( "): " );
774                                // for ( Color4f color : colors ) {
775                                // parent.append( color ).append( " " );
776                                // }
777                            }
778    
779                            // Element appearance = XMLTools.appendElement( texturedSurface,
780                            // CITYGMLNS,
781                            // PRE_C + "appearance" );
782                        }
783                    } else {
784                        LOG.logInfo( "Only Shape3d Objects with geometry rasters are supported for outputing" );
785                    }
786                }
787            }
788    
789        }
790    
791        /**
792         * Don't know how to export the background yet.
793         * 
794         * @param b
795         *            leaf of the j3dScene
796         * @param parent
797         *            to append to.
798         */
799        private void outputBackground( @SuppressWarnings("unused")
800        Background b, @SuppressWarnings("unused")
801        Element parent ) {
802            LOG.logError( "Cannot output a background yet." );
803        }
804    
805        public String getName() {
806            return "CityGML exporter";
807        }
808    
809        public String getShortDescription() {
810            return "Convert a given j3d:BranchGroup to CityGML LOD1";
811        }
812    
813        /**
814         * @param args
815         */
816        public static void main( String[] args ) {
817            if ( args.length == 0 ) {
818                outputHelp();
819            }
820            Map<String, String> params = new HashMap<String, String>( 5 );
821            for ( int i = 0; i < args.length; i++ ) {
822                String arg = args[i];
823                if ( args != null && !"".equals( arg.trim() ) ) {
824                    arg = arg.trim();
825                    if ( arg.equalsIgnoreCase( "-?" ) || arg.equalsIgnoreCase( "-h" ) ) {
826                        outputHelp();
827                    } else {
828                        if ( i + 1 < args.length ) {
829                            String val = args[++i];
830                            if ( val != null && !"".equals( val.trim() ) ) {
831                                params.put( arg.toLowerCase(), val.trim() );
832                            } else {
833                                System.out.println( "Invalid value for parameter: " + arg );
834                            }
835                        } else {
836                            System.out.println( "No value for parameter: " + arg );
837                        }
838                    }
839                }
840            }
841    
842            String fileName = params.get( "-infile" );
843            if ( fileName == null || "".equals( fileName.trim() ) ) {
844                System.out.println( "\n\n" );
845                LOG.logError( "The -inFile parameter must be defined." );
846                System.out.println( "\n\n" );
847                outputHelp();
848            }
849            String name = fileName;
850            int last = fileName.lastIndexOf( File.separator );
851            if ( last != -1 ) {
852                name = fileName.substring( last + 1 );
853                last = name.lastIndexOf( "." );
854                if ( last != -1 ) {
855                    name = name.substring( 0, last );
856                }
857    
858            }
859    
860            String texDir = params.get( "-texdir" );
861            if ( texDir == null || "".equals( texDir.trim() ) ) {
862                System.out.println( "\n\n" );
863                LOG.logError( "The -texdir parameter must be defined." );
864                System.out.println( "\n\n" );
865                outputHelp();
866            }
867    
868            VrmlLoader loader = new VrmlLoader();
869            try {
870                double xTrans = Double.NaN;
871                String t = params.get( "-xtranslation" );
872                if ( t != null && !"".equals( t ) ) {
873                    try {
874                        xTrans = Double.parseDouble( t );
875                    } catch ( NumberFormatException nfe ) {
876                        // nottin
877                    }
878                }
879                double yTrans = Double.NaN;
880                t = params.get( "-ytranslation" );
881                if ( t != null && !"".equals( t ) ) {
882                    try {
883                        yTrans = Double.parseDouble( t );
884                    } catch ( NumberFormatException nfe ) {
885                        // nottin
886                    }
887                }
888                double zTrans = Double.NaN;
889                t = params.get( "-ztranslation" );
890                if ( t != null && !"".equals( t ) ) {
891                    try {
892                        zTrans = Double.parseDouble( t );
893                    } catch ( NumberFormatException nfe ) {
894                        // nottin
895                    }
896                }
897                boolean asWFS = false;
898                t = params.get( "-wfstransaction" );
899                if ( t != null && !"".equals( t ) ) {
900                    if ( t.equalsIgnoreCase( "y" ) || t.equalsIgnoreCase( "yes" ) ) {
901                        asWFS = true;
902                    }
903                }
904                J3DToCityGMLExporter converter = new J3DToCityGMLExporter( name,
905                                                                           params.get( "-srs" ),
906                                                                           params.get( "-buildingFunction" ),
907                                                                           texDir,
908                                                                           xTrans,
909                                                                           yTrans,
910                                                                           zTrans,
911                                                                           asWFS );
912                System.out.println( "\n\nTrying to load scene, if you see no message beneath this message, please check your vrml file: " + fileName
913                                    + " if the refrerred textures are java qouted, e.g. in Windows \\\\ should be used instead of a single \\" );
914                Scene scene = loader.load( fileName );
915                if ( scene != null ) {
916                    System.out.println( "Successfully loaded the scene." );
917                    BranchGroup bg = scene.getSceneGroup();
918                    if ( bg != null && bg.numChildren() > 0 ) {
919                        System.out.println( "Retrieval of the j3d.Branchgroup was successful." );
920                        StringBuilder output = new StringBuilder( 20000 );
921                        converter.export( output, bg );
922                        String outFile = params.get( "-outfile" );
923                        if ( outFile == null || "".equals( outFile.trim() ) ) {
924                            System.out.println( output.toString() );
925                        } else {
926                            BufferedWriter fWriter = new BufferedWriter( new FileWriter( new File( outFile ) ) );
927                            fWriter.write( output.toString() );
928                            fWriter.flush();
929                            fWriter.close();
930                        }
931                    } else {
932                        System.out.println( "The branchgroup is null or has no childres, which means that the scene was not loaded correctly (did you correctly quoted the texture paths in your vrml-file: " + fileName
933                                            + ")" );
934                    }
935                } else {
936                    System.out.println( "The scene was not loaded (did you correctly quoted the texture paths in your vrml-file: " + fileName
937                                        + ")" );
938                }
939            } catch ( FileNotFoundException e ) {
940                LOG.logDebug( "Could not load vrm-file because: " + e.getMessage(), e );
941            } catch ( IncorrectFormatException e ) {
942                LOG.logDebug( "Could not load vrm-file because: " + e.getMessage(), e );
943            } catch ( ParsingErrorException e ) {
944                LOG.logDebug( "Could not load vrm-file because: " + e.getMessage(), e );
945            } catch ( IOException e ) {
946                LOG.logDebug( "Could not create converter because: " + e.getMessage(), e );
947            } catch ( Exception e ) {
948                LOG.logDebug( "Error while loading the file: " + fileName + " because: " + e.getMessage(), e );
949            } catch ( Throwable t ) {
950                LOG.logDebug( "Error while loading the file: " + fileName + " because: " + t.getMessage(), t );
951            }
952        }
953    
954        private static void outputHelp() {
955            StringBuilder sb = new StringBuilder();
956            sb.append( "The J3DToCityGMLExported program can be used to export a j3d branchgroup created from a vrml-file to citygml. \n" );
957            sb.append( "Following parameters are supported:\n" );
958            sb.append( "-inFile the /path/to/vrml-file\n" );
959            sb.append( "-texDir directory to output the found textures to.\n" );
960            sb.append( "[-outFile] the /path/to/the/output/file or standard output if not supplied.\n" );
961            sb.append( "[-wfsTransaction y/n] output the citygml as part of a wfs-insert request (if omitted the citygml-building element will be root element).\n" );
962            sb.append( "[-buildingFunction] the function this citygml building will have (if omitted 1001 will be used).\n" );
963            sb.append( "[-srs] the coordinateSystem used for the citygml (if omitted 'EPSG:31466' will be used).\n" );
964            sb.append( "[-xTranslation] the amount to add up to the x part of the found coordinates(if omitted '0' will be used).\n" );
965            sb.append( "[-yTranslation] the amount to add up to the y part of the found coordinates(if omitted '0' will be used).\n" );
966            sb.append( "-?|-h output this text\n" );
967            sb.append( "example usage (converting the cool.vrml to a wfs:Transaction in 'epsg:12345' wit a xtranslation of 10000 and yTranslation of 50,\n" );
968            sb.append( "outputing to cool_tower.xml in the directory my_output_dir (relative to the current dir)\n" );
969            sb.append( "java -cp deegree.jar:lib/j3d/j3d-vrml97.jar:lib/log4j/log4j-1.2.9.jar:/lib/xml/jaxen-1.1-beta-8.jar org.deegree.tools.app3d.J3DToCityGMLExporter -inFile 'cool.vrml' -texDir 'my_output_dir' -outFile 'cool_tower.xml' -wfsTransaction 'y' -buildingFunction 'home office buildings' -xTranslation 10000 -yTranslation 50\n" );
970            System.out.println( sb.toString() );
971            System.exit( 1 );
972        }
973    
974    }