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