001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wpvs/j3d/Object3DFactory.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.ogcwebservices.wpvs.j3d;
037    
038    import java.awt.image.BufferedImage;
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.math.BigDecimal;
042    import java.net.MalformedURLException;
043    import java.net.URL;
044    import java.util.ArrayList;
045    import java.util.HashMap;
046    import java.util.List;
047    import java.util.Map;
048    import java.util.Properties;
049    
050    import javax.media.j3d.Material;
051    import javax.vecmath.Color3f;
052    import javax.vecmath.TexCoord2f;
053    import javax.xml.namespace.QName;
054    
055    import org.deegree.datatypes.QualifiedName;
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.framework.util.BootLogger;
059    import org.deegree.framework.util.ImageUtils;
060    import org.deegree.framework.util.StringTools;
061    import org.deegree.model.feature.Feature;
062    import org.deegree.model.feature.FeatureProperty;
063    import org.deegree.model.spatialschema.Geometry;
064    import org.deegree.ogcbase.CommonNamespaces;
065    import org.deegree.ogcwebservices.wpvs.configuration.RenderingConfiguration;
066    
067    /**
068     * 
069     * 
070     * 
071     * @version $Revision: 20594 $
072     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
073     * @author last edited by: $Author: rbezema $
074     * 
075     *         $Revision: 20594 $, $Date: 2009-11-05 13:26:07 +0100 (Do, 05. Nov 2009) $
076     * 
077     */
078    public class Object3DFactory {
079    
080        private static InputStream materialURL;
081    
082        private static Properties material = new Properties();
083    
084        private static final ILogger LOG = LoggerFactory.getLogger( Object3DFactory.class );
085    
086        /**
087         * all texture images will be stored on a Map to avoid double loading and creating a BufferedImage from an image
088         * source (textureMap property)
089         */
090        private static Map<String, BufferedImage> textImgMap = new HashMap<String, BufferedImage>( 200 );
091    
092        private static final QualifiedName objectID = new QualifiedName( new QName( "http://www.deegree.org/app",
093                                                                                    "fk_feature", "app" ) );
094    
095        private static final QualifiedName textMapQn = new QualifiedName( new QName( "http://www.deegree.org/app",
096                                                                                     "texturemap", "app" ) );
097    
098        private static final QualifiedName city_textMapQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
099                                                                               "textureMap", CommonNamespaces.CITYGMLNS );
100    
101        private static final QualifiedName textCoordsQn = new QualifiedName( new QName( "http://www.deegree.org/app",
102                                                                                        "texturecoordinates", "app" ) );
103    
104        private static final QualifiedName city_textCoordsQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
105                                                                                  "textureCoordinates",
106                                                                                  CommonNamespaces.CITYGMLNS );
107    
108        private static final QualifiedName shininessQn = new QualifiedName( new QName( "http://www.deegree.org/app",
109                                                                                       "shininess", "app" ) );
110    
111        private static final QualifiedName city_shininessQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
112                                                                                 "shininess", CommonNamespaces.CITYGMLNS );
113    
114        private static final QualifiedName transparencyQn = new QualifiedName( new QName( "http://www.deegree.org/app",
115                                                                                          "transparency", "app" ) );
116    
117        private static final QualifiedName city_transparencyQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
118                                                                                    "transparency",
119                                                                                    CommonNamespaces.CITYGMLNS );
120    
121        private static final QualifiedName ambientintensityQn = new QualifiedName( new QName( "http://www.deegree.org/app",
122                                                                                              "ambientintensity", "app" ) );
123    
124        private static final QualifiedName city_ambientintensityQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
125                                                                                        "ambientIntensity",
126                                                                                        CommonNamespaces.CITYGMLNS );
127    
128        private static final QualifiedName specularcolorQn = new QualifiedName( new QName( "http://www.deegree.org/app",
129                                                                                           "specularcolor", "app" ) );
130    
131        private static final QualifiedName city_specularcolorQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
132                                                                                     "specularColor",
133                                                                                     CommonNamespaces.CITYGMLNS );
134    
135        private static final QualifiedName diffusecolorQn = new QualifiedName( new QName( "http://www.deegree.org/app",
136                                                                                          "diffusecolor", "app" ) );
137    
138        private static final QualifiedName city_diffusecolorQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
139                                                                                    "diffuseColor",
140                                                                                    CommonNamespaces.CITYGMLNS );
141    
142        private static final QualifiedName emissivecolorQn = new QualifiedName( new QName( "http://www.deegree.org/app",
143                                                                                           "emissivecolor", "app" ) );
144    
145        private static final QualifiedName city_emissivecolorQn = new QualifiedName( CommonNamespaces.CITYGML_PREFIX,
146                                                                                     "emissiveColor",
147                                                                                     CommonNamespaces.CITYGMLNS );
148    
149        private static final RenderingConfiguration rc = RenderingConfiguration.getInstance();
150        static {
151            try {
152                materialURL = Object3DFactory.class.getResourceAsStream( "material.properties" );
153                material.load( materialURL );
154            } catch ( IOException e ) {
155                BootLogger.logError( e.getMessage(), e );
156            }
157        }
158    
159        /**
160         * creates a Surface from the passed feature. It is assumed the feature is simple, contains one surfac/polygon
161         * geometry and optional has material and/or texture informations. The GML schema for a valid feature is defined as:
162         * 
163         * <pre>
164         *    &lt;xsd:schema targetNamespace=&quot;http://www.deegree.org/app&quot; xmlns:app=&quot;http://www.deegree.org/app&quot; xmlns:ogc=&quot;http://www.opengis.net/ogc&quot; xmlns:deegreewfs=&quot;http://www.deegree.org/wfs&quot; xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:gml=&quot;http://www.opengis.net/gml&quot; elementFormDefault=&quot;qualified&quot; attributeFormDefault=&quot;unqualified&quot;&gt;
165         *        &lt;xsd:import namespace=&quot;http://www.opengis.net/gml&quot; schemaLocation=&quot;http://schemas.opengis.net/gml/3.1.1/base/feature.xsd&quot;/&gt;
166         *         &lt;xsd:import namespace=&quot;http://www.opengis.net/gml&quot; schemaLocation=&quot;http://schemas.opengis.net/gml/3.1.1/base/geometryAggregates.xsd&quot;/&gt;
167         *         &lt;xsd:element name=&quot;WPVS&quot; type=&quot;app:WPVSType&quot; substitutionGroup=&quot;gml:_Feature&quot;/&gt;
168         *         &lt;xsd:complexType name=&quot;WPVSType&quot;&gt;
169         *             &lt;xsd:complexContent&gt;
170         *                 &lt;xsd:extension base=&quot;gml:AbstractFeatureType&quot;&gt;
171         *                     &lt;xsd:sequence&gt;
172         *                         &lt;xsd:element name=&quot;fk_feature&quot; type=&quot;xsd:double&quot;/&gt;
173         *                         &lt;xsd:element name=&quot;geometry&quot; type=&quot;gml:GeometryPropertyType&quot;/&gt;
174         *                         &lt;xsd:element name=&quot;shininess&quot; type=&quot;xsd:double&quot; minOccurs=&quot;0&quot;/&gt;
175         *                         &lt;xsd:element name=&quot;transparency&quot; type=&quot;xsd:double&quot; minOccurs=&quot;0&quot;/&gt;
176         *                         &lt;xsd:element name=&quot;ambientintensity&quot; type=&quot;xsd:double&quot; minOccurs=&quot;0&quot;/&gt;
177         *                         &lt;xsd:element name=&quot;specularcolor&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
178         *                         &lt;xsd:element name=&quot;diffusecolor&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
179         *                         &lt;xsd:element name=&quot;emissivecolor&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
180         *                         &lt;xsd:element name=&quot;texturemap&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
181         *                         &lt;xsd:element name=&quot;texturecoordinates&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
182         *                         &lt;xsd:element name=&quot;texturetype&quot; type=&quot;xsd:string&quot; minOccurs=&quot;0&quot;/&gt;
183         *                         &lt;xsd:element name=&quot;repeat&quot; type=&quot;xsd:integer&quot; minOccurs=&quot;0&quot;/&gt;
184         *                     &lt;/xsd:sequence&gt;
185         *                 &lt;/xsd:extension&gt;
186         *             &lt;/xsd:complexContent&gt;
187         *         &lt;/xsd:complexType&gt;
188         *     &lt;/xsd:schema&gt;
189         * </pre>
190         * 
191         * @param feature
192         * @param texturedShapes
193         *            which were loaded already
194         * @return a DefaultSurface which is a derivative of a Shape3D. NOTE, the surface is not yet 'compiled'.
195         */
196        public DefaultSurface createSurface( Feature feature, Map<String, TexturedSurface> texturedShapes ) {
197            double oId = -1d;
198            if ( feature.getDefaultProperty( objectID ) != null ) {
199                if ( feature.getDefaultProperty( objectID ).getValue( new Double( -1 ) ) instanceof BigDecimal ) {
200                    oId = ( (BigDecimal) feature.getDefaultProperty( objectID ).getValue( new Double( -1 ) ) ).doubleValue();
201                } else if ( feature.getDefaultProperty( objectID ).getValue( new Double( -1 ) ) instanceof Double ) {
202                    oId = ( (Double) feature.getDefaultProperty( objectID ).getValue( new Double( -1d ) ) ).doubleValue();
203                }
204            } else {
205                LOG.logDebug( "use genereted gml:id" );
206                oId = Math.random();
207            }
208    
209            // read texture informations (if available) from feature
210            BufferedImage textImage = null;
211            TexturedSurface cachedSurface = null;
212            String textureFile = null;
213            FeatureProperty[] fp = feature.getProperties( textMapQn );
214            if ( fp == null || fp.length == 0 ) {
215                fp = feature.getProperties( city_textMapQn );
216            }
217            if ( fp != null && fp.length > 0 ) {
218                textureFile = (String) fp[0].getValue();
219                if ( textureFile != null && !"".equals( textureFile.trim() ) ) {
220                    if ( texturedShapes.containsKey( textureFile ) ) {
221                        cachedSurface = texturedShapes.get( textureFile );
222                    } else {
223                        textImage = textImgMap.get( textureFile );
224                        if ( textImage == null ) {
225                            String lt = textureFile.toLowerCase();
226                            try {
227                                if ( lt.startsWith( "file:" ) || lt.startsWith( "http:" ) ) {
228                                    textImage = ImageUtils.loadImage( new URL( textureFile ) );
229                                } else {
230                                    textImage = ImageUtils.loadImage( textureFile );
231                                }
232                                // textImage = ImageIO.read( new URL( textureFile ) );
233    
234                            } catch ( MalformedURLException e ) {
235                                e.printStackTrace();
236                            } catch ( IOException e ) {
237                                e.printStackTrace();
238                            }
239                            if ( textImage != null ) {
240                                textImgMap.put( textureFile, textImage );
241                            } else {
242                                LOG.logWarning( "Failed to load texture image: " + textureFile );
243                            }
244                        }
245                    }
246                }
247            }
248            // float[][] textureCoords = new float[1][];
249            List<TexCoord2f> textureCoords = null;
250            if ( textImage != null || cachedSurface != null ) {
251                fp = feature.getProperties( textCoordsQn );
252                if ( fp == null || fp.length == 0 ) {
253                    fp = feature.getProperties( city_textCoordsQn );
254                }
255                if ( fp != null && fp.length > 0 ) {
256                    String tmp = (String) fp[0].getValue();
257                    LOG.logDebug( "Texture Coordinates: " + tmp );
258                    if ( tmp != null ) {
259                        float[] tc = StringTools.toArrayFloat( tmp, ", " );
260                        if ( tc != null && tc.length > 0 ) {
261                            textureCoords = new ArrayList<TexCoord2f>( tc.length );
262                            for ( int i = 0; i < tc.length; i += 2 ) {
263                                textureCoords.add( new TexCoord2f( tc[i], tc[i + 1] ) );
264                            }
265                        }
266                    }
267                }
268            }
269    
270            // read color informations from feature. If not available use default values
271            // from material.properties
272            Double shininess = new Double( material.getProperty( "shininess" ) );
273            fp = feature.getProperties( shininessQn );
274            if ( fp == null || fp.length == 0 ) {
275                fp = feature.getProperties( city_shininessQn );
276            }
277            if ( fp != null && fp.length > 0 ) {
278                if ( fp[0].getValue() instanceof String ) {
279                    shininess = Double.parseDouble( (String) fp[0].getValue( Double.toString( shininess ) ) );
280                } else {
281                    shininess = (Double) fp[0].getValue( shininess );
282                }
283            }
284    
285            float transparency = new Float( material.getProperty( "transparency" ) );
286            fp = feature.getProperties( transparencyQn );
287            if ( fp == null || fp.length == 0 ) {
288                fp = feature.getProperties( city_transparencyQn );
289            }
290            if ( fp != null && fp.length > 0 ) {
291                if ( fp[0].getValue() instanceof String ) {
292                    transparency = Float.parseFloat( (String) fp[0].getValue( Float.toString( transparency ) ) );
293                } else {
294                    if ( fp[0].getValue() instanceof Double ) {
295                        transparency = ( (Double) fp[0].getValue( transparency ) ).floatValue();
296                    } else {
297                        transparency = (Float) fp[0].getValue( transparency );
298                    }
299                }
300            }
301    
302            Double ambientintensity = new Double( material.getProperty( "ambientintensity" ) );
303            fp = feature.getProperties( ambientintensityQn );
304            if ( fp == null || fp.length == 0 ) {
305                fp = feature.getProperties( city_ambientintensityQn );
306            }
307            if ( fp != null && fp.length > 0 ) {
308                if ( fp[0].getValue() instanceof String ) {
309                    ambientintensity = Double.parseDouble( (String) fp[0].getValue( Double.toString( ambientintensity ) ) );
310                } else {
311                    ambientintensity = (Double) fp[0].getValue( ambientintensity );
312                }
313            }
314            Color3f ambientcolor = new Color3f( ambientintensity.floatValue(), ambientintensity.floatValue(),
315                                                ambientintensity.floatValue() );
316    
317            String tmp = material.getProperty( "specularcolor" );
318            fp = feature.getProperties( specularcolorQn );
319            if ( fp == null || fp.length == 0 ) {
320                fp = feature.getProperties( city_specularcolorQn );
321            }
322            if ( fp != null && fp.length > 0 ) {
323                tmp = (String) fp[0].getValue( tmp );
324                tmp = createStringToolsSecureString( tmp, material.getProperty( "specularcolor" ) );
325            }
326            float[] tmpFl = StringTools.toArrayFloat( tmp.trim(), " " );
327            Color3f specularcolor = new Color3f( tmpFl[0], tmpFl[1], tmpFl[2] );
328    
329            tmp = material.getProperty( "diffusecolor" );
330            fp = feature.getProperties( diffusecolorQn );
331            if ( fp == null || fp.length == 0 ) {
332                fp = feature.getProperties( city_diffusecolorQn );
333            }
334            if ( fp != null && fp.length > 0 ) {
335                tmp = (String) fp[0].getValue( tmp );
336                tmp = createStringToolsSecureString( tmp, material.getProperty( "diffusecolor" ) );
337            }
338            tmpFl = StringTools.toArrayFloat( tmp.trim(), " " );
339            Color3f diffusecolor = new Color3f( tmpFl[0], tmpFl[1], tmpFl[2] );
340    
341            tmp = material.getProperty( "emissivecolor" );
342            fp = feature.getProperties( emissivecolorQn );
343            if ( fp == null || fp.length == 0 ) {
344                fp = feature.getProperties( city_emissivecolorQn );
345            }
346            if ( fp != null && fp.length > 0 ) {
347                tmp = (String) fp[0].getValue( tmp );
348                tmp = createStringToolsSecureString( tmp, material.getProperty( "emissivecolor" ) );
349            }
350            tmpFl = StringTools.toArrayFloat( tmp.trim(), " " );
351            Color3f emissivecolor = new Color3f( tmpFl[0], tmpFl[1], tmpFl[2] );
352    
353            Material mat = new Material( ambientcolor, emissivecolor, diffusecolor, specularcolor, shininess.floatValue() );
354            // for diffuse-color to work the ambient lighting must be switched on
355            mat.setColorTarget( Material.AMBIENT_AND_DIFFUSE );
356    
357            /**
358             * Please check for the right property here. The defaultPropertyValue just delivers the first geometry defined
359             * in the wfs configuration.
360             */
361            DefaultSurface resultSurface = null;
362            org.deegree.model.spatialschema.Geometry geometry = feature.getDefaultGeometryPropertyValue();
363            // if ( geometry instanceof MultiSurface ) {
364            // MultiSurface multiSurfaces = (MultiSurface) geometry;
365            // LOG.logDebug( "Found a Multi surface" );
366            // Surface[] allSurfaces = multiSurfaces.getAllSurfaces();
367            // // so no texture create a new Default surface
368            // } else if ( geometry instanceof Surface ) {
369            resultSurface = createSurface( texturedShapes, cachedSurface, geometry, textImage, textureFile, mat,
370                                           transparency, feature.getId(), Double.toString( oId ), textureCoords );
371            if ( resultSurface instanceof TexturedSurface ) {
372                // necessary for the caching mechanism of textures, because they will be added to the map later.
373                resultSurface = null;
374            }
375            //
376            // }
377            // surface.compile();
378            return resultSurface;
379        }
380    
381        private String createStringToolsSecureString( String someColor, String defaultValue ) {
382            if ( "".equals( someColor.trim() ) ) {
383                someColor = defaultValue;
384            } else {
385                String[] s = someColor.split( "\\s" );
386                if ( s.length == 1 || s.length > 3 ) {
387                    someColor = defaultValue;
388                } else {
389                    StringBuilder sb = new StringBuilder( 50 );
390                    for ( int i = 0; i < s.length; i++ ) {
391                        sb.append( s[i] ).append( " " );
392                    }
393                    someColor = sb.toString().trim();
394                }
395            }
396            return someColor;
397        }
398    
399        private DefaultSurface createSurface( Map<String, TexturedSurface> texturedShapes, TexturedSurface cachedSurface,
400                                              Geometry surfaceToAdd, BufferedImage textImage, String textureFile,
401                                              Material material, float transparency, String id, String parentId,
402                                              List<TexCoord2f> textureCoords ) {
403            DefaultSurface result = null;
404            if ( cachedSurface != null ) {
405                result = cachedSurface;
406                LOG.logDebug( "Textured-Surface cached" );
407                cachedSurface.addGeometry( surfaceToAdd, textureCoords );
408            } else if ( textImage != null ) {
409                LOG.logDebug( "Textured-Surface not cached" );
410                result = new TexturedSurface( id, parentId, surfaceToAdd, material, transparency, textImage, textureCoords );
411                texturedShapes.put( textureFile, (TexturedSurface) result );
412            } else {
413                LOG.logDebug( "3D-Surface without texture: ", surfaceToAdd );
414                result = new ColoredSurface( id, parentId, surfaceToAdd, material, transparency );
415            }
416            return result;
417        }
418    }