/* * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package thredds.wcs.v1_0_0_Plus; import java.io.File; import java.text.ParseException; import java.util.Arrays; import java.util.List; import java.util.ArrayList; import ucar.nc2.time.CalendarDateRange; import ucar.unidata.geoloc.ogc.EPSG_OGC_CF_Helper; import ucar.unidata.geoloc.LatLonRect; import ucar.unidata.geoloc.LatLonPointImpl; import ucar.nc2.dt.GridCoordSystem; import ucar.nc2.dataset.CoordinateAxis1D; import ucar.nc2.units.DateRange; import ucar.nc2.units.DateType; import ucar.ma2.Range; import ucar.ma2.InvalidRangeException; /** * @author edavis * @since 4.0 */ public class GetCoverage extends WcsRequest { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( GetCoverage.class ); private WcsCoverage coverage; private LatLonRect requestLatLonBBox; private AxisSubset requestVertSubset; private CalendarDateRange timeRange; private List<String> rangeSubset; private Format format; // Requests for GeoTIFF encoding must be for a single time, single vertical level, and single range field. private boolean isSingleTimeRequest = false; private boolean isSingleVerticalRequest = false; private boolean isSingleRangeFieldRequest = false; public GetCoverage( Operation operation, String version, WcsDataset dataset, String coverageId, String crs, String responseCRS, String bbox, String time, String rangeSubset, String format ) throws WcsException { super( operation, version, dataset); // Validate coverage ID parameter. if ( coverageId == null ) throw new WcsException( WcsException.Code.MissingParameterValue, "coverage", "Coverage identifier required." ); if ( !this.getDataset().isAvailableCoverageName( coverageId ) ) throw new WcsException( WcsException.Code.InvalidParameterValue, "coverage", "Unknown coverage identifier <" + coverageId + ">." ); this.coverage = this.getDataset().getAvailableCoverage( coverageId ); if ( this.coverage == null ) // Double check just in case. throw new WcsException( WcsException.Code.InvalidParameterValue, "coverage", "Unknown coverage identifier <" + coverageId + ">." ); // Assign and validate request and response CRS parameters. if ( crs == null ) throw new WcsException( WcsException.Code.MissingParameterValue, "CRS", "Request CRS required."); if ( ! crs.equalsIgnoreCase( this.coverage.getDefaultRequestCrs() ) ) throw new WcsException( WcsException.Code.InvalidParameterValue, "CRS", "Request CRS <" + crs + "> not allowed <" + this.coverage.getDefaultRequestCrs() + ">." ); String nativeCRS = EPSG_OGC_CF_Helper.getWcs1_0CrsId( coverage.getCoordinateSystem().getProjection() ); if ( nativeCRS == null ) throw new WcsException( WcsException.Code.CoverageNotDefined, "", "Coverage not in recognized CRS. (???)"); // Response CRS not required if data is in latLon ("OGC:CRS84"). Default is request CRS. if ( responseCRS == null ) { if ( ! nativeCRS.equalsIgnoreCase( this.coverage.getDefaultRequestCrs())) throw new WcsException( WcsException.Code.MissingParameterValue, "Response_CRS", "Response CRS required." ); } else if ( ! responseCRS.equalsIgnoreCase( nativeCRS)) throw new WcsException( WcsException.Code.InvalidParameterValue, "response_CRS", "Respnse CRS <" + responseCRS + "> not allowed <" + nativeCRS + ">." ); // Assign and validate BBOX and TIME parameters. // ----- // WCS Spec says at least one of BBOX and TIME are required in a request. // We will not require, default is everything. // // if ( bbox == null && time == null ) // throw new WcsException( WcsException.Code.MissingParameterValue, "BBOX", "BBOX and/or TIME required."); // ----- if ( bbox != null && ( ! bbox.equals( "" ) ) ) { String[] bboxSplit = splitBoundingBox( bbox); requestLatLonBBox = genRequestLatLonBoundingBox( bboxSplit, coverage.getCoordinateSystem()); CoordinateAxis1D vertAxis = this.coverage.getCoordinateSystem().getVerticalAxis(); if ( vertAxis != null ) requestVertSubset = genRequestVertSubset( bboxSplit, vertAxis ); } if ( time != null && ( ! time.equals( "" )) ) timeRange = parseTime( time); // WIDTH, HEIGHT, DEPTH parameters not needed since the only interpolation method is "NONE". // RESX, RESY, RESZ parameters not needed since the only interpolation method is "NONE". // Assign and validate RangeSubset parameter. this.rangeSubset = parseRangeSubset( rangeSubset);//, coverage.getRange()); // Assign and validate FORMAT parameter. if ( format == null || format.equals( "" )) { log.error( "GetCoverage(): FORMAT parameter required."); throw new WcsException( WcsException.Code.InvalidParameterValue, "FORMAT", "FORMAT parameter required."); } try { this.format = Format.valueOf( format.trim()); } catch ( IllegalArgumentException e ) { String msg = "Unknown format value [" + format + "]."; log.error( "GetCoverage(): " + msg ); throw new WcsException( WcsException.Code.InvalidParameterValue, "FORMAT", msg ); } if ( ! this.coverage.isSupportedCoverageFormat( this.format )) { String msg = "Unsupported format value [" + format + "]."; log.error( "GetCoverage(): " + msg ); throw new WcsException( WcsException.Code.InvalidParameterValue, "FORMAT", msg ); } if ( this.format == WcsRequest.Format.GeoTIFF || this.format == WcsRequest.Format.GeoTIFF_Float) { // Check that request is for one time and one vertical level and one range field // since that is all we support for GeoTIFF[-Float]. if ( ! this.isSingleTimeRequest && ! this.isSingleVerticalRequest && ! this.isSingleRangeFieldRequest ) { StringBuilder msgB = new StringBuilder( "GeoTIFF supported only for requests at a single time ["); if ( time != null ) msgB.append( time); msgB.append( "] and a single vertical level ["); if ( bbox != null ) msgB.append( bbox); msgB.append( "] and a single range field ["); if ( rangeSubset != null ) msgB.append( rangeSubset ); msgB.append( "]."); log.error( "GetCoverage(): " + msgB ); throw new WcsException( WcsException.Code.InvalidParameterValue, "FORMAT", msgB.toString() ); } } } public Format getFormat() { return format; } public File writeCoverageDataToFile() throws WcsException { return this.coverage.writeCoverageDataToFile( this.format, this.requestLatLonBBox, this.requestVertSubset, this.rangeSubset, this.timeRange); } private String[] splitBoundingBox( String bbox) throws WcsException { if ( bbox == null || bbox.equals( "" ) ) return null; String[] bboxSplit = bbox.split( "," ); if ( bboxSplit.length != 4 && bboxSplit.length != 6 ) { log.error( "splitBoundingBox(): BBOX <" + bbox + "> must be \"minx,miny,maxx,maxy[,minz,maxz]\"." ); throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", "BBOX <" + bbox + "> not in expected format \"minx,miny,maxx,maxy[,minz,maxz]\"." ); } return bboxSplit; } private LatLonRect genRequestLatLonBoundingBox( String[] bboxSplit, GridCoordSystem gcs) throws WcsException { if ( bboxSplit == null || gcs == null ) return null; if ( bboxSplit.length < 4 ) throw new IllegalArgumentException( "BBOX contains fewer than four items \"" + Arrays.toString(bboxSplit) + "\"."); double minx; double miny; double maxx; double maxy; try { minx = Double.parseDouble( bboxSplit[0] ); miny = Double.parseDouble( bboxSplit[1] ); maxx = Double.parseDouble( bboxSplit[2] ); maxy = Double.parseDouble( bboxSplit[3] ); } catch ( NumberFormatException e ) { String message = "BBOX item(s) have incorrect number format [not double] <" + Arrays.toString(bboxSplit) + ">."; log.error( "genRequestLatLonBoundingBox(): " + message + " - " + e.getMessage()); throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", message ); } LatLonPointImpl minll = new LatLonPointImpl( miny, minx ); LatLonPointImpl maxll = new LatLonPointImpl( maxy, maxx ); LatLonRect requestLatLonRect = new LatLonRect( minll, maxll ); // LatLonRect covLatLonRect = gcs.getLatLonBoundingBox(); // if ( ! requestLatLonRect.containedIn( covLatLonRect)) // { // log.error( "genRequestLatLonBoundingBox(): BBOX <" + bbox + "> not contained in coverage BBOX <"+ covLatLonRect.toString2()+">."); // throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", "BBOX <" + bbox + "> not contained in coverage."); // } return requestLatLonRect; } private AxisSubset genRequestVertSubset( String[] bboxSplit, CoordinateAxis1D vertAxis ) throws WcsException { // Check if no vertical range request. if ( bboxSplit == null || bboxSplit.length == 4 ) { // If there is no vertical axis (or only one level), still a single level request. if ( vertAxis == null || vertAxis.getShape(0) == 1 ) this.isSingleVerticalRequest = true; return null; } if ( bboxSplit.length != 6 ) { String message = "BBOX must have 4 or 6 items [" + Arrays.toString(bboxSplit) + "]."; log.error( "genRequestVertSubset(): " + message ); throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", message ); } // If there is no vertical axis (or only one level), still a single level request. if ( vertAxis == null || vertAxis.getShape(0) == 1 ) { this.isSingleVerticalRequest = true; return null; } double minz; double maxz; try { minz = Double.parseDouble( bboxSplit[4] ); maxz = Double.parseDouble( bboxSplit[5] ); } catch ( NumberFormatException e ) { String message = "BBOX item(s) have incorrect number format (not double) [" + Arrays.toString(bboxSplit) + "]."; log.error( "genRequestVertSubset(): " + message + " - " + e.getMessage() ); throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", message ); } AxisSubset axisSubset = new AxisSubset( vertAxis, minz, maxz, 1 ); Range range; try { range = axisSubset.getRange(); } catch ( InvalidRangeException e ) { String message = "BBOX results in invalid array index range [" + Arrays.toString(bboxSplit) + "]."; log.error( "genRequestVertSubset(): " + message + " - " + e.getMessage() ); throw new WcsException( WcsException.Code.InvalidParameterValue, "BBOX", message ); } if ( range.length() == 1 ) { // Check whether vertical range results in a single level. this.isSingleVerticalRequest = true; return null; } return axisSubset; } private CalendarDateRange parseTime( String time ) throws WcsException { if ( time == null || time.equals( "")) return null; DateRange dateRange; try { if (time.contains(",")) { log.error( "parseTime(): Unsupported time parameter (list) <" + time + ">."); throw new WcsException( WcsException.Code.InvalidParameterValue, "TIME", "Not currently supporting time list." ); //String[] timeList = time.split( "," ); //dateRange = new DateRange( date, date, null, null ); } else if (time.contains("/")) { String[] timeRange = time.split( "/" ); if ( timeRange.length != 2) { log.error( "parseTime(): Unsupported time parameter (time range with resolution) <" + time + ">."); throw new WcsException( WcsException.Code.InvalidParameterValue, "TIME", "Not currently supporting time range with resolution."); } dateRange = new DateRange( new DateType( timeRange[0], null, null ), new DateType( timeRange[1], null, null ), null, null ); } else { DateType date = new DateType( time, null, null ); dateRange = new DateRange( date, date, null, null ); this.isSingleTimeRequest = true; } } catch ( ParseException e ) { log.error( "parseTime(): Failed to parse time parameter <" + time + ">: " + e.getMessage() ); throw new WcsException( WcsException.Code.InvalidParameterValue, "TIME", "Invalid time format <" + time + ">." ); } return CalendarDateRange.of(dateRange); } private List<String> parseRangeSubset( String rangeSubset) throws WcsException { List<String> response = new ArrayList<>(); // Default is to return all fields. if ( rangeSubset == null || rangeSubset.equals( "" ) ) { response.addAll( this.coverage.getRangeFieldNames() ); if ( response.size() == 1 ) this.isSingleRangeFieldRequest = true; return response; } // Split the rangeSubset request into fieldSubset requests. String[] fieldSubsetArray; if (!rangeSubset.contains(";")) { fieldSubsetArray = new String[1]; fieldSubsetArray[0] = rangeSubset; } else { fieldSubsetArray = rangeSubset.split( ";" ); } for ( String curFieldSubset : fieldSubsetArray ) { if ( this.coverage.isRangeFieldName( curFieldSubset) ) response.add( curFieldSubset ); else { String message = "Requested range field <" + curFieldSubset + "> not available."; log.warn( "parseRangeSubset(): " + message ); throw new WcsException( WcsException.Code.InvalidParameterValue, "RangeSubset", message ); } } if ( response.size() == 1 ) this.isSingleRangeFieldRequest = true; return response; } }