001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wmps/GetMapServiceInvokerForNL.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wmps;
044    
045    import java.awt.Color;
046    import java.awt.Graphics;
047    import java.awt.image.BufferedImage;
048    import java.io.StringReader;
049    import java.util.ArrayList;
050    
051    import org.deegree.datatypes.QualifiedName;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.framework.util.CharsetUtils;
055    import org.deegree.framework.util.IDGenerator;
056    import org.deegree.framework.xml.XMLTools;
057    import org.deegree.graphics.MapFactory;
058    import org.deegree.graphics.Theme;
059    import org.deegree.graphics.sld.UserStyle;
060    import org.deegree.model.coverage.grid.GridCoverage;
061    import org.deegree.model.coverage.grid.ImageGridCoverage;
062    import org.deegree.model.crs.CRSFactory;
063    import org.deegree.model.crs.GeoTransformer;
064    import org.deegree.model.feature.FeatureCollection;
065    import org.deegree.model.filterencoding.ComplexFilter;
066    import org.deegree.model.filterencoding.FeatureFilter;
067    import org.deegree.model.filterencoding.FeatureId;
068    import org.deegree.model.filterencoding.Filter;
069    import org.deegree.model.spatialschema.Envelope;
070    import org.deegree.model.spatialschema.GMLGeometryAdapter;
071    import org.deegree.ogcwebservices.InconsistentRequestException;
072    import org.deegree.ogcwebservices.OGCWebServiceException;
073    import org.deegree.ogcwebservices.OGCWebServiceRequest;
074    import org.deegree.ogcwebservices.wcs.WCSException;
075    import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage;
076    import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
077    import org.deegree.ogcwebservices.wfs.WFService;
078    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
079    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
080    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
081    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
082    import org.deegree.ogcwebservices.wfs.operation.Query;
083    import org.deegree.ogcwebservices.wms.capabilities.Layer;
084    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
085    import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource;
086    import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
087    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
088    import org.deegree.ogcwebservices.wms.operation.GetMap;
089    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
090    import org.w3c.dom.Document;
091    
092    /**
093     * This is a copy of the WMS package.
094     * 
095     * @version $Revision: 9447 $
096     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
097     * @author last edited by: $Author: rbezema $
098     * 
099     * @version 1.0. $Revision: 9447 $, $Date: 2008-01-08 12:01:24 +0100 (Di, 08 Jan 2008) $
100     * 
101     * @since 2.0
102     */
103    /**
104     * Inner class for accessing the data of one named layer and creating <tt>DisplayElement</tt>s
105     * and a <tt>Thrme</tt> from it. The class extends <tt>Thread</tt> and implements the run
106     * method, so that a parallel data accessing from several layers is possible.
107     * 
108     * @version $Revision: 9447 $
109     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
110     */
111    class GetMapServiceInvokerForNL extends Thread {
112    
113        private static final ILogger LOG = LoggerFactory.getLogger( GetMapServiceInvokerForNL.class );
114    
115        private final DefaultGetMapHandler handler;
116    
117        private Layer layer;
118    
119        private UserStyle style;
120    
121        private int index = 0;
122    
123        private AbstractDataSource datasource;
124    
125        /**
126         * Creates a new ServiceInvokerForNL object.
127         * 
128         * @param handler
129         * @param lay
130         * @param source
131         * @param style
132         * @param index
133         *            index of the requested layer
134         */
135        GetMapServiceInvokerForNL( DefaultGetMapHandler handler, Layer lay, AbstractDataSource source, UserStyle style,
136                                   int index ) {
137    
138            this.layer = lay;
139            this.handler = handler;
140            this.index = index;
141            this.style = style;
142            this.datasource = source;
143        }
144    
145        /**
146         * overrides the run-method of the parent class <tt>Thread</tt> for enabling a multi-threaded
147         * access to the data.
148         */
149        @Override
150        public void run() {
151            
152    
153            if ( this.datasource != null ) {
154    
155                OGCWebServiceRequest request = null;
156                try {
157                    int type = this.datasource.getType();
158                    switch ( type ) {
159                    case AbstractDataSource.LOCALWFS:
160                    case AbstractDataSource.REMOTEWFS: {
161                        request = createGetFeatureRequest( (LocalWFSDataSource) this.datasource );
162                        break;
163                    }
164                    case AbstractDataSource.LOCALWCS:
165                    case AbstractDataSource.REMOTEWCS: {
166                        request = createGetCoverageRequest( this.datasource );
167                        break;
168                    }
169                    case AbstractDataSource.REMOTEWMS: {
170                        String styleName = null;
171    
172                        if ( style != null ) {
173                            styleName = style.getName();
174                        }
175                        request = GetMap.createGetMapRequest( this.datasource, handler.request, styleName, layer.getName() );
176                        break;
177                    }
178                    }
179                } catch ( Exception e ) {
180                    LOG.logError( e.getMessage(), e );
181                    OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: "
182                                                                              + this.layer.getName(),
183                                                                              "Couldn't create query!" );
184                    this.handler.putTheme( this.index, exce );
185                    this.handler.increaseCounter();
186                    
187                    return;
188                }
189    
190                try {
191                    Object o = this.datasource.getOGCWebService().doService( request );
192                    handleResponse( o );
193                } catch ( Exception e ) {
194                    LOG.logError( "", e );
195                    OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: "
196                                                                              + this.layer.getName(),
197                                                                              "Couldn't perform doService()!"
198                                                                                                      + e.getMessage() );
199                    this.handler.putTheme( this.index, exce );
200                    this.handler.increaseCounter();
201                    
202                    return;
203                }
204            } else {
205                // increase counter because there is no service to call so it
206                // is assumed that the request for the current layer if fullfilled
207                this.handler.increaseCounter();
208            }
209    
210            
211        }
212    
213        /**
214         * creates a getFeature request considering the getMap request and the filterconditions defined
215         * in the submitted <tt>DataSource</tt> object. The request will be encapsualted within a
216         * <tt>OGCWebServiceEvent</tt>.
217         * 
218         * @param ds
219         * @return GetFeature event object containing a GetFeature request
220         * @throws Exception
221         */
222        private GetFeature createGetFeatureRequest( LocalWFSDataSource ds )
223                                throws Exception {
224            
225    
226            Envelope bbox = this.handler.request.getBoundingBox();
227    
228            // transform request bounding box to the coordinate reference
229            // system the WFS holds the data if requesting CRS and WFS-Data
230            // crs are different
231            WFService wfs = (WFService) ds.getOGCWebService();
232            // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities();
233            WFSCapabilities capa = wfs.getCapabilities();
234    
235            QualifiedName gn = ds.getName();
236            WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
237    
238            if ( ft == null ) {
239                throw new OGCWebServiceException( "Feature Type:" + ds.getName() + " is not known by the WFS" );
240            }
241    
242            // enable different formatations of the crs encoding for GML geometries
243            String GML_SRS = "http://www.opengis.net/gml/srs/";
244            String old_gml_srs = ft.getDefaultSRS().toASCIIString();
245            String old_srs;
246            if ( old_gml_srs.startsWith( GML_SRS ) ) {
247                old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase();
248            } else {
249                old_srs = old_gml_srs;
250            }
251    
252            String new_srs = this.handler.request.getSrs();
253            String new_gml_srs;
254            if ( old_gml_srs.startsWith( GML_SRS ) ) {
255                new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase();
256            } else {
257                new_gml_srs = new_srs;
258            }
259    
260            if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) {
261                GeoTransformer gt = new GeoTransformer( CRSFactory.create( old_srs ) );
262                bbox = gt.transform( bbox, this.handler.reqCRS );
263            }
264    
265            // no filter condition has been defined
266            StringBuffer sb = new StringBuffer( 5000 );
267            sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
268            sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " );
269            sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
270            sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
271            sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' );
272            sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " );
273            sb.append( "service='WFS' version='1.1.0' " );
274            if ( ds.getType() == AbstractDataSource.LOCALWFS ) {
275                sb.append( "outputFormat='FEATURECOLLECTION'>" );
276            } else {
277                sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
278            }
279            sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" );
280    
281            Query query = ds.getQuery();
282            if ( query == null ) {
283                sb.append( "<ogc:Filter><ogc:BBOX>" );
284                sb.append( "<PropertyName>" );
285                sb.append( ds.getGeometryProperty().getPrefixedName() );
286                sb.append( "</PropertyName>" );
287                sb.append( GMLGeometryAdapter.exportAsBox( bbox ) );
288                sb.append( "</ogc:BBOX>" );
289                sb.append( "</ogc:Filter></Query></GetFeature>" );
290            } else {
291                Filter filter = query.getFilter();
292                sb.append( "<ogc:Filter>" );
293                if ( filter instanceof ComplexFilter ) {
294                    sb.append( "<ogc:And>" );
295                    sb.append( "<ogc:BBOX><PropertyName>" ).append( ds.getGeometryProperty().getPrefixedName() );
296                    sb.append( "</PropertyName>" );
297                    sb.append( GMLGeometryAdapter.exportAsBox( bbox ) );
298                    sb.append( "</ogc:BBOX>" );
299    
300                    // add filter as defined in the layers datasource description
301                    // to the filter expression
302                    org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation();
303                    sb.append( op.toXML() ).append( "</ogc:And>" );
304                } else {
305                    ArrayList featureIds = ( (FeatureFilter) filter ).getFeatureIds();
306                    if ( featureIds.size() > 1 ) {
307                        sb.append( "<ogc:And>" );
308                    }
309                    for ( int i = 0; i < featureIds.size(); i++ ) {
310                        FeatureId fid = (FeatureId) featureIds.get( i );
311                        sb.append( fid.toXML() );
312                    }
313                    if ( featureIds.size() > 1 ) {
314                        sb.append( "</ogc:And>" );
315                    }
316                }
317                sb.append( "</ogc:Filter></Query></GetFeature>" );
318            }
319    
320            // create dom representation of the request
321            Document doc = XMLTools.parse( new StringReader( sb.toString() ) );
322    
323            // create OGCWebServiceEvent object
324            IDGenerator idg = IDGenerator.getInstance();
325            GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
326    
327            
328            return gfr;
329        }
330    
331        /**
332         * creates a getCoverage request considering the getMap request and the filterconditions defined
333         * in the submitted <tt>DataSource</tt> object The request will be encapsualted within a
334         * <tt>OGCWebServiceEvent</tt>.
335         * 
336         * @param ds
337         * @return GetCoverage event object containing a GetCoverage request
338         * @throws InconsistentRequestException
339         */
340        private GetCoverage createGetCoverageRequest( AbstractDataSource ds )
341                                throws InconsistentRequestException {
342            
343    
344            Envelope bbox = this.handler.request.getBoundingBox();
345    
346            GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest();
347    
348            String crs = this.handler.request.getSrs();
349            if ( gcr != null && gcr.getDomainSubset().getRequestSRS() != null ) {
350                crs = gcr.getDomainSubset().getRequestSRS().getCode();
351            }
352            String format = this.handler.request.getFormat();
353            int pos = format.indexOf( '/' );
354            if ( pos > -1 )
355                format = format.substring( pos + 1, format.length() );
356            if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) {
357                format = gcr.getOutput().getFormat().getCode();
358            }
359            if ( format.indexOf( "svg" ) > -1 ) {
360                format = "tiff";
361            }
362    
363            String version = "1.0.0";
364            if ( gcr != null && gcr.getVersion() != null ) {
365                version = gcr.getVersion();
366            }
367            String lay = ds.getName().getPrefixedName();
368            if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) {
369                lay = gcr.getSourceCoverage();
370            }
371            String ipm = null;
372            if ( gcr != null && gcr.getInterpolationMethod() != null ) {
373                ipm = gcr.getInterpolationMethod().value;
374            }
375    
376            // TODO
377            // handle rangesets e.g. time and elevation
378            StringBuffer sb = new StringBuffer( 1000 );
379            sb.append( "service=WCS&request=GetCoverage" );
380            sb.append( "&version=" ).append( version );
381            sb.append( "&COVERAGE=" ).append( lay );
382            sb.append( "&CRS=" ).append( crs );
383            sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' ).append( bbox.getMin().getY() ).append( ',' ).append(
384                                                                                                                                    bbox.getMax().getX() ).append(
385                                                                                                                                                                   ',' ).append(
386                                                                                                                                                                                 bbox.getMax().getY() );
387            sb.append( "&WIDTH=" ).append( this.handler.request.getWidth() );
388            sb.append( "&HEIGHT=" ).append( this.handler.request.getHeight() );
389            sb.append( "&FORMAT=" ).append( format );
390            sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm );
391            try {
392                IDGenerator idg = IDGenerator.getInstance();
393                gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() );
394            } catch ( WCSException e ) {
395                throw new InconsistentRequestException( e.getMessage() );
396            } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) {
397                throw new InconsistentRequestException( e.getMessage() );
398            }
399    
400            
401            return gcr;
402    
403        }
404    
405        /**
406         * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS
407         * implementation accessed by this class is able to return the result of a request by calling
408         * the write-method.
409         * 
410         * @param result
411         *            to a GetXXX request
412         */
413        private void handleResponse( Object result ) {
414    
415            try {
416                if ( result instanceof ResultCoverage ) {
417                    handleGetCoverageResponse( (ResultCoverage) result );
418                } else if ( result instanceof FeatureResult ) {
419                    handleGetFeatureResponse( (FeatureResult) result );
420                } else if ( result instanceof GetMapResult ) {
421                    handleGetMapResponse( (GetMapResult) result );
422                } else {
423                    OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: "
424                                                                              + this.layer.getName(),
425                                                                              "unknown response format!" );
426                    this.handler.putTheme( this.index, exce );
427                }
428            } catch ( Exception e ) {
429                LOG.logError( "-", e );
430                OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: " + this.layer.getName(),
431                                                                          e.toString() );
432                this.handler.putTheme( this.index, exce );
433            }
434            // increase counter to indicate that one more layers requesting is
435            // completed
436            this.handler.increaseCounter();
437        }
438    
439        /**
440         * replaces all pixels within the passed image having a color that is defined to be transparent
441         * within their datasource with a transparent color.
442         * 
443         * @param img
444         * @return BufferedImage
445         */
446        private BufferedImage setTransparentColors( BufferedImage img ) {
447    
448            
449            Color[] colors = null;
450            if ( this.datasource.getType() == AbstractDataSource.LOCALWCS ) {
451                LocalWCSDataSource ds = (LocalWCSDataSource) this.datasource;
452                colors = ds.getTransparentColors();
453            } else {
454                RemoteWMSDataSource ds = (RemoteWMSDataSource) this.datasource;
455                colors = ds.getTransparentColors();
456            }
457    
458            if ( colors != null && colors.length > 0 ) {
459    
460                int[] clrs = new int[colors.length];
461                for ( int i = 0; i < clrs.length; i++ ) {
462                    clrs[i] = colors[i].getRGB();
463                }
464    
465                if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) {
466                    // if the incoming image does not allow transparency
467                    // it must be copyed to a image of ARGB type
468                    BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
469                    Graphics g = tmp.getGraphics();
470                    g.drawImage( img, 0, 0, null );
471                    g.dispose();
472                    img = tmp;
473                }
474    
475                // TODO
476                // should be replaced by a JAI operation
477                int w = img.getWidth();
478                int h = img.getHeight();
479                for ( int i = 0; i < w; i++ ) {
480                    for ( int j = 0; j < h; j++ ) {
481                        int col = img.getRGB( i, j );
482                        if ( shouldBeTransparent( clrs, col ) ) {
483                            img.setRGB( i, j, 0x00FFFFFF );
484                        }
485                    }
486                }
487            }
488            
489            return img;
490        }
491    
492        /**
493         * Should be transparent.
494         * 
495         * @param colors
496         * @param color
497         * @return boolean
498         */
499        private boolean shouldBeTransparent( int[] colors, int color ) {
500            for ( int i = 0; i < colors.length; i++ ) {
501                if ( colors[i] == color ) {
502                    return true;
503                }
504            }
505            return false;
506        }
507    
508        /**
509         * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt>
510         * and a <tt>Theme</tt> from it
511         * 
512         * @param response
513         * @throws Exception
514         */
515        private void handleGetMapResponse( GetMapResult response )
516                                throws Exception {
517            
518    
519            BufferedImage bi = (BufferedImage) response.getMap();
520            bi = setTransparentColors( bi );
521            GridCoverage gc = new ImageGridCoverage( null, this.handler.request.getBoundingBox(), bi );
522            org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( this.layer.getName(), gc );
523            Theme theme = MapFactory.createTheme( this.datasource.getName().getPrefixedName(), rl );
524            this.handler.putTheme( this.index, theme );
525            
526        }
527    
528        /**
529         * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a
530         * <tt>Theme</tt> from it
531         * 
532         * @param response
533         * @throws Exception
534         */
535        private void handleGetFeatureResponse( FeatureResult response )
536                                throws Exception {
537            
538    
539            FeatureCollection fc = null;
540    
541            Object o = response.getResponse();
542    
543            if ( o instanceof FeatureCollection ) {
544                fc = (FeatureCollection) o;
545            } else {
546                throw new Exception( "unknown data format at a GetFeature response" );
547            }
548            org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( this.layer.getName(), this.handler.reqCRS, fc );
549    
550            this.handler.putTheme( this.index, MapFactory.createTheme( this.datasource.getName().getPrefixedName(), fl,
551                                                                       new UserStyle[] { this.style } ) );
552            
553    
554        }
555    
556        /**
557         * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a
558         * <tt>Theme</tt> from it
559         * 
560         * @param response
561         * @throws Exception
562         */
563        private void handleGetCoverageResponse( ResultCoverage response )
564                                throws Exception {
565            
566    
567            ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
568            BufferedImage bi = gc.getAsImage( -1, -1 );
569    
570            bi = setTransparentColors( bi );
571    
572            gc = new ImageGridCoverage( null, this.handler.request.getBoundingBox(), bi );
573    
574            org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( this.layer.getName(), gc );
575    
576            this.handler.putTheme( this.index, MapFactory.createTheme( this.datasource.getName().getPrefixedName(), rl ) );
577            
578        }
579    
580    }