/* * Copyright (c) 1998 - 2010. 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.server.wms; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import thredds.server.dataset.DatasetException; import thredds.server.dataset.TdsRequestedDataset; import thredds.server.wms.config.WmsDetailedConfig; import ucar.nc2.Attribute; import ucar.nc2.dataset.NetcdfDataset; import ucar.nc2.dataset.VariableDS; import ucar.nc2.dt.GridDataset; import ucar.nc2.dt.GridDatatype; import uk.ac.rdg.resc.edal.cdm.CdmUtils; import uk.ac.rdg.resc.edal.cdm.DataReadingStrategy; import uk.ac.rdg.resc.edal.coverage.CoverageMetadata; import uk.ac.rdg.resc.ncwms.controller.RequestParams; import uk.ac.rdg.resc.ncwms.exceptions.WmsException; import uk.ac.rdg.resc.ncwms.util.WmsUtils; import uk.ac.rdg.resc.ncwms.wms.Dataset; import uk.ac.rdg.resc.ncwms.wms.Layer; import uk.ac.rdg.resc.ncwms.wms.VectorLayer; /** * A {@link uk.ac.rdg.resc.ncwms.wms.Dataset} that provides access to layers read from * {@link ucar.nc2.dataset.NetcdfDataset} objects. * * @author Jon */ public class ThreddsDataset implements Dataset { private static final Logger log = LoggerFactory.getLogger(ThreddsDataset.class); private final String urlPath; private final String title; private final Map<String, ThreddsScalarLayer> scalarLayers = new LinkedHashMap<>(); private final Map<String, ThreddsVectorLayer> vectorLayers = new LinkedHashMap<>(); /** * Creates a new ThreddsDataset with the given id from the given NetcdfDataset */ private ThreddsDataset(String urlPath, GridDataset gridDataset, WmsDetailedConfig wmsConfig) throws IOException { this.urlPath = urlPath; this.title = gridDataset.getTitle(); NetcdfDataset ncDataset = (NetcdfDataset) gridDataset.getNetcdfFile(); DataReadingStrategy drStrategy = CdmUtils.getOptimumDataReadingStrategy(ncDataset); // Now load the scalar layers Collection<CoverageMetadata> ccm = CdmUtils.readCoverageMetadata(gridDataset); Iterator<CoverageMetadata> icm = ccm.iterator(); while (icm.hasNext()) { CoverageMetadata cm = icm.next(); // Get the most appropriate data-reading strategy for this dataset //PixelMap pxm = new PixelMap(cm.getHorizontalGrid(), null); //DataReadingStrategy drStrategy = CdmUtils.getOptimumDataReadingStrategy( pxm, ncDataset ); GridDatatype gdt = gridDataset.findGridDatatype(cm.getId()); //GridDatatype gdt = gridDataset.findGridByShortName(cm.getId()); ThreddsScalarLayer tsl = ThreddsScalarLayer.getNewLayer(cm, gdt, drStrategy, this, wmsConfig); this.scalarLayers.put(tsl.getName(), tsl); } //CdmUtils.findAndUpdateLayers( gridDataset, THREDDS_LAYER_BUILDER, this.scalarLayers ); // Find the vector quantities Collection<VectorLayer> vectorLayersColl = WmsUtils.findVectorLayers(this.scalarLayers.values()); // Add the vector quantities to the map of layers for (VectorLayer vecLayer : vectorLayersColl) { // We must wrap these vector layers as ThreddsVectorLayers to ensure that // the name of each layer matches its id. ThreddsVectorLayer tdsVecLayer = new ThreddsVectorLayer(vecLayer); tdsVecLayer.setLayerSettings(wmsConfig.getSettings(tdsVecLayer)); this.vectorLayers.put(vecLayer.getId(), tdsVecLayer); } } /** * Creates a new ThreddsDataset for one single layer */ private ThreddsDataset(String urlPath, GridDataset gd, List<String> layers, WmsDetailedConfig wmsConfig) { this.urlPath = urlPath; this.title = gd.getTitle(); NetcdfDataset ncDataset = (NetcdfDataset) gd.getNetcdfFile(); DataReadingStrategy drStrategy = CdmUtils.getOptimumDataReadingStrategy(ncDataset); for (String layer : layers) { //GridDatatype gdt = gd.findGridByShortName(layer); GridDatatype gdt = gd.findGridDatatype(layer); CoverageMetadata cm = CdmUtils.readCoverageMetadata(gdt); ThreddsScalarLayer tsl = ThreddsScalarLayer.getNewLayer(cm, gdt, drStrategy, this, wmsConfig); this.scalarLayers.put(tsl.getName(), tsl); } // Find the vector quantities Collection<VectorLayer> vectorLayersColl = WmsUtils.findVectorLayers(this.scalarLayers.values()); // Add the vector quantities to the map of layers for (VectorLayer vecLayer : vectorLayersColl) { // We must wrap these vector layers as ThreddsVectorLayers to ensure that // the name of each layer matches its id. ThreddsVectorLayer tdsVecLayer = new ThreddsVectorLayer(vecLayer); tdsVecLayer.setLayerSettings(wmsConfig.getSettings(tdsVecLayer)); this.vectorLayers.put(vecLayer.getId(), tdsVecLayer); } } /** * Uses the {@link #getDatasetPath() url path} as the unique id. */ @Override public String getId() { return this.urlPath; } @Override public String getTitle() { return this.title; } /** * Gets the path that was specified on the incoming URL */ public String getDatasetPath() { return this.urlPath; } /** * Returns the current time, since datasets could change at any time without * our knowledge. * * @see ThreddsServerConfig#getLastUpdateTime() */ @Override public DateTime getLastUpdateTime() { return new DateTime(); } /** * Gets the {@link uk.ac.rdg.resc.ncwms.wms.Layer} with the given {@link uk.ac.rdg.resc.ncwms.wms.Layer#getId() id}. The id * is unique within the dataset, not necessarily on the whole server. * * @return The layer with the given id, or null if there is no layer with * the given id. * @todo repetitive of code in ncwms.config.Dataset: any way to refactor? */ @Override public ThreddsLayer getLayerById(String layerId) { ThreddsLayer layer = this.scalarLayers.get(layerId); if (layer == null) layer = this.vectorLayers.get(layerId); return layer; } /** * @todo repetitive of code in ncwms.config.Dataset: any way to refactor? */ @Override public Set<Layer> getLayers() { Set<Layer> layerSet = new LinkedHashSet<>(); layerSet.addAll(this.scalarLayers.values()); layerSet.addAll(this.vectorLayers.values()); return layerSet; } /** * Returns an empty string */ @Override public String getCopyrightStatement() { return ""; } /** * Returns an empty string */ @Override public String getMoreInfoUrl() { return ""; } @Override public boolean isReady() { return true; } @Override public boolean isLoading() { return false; } @Override public boolean isError() { return false; } @Override public Exception getException() { return null; } @Override public boolean isDisabled() { return false; } /** * Builds a ThreddsDataset specific for each WMS requests that contains only the layers needed by the requests. * * @return ThreddsDataset * @throws IOException * @throws DatasetException * @throws WmsException */ static ThreddsDataset getThreddsDatasetForRequest(String request, GridDataset gridDataset, TdsRequestedDataset reqDataset, WmsDetailedConfig wmsConfig, RequestParams params) throws IOException, DatasetException, WmsException { ThreddsDataset tdsds = null; //GetLegendGraphic may not even need to create a ThreddsDataset (if no text is required) if (request.equals("GetLegendGraphic") && params.getString("LAYER") == null) return null; if (params.getString("LAYERS") == null && params.getString("LAYER") == null && params.getString("LAYERNAME") == null) { tdsds = new ThreddsDataset(reqDataset.getPath(), gridDataset, wmsConfig); } else { //Only one layer for each request. Need two components for vector layers! String layers = params.getString("LAYERS"); if (layers == null) layers = params.getString("LAYER"); if (layers == null) layers = params.getString("LAYERNAME"); List<String> requestedLayers = getLayerComponents(gridDataset, layers); tdsds = new ThreddsDataset(reqDataset.getPath(), gridDataset, requestedLayers, wmsConfig); } return tdsds; } /** * Here we have to revert what was done in WMSUtils methods for creating the virtual datasets */ private static List<String> getLayerComponents(GridDataset gd, String layer) { List<String> layers = new ArrayList<>(); //Layer name is grid.getFullName() --> vs.getFullName() GridDatatype grid = gd.findGridDatatype(layer); //Actually searches by vs.getFullName() if (grid == null) { List<GridDatatype> grids = gd.getGrids(); Iterator<GridDatatype> gridsIt = grids.iterator(); while (gridsIt.hasNext() && layers.size() < 2) { GridDatatype g = gridsIt.next(); //Search the components by standard_name, long_name and fullname VariableDS var = g.getVariable(); Attribute stdName = var.findAttributeIgnoreCase("standard_name"); if (stdName != null) { if (isComponent(layer, stdName.getStringValue())) layers.add(var.getFullName()); } else { Attribute longName = var.findAttributeIgnoreCase("long_name"); if (longName != null) { if (isComponent(layer, longName.getStringValue())) layers.add(var.getFullName()); } else {//full name if (isComponent(layer, var.getFullName())) layers.add(var.getFullName()); } } } } else { layers.add(layer); } return layers; } /** * Returns true if varAtt is the name of a scalar component of the vector layer layerName * * Check if the result of removing standard components prefixes for CF-1.0 or * Grib convention from varAtt is the layerName. * * Handled standard components: * - x, y * - eastward, northward * - Meridional, Zonal * - u-component of, v-component of * * They can appear at the begining, in the middle or at the end, separated by _ or space characters * * @param layerName * @param varAtt * @return */ static boolean isComponent(String layerName, String varAtt) { String componentRegex = "(x|y|eastward|northward|meridional|zonal|u-component of|v-component of)"; String separatorRegex = "(_|\\s+)"; String lookBehindRegex = "(?<=_|\\s|^)"; // Regex in two cases: // 1: begining and middle (ex: eastward_sea_water_velocity or barotropic_sea_water_x_velocity) // 2: end (ex: sea_water_velocity_x) String regex = lookBehindRegex + componentRegex + separatorRegex + "|" + separatorRegex + componentRegex + "$"; return layerName.equals(varAtt.replaceAll("(?i)" + regex, "")); } }