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