    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }