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 }