/*
* 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import thredds.server.ncss.util.NcssRequestUtils;
import thredds.util.ContentType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.constants.CDM;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dt.GridDataset;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridAsPointDataset;
import ucar.nc2.time.CalendarDate;
import ucar.unidata.geoloc.LatLonPoint;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.Map.Entry;
class XMLPointDataWriter implements PointDataWriter {
static private Logger log = LoggerFactory.getLogger(XMLPointDataWriter.class);
public static XMLPointDataWriter factory(OutputStream os){
return new XMLPointDataWriter(os);
}
private Map<String,List<String>> allVars;
private Map<String, GridAsPointDataset> gridAsPointDatasets=new HashMap<>();
private XMLStreamWriter xmlStreamWriter;
private HttpHeaders httpHeaders = new HttpHeaders();
private XMLPointDataWriter(OutputStream os) {
xmlStreamWriter = createXMLStreamWriter(os);
}
//public boolean header(Map<String, List<String>> vars, GridDataset gridDataset, List<CalendarDate> wDates, DateUnit dateUnit,LatLonPoint point, Double vertCoord) {
public boolean header(Map<String, List<String>> groupedVars, GridDataset gridDataset, List<CalendarDate> wDates, List<Attribute> timeDimAtts,LatLonPoint point, Double vertCoord) {
allVars = groupedVars;
for(Entry<String, List<String>> entry : groupedVars.entrySet() ){
gridAsPointDatasets.put( entry.getKey(), NcssRequestUtils.buildGridAsPointDataset(gridDataset, entry.getValue() ) );
}
boolean headerWritten = false;
try {
xmlStreamWriter.writeStartDocument("UTF-8", "1.0");
xmlStreamWriter.writeStartElement("grid");
xmlStreamWriter.writeAttribute("dataset",
gridDataset.getLocationURI());
headerWritten = true;
} catch (XMLStreamException xse) {
log.error("Error writting xml header", xse);
}
return headerWritten;
}
public boolean write(Map<String, List<String>> groupedVars, GridDataset gds, List<CalendarDate> wDates, LatLonPoint point, Double vertCoord) throws InvalidRangeException{
//loop over wDates
CalendarDate date;
Iterator<CalendarDate> it = wDates.iterator();
boolean pointRead =true;
List<String> keysAsList = new ArrayList<>(groupedVars.keySet());
//Check wDates -> Could be empty (dataset with not time axis)
if( wDates.isEmpty() ){
//pointRead = write(groupedVars, gds, point, vertCoord);
pointRead = writeNoTimeAxis(keysAsList, gds, point, vertCoord);
}else{
while( pointRead && it.hasNext() ){
date = it.next();
//pointRead = write(groupedVars, gds, date, point, vertCoord);
pointRead = writeOneDate(keysAsList, gds, date, point, vertCoord);
}
}
return pointRead;
}
private boolean writeNoTimeAxis(List<String> groupsKeys, GridDataset gds, LatLonPoint point, Double targetLevel) {
boolean allDone = true;
//List<String> keys =new ArrayList<String>(groupedVars.keySet());
//loop over variable groups
for(String key : groupsKeys){
//get wanted vertCoords for group (all if vertCoord==null just one otherwise)
//List<String> varsGroup = groupedVars.get(key);
List<String> varsGroup = allVars.get(key);
//GridAsPointDataset gap = NcssRequestUtils.buildGridAsPointDataset(gds, varsGroup);
GridAsPointDataset gap = gridAsPointDatasets.get(key);
CoordinateAxis1D verticalAxisForGroup = gds.findGridDatatype(varsGroup.get(0)).getCoordinateSystem().getVerticalAxis();
if(verticalAxisForGroup ==null){
//Read and write vars--> time, point
allDone = allDone && writeNoTimeNoVert(varsGroup, gds, gap, point);
}else{
//read and write time, verCoord for each variable in group
if(targetLevel != null){
Double vertCoord = NcssRequestUtils.getTargetLevelForVertCoord(verticalAxisForGroup, targetLevel);
allDone = writeVertNoTime(varsGroup, gds, gap, point, vertCoord, verticalAxisForGroup.getUnitsString());
}else{//All levels
for(Double vertCoord : verticalAxisForGroup.getCoordValues() ){
/////Fix axis!!!!
if(verticalAxisForGroup.getCoordValues().length == 1 )
vertCoord =NcssRequestUtils.getTargetLevelForVertCoord(verticalAxisForGroup, vertCoord);
allDone = allDone && writeVertNoTime(varsGroup, gds, gap, point, vertCoord, verticalAxisForGroup.getUnitsString());
}
}
}
}
return allDone;
}
private boolean writeOneDate(List<String> groupsKeys, GridDataset gridDataset, CalendarDate date, LatLonPoint point, Double targetLevel) throws InvalidRangeException {
boolean allDone = true;
//List<String> keys =new ArrayList<String>(groupedVars.keySet());
//loop over variable groups
//for(String key : keys){
for(String key : groupsKeys){
//get wanted vertCoords for group (all if vertCoord==null just one otherwise)
List<String> varsGroup = allVars.get(key);
//GridAsPointDataset gap = NcssRequestUtils.buildGridAsPointDataset(gridDataset, varsGroup);
GridAsPointDataset gap = gridAsPointDatasets.get(key);
CoordinateAxis1D verticalAxisForGroup = gridDataset.findGridDatatype(varsGroup.get(0)).getCoordinateSystem().getVerticalAxis();
if(verticalAxisForGroup ==null){
//Read and write vars--> time, point
allDone = allDone && writeTimeNoVert(varsGroup, gridDataset, gap, date, point);
}else{
//read and write time, verCoord for each variable in group
if(targetLevel != null){
Double vertCoord = NcssRequestUtils.getTargetLevelForVertCoord(verticalAxisForGroup, targetLevel);
allDone = writeTimeAndVert(varsGroup, gridDataset, gap, date, point, vertCoord, verticalAxisForGroup.getUnitsString());
}else{//All levels
for(Double vertCoord : verticalAxisForGroup.getCoordValues() ){
/////Fix axis!!!!
if(verticalAxisForGroup.getCoordValues().length ==1 )
vertCoord =NcssRequestUtils.getTargetLevelForVertCoord(verticalAxisForGroup, vertCoord);
allDone = allDone && writeTimeAndVert(varsGroup, gridDataset, gap, date, point, vertCoord, verticalAxisForGroup.getUnitsString());
}
}
}
}
return allDone;
}
private boolean writeTimeAndVert(List<String> vars, GridDataset gridDataset, GridAsPointDataset gap, CalendarDate date, LatLonPoint point, Double targetLevel, String zUnits) throws InvalidRangeException {
Iterator<String> itVars = vars.iterator();
boolean pointDone=false;
try {
xmlStreamWriter.writeStartElement("point");
Map<String, String> attributes = new HashMap<>();
attributes.put("name", "date");
writeDataTag( xmlStreamWriter, attributes, date.toString() );
attributes.clear();
int contVars = 0;
while (itVars.hasNext()) {
String varName = itVars.next();
GridDatatype grid = gridDataset.findGridDatatype(varName);
//Handling the ensemble dimension...
CoordinateAxis1D ensembleAxis = grid.getCoordinateSystem().getEnsembleAxis();
boolean hasEnsembleDim = false;
double[] ensCoords = new double[]{-1};
if(ensembleAxis !=null ){
ensCoords = ensembleAxis.getCoordValues();
hasEnsembleDim = true;
}
double actualLevel = NcssRequestUtils.getActualVertLevel(grid, date, point, targetLevel);
for(double ensCoord : ensCoords){
if (gap.hasTime(grid, date) && gap.hasVert(grid, targetLevel)) {
GridAsPointDataset.Point p = gap.readData(grid, date, ensCoord, targetLevel, point.getLatitude(), point.getLongitude());
if (contVars == 0) {
//writeCoordinates(xmlStreamWriter, Double.valueOf(p.lat), Double.valueOf(p.lon));
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
attributes.put("name", "vertCoord");
attributes.put("units", zUnits);
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.z).toString());
attributes.clear();
if (Double.compare(actualLevel, -9999.9) != 0) { // LOOK WTF ??
attributes.put("name", "vertCoord");
attributes.put("units", grid.getCoordinateSystem().getVerticalTransform().getUnitString() );
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(actualLevel).toString());
attributes.clear();
}
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
if(hasEnsembleDim)
//attributes.put("ensMember", Integer.valueOf((int)ensCoord).toString() );
attributes.put("ensMember", Double.valueOf(p.ens).toString() );
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.dataValue).toString());
attributes.clear();
} else {
// write missingvalues!!!
if (contVars == 0) {
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(gap.getMissingValue(grid)).toString());
attributes.clear();
}
contVars++;
}
}
xmlStreamWriter.writeEndElement(); //Closes point
pointDone = true;
} catch (XMLStreamException xse) {
log.error("Error writting tag point", xse);
} catch (IOException ioe){
log.error("Error reading point data", ioe);
}
return pointDone;
}
/**
*
* Write method when the grid has no time axis but has vertical axis
*/
private boolean writeVertNoTime(List<String> vars, GridDataset gridDataset, GridAsPointDataset gap, LatLonPoint point, Double targetLevel, String zUnits) {
Iterator<String> itVars = vars.iterator();
boolean pointDone=false;
try {
xmlStreamWriter.writeStartElement("point");
Map<String, String> attributes = new HashMap<>();
//attributes.put("name", "date");
//writeDataTag( xmlStreamWriter, attributes, date.toString() );
//attributes.clear();
int contVars = 0;
while (itVars.hasNext()) {
String varName = itVars.next();
GridDatatype grid = gridDataset.findGridDatatype(varName);
if ( gap.hasVert(grid, targetLevel)) {
GridAsPointDataset.Point p = gap.readData(grid, null, targetLevel, point.getLatitude(), point.getLongitude());
if (contVars == 0) {
//writeCoordinates(xmlStreamWriter, Double.valueOf(p.lat), Double.valueOf(p.lon));
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
attributes.put("name", "vertCoord");
attributes.put("units", zUnits);
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.z).toString());
attributes.clear();
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.dataValue).toString());
attributes.clear();
} else {
// write missingvalues!!!
if (contVars == 0) {
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(gap.getMissingValue(grid)).toString());
attributes.clear();
}
contVars++;
}
xmlStreamWriter.writeEndElement(); //Closes point
pointDone = true;
} catch (XMLStreamException xse) {
log.error("Error writting tag point", xse);
} catch (IOException ioe){
log.error("Error reading point data", ioe);
}
return pointDone;
}
/**
*
* Write method when the grid has no time axis and no vertical axis
*/
private boolean writeNoTimeNoVert(List<String> vars, GridDataset gridDataset, GridAsPointDataset gap, LatLonPoint point){
Iterator<String> itVars = vars.iterator();
boolean pointDone=false;
try {
xmlStreamWriter.writeStartElement("point");
Map<String, String> attributes = new HashMap<>();
attributes.clear();
int contVars = 0;
while (itVars.hasNext()) {
String varName = itVars.next();
GridDatatype grid = gridDataset.findGridDatatype(varName);
GridAsPointDataset.Point p = gap.readData(grid, null, point.getLatitude(), point.getLongitude());
if (contVars == 0) {
//writeCoordinates(xmlStreamWriter, Double.valueOf(p.lat), Double.valueOf(p.lon));
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
attributes.clear();
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.dataValue).toString());
attributes.clear();
contVars++;
}
xmlStreamWriter.writeEndElement(); //Closes point
pointDone = true;
} catch (XMLStreamException xse) {
log.error("Error writting tag point", xse);
} catch (IOException ioe){
log.error("Error reading point data", ioe);
}
return pointDone;
}
/**
*
* Write method for grids with time axis but not vertical level
*/
private boolean writeTimeNoVert(List<String> vars, GridDataset gridDataset, GridAsPointDataset gap, CalendarDate date, LatLonPoint point){
Iterator<String> itVars = vars.iterator();
boolean pointDone=false;
try {
xmlStreamWriter.writeStartElement("point");
Map<String, String> attributes = new HashMap<>();
attributes.put("name", "date");
writeDataTag( xmlStreamWriter, attributes, date.toString() );
attributes.clear();
int contVars = 0;
while (itVars.hasNext()) {
String varName = itVars.next();
GridDatatype grid = gridDataset.findGridDatatype(varName);
//Handling the ensemble dimension...
CoordinateAxis1D ensembleAxis = grid.getCoordinateSystem().getEnsembleAxis();
boolean hasEnsembleDim = false;
double[] ensCoords = new double[]{-1};
if(ensembleAxis !=null ){
ensCoords = ensembleAxis.getCoordValues();
hasEnsembleDim = true;
}
for(double ensCoord : ensCoords){
if (gap.hasTime(grid, date) ) {
GridAsPointDataset.Point p = gap.readData(grid, date, ensCoord, -1, point.getLatitude(), point.getLongitude());
if (contVars == 0) {
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
attributes.clear();
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
if(hasEnsembleDim)
attributes.put("ensMember", Double.valueOf(p.ens).toString() );
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(p.dataValue).toString());
attributes.clear();
} else {
// write missingvalues!!!
if (contVars == 0) {
writeCoordinates(xmlStreamWriter, point.getLatitude(), point.getLongitude());
}
attributes.put("name", varName);
attributes.put("units", grid.getUnitsString());
writeDataTag(xmlStreamWriter, attributes, Double.valueOf(gap.getMissingValue(grid)).toString());
attributes.clear();
}
contVars++;
}
}
xmlStreamWriter.writeEndElement(); //Closes point
pointDone = true;
} catch (XMLStreamException xse) {
log.error("Error writting tag point", xse);
} catch (IOException ioe){
log.error("Error reading point data", ioe);
}
return pointDone;
}
@Override
public boolean trailer() {
boolean endDocument = false;
try {
xmlStreamWriter.writeEndElement(); // Closes tag grid
xmlStreamWriter.writeEndDocument();
endDocument = true;
} catch (XMLStreamException xse) {
log.error("Error writing end document", xse);
}
return endDocument;
}
@Override
public HttpHeaders getResponseHeaders(){
return httpHeaders;
}
private XMLStreamWriter createXMLStreamWriter(OutputStream os) {
XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
XMLStreamWriter writer = null;
try {
writer = outputFactory.createXMLStreamWriter(os, "UTF-8");
} catch (XMLStreamException xse) {
log.error(xse.getMessage());
}
return writer;
}
@Override
public void setHTTPHeaders(GridDataset gds, String pathInfo, boolean isStream){
//Set the response headers...
if(!isStream){
httpHeaders.set("Content-Location", pathInfo );
String fileName = NcssRequestUtils.getFileNameForResponse(pathInfo, ".xml");
httpHeaders.set("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
}
httpHeaders.set(ContentType.HEADER, ContentType.xml.getContentHeader());
//httpHeaders.setContentType(MediaType.APPLICATION_XML);
}
private void writeDataTag(XMLStreamWriter writer, Map<String, String> attributes, String content) throws XMLStreamException {
writer.writeStartElement("data");
for (Map.Entry<String, String> entry : attributes.entrySet()) {
writer.writeAttribute(entry.getKey(), entry.getValue());
}
writer.writeCharacters(content);
writer.writeEndElement();
}
private void writeCoordinates(XMLStreamWriter writer, Double lat, Double lon) throws XMLStreamException {
Map<String, String> attributes = new HashMap<>();
// tag data for lat
attributes.put("name", "lat");
attributes.put("units", CDM.LAT_UNITS);
writeDataTag(writer, attributes, lat.toString());
attributes.clear();
// tag data for lon
attributes.put("name", "lon");
attributes.put("units", CDM.LON_UNITS);
writeDataTag(writer, attributes, lon.toString());
attributes.clear();
}
}