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