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