/*
* Copyright 1998-2014 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.ncss.util;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import thredds.server.config.TdsContext;
import thredds.server.ncss.exception.OutOfBoundariesException;
import thredds.server.ncss.exception.TimeOutOfWindowException;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.dataset.*;
import ucar.nc2.dt.GridCoordSystem;
import ucar.nc2.dt.GridDataset;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridAsPointDataset;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.vertical.VerticalTransform;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Component
public final class NcssRequestUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private NcssRequestUtils() {
}
public static GridAsPointDataset buildGridAsPointDataset(GridDataset gds, List<String> vars) {
List<GridDatatype> grids = new ArrayList<>();
for (String gridName : vars) {
GridDatatype grid = gds.findGridDatatype(gridName);
if (grid == null) {
continue;
}
grids.add(grid);
}
return new GridAsPointDataset(grids);
}
public static List<String> getAllVarsAsList(GridDataset gds) {
List<String> vars = new ArrayList<>();
List<VariableSimpleIF> allVars = gds.getDataVariables();
for (VariableSimpleIF var : allVars) {
vars.add(var.getShortName());
}
return vars;
}
public static List<CalendarDate> wantedDates(GridAsPointDataset gap, CalendarDateRange dates,
long timeWindow) throws TimeOutOfWindowException, OutOfBoundariesException {
CalendarDate start = dates.getStart();
CalendarDate end = dates.getEnd();
List<CalendarDate> gdsDates = gap.getDates();
if (start.isAfter(gdsDates.get(gdsDates.size() - 1)) || end.isBefore(gdsDates.get(0))) {
throw new OutOfBoundariesException("Requested time range does not intersect the Data Time Range = " +
gdsDates.get(0) + " to " + gdsDates.get(gdsDates.size() - 1));
}
List<CalendarDate> wantDates = new ArrayList<>();
if (dates.isPoint()) {
int best_index = 0;
long best_diff = Long.MAX_VALUE;
for (int i = 0; i < gdsDates.size(); i++) {
CalendarDate date = gdsDates.get(i);
long diff = Math.abs(date.getDifferenceInMsecs(start));
if (diff < best_diff) {
best_index = i;
best_diff = diff;
}
}
if (timeWindow > 0 && best_diff > timeWindow) //Best time is out of our acceptable timeWindow
{
throw new TimeOutOfWindowException("There is not time within the provided time window");
}
wantDates.add(gdsDates.get(best_index));
} else {
for (CalendarDate date : gdsDates) {
boolean tooEarly = date.isBefore(start);
boolean tooLate = date.isAfter(end);
if (tooEarly || tooLate) {
continue;
}
wantDates.add(date);
}
}
return wantDates;
}
public static List<VariableSimpleIF> wantedVars2VariableSimple(List<String> wantedVars, GridDataset gds,
NetcdfDataset ncfile) {
// need VariableSimpleIF for each variable
List<VariableSimpleIF> varList = new ArrayList<>(wantedVars.size());
//And wantedVars must be in the dataset
for (String var : wantedVars) {
VariableEnhanced ve = gds.findGridDatatype(var).getVariable();
//List<Dimension> lDims =ve.getDimensions();
//StringBuilder dims = new StringBuilder("");
//for(Dimension d: lDims){
// dims.append(" ").append(d.getName());
//}
String dims = ""; // always scalar ????
VariableSimpleIF want = new VariableDS(ncfile, null, null, ve.getShortName(), ve.getDataType(), dims,
ve.getUnitsString(), ve.getDescription());
varList.add(want);
}
return varList;
}
public static GridDatatype getTimeGrid(Map<String, List<String>> groupedVars, GridDataset gridDataset) {
List<String> keys = new ArrayList<>(groupedVars.keySet());
GridDatatype timeGrid = null;
List<String> allVars = new ArrayList<>();
for (String key : keys) {
allVars.addAll(groupedVars.get(key));
}
Iterator<String> it = allVars.iterator();
while (timeGrid == null && it.hasNext()) {
String var = it.next();
if (gridDataset.findGridDatatype(var).getCoordinateSystem().hasTimeAxis()) {
timeGrid = gridDataset.findGridDatatype(var);
}
}
///
return timeGrid;
}
public static Double getTimeCoordValue(GridDatatype grid, CalendarDate date, CalendarDate origin) {
CoordinateAxis1DTime tAxis = grid.getCoordinateSystem().getTimeAxis1D();
if (tAxis == null) {
return -1.0;
}
Integer wIndex = tAxis.findTimeIndexFromCalendarDate(date);
Double coordVal;
//Check axis dataType --> Time axis for some collections (joinExistingOne) is String
//In that case we use the seconds since the origin of the time axis as unit
if (tAxis.getDataType() == DataType.STRING) {
CalendarDate wanted = tAxis.getCalendarDate(wIndex);
coordVal = (double) wanted.getDifferenceInMsecs(origin) / 1000;
} else {
coordVal = tAxis.getCoordValue(wIndex);
}
return coordVal;
}
public static Double getTargetLevelForVertCoord(CoordinateAxis1D zAxis, Double vertLevel) {
Double targetLevel = vertLevel;
int coordLevel;
// If zAxis has one level zAxis.findCoordElement(vertLevel) returns -1 and only works with vertLevel = 0
// Workaround while not fixed in CoordinateAxis1D
if (zAxis.getSize() == 1) {
targetLevel = 0.0;
} else {
//coordLevel = zAxis.findCoordElement(vertLevel);
coordLevel = zAxis.findCoordElementBounded(vertLevel);
if (coordLevel > 0) {
targetLevel = zAxis.getCoordValue(coordLevel);
}
}
return targetLevel;
}
/**
* Returns the actual vertical level if the grid has vertical transformation or -9999.9 otherwise
*/
public static double getActualVertLevel(GridDatatype grid, CalendarDate date, LatLonPoint point,
double targetLevel) throws IOException, InvalidRangeException {
double actualLevel = -9999.9;
//Check vertical transformations for the grid
GridCoordSystem cs = grid.getCoordinateSystem();
VerticalTransform vt = cs.getVerticalTransform();
if (vt != null) {
int[] result = new int[2];
cs.findXYindexFromLatLon(point.getLatitude(), point.getLongitude(), result);
CoordinateAxis1DTime timeAxis = cs.getTimeAxis1D();
int vertCoord = cs.getVerticalAxis().findCoordElement(targetLevel);
int timeIndex = 0;
if (timeAxis != null) {
timeIndex = timeAxis.findTimeIndexFromCalendarDate(date);
}//If null timAxis might be 2D -> not supported (handle this)
ArrayDouble.D1 actualLevels = vt.getCoordinateArray1D(timeIndex, result[0], result[1]);
actualLevel = actualLevels.get(vertCoord);
}
return actualLevel;
}
/**
* Makes the TdsContext available
*
* @return TdsContext
*/
public static TdsContext getTdsContext() {
return applicationContext.getBean(TdsContext.class);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
NcssRequestUtils.applicationContext = applicationContext;
}
public static String getFileNameForResponse(String pathInfo, NetcdfFileWriter.Version version) {
Preconditions.checkNotNull(version, "version == null");
return getFileNameForResponse(pathInfo, version.getSuffix());
}
public static String getFileNameForResponse(String pathInfo, String extension) {
Preconditions.checkArgument(pathInfo != null && !pathInfo.isEmpty(), "pathInfo == %s", pathInfo);
Preconditions.checkNotNull(extension, "extension == null");
String parentDirName = FilenameUtils.getBaseName(FilenameUtils.getPathNoEndSeparator(pathInfo));
String baseName = FilenameUtils.getBaseName(pathInfo);
String dotExtension = extension.startsWith(".") ? extension : "." + extension;
if (!parentDirName.isEmpty()) {
return parentDirName + "_" + baseName + dotExtension;
} else {
return baseName + dotExtension;
}
}
}