/* * 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 org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Attribute; import org.jdom2.output.XMLOutputter; import java.util.List; import java.io.PrintWriter; import java.io.IOException; import ucar.nc2.dt.GridCoordSystem; import ucar.nc2.dataset.CoordinateAxis1D; import ucar.nc2.dataset.CoordinateAxis1DTime; import ucar.nc2.time.CalendarDate; import ucar.unidata.geoloc.LatLonRect; import ucar.unidata.geoloc.LatLonPoint; /** * _more_ * * @author edavis * @since 4.0 */ public class DescribeCoverage extends WcsRequest { // private static org.slf4j.Logger log = // org.slf4j.LoggerFactory.getLogger( DescribeCoverage.class ); private List<String> coverages; private Document describeCoverageDoc; public DescribeCoverage(Operation operation, String version, WcsDataset dataset, List<String> coverages) { super(operation, version, dataset); this.coverages = coverages; if (this.coverages == null) throw new IllegalArgumentException("Non-null coverage list required."); if (this.coverages.size() < 1) throw new IllegalArgumentException("Coverage list must contain at least one ID <" + this.coverages.size() + ">."); StringBuilder badCovIds = new StringBuilder(); for (String curCov : coverages) { if (!this.getDataset().isAvailableCoverageName(curCov)) { if (badCovIds.length() > 0) badCovIds.append(", "); badCovIds.append(curCov); } } if (badCovIds.length() > 0) throw new IllegalArgumentException("Coverage ID list contains one or more unknown IDs <" + badCovIds + ">."); } public Document getDescribeCoverageDoc() { if (this.describeCoverageDoc == null) describeCoverageDoc = generateDescribeCoverageDoc(); return describeCoverageDoc; } public void writeDescribeCoverageDoc(PrintWriter pw) throws IOException { XMLOutputter xmlOutputter = new XMLOutputter(org.jdom2.output.Format.getPrettyFormat()); xmlOutputter.output(getDescribeCoverageDoc(), pw); } public Document generateDescribeCoverageDoc() { // CoverageDescription (wcs) [1] Element coverageDescriptionsElem = new Element("CoverageDescription", wcsNS); coverageDescriptionsElem.addNamespaceDeclaration(gmlNS); coverageDescriptionsElem.addNamespaceDeclaration(xlinkNS); coverageDescriptionsElem.setAttribute("version", this.getVersion()); // ToDo Consider dealing with "updateSequence" // coverageDescriptionsElem.setAttribute( "updateSequence", this.getCurrentUpdateSequence() ); for (String curCoverageId : this.coverages) coverageDescriptionsElem.addContent(genCoverageOfferingElem(curCoverageId)); return new Document(coverageDescriptionsElem); } public Element genCoverageOfferingElem(String covId) { WcsCoverage coverage = this.getDataset().getAvailableCoverage(covId); GridCoordSystem gridCoordSystem = coverage.getCoordinateSystem(); // CoverageDescription/CoverageOffering (wcs) [1..*] Element covDescripElem = genCoverageOfferingBriefElem("CoverageOffering", covId, coverage.getLabel(), coverage.getDescription(), gridCoordSystem); // CoverageDescription/CoverageOffering/domainSet [1] covDescripElem.addContent(genDomainSetElem(coverage)); // CoverageDescription/CoverageOffering/rangeSet [1] covDescripElem.addContent(genRangeSetElem(coverage)); // CoverageDescription/CoverageOffering/supportedCRSs [1] covDescripElem.addContent(genSupportedCRSsElem(coverage)); // CoverageDescription/CoverageOffering/supportedFormats [1] covDescripElem.addContent(genSupportedFormatsElem(coverage)); // CoverageDescription/CoverageOffering/supportedInterpolations [0..1] covDescripElem.addContent(genSupportedInterpolationsElem()); return covDescripElem; } private Element genDomainSetElem(WcsCoverage coverage) { // ../domainSet Element domainSetElem = new Element("domainSet", wcsNS); // ../domainSet/spatialDomain [0..1] AND/OR temporalDomain [0..1] domainSetElem.addContent(genSpatialDomainElem(coverage)); if (coverage.getCoordinateSystem().hasTimeAxis()) { domainSetElem.addContent(genTemporalDomainElem(coverage.getCoordinateSystem().getTimeAxis1D())); } return domainSetElem; } private Element genSpatialDomainElem(WcsCoverage coverage) { // ../domainSet/spatialDomain Element spatialDomainElem = new Element("spatialDomain", wcsNS); // ../domainSet/spatialDomain/gml:Envelope [1..*] spatialDomainElem.addContent(this.genEnvelopeElem(coverage.getCoordinateSystem())); // ../domainSet/spatialDomain/gml:RectifiedGrid [0..*] spatialDomainElem.addContent(this.genRectifiedGridElem(coverage)); // ../domainSet/spatialDomain/gml:Polygon [0..*] return spatialDomainElem; } private Element genRectifiedGridElem(WcsCoverage coverage) { // ../spatialDomain/gml:RectifiedGrid Element rectifiedGridElem = new Element("RectifiedGrid", gmlNS); CoordinateAxis1D xaxis = (CoordinateAxis1D) coverage.getCoordinateSystem().getXHorizAxis(); CoordinateAxis1D yaxis = (CoordinateAxis1D) coverage.getCoordinateSystem().getYHorizAxis(); CoordinateAxis1D zaxis = coverage.getCoordinateSystem().getVerticalAxis(); // ../spatialDomain/gml:RectifiedGrid@srsName [0..1] (URI) rectifiedGridElem.setAttribute("srsName", coverage.getNativeCrs()); // ../spatialDomain/gml:RectifiedGrid@dimension [1] (positive integer) int ndim = (zaxis != null) ? 3 : 2; rectifiedGridElem.setAttribute("dimension", Integer.toString(ndim)); // ../spatialDomain/gml:RectifiedGrid/gml:limits [1] int[] minValues = new int[ndim]; int[] maxValues = new int[ndim]; maxValues[0] = (int) (xaxis.getSize() - 1); maxValues[1] = (int) (yaxis.getSize() - 1); if (zaxis != null) maxValues[2] = (int) (zaxis.getSize() - 1); Element limitsElem = new Element("limits", gmlNS); // ../spatialDomain/gml:RectifiedGrid/gml:limits/gml:GridEnvelope [1] // ../spatialDomain/gml:RectifiedGrid/gml:limits/gml:GridEnvelope/gml:low [1] (integer list) // ../spatialDomain/gml:RectifiedGrid/gml:limits/gml:GridEnvelope/gml:high [1] (integer list) limitsElem.addContent( new Element("GridEnvelope", gmlNS) .addContent(new Element("low", gmlNS).addContent(genIntegerListString(minValues))) .addContent(new Element("high", gmlNS).addContent(genIntegerListString(maxValues)))); rectifiedGridElem.addContent(limitsElem); // ../spatialDomain/gml:RectifiedGrid/gml:axisName [1..*] (string) rectifiedGridElem.addContent(new Element("axisName", gmlNS).addContent("x")); rectifiedGridElem.addContent(new Element("axisName", gmlNS).addContent("y")); if (zaxis != null) rectifiedGridElem.addContent(new Element("axisName", gmlNS).addContent("z")); // ../spatialDomain/gml:RectifiedGrid/gml:origin [1] // ../spatialDomain/gml:RectifiedGrid/gml:origin/gml:pos [1] (space seperated list of double values) // ../spatialDomain/gml:RectifiedGrid/gml:origin/gml:pos@dimension [0..1] (number of entries in list) double[] origin = new double[ndim]; origin[0] = xaxis.getStart(); origin[1] = yaxis.getStart(); if (zaxis != null) origin[2] = zaxis.getStart(); rectifiedGridElem.addContent( new Element("origin", gmlNS).addContent( new Element("pos", gmlNS).addContent(genDoubleListString(origin)))); // ../spatialDomain/gml:RectifiedGrid/gml:offsetVector [1..*] (space seperated list of double values) // ../spatialDomain/gml:RectifiedGrid/gml:offsetVector@dimension [0..1] (number of entries in list) double[] xoffset = new double[ndim]; xoffset[0] = xaxis.getIncrement(); rectifiedGridElem.addContent( new Element("offsetVector", gmlNS) .addContent(genDoubleListString(xoffset))); double[] yoffset = new double[ndim]; yoffset[1] = yaxis.getIncrement(); rectifiedGridElem.addContent( new Element("offsetVector", gmlNS) .addContent(genDoubleListString(yoffset))); if (zaxis != null) { double[] zoffset = new double[ndim]; zoffset[2] = zaxis.getIncrement(); rectifiedGridElem.addContent( new Element("offsetVector", gmlNS) .addContent(genDoubleListString(zoffset))); } return rectifiedGridElem; } private String genIntegerListString(int[] values) { StringBuilder buf = new StringBuilder(); for (int intValue : values) { if (buf.length() > 0) buf.append(" "); buf.append(intValue); } return buf.toString(); } private String genDoubleListString(double[] values) { StringBuffer buf = new StringBuffer(); for (double doubleValue : values) { if (buf.length() > 0) buf.append(" "); buf.append(doubleValue); } return buf.toString(); } private Element genEnvelopeElem(GridCoordSystem gcs) { // spatialDomain/Envelope Element envelopeElem; if (gcs.hasTimeAxis()) envelopeElem = new Element("EnvelopeWithTimePeriod", wcsNS); else envelopeElem = new Element("Envelope", wcsNS); // spatialDomain/Envelope@srsName [0..1] (URI) envelopeElem.setAttribute("srsName", "urn:ogc:def:crs:OGC:1.3:CRS84"); LatLonRect llbb = gcs.getLatLonBoundingBox(); LatLonPoint llpt = llbb.getLowerLeftPoint(); LatLonPoint urpt = llbb.getUpperRightPoint(); double lon = llpt.getLongitude() + llbb.getWidth(); int posDim = 2; String firstPosition = llpt.getLongitude() + " " + llpt.getLatitude(); String secondPosition = lon + " " + urpt.getLatitude(); // ToDo WCS 1.0Plus - Deal with conversion to meters. (Yikes!!) CoordinateAxis1D vertAxis = gcs.getVerticalAxis(); if (vertAxis != null) { posDim++; // See verAxis.getUnitsString() double zeroIndexValue = vertAxis.getCoordValue(0); double sizeIndexValue = vertAxis.getCoordValue(((int) vertAxis.getSize()) - 1); if (vertAxis.getPositive().equals(ucar.nc2.constants.CF.POSITIVE_UP)) { firstPosition += " " + zeroIndexValue; secondPosition += " " + sizeIndexValue; } else { firstPosition += " " + sizeIndexValue; secondPosition += " " + zeroIndexValue; } } String posDimString = Integer.toString(posDim); // spatialDomain/Envelope/gml:pos [2] (space seperated list of double values) // spatialDomain/Envelope/gml:pos@dimension [0..1] (number of entries in list) envelopeElem.addContent( new Element("pos", gmlNS) .addContent(firstPosition) .setAttribute(new Attribute("dimension", posDimString))); envelopeElem.addContent( new Element("pos", gmlNS) .addContent(secondPosition) .setAttribute(new Attribute("dimension", posDimString))); // spatialDomain/Envelope/gml:timePostion [2] if (gcs.hasTimeAxis()) { envelopeElem.addContent( new Element("timePosition", gmlNS).addContent( gcs.getCalendarDateRange().getStart().toString())); envelopeElem.addContent( new Element("timePosition", gmlNS).addContent( gcs.getCalendarDateRange().getEnd().toString())); } return envelopeElem; } private Element genTemporalDomainElem(CoordinateAxis1DTime timeAxis) { // temporalDomain Element temporalDomainElem = new Element("temporalDomain", wcsNS); List<CalendarDate> dates = timeAxis.getCalendarDates(); // temporalDomain/timePosition [1..*] for (CalendarDate curDate : dates) { temporalDomainElem.addContent( new Element("timePosition", gmlNS).addContent(curDate.toString())); } return temporalDomainElem; } private Element genRangeSetElem(WcsCoverage coverage) { // rangeSet Element rangeSetElem = new Element("rangeSet", wcsNS); // rangeSet/RangeSet // rangeSet/RangeSet@semantic // rangeSet/RangeSet@refSys // rangeSet/RangeSet@refSysLabel //Element innerRangeSetElem = new Element( "RangeSet", wcsNS); // ToDo How deal with range fields and thier axes? for (WcsRangeField curField : coverage.getRange()) { Element fieldElem = new Element("Field", wcsNS); // rangeSet/RangeSet/description [0..1] if (curField.getDescription() != null) fieldElem.addContent( new Element("description") .addContent(curField.getDescription())); // rangeSet/RangeSet/name [1] fieldElem.addContent( new Element("name", wcsNS).addContent(curField.getName())); // rangeSet/RangeSet/label [1] fieldElem.addContent( new Element("label", wcsNS).addContent(curField.getLabel())); fieldElem.addContent( new Element("dataType", wcsNS).addContent(curField.getDatatypeString())); fieldElem.addContent( new Element("units", wcsNS).addContent(curField.getUnitsString())); // .../values/interval // .../values/interval/min [0..1] // .../values/interval/max [0..1] // .../values/interval/res [0..1] Element valuesElem = new Element("AllowedValues", wcsNS); Element minElem = new Element("MinimumValue", wcsNS) .addContent(Double.toString( curField.getValidMin())); Element maxElem = new Element("MaximumValue", wcsNS) .addContent(Double.toString( curField.getValidMin())); valuesElem.addContent(new Element("Range", wcsNS) .addContent(minElem) .addContent(maxElem)); List<WcsRangeField.Axis> axes = curField.getAxes(); for (WcsRangeField.Axis curAxis : axes) { if (curAxis != null) { // rangeSet/RangeSet/axisDescription [0..*] //Element axisDescElem = new Element( "axisDescription", wcsNS ); Element axisDescElem = new Element("Axis", wcsNS); // rangeSet/RangeSet/axisDescription/AxisDescription [1] //Element innerAxisDescElem = new Element( "AxisDescription", wcsNS); // rangeSet/RangeSet/axisDescription/AxisDescription/name [1] // rangeSet/RangeSet/axisDescription/AxisDescription/label [1] axisDescElem.addContent(new Element("name", wcsNS).addContent(curAxis.getName())); axisDescElem.addContent(new Element("label", wcsNS).addContent(curAxis.getLabel())); // rangeSet/RangeSet/axisDescription/AxisDescription/values [1] Element axisValuesElem = new Element("values", wcsNS); // rangeSet/RangeSet/axisDescription/AxisDescription/values/singleValue [1..*] // ----- interval is alternate for singleValue // rangeSet/RangeSet/axisDescription/AxisDescription/values/interval // rangeSet/RangeSet/axisDescription/AxisDescription/values/interval/min [0..1] // rangeSet/RangeSet/axisDescription/AxisDescription/values/interval/max [0..1] // rangeSet/RangeSet/axisDescription/AxisDescription/values/interval/res [0..1] // ----- for (String curVal : curAxis.getValues()) axisValuesElem.addContent( new Element("singleValue", wcsNS) .addContent(curVal)); // rangeSet/RangeSet/axisDescription/AxisDescription/values/default [0..1] axisDescElem.addContent(axisValuesElem); fieldElem.addContent(axisDescElem); } } // rangeSet/RangeSet/nullValues [0..1] // rangeSet/RangeSet/nullValues/{interval|singleValue} [1..*] if (curField.hasMissingData()) { fieldElem.addContent( new Element("nullValues", wcsNS).addContent( new Element("singleValue", wcsNS) // ToDo Is missing always NaN? .addContent("NaN"))); } rangeSetElem.addContent(fieldElem); } return rangeSetElem; } private Element genSupportedCRSsElem(WcsCoverage coverage) { // supportedCRSs Element supportedCRSsElem = new Element("supportedCRSs", wcsNS); // supportedCRSs/requestCRSs [1..*] (wcs) (space seperated list of strings) // supportedCRSs/requestCRSs@codeSpace [0..1] (URI) supportedCRSsElem.addContent( new Element("requestCRSs", wcsNS) .addContent(coverage.getDefaultRequestCrs())); // supportedCRSs/responseCRSs [1..*] (wcs) (space seperated list of strings) // supportedCRSs/responseCRSs@codeSpace [0..1] (URI) supportedCRSsElem.addContent( new Element("responseCRSs", wcsNS) .addContent(coverage.getNativeCrs())); return supportedCRSsElem; } private Element genSupportedFormatsElem(WcsCoverage coverage) { // supportedFormats // supportedFormats@nativeFormat [0..1] (string) Element supportedFormatsElem = new Element("supportedFormats", wcsNS); // supportedFormats/formats [1..*] (wcs) (space seperated list of strings) // supportedFormats/formats@codeSpace [0..1] (URI) for (WcsRequest.Format curFormat : coverage.getSupportedCoverageFormatList()) { supportedFormatsElem.addContent( new Element("formats", wcsNS) .addContent(curFormat.toString())); } return supportedFormatsElem; } private Element genSupportedInterpolationsElem() { // supportedInterpolations // supportedInterpolations@default [0..1] ??? Element supportedInterpolationsElem = new Element("supportedInterpolations", wcsNS); // supportedInterpolations/interpolationMethod [1..*] supportedInterpolationsElem.addContent( new Element("interpolationMethod", wcsNS) .addContent("none")); return supportedInterpolationsElem; } }