/* * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ package thredds.server.wcs.v1_0_0_1; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.XMLOutputter; import thredds.server.wcs.Request; import ucar.nc2.ft2.coverage.*; import ucar.nc2.time.CalendarDateRange; import ucar.unidata.geoloc.LatLonPoint; import ucar.unidata.geoloc.LatLonRect; import javax.annotation.Nonnull; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; public class DescribeCoverage extends WcsRequest { private List<String> coverages; private Document describeCoverageDoc; public DescribeCoverage(Request.Operation operation, String version, WcsDataset dataset, @Nonnull List<String> coverages) throws WcsException { super(operation, version, dataset); this.coverages = coverages; if (this.coverages.size() < 1) throw new IllegalArgumentException("Coverage list must contain at least one ID [" + this.coverages.size() + "]."); List<String> badCovIds = new ArrayList<>(); for (String curCov : coverages) { if (!this.getWcsDataset().isAvailableCoverageName(curCov)) badCovIds.add(curCov); } if (badCovIds.size() > 0) { throw new WcsException("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 String writeDescribeCoverageDocAsString() throws IOException { XMLOutputter xmlOutputter = new XMLOutputter(org.jdom2.output.Format.getPrettyFormat()); return xmlOutputter.outputString(getDescribeCoverageDoc()); } 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.getWcsDataset().getAvailableCoverage(covId); CoverageCoordSys 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)); CoverageCoordAxis1D timeCoord = (CoverageCoordAxis1D) coverage.getCoordinateSystem().getTimeAxis(); if (timeCoord != null) { domainSetElem.addContent(genTemporalDomainElem(timeCoord)); } 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); CoverageCoordAxis1D xaxis = (CoverageCoordAxis1D) coverage.getCoordinateSystem().getXAxis(); CoverageCoordAxis1D yaxis = (CoverageCoordAxis1D) coverage.getCoordinateSystem().getYAxis(); CoverageCoordAxis1D zaxis = (CoverageCoordAxis1D) coverage.getCoordinateSystem().getZAxis(); // ../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] = (xaxis.getNcoords() - 1); maxValues[1] = (yaxis.getNcoords() - 1); if (zaxis != null) maxValues[2] = (zaxis.getNcoords() - 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.getCoordMidpoint(0); origin[1] = yaxis.getCoordMidpoint(0); if (zaxis != null) origin[2] = zaxis.getCoordMidpoint(0); 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.getResolution(); rectifiedGridElem.addContent(new Element("offsetVector", gmlNS).addContent(genDoubleListString(xoffset))); double[] yoffset = new double[ndim]; yoffset[1] = yaxis.getResolution(); rectifiedGridElem.addContent(new Element("offsetVector", gmlNS).addContent(genDoubleListString(yoffset))); if (zaxis != null) { double[] zoffset = new double[ndim]; zoffset[2] = zaxis.getResolution(); 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) { StringBuilder buf = new StringBuilder(); for (double doubleValue : values) { if (buf.length() > 0) buf.append(" "); buf.append(doubleValue); } return buf.toString(); } private Element genEnvelopeElem(CoverageCoordSys gcs) { // spatialDomain/Envelope Element envelopeElem; CoverageCoordAxis timeCoord = gcs.getTimeAxis(); if (timeCoord != null) 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 = wcsDataset.getDataset().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(); 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 (timeCoord != null) { CalendarDateRange dateRange = timeCoord.getDateRange(); envelopeElem.addContent(new Element("timePosition", gmlNS).addContent(dateRange.getStart().toString())); envelopeElem.addContent(new Element("timePosition", gmlNS).addContent(dateRange.getEnd().toString())); } return envelopeElem; } private Element genTemporalDomainElem(CoverageCoordAxis1D timeAxis) { Element temporalDomainElem = new Element("temporalDomain", wcsNS); // temporalDomain/timePosition [1..*] for (int i = 0; i < timeAxis.getNcoords(); i++) { double val = timeAxis.getCoordMidpoint(i); temporalDomainElem.addContent(new Element("timePosition", gmlNS).addContent(timeAxis.makeDate(val).toString())); } return temporalDomainElem; } private Element genRangeSetElem(WcsCoverage coverage) { WcsRangeField rangeField = coverage.getRangeField(); // rangeSet Element rangeSetElem = new Element("rangeSet", wcsNS); // rangeSet/RangeSet // rangeSet/RangeSet@semantic // rangeSet/RangeSet@refSys // rangeSet/RangeSet@refSysLabel Element innerRangeSetElem = new Element("RangeSet", wcsNS); // rangeSet/RangeSet/description [0..1] if (rangeField.getDescription() != null) innerRangeSetElem.addContent(new Element("description").addContent(rangeField.getDescription())); // rangeSet/RangeSet/name [1] innerRangeSetElem.addContent(new Element("name", wcsNS).addContent(rangeField.getName())); // rangeSet/RangeSet/label [1] innerRangeSetElem.addContent(new Element("label", wcsNS).addContent(rangeField.getLabel())); WcsRangeField.Axis vertAxis = rangeField.getAxis(); if (vertAxis != null) { // rangeSet/RangeSet/axisDescription [0..*] Element axisDescElem = new Element("axisDescription", wcsNS); // rangeSet/RangeSet/axisDescription/AxisDescription [1] Element innerAxisDescElem = new Element("AxisDescription", wcsNS); // rangeSet/RangeSet/axisDescription/AxisDescription/name [1] // rangeSet/RangeSet/axisDescription/AxisDescription/label [1] innerAxisDescElem.addContent(new Element("name", wcsNS).addContent(vertAxis.getName())); innerAxisDescElem.addContent(new Element("label", wcsNS).addContent(vertAxis.getLabel())); // rangeSet/RangeSet/axisDescription/AxisDescription/values [1] Element valuesElem = 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 : vertAxis.getValues()) valuesElem.addContent(new Element("singleValue", wcsNS).addContent(curVal)); // rangeSet/RangeSet/axisDescription/AxisDescription/values/default [0..1] innerAxisDescElem.addContent(valuesElem); axisDescElem.addContent(innerAxisDescElem); innerRangeSetElem.addContent(axisDescElem); } // rangeSet/RangeSet/nullValues [0..1] // rangeSet/RangeSet/nullValues/{interval|singleValue} [1..*] if (coverage.hasMissingData()) { innerRangeSetElem.addContent(new Element("nullValues", wcsNS).addContent(new Element("singleValue", wcsNS) // ToDo Is missing always NaN? .addContent("NaN"))); } return rangeSetElem.addContent(innerRangeSetElem); } 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 (Request.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; } }