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 }