001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wcs/getcoverage/GetCoverage.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.wcs.getcoverage;
037    
038    import java.net.URI;
039    import java.util.List;
040    import java.util.Map;
041    
042    import org.deegree.datatypes.Code;
043    import org.deegree.datatypes.time.TimeSequence;
044    import org.deegree.framework.log.ILogger;
045    import org.deegree.framework.log.LoggerFactory;
046    import org.deegree.framework.util.KVP2Map;
047    import org.deegree.framework.util.StringTools;
048    import org.deegree.framework.xml.NamespaceContext;
049    import org.deegree.framework.xml.XMLTools;
050    import org.deegree.model.coverage.grid.Grid;
051    import org.deegree.model.crs.CRSFactory;
052    import org.deegree.model.crs.CoordinateSystem;
053    import org.deegree.model.crs.UnknownCRSException;
054    import org.deegree.model.spatialschema.Envelope;
055    import org.deegree.model.spatialschema.GeometryFactory;
056    import org.deegree.model.spatialschema.Position;
057    import org.deegree.ogcbase.CommonNamespaces;
058    import org.deegree.ogcbase.ExceptionCode;
059    import org.deegree.ogcbase.GMLDocument;
060    import org.deegree.ogcwebservices.InvalidParameterValueException;
061    import org.deegree.ogcwebservices.MissingParameterValueException;
062    import org.deegree.ogcwebservices.OGCWebServiceException;
063    import org.deegree.ogcwebservices.wcs.InterpolationMethod;
064    import org.deegree.ogcwebservices.wcs.WCSException;
065    import org.deegree.ogcwebservices.wcs.WCSRequestBase;
066    import org.w3c.dom.Document;
067    import org.w3c.dom.Element;
068    import org.w3c.dom.Node;
069    
070    /**
071     * encapsulates a WCS GetCoverage request
072     *
073     * @version $Revision: 24440 $
074     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
075     * @author last edited by: $Author: apoth $
076     *
077     * @version 1.0. $Revision: 24440 $, $Date: 2010-05-18 11:53:54 +0200 (Di, 18 Mai 2010) $
078     *
079     * @since 2.0
080     */
081    
082    public class GetCoverage extends WCSRequestBase {
083    
084        private static final ILogger LOG = LoggerFactory.getLogger( GetCoverage.class );
085    
086        private static final long serialVersionUID = 44735033754048955L;
087    
088        private static final NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
089    
090        private String sourceCoverage = null;
091    
092        private DomainSubset domainSubset = null;
093    
094        private RangeSubset rangeSubset = null;
095    
096        private InterpolationMethod interpolationMethod = null;
097    
098        private Output output = null;
099    
100        /**
101         * @param id
102         * @param version
103         * @param sourceCoverage
104         * @param domainSubset
105         * @param output
106         * @throws WCSException
107         * @throws OGCWebServiceException
108         */
109        public GetCoverage( String id, String version, String sourceCoverage, DomainSubset domainSubset, Output output )
110                                throws WCSException, OGCWebServiceException {
111            this( id, version, sourceCoverage, domainSubset, null, null, output );
112        }
113    
114        /**
115         * @param id
116         * @param version
117         * @param sourceCoverage
118         * @param domainSubset
119         * @param interpolationMethod
120         * @param output
121         * @throws WCSException
122         * @throws OGCWebServiceException
123         */
124        public GetCoverage( String id, String version, String sourceCoverage, DomainSubset domainSubset,
125                            InterpolationMethod interpolationMethod, Output output ) throws WCSException,
126                                OGCWebServiceException {
127            this( id, version, sourceCoverage, domainSubset, null, interpolationMethod, output );
128        }
129    
130        /**
131         * @param id
132         * @param version
133         * @param sourceCoverage
134         * @param domainSubset
135         * @param rangeSubset
136         * @param output
137         * @throws WCSException
138         * @throws OGCWebServiceException
139         */
140        public GetCoverage( String id, String version, String sourceCoverage, DomainSubset domainSubset,
141                            RangeSubset rangeSubset, Output output ) throws WCSException, OGCWebServiceException {
142            this( id, version, sourceCoverage, domainSubset, rangeSubset, null, output );
143        }
144    
145        /**
146         * @param id
147         * @param version
148         * @param sourceCoverage
149         * @param domainSubset
150         * @param rangeSubset
151         * @param interpolationMethod
152         * @param output
153         * @throws WCSException
154         * @throws OGCWebServiceException
155         */
156        public GetCoverage( String id, String version, String sourceCoverage, DomainSubset domainSubset,
157                            RangeSubset rangeSubset, InterpolationMethod interpolationMethod, Output output )
158                                throws WCSException, OGCWebServiceException {
159            super( id, version );
160            if ( sourceCoverage == null || sourceCoverage.length() == 0 ) {
161                throw new WCSException( "sourceCoverage must be a valid string with length > 0" );
162            }
163            if ( domainSubset == null ) {
164                throw new WCSException( "domainSubset must be <> null in GetCoverage" );
165            }
166            if ( output == null ) {
167                throw new WCSException( "output must be <> null in GetCoverage" );
168            }
169            this.sourceCoverage = sourceCoverage;
170            this.domainSubset = domainSubset;
171            this.rangeSubset = rangeSubset;
172            this.interpolationMethod = interpolationMethod;
173            this.output = output;
174        }
175    
176        /**
177         * creates a GetCoverage request from its KVP representation
178         *
179         * @param id
180         *            unique ID of the request
181         * @param kvp
182         *            request
183         * @return created <tt>GetCoverage</tt>
184         * @throws OGCWebServiceException
185         *             will be thrown if something general is wrong
186         * @throws WCSException
187         *             will be thrown if a WCS/GetCoverage specific part of the request is erroreous
188         */
189        public static GetCoverage create( String id, String kvp )
190                                throws OGCWebServiceException, WCSException {
191            Map<String, String> map = KVP2Map.toMap( kvp );
192            map.put( "ID", id );
193            return create( map );
194        }
195    
196        /**
197         * creates a GetCoverage request from its KVP representation
198         *
199         * @param map
200         *            request
201         * @return created <tt>GetCoverage</tt>
202         * @throws OGCWebServiceException
203         *             will be thrown if something general is wrong
204         * @throws MissingParameterValueException
205         * @throws InvalidParameterValueException
206         * @throws WCSException
207         *             will be thrown if a WCS/GetCoverage specific part of the request is erroreous
208         */
209        public static GetCoverage create( Map<String, String> map )
210                                throws OGCWebServiceException, MissingParameterValueException,
211                                InvalidParameterValueException {
212    
213            String version = map.remove( "VERSION" );
214            if ( version == null ) {
215                throw new MissingParameterValueException( "WCS", "'version' must be set" );
216            }
217            if ( !"1.0.0".equals( version ) ) {
218                ExceptionCode ecode = ExceptionCode.INVALIDPARAMETERVALUE;
219                throw new InvalidParameterValueException( "WCS", "'version' must be 1.0.0", ecode );
220            }
221            String coverage = map.remove( "COVERAGE" );
222            String crs = map.remove( "CRS" );
223            if ( crs == null ) {
224                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
225                throw new MissingParameterValueException( "WCS", "'crs' is missing", code );
226            }
227            String response_crs = map.remove( "RESPONSE_CRS" );
228            if ( response_crs == null ) {
229                response_crs = crs;
230            }
231            String format = map.remove( "FORMAT" );
232            Output output = createOutput( response_crs, null, format, null );
233            SpatialSubset sps = createSpatialSubset( map, crs );
234    
235            String time = map.remove( "TIME" );
236            TimeSequence temporalSubset = null;
237            if ( time != null ) {
238                temporalSubset = new TimeSequence( time );
239            }
240    
241            Code code = new Code( crs, null );
242            DomainSubset domainSubset = new DomainSubset( code, sps, temporalSubset );
243    
244            String except = map.remove( "EXCEPTIONS" );
245            if ( except == null ) {
246                except = "application/vnd.ogc.se_xml";
247            } else if ( !except.equals( "application/vnd.ogc.se_xml" ) ) {
248                ExceptionCode ecode = ExceptionCode.INVALIDPARAMETERVALUE;
249                throw new InvalidParameterValueException( "WCS", "exceptions != application/vnd.ogc.se_xml", ecode );
250            }
251            String id = map.remove( "ID" );
252    
253            GetCoverage gc = new GetCoverage( id, version, coverage, domainSubset, null, null, output );
254            gc.validate();
255            return gc;
256        }
257    
258        /**
259         * creates a GetCoverage request from its XML representation
260         *
261         * @param id
262         *            unique ID of the request
263         * @param doc
264         *            XML representation of the request
265         * @return created <tt>DescribeCoverage</tt>
266         * @throws OGCWebServiceException
267         *             will be thrown if something general is wrong
268         * @throws WCSException
269         *             will be thrown if a WCS/GetCoverage specific part of the request is erroreous
270         */
271        public static GetCoverage create( String id, Document doc )
272                                throws OGCWebServiceException, WCSException {
273    
274            GetCoverage gc = null;
275            try {
276    
277                String version = XMLTools.getNodeAsString( doc, "/wcs:GetCoverage/@version", nsContext, null );
278                if ( version == null ) {
279                    throw new MissingParameterValueException( "WCS", "'version' must be set" );
280                }
281                if ( !"1.0.0".equals( version ) ) {
282                    ExceptionCode ecode = ExceptionCode.INVALIDPARAMETERVALUE;
283                    throw new InvalidParameterValueException( "WCS", "'version' must be 1.0.0", ecode );
284                }
285    
286                String coverage = XMLTools.getRequiredNodeAsString( doc, "/wcs:GetCoverage/wcs:sourceCoverage", nsContext );
287                String interpol = XMLTools.getNodeAsString( doc, "/wcs:GetCoverage/wcs:interpolationMethod", nsContext,
288                                                            null );
289                InterpolationMethod interpolMeth = null;
290                if ( interpol == null || "nearest neighbor".equals( interpol ) ) {
291                    interpolMeth = new InterpolationMethod( "nearest neighbor" );
292                }
293                String path = "/wcs:GetCoverage/wcs:domainSubset/wcs:spatialSubset";
294                List<Node> nl = XMLTools.getNodes( doc, path, nsContext );
295                SpatialSubset sp = null;
296                if ( nl.size() > 0 ) {
297                    Node node = (Node) nl.get( 0 );
298                    sp = createSpatialSubset( (Element) node );
299                } else {
300                    // TODO
301                    // temporal subset
302                }
303                // TODO
304                // path = "/wcs:GetCoverage/wcs:rangeSubset/wcs:axisSubset";
305                // nl = XMLTools.getXPath(path, doc, nsContext);
306                // evaluate possible ranges; e.g.time, extent
307                String format = XMLTools.getRequiredNodeAsString( doc, "/wcs:GetCoverage/wcs:output/wcs:format", nsContext );
308                // use crs defined for the requested envelope if no CRS is defined
309                // in the request
310                String crsName = "EPSG:4326";
311                if ( sp.getEnvelope().getCoordinateSystem() != null ) {
312                    crsName = sp.getEnvelope().getCoordinateSystem().getPrefixedName();
313                }
314                String crs = XMLTools.getNodeAsString( doc, "/wcs:GetCoverage/wcs:output/wcs:crs", nsContext, crsName );
315    
316                String ipm = XMLTools.getNodeAsString( doc, "/wcs:GetCoverage/wcs:interpolationMethod", nsContext, null );
317                if ( ipm != null && !ipm.equals( "nearest neighbor" ) ) {
318                    throw new InvalidParameterValueException( "interpolationMethod must "
319                                                              + "have the value 'nearest neighbor'" );
320                }
321    
322                Output output = createOutput( crs, null, format, null );
323                DomainSubset domainSubset = new DomainSubset( new Code( crsName ), sp );
324    
325                gc = new GetCoverage( id, version, coverage, domainSubset, null, interpolMeth, output );
326            } catch ( Exception e ) {
327                ExceptionCode code = ExceptionCode.INVALID_FORMAT;
328                throw new WCSException( "WCS", StringTools.stackTraceToString( e ), code );
329            }
330    
331            gc.validate();
332            return gc;
333        }
334    
335        /**
336         * @param element
337         * @return a new Spatial subset
338         * @throws WCSException
339         */
340        private static SpatialSubset createSpatialSubset( Element element )
341                                throws WCSException {
342            SpatialSubset sp = null;
343            try {
344                List<Node> nl = XMLTools.getNodes( element, "gml:Envelope", nsContext );
345                Envelope env = GMLDocument.parseEnvelope( (Element) nl.get( 0 ) );
346                nl = XMLTools.getNodes( element, "gml:Grid", nsContext );
347                Grid grid = GMLDocument.parseGrid( (Element) nl.get( 0 ) );
348                sp = new SpatialSubset( env, grid.getGridEnvelope() );
349            } catch ( Exception e ) {
350                ExceptionCode code = ExceptionCode.INVALID_FORMAT;
351                throw new WCSException( "WCS", StringTools.stackTraceToString( e ), code );
352            }
353            return sp;
354        }
355    
356        /**
357         * @param map
358         * @param crs
359         * @return a new SpatialSubset with given crs
360         * @throws WCSException
361         */
362        public static final SpatialSubset createSpatialSubset( Map<String,String> map, String crs )
363                                throws WCSException {
364            Envelope envelope = createEnvelope( map, crs );
365    
366            int width = (int) getNumber( (String) map.remove( "WIDTH" ), "WIDTH" );
367            int height = (int) getNumber( (String) map.remove( "HEIGHT" ), "HEIGHT" );
368            int depth = (int) getNumber( (String) map.remove( "DEPTH" ), "DEPTH" );
369    
370            double resx = getNumber( (String) map.remove( "RESX" ), "RESX" );
371            double resy = getNumber( (String) map.remove( "RESY" ), "RESY" );
372            double resz = getNumber( (String) map.remove( "RESZ" ), "RESZ" );
373    
374            Position low = null;
375            Position high = null;
376            if ( width > 0 && height > 0 ) {
377                if ( depth > 0 ) {
378                    low = GeometryFactory.createPosition( 0, 0, 0 );
379                    high = GeometryFactory.createPosition( width - 1, height - 1, depth );
380                } else {
381                    low = GeometryFactory.createPosition( 0, 0 );
382                    high = GeometryFactory.createPosition( width - 1, height - 1 );
383                }
384            } else if ( resx > 0 && resy > 0 ) {
385                if ( resz > 0 ) {
386                    ExceptionCode code = ExceptionCode.INVALIDPARAMETERVALUE;
387                    throw new WCSException( "WCS", "resz is not supported yet", code );
388                }
389                width = (int) Math.round( envelope.getWidth() / resx );
390                height = (int) Math.round( envelope.getHeight() / resy );
391                low = GeometryFactory.createPosition( 0, 0 );
392                high = GeometryFactory.createPosition( width, height );
393            } else {
394                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
395                throw new WCSException( "WCS", "width/height or resx/resy must be set", code );
396            }
397    
398            Envelope grid = GeometryFactory.createEnvelope( low, high, null );
399    
400            return new SpatialSubset( envelope, grid );
401    
402        }
403    
404        /**
405         * @param map
406         * @return an envelope.
407         * @throws WCSException
408         */
409        private static Envelope createEnvelope( Map<String,String> map, String crs )
410                                throws WCSException {
411            String tmp = (String) map.remove( "BBOX" );
412            double[] bbox = null;
413            if ( tmp != null ) {
414                try {
415                    bbox = StringTools.toArrayDouble( tmp, "," );
416                } catch ( Exception e ) {
417                    ExceptionCode code = ExceptionCode.INVALIDPARAMETERVALUE;
418                    throw new WCSException( "WCS", "can't read BBOX", code );
419                }
420    
421                Position min = null;
422                Position max = null;
423                if ( bbox.length == 4 ) {
424                    min = GeometryFactory.createPosition( bbox[0], bbox[1] );
425                    max = GeometryFactory.createPosition( bbox[2], bbox[3] );
426                } else {
427                    min = GeometryFactory.createPosition( bbox[0], bbox[1], bbox[2] );
428                    max = GeometryFactory.createPosition( bbox[3], bbox[4], bbox[5] );
429                }
430                CoordinateSystem srs;
431                try {
432                    srs = CRSFactory.create( crs );
433                } catch ( UnknownCRSException e ) {
434                    throw new WCSException( GetCoverage.class.getName(), e.getMessage() );
435                }
436                return GeometryFactory.createEnvelope( min, max, srs );
437            }
438            return null;
439    
440        }
441    
442        /**
443         * creates an <tt>Output</tt> object for a GetCoverage request
444         *
445         * @param response_crs
446         * @param crsNS
447         * @param format
448         * @param formatNS
449         * @return an Output
450         * @throws WCSException
451         *             will be thrown if the response_crs prefix isn't a valid URI
452         */
453        public static final Output createOutput( String response_crs, String crsNS, String format, String formatNS )
454                                throws WCSException {
455            URI crsURI = null;
456            if ( crsNS != null ) {
457                try {
458                    crsURI = new URI( crsNS );
459                } catch ( Exception e ) {
460                    throw new WCSException( "invalid response crs namespace: " + crsNS );
461                }
462            }
463    
464            URI formatURI = null;
465            if ( formatNS != null ) {
466                try {
467                    formatURI = new URI( formatNS );
468                } catch ( Exception e ) {
469                    throw new WCSException( "invalid response crs namespace: " + formatNS );
470                }
471            }
472    
473            Code crs = new Code( response_crs, crsURI );
474            Code cformat = new Code( format, formatURI );
475            return new Output( crs, cformat );
476        }
477    
478        /**
479         * @param val
480         * @param name
481         * @return a Number
482         * @throws WCSException
483         */
484        private static double getNumber( String val, String name )
485                                throws WCSException {
486            if ( val == null )
487                return -1;
488            double d = -1;
489            try {
490                d = Double.parseDouble( val );
491            } catch ( Exception e ) {
492                ExceptionCode code = ExceptionCode.INVALIDPARAMETERVALUE;
493                throw new WCSException( "WCS", name + " isn't a valid number format", code );
494            }
495            return d;
496        }
497    
498        /**
499         * @return Returns the domainSubset.
500         */
501        public DomainSubset getDomainSubset() {
502            return domainSubset;
503        }
504    
505        /**
506         * @return Returns the interpolationMethod.
507         */
508        public InterpolationMethod getInterpolationMethod() {
509            return interpolationMethod;
510        }
511    
512        /**
513         * @return Returns the output.
514         */
515        public Output getOutput() {
516            return output;
517        }
518    
519        /**
520         * @return Returns the rangeSubset.
521         */
522        public RangeSubset getRangeSubset() {
523            return rangeSubset;
524        }
525    
526        /**
527         * @return Returns the sourceCoverage.
528         *
529         */
530        public String getSourceCoverage() {
531            return sourceCoverage;
532        }
533    
534        /**
535         * @throws WCSException
536         */
537        protected void validate()
538                                throws WCSException {
539    
540            if ( getVersion() == null ) {
541                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
542                throw new WCSException( "WCS", "'version' is missing", code );
543            }
544    
545            if ( getSourceCoverage() == null ) {
546                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
547                throw new WCSException( "WCS", "'coverage' is missing", code );
548            }
549    
550            DomainSubset ds = getDomainSubset();
551            if ( ds.getRequestSRS() == null ) {
552                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
553                throw new WCSException( "WCS", "'crs' is missing", code );
554            }
555    
556            if ( ds.getSpatialSubset() == null && ds.getTemporalSubset() == null ) {
557                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
558                throw new WCSException( "WCS", "either temporal subset or spatial " + "subset must be defined", code );
559            }
560    
561            if ( getOutput().getFormat() == null ) {
562                ExceptionCode code = ExceptionCode.MISSINGPARAMETERVALUE;
563                throw new WCSException( "WCS", "'format' is missing", code );
564            }
565    
566        }
567    
568    
569    
570        @Override
571        public String getRequestParameter()
572                                throws OGCWebServiceException {
573            StringBuffer sb = new StringBuffer(1000);
574            sb.append( "REQUEST=GetCoverage&VERSION=1.0.0&coverage=" );
575            sb.append( getSourceCoverage() ).append( "&TRANSPARENT=true&Format=" );
576            sb.append( getOutput().getFormat().getCode() ).append( "&EXCEPTIONS=application/vnd.ogc.se_xml&Width=" );
577            Envelope grid = (Envelope)getDomainSubset().getSpatialSubset().getGrid();
578            sb.append( Math.round( grid.getWidth( )) ).append( "&height=" ).append( Math.round( grid.getHeight() ) );
579            sb.append( "&crs=" ).append( getOutput().getCrs().getCode() ).append( "&bbox=" );
580            Envelope bbox = getDomainSubset().getSpatialSubset().getEnvelope();
581            sb.append( bbox.getMin().getX() ).append( ',' ).append( bbox.getMin().getY() ).append( ',' );
582            sb.append( bbox.getMax().getX() ).append( ',' ).append( bbox.getMax().getY() );
583    
584            LOG.logDebug( "GetCoverage request parameters", sb  );
585    
586            return sb.toString();
587    
588        }
589    
590        @Override
591        public String toString() {
592            String response = super.toString();
593            response += "\nOutput: " + output;
594            response += "\ndomainSubset: " + domainSubset;
595            response += "\nsourceCoverage: " + sourceCoverage;
596            response += "\ninterpolationMethod: " + interpolationMethod;
597            return response;
598        }
599    
600    }