/*
* 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.view.gridaspoint.netcdf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ucar.ma2.*;
import ucar.ma2.StructureMembers.Member;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.Variable;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.VerticalCT;
import ucar.nc2.dt.GridDataset;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.ft.point.writer.CFPointWriter;
import ucar.nc2.ft.point.writer.CFPointWriterUtils;
import ucar.nc2.time.CalendarDate;
import ucar.unidata.geoloc.EarthLocation;
import ucar.unidata.geoloc.Station;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
* Write a CF "Discrete Sample" time series profile collection file (single station).
* Example H.5.2 Multidimensional array representations of time series profiles
*
*
* <p/>
* <pre>
* writeHeader()
* iterate { writeRecord() }
* finish()
* </pre>
*
* @see "http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.6/cf-conventions.html#idp8414816"
*
* @author mhermida
*
*/
class WriterCFTimeSeriesProfileCollection extends CFPointWriter {
static private Logger log = LoggerFactory.getLogger(WriterCFTimeSeriesProfileCollection.class);
private static final String PROFILE_DIM_NAME ="profile";
private static final String STATION_DIM_NAME ="station";
private double lastTimeCoordValue=-1; //Keep track of the last written time
private int recno = -1; // Keeps track of the record number (profile)
private int[] origin = new int[1];
private Variable stnName, stnDesc, lat, lon, ensVar;
WriterCFTimeSeriesProfileCollection(NetcdfFileWriter.Version version, String fileOut,
List<Attribute> atts) throws IOException {
super( fileOut, atts, version);
writer.addGroupAttribute(null, new Attribute(CF.FEATURE_TYPE, CF.FeatureType.timeSeriesProfile.name() ));
}
void writeHeader(List<Station> stns, Map<String, List<String>> groupedVars,
GridDataset gds, List<Attribute> timeDimAtts, int timeDimLength, Double vertCoord) throws IOException{
//--> Create dimensions and variables:
//
// Dimensions:
// - profile:
// - vertical dimensions: one for each different vertical level: zn
// - station (we only have station but keep this dimension)
// add the dimensions
Dimension profile = writer.addDimension(null,PROFILE_DIM_NAME, timeDimLength);
Dimension station = writer.addDimension(null, STATION_DIM_NAME, stns.size());
List<Dimension> stationDims = new ArrayList<>();
stationDims.add(station);
List<Dimension> dims = new ArrayList<>();
dims.add(station);
dims.add(profile);
List<Dimension> timeDims = new ArrayList<>();
timeDims.addAll(dims);
List<String> keys = new ArrayList<>(groupedVars.keySet());
//Ensemble dimension (all variables should have it, if present)...
CoordinateAxis1D ensAxis = gds.findGridDatatype( groupedVars.get(keys.get(0)).get(0)).getCoordinateSystem().getEnsembleAxis();
if(ensAxis != null ){
Dimension d = writer.addDimension(null, ensAxis.getShortName(), ensAxis.getCoordValues().length);
dims.add(d);
List<Dimension> ensDim = new ArrayList<>();
ensDim.add(d);
ensVar = writer.addVariable(null, ensAxis.getShortName() , ensAxis.getDataType() , ensDim );
}
//Vertical dimensions and variables
for(String key : keys){
List<String> vars = groupedVars.get(key);
List<Dimension> tempDims = new ArrayList<>();
tempDims.addAll(dims);
String coordinates ="time "+lonName+" "+latName;
//Vertical
CoordinateAxis1D zAxis = gds.findGridDatatype(vars.get(0)).getCoordinateSystem().getVerticalAxis();
if(zAxis != null){
int zAxisLength = zAxis.getCoordValues().length;
if(vertCoord != null){
zAxisLength = 1;
}
Dimension d = writer.addDimension(null, zAxis.getShortName(), zAxisLength);
tempDims.add(d);
Variable zVar = writer.addVariable(null, zAxis.getShortName() , zAxis.getDataType() , tempDims);
//Variable atts
writer.addVariableAttribute(zVar, new Attribute(CF.STANDARD_NAME, zAxis.getShortName() ));
writer.addVariableAttribute(zVar, new Attribute(CDM.LONG_NAME, zAxis.getFullName() ));
writer.addVariableAttribute(zVar, new Attribute(CDM.UNITS , zAxis.getUnitsString() ));
if (zAxis.getPositive() != null) {
writer.addVariableAttribute(zVar, new Attribute(CF.POSITIVE, zAxis.getPositive()));
}
writer.addVariableAttribute(zVar, new Attribute(CF.AXIS , "Z" ));
coordinates = coordinates +" "+d.getShortName();
}
for(String var : vars){
GridDatatype grid = gds.findGridDatatype(var);
Variable v = writer.addVariable(null, grid.getShortName() , grid.getDataType() , tempDims);
//Variable atts
writer.addVariableAttribute(v, new Attribute( CF.STANDARD_NAME, grid.getShortName() ));
writer.addVariableAttribute(v, new Attribute( CDM.LONG_NAME, grid.getFullName() ));
writer.addVariableAttribute(v, new Attribute( CDM.UNITS, grid.getUnitsString() ));
writer.addVariableAttribute(v, new Attribute( CF.COORDINATES , coordinates ));
//Vertical transformations -> if the grid has vertical transformations
VerticalCT vct = grid.getCoordinateSystem().getVerticalCT();
if( vct != null && writer.findVariable( vct.getName() ) == null ){
Variable av = writer.addVariable(null, vct.getName(), DataType.DOUBLE, tempDims);
writer.addVariableAttribute(av, new Attribute( CF.STANDARD_NAME, vct.getName() )); //Check the names
writer.addVariableAttribute(av, new Attribute( CDM.LONG_NAME, vct.getName() )); //Check the names
writer.addVariableAttribute(av, new Attribute( CDM.UNITS, grid.getCoordinateSystem().getVerticalTransform().getUnitString() ));
writer.addVariableAttribute(av, new Attribute( CF.COORDINATES , coordinates ));
}
}
}
//Station names
int name_strlen =1;
int desc_strlen =1;
for (Station stn : stns) {
name_strlen = Math.max(name_strlen, stn.getName().length());
desc_strlen = Math.max(name_strlen, stn.getDescription().length());
}
stnName = writer.addStringVariable(null, "station_name", stationDims, name_strlen);
writer.addVariableAttribute(stnName, new Attribute(CDM.LONG_NAME, "station name"));
writer.addVariableAttribute(stnName, new Attribute(CF.CF_ROLE, CF.TIMESERIES_ID ));
stnDesc = writer.addStringVariable(null, "station_description", stationDims, desc_strlen);
writer.addVariableAttribute(stnDesc, new Attribute(CDM.LONG_NAME, "station description"));
writer.addVariableAttribute(stnDesc, new Attribute(CF.STANDARD_NAME, CF.PLATFORM_NAME));
//Lon
lat = writer.addVariable(null, latName, DataType.DOUBLE, STATION_DIM_NAME);
writer.addVariableAttribute(lat, new Attribute(CDM.UNITS, CDM.LAT_UNITS));
writer.addVariableAttribute(lat, new Attribute(CDM.LONG_NAME, "profile latitude"));
//Lat
lon = writer.addVariable(null, lonName, DataType.DOUBLE, STATION_DIM_NAME);
writer.addVariableAttribute(lon, new Attribute(CDM.UNITS, CDM.LON_UNITS));
writer.addVariableAttribute(lon, new Attribute(CDM.LONG_NAME, "profile longitude"));
//TIME
Variable time = writer.addVariable(null, timeName, DataType.DOUBLE, timeDims);
for(Attribute att : timeDimAtts){
writer.addVariableAttribute(time, att);
}
//writer.addVariableAttribute(time, new Attribute(CDM.UNITS, timeUnit.getUnitsString()));
//writer.addVariableAttribute(time, new Attribute(CDM.LONG_NAME, "time of measurement"));
writer.create();
writeStations(stns);
}
private void writeStations(List<Station> stations){
llbb = CFPointWriterUtils.getBoundingBox(stations); // gets written in super.finish();
int nstns = stations.size();
ArrayObject.D1 namesArray = new ArrayObject.D1(String.class, stations.size());
ArrayObject.D1 descArray = new ArrayObject.D1(String.class, stations.size());
ArrayDouble.D1 latArray = new ArrayDouble.D1(nstns);
ArrayDouble.D1 lonArray = new ArrayDouble.D1(nstns);
int i = 0;
for (Station station : stations) {
namesArray.set(i, station.getName() );
descArray.set(i, station.getDescription() );
latArray.set(i, station.getLatitude());
lonArray.set(i, station.getLongitude());
i++;
}
try {
writer.writeStringData(stnName , namesArray);
writer.writeStringData(stnDesc , descArray);
writer.write(lat, latArray);
writer.write(lon, lonArray);
} catch (IOException ioe) {
log.error("Error writing station names:"+ioe );
} catch (InvalidRangeException ire) {
log.error("Invalid range exception error writing station names:"+ire );
}
}
void writeRecord(double timeCoordValue, CalendarDate obsDate, EarthLocation loc, StructureData sdata) throws IOException{
trackBB(null, obsDate);
try{
updateRecno(timeCoordValue);
//Variables without vert levels -> dimensions are (profile, station)
int[] origin = new int[2];
origin[0] = 0;
origin[1] = recno;
StructureMembers sm = sdata.getStructureMembers();
for( Member m : sm.getMembers() ){
Variable v = writer.findVariable(m.getName());
if(v != null && !v.getShortName().equals("time")){
if( writer.findVariable(m.getName()) != null){
Array arr = CFPointWriterUtils.getArrayFromMember(writer.findVariable(m.getName()), m);
writer.write( writer.findVariable(m.getName()) , origin, arr );
}
}
}
}catch(InvalidRangeException ire){
log.error("Error writing data: "+ire);
}
}
void writeRecord(String profileName, double timeCoordValue, CalendarDate obsDate, EarthLocation loc, StructureData sdata, int vIndex) throws IOException{
trackBB(null, obsDate);
try{
updateRecno(timeCoordValue);
// write the recno record
origin[0] = recno;
int[] tmp3D= new int[3];
tmp3D[0] = 0; //Station -> one single station!!
tmp3D[1] = recno;
tmp3D[2] = vIndex;
//writer.write(record, origin, sArray);
StructureMembers sm = sdata.getStructureMembers();
for( Member m : sm.getMembers() ){
Variable v = writer.findVariable(m.getName());
//Its a variable --> 3D (profile, station, z)
if( v != null && !v.getShortName().equals(lonName) && !v.getShortName().equals(latName) && !v.getShortName().equals("time")){
Array arr = CFPointWriterUtils.getArrayFromMember(writer.findVariable(m.getName()), m);
writer.write( writer.findVariable(m.getName()) , tmp3D, arr );
}
}
}catch(InvalidRangeException ire){
log.error("Error writing data: "+ire);
}
}
void writeRecord(String profileName, double timeCoordValue, double ensCoordValue, CalendarDate obsDate, EarthLocation loc, StructureData sdata, int vIndex) throws IOException{
trackBB(null, obsDate);
try{
updateRecno(timeCoordValue);
// write the recno record
origin[0] = recno;
int[] tmp4D= new int[4];
tmp4D[0] = 0; //Station -> one single station!!
tmp4D[1] = recno;
tmp4D[2] = (int) ensCoordValue;
tmp4D[3] = vIndex;
StructureMembers sm = sdata.getStructureMembers();
for( Member m : sm.getMembers() ){
Variable v = writer.findVariable(m.getName());
//Its a variable --> 4D (profile, ensemble, station, z)
if( v != null && !v.getShortName().equals(lonName) && !v.getShortName().equals(latName) && !v.getShortName().equals("time")){
Array arr = CFPointWriterUtils.getArrayFromMember(writer.findVariable(m.getName()), m);
writer.write( writer.findVariable(m.getName()) , tmp4D, arr );
}
}
}catch(InvalidRangeException ire){
log.error("Error writing data: "+ire);
}
}
void writeRecord( double timeCoordValue, double ensCoordValue, CalendarDate obsDate, EarthLocation loc, StructureData sdata) throws IOException{
trackBB(null, obsDate);
try{
updateRecno(timeCoordValue);
// write the recno record
origin[0] = recno;
int[] tmp3D= new int[3];
tmp3D[0] = 0; //Station -> one single station!!
tmp3D[1] = recno;
tmp3D[2] = (int) ensCoordValue;
StructureMembers sm = sdata.getStructureMembers();
for( Member m : sm.getMembers() ){
Variable v = writer.findVariable(m.getName());
//Its a variable --> 3D (profile, ensemble, station)
if( v != null && !v.getShortName().equals(lonName) && !v.getShortName().equals(latName) && !v.getShortName().equals("time")){
Array arr = CFPointWriterUtils.getArrayFromMember(writer.findVariable(m.getName()), m);
writer.write( writer.findVariable(m.getName()) , tmp3D, arr );
}
}
}catch(InvalidRangeException ire){
log.error("Error writing data: "+ire);
}
}
void writeEnsCoord(int ensIdx, double ensCoord) throws IOException{
ArrayDouble.D1 tmpArray = new ArrayDouble.D1(1);
tmpArray.setDouble(0, ensCoord);
int[] idx = new int[]{ensIdx};
try {
writer.write( ensVar , idx, tmpArray );
}catch(InvalidRangeException ire){
log.error("Error writing data: "+ire);
}
}
private void updateRecno( double timeCoordValue ) throws IOException, InvalidRangeException{
if(timeCoordValue != lastTimeCoordValue){
//Updates recno = profile!!!
recno++;
int[] tmp= new int[2];
tmp[0] = 0; //Station -> one single station!!
tmp[1] = recno;
Double data = timeCoordValue;
lastTimeCoordValue = timeCoordValue;
ArrayDouble.D2 tmpArray = new ArrayDouble.D2(1,1);
tmpArray.setDouble(0, data);
//Writes time...
writer.write( writer.findVariable( "time") , tmp, tmpArray );
}
}
@Override
protected void makeFeatureVariables(StructureData featureData, boolean isExtended) throws IOException {
// LOOK FAKE
}
}