/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.imageio.netcdf;
import it.geosolutions.imageio.plugins.netcdf.NetCDFUtilities;
import java.io.IOException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Logger;
import org.geotools.temporal.object.DefaultInstant;
import org.geotools.temporal.object.DefaultPosition;
import org.opengis.temporal.TemporalObject;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.CoordinateSystem;
class NetCDFSliceUtilities {
private final static Logger LOGGER = Logger
.getLogger("org.geotools.imageio.netcdf.util");
/**
* Gets the name, as the "description", "title" or "standard name" attribute
* if possible, or as the variable name otherwise.
*/
public static String getName(final Variable variable) {
String name = variable.getDescription();
if (name == null || (name = name.trim()).length() == 0) {
name = variable.getName();
}
return name;
}
public static double[] getEnvelope(CoordinateSystem cs) {
// TODO: Handle 3D GEO CoordinateReferenceSystem
double[] envelope = null;
if (cs != null) {
/*
* Adds the axis in reverse order, because the NetCDF image reader
* put the last dimensions in the rendered image. Typical NetCDF
* convention is to put axis in the (time, depth, latitude,
* longitude) order, which typically maps to (longitude, latitude,
* depth, time) order in our referencing framework.
*/
final List<CoordinateAxis> axes = cs.getCoordinateAxes();
envelope = new double[] { Double.NaN, Double.NaN, Double.NaN,
Double.NaN };
for (int i = axes.size(); --i >= 0;) {
final CoordinateAxis axis = axes.get(i);
// final String name = NetCDFSliceUtilities.getName(axis);
final AxisType type = axis.getAxisType();
// final String units = axis.getUnitsString();
/*
* Gets the axis direction, taking in account the possible
* reversal or vertical axis. Note that geographic and projected
* CoordinateReferenceSystem have the same directions. We can
* distinguish them either using the ISO
* CoordinateReferenceSystem type ("geographic" or "projected"),
* the ISO CS type ("ellipsoidal" or "cartesian") or the units
* ("degrees" or "m").
*/
/*
* If the axis is not numeric, we can't process any further. If
* it is, then adds the coordinate and index ranges.
*/
if (axis.isNumeric() && axis instanceof CoordinateAxis1D
&& !AxisType.Time.equals(type)) {
final CoordinateAxis1D axis1D = (CoordinateAxis1D) axis;
final int length = axis1D.getDimension(0).getLength();
if (length > 2 && axis1D.isRegular()) {
final double increment = axis1D.getIncrement();
final double start = axis1D.getStart();
final double end = start + increment * (length - 1); // Inclusive
if (AxisType.Lon.equals(type)
|| AxisType.GeoX.equals(type)) {
if (increment > 0) {
envelope[0] = start;
envelope[2] = end;
} else {
envelope[0] = end;
envelope[2] = start;
}
}
if (AxisType.Lat.equals(type)
|| AxisType.GeoY.equals(type)) {
if (increment > 0) {
envelope[1] = start;
envelope[3] = end;
} else {
envelope[1] = end;
envelope[3] = start;
}
}
} else {
final double[] values = axis1D.getCoordValues();
final double val0 = values[0];
final double valN = values[values.length - 1];
if (AxisType.Lon.equals(type)
|| AxisType.GeoX.equals(type)) {
// if (CoordinateAxis.POSITIVE_DOWN
// .equalsIgnoreCase(axis.getPositive())) {
// envelope[1] = values[0];
// envelope[3] = values[values.length - 1];
// } else {
envelope[0] = val0;
envelope[2] = valN;
// }
}
if (AxisType.Lat.equals(type)
|| AxisType.GeoY.equals(type)) {
// if (CoordinateAxis.POSITIVE_DOWN
// .equalsIgnoreCase(axis.getPositive())) {
// envelope[0] = values[0];
// envelope[2] = values[values.length - 1];
// } else {
envelope[1] = val0;
envelope[3] = valN;
// }
}
}
}
}
for (int i = 0; i < envelope.length; i++)
if (Double.isNaN(envelope[i])) {
envelope = null;
break;
}
}
return envelope;
}
public static double getVerticalValue(final NetCDFSpatioTemporalImageReader geoNetCDFReader, Variable variable, Range range,
final int imageIndex, final CoordinateSystem cs) {
double ve = Double.NaN;
if (cs != null && cs.hasVerticalAxis()) {
final int zIndex = NetCDFUtilities.getZIndex(variable, range,
imageIndex);
final int rank = variable.getRank();
final Dimension verticalDimension = variable.getDimension(rank
- NetCDFUtilities.Z_DIMENSION);
final Variable axis = geoNetCDFReader.getCoordinate(verticalDimension.getName());
if ((axis != null) && axis.isCoordinateVariable()){
final AxisType type = ((CoordinateAxis)axis).getAxisType();
if (!AxisType.GeoZ.equals(type)
&& !AxisType.Pressure.equals(type)
&& !AxisType.Height.equals(type))
return ve;
// String units = axis.getUnitsString();
if (((CoordinateAxis)axis).isNumeric() && axis instanceof CoordinateAxis1D) {
final CoordinateAxis1D axis1D = (CoordinateAxis1D) axis;
final double[] values = axis1D.getCoordValues();
if (values != null && values.length > zIndex) {
ve = values[zIndex];
}
}
}
}
return ve;
}
/**
*
* @param geoNetCDFReader
* @param imageIndex
* @throws InvalidRangeException
* @throws IOException
* @throws ParseException
*/
public static TemporalObject getTimeValue(final NetCDFSpatioTemporalImageReader geoNetCDFReader, Variable variable, Range range,
final int imageIndex, final CoordinateSystem cs) {
TemporalObject varTime = null;
if (cs != null && cs.hasTimeAxis()) {
int timeIndex = NetCDFUtilities.getTIndex(variable, range, imageIndex);
final int rank = variable.getRank();
final Dimension temporalDimension = variable.getDimension(rank
- ((cs.hasVerticalAxis() ? NetCDFUtilities.Z_DIMENSION : 2) + 1));
final Variable axis = geoNetCDFReader.getCoordinate(temporalDimension.getName());
if ((axis != null) && axis.isCoordinateVariable()){
// for (Variable axis : coordVars) {
final AxisType type = ((CoordinateAxis)axis).getAxisType();
if (!AxisType.Time.equals(type))
return varTime;
String units = axis.getUnitsString();
Date epoch = null, start_time = null, end_time = null;
/*
* Gets the axis origin. In the particular case of time axis,
* units are typically written in the form "days since
* 1990-01-01 00:00:00". We extract the part before "since" as
* the units and the part after "since" as the date.
*/
String origin = null;
final String[] unitsParts = units.split("(?i)\\s+since\\s+");
if (unitsParts.length == 2) {
units = unitsParts[0].trim();
origin = unitsParts[1].trim();
} else {
final Attribute attribute = axis
.findAttribute("time_origin");
if (attribute != null) {
origin = attribute.getStringValue();
}
}
if (origin != null) {
origin = NetCDFUtilities.trimFractionalPart(origin);
// add 0 digits if absent
origin = checkDateDigits(origin);
try {
epoch = (Date) NetCDFUtilities.getAxisFormat(type, origin).parseObject(
origin);
} catch (ParseException e) {
LOGGER
.warning("Error while parsing time Axis. Skip setting the TemporalExtent from coordinateAxis");
}
}
if (((CoordinateAxis)axis).isNumeric() && epoch != null
&& axis instanceof CoordinateAxis1D) {
Calendar cal = null;
final CoordinateAxis1D axis1D = (CoordinateAxis1D) axis;
final double[] values = axis1D.getCoordValues();
cal = new GregorianCalendar();
cal.setTime(epoch);
int vi = (int) Math.floor(values[timeIndex]);
double vd = values[timeIndex] - vi;
cal.add(getTimeUnits(units, null), vi);
if (vd != 0.0)
cal.add(getTimeUnits(units, vd), getTimeSubUnitsValue(units, vd));
start_time = cal.getTime();
// if (values.length > 1 && values[0] != values[1]) {
// cal.setTime(epoch);
// cal.add(getTimeUnits(units), (int) values[1]
// + timeIndex);
// end_time = cal.getTime();
// }
// if (start_time != null && end_time != null
// && start_time.before(end_time)) {
// varTime = new DefaultPeriod(new DefaultInstant(
// new DefaultPosition(start_time)),
// new DefaultInstant(
// new DefaultPosition(end_time)));
// } else
if (start_time != null) {
varTime = new DefaultInstant(new DefaultPosition(start_time));
}
}
}
}
return varTime;
}
/**
* Converts NetCDF time units into opportune Calendar ones.
*
* @param units
* {@link String}
* @param d
* @return int
*/
private static int getTimeUnits(String units, Double vd) {
if ("months".equalsIgnoreCase(units)) {
if (vd == null || vd == 0.0)
// if no day, it is the first day
return 1;
else {
//TODO: FIXME
}
} else if ("days".equalsIgnoreCase(units)) {
if (vd == null || vd == 0.0)
return Calendar.DATE;
else {
double hours = vd * 24;
if (hours - Math.floor(hours) == 0.0)
return Calendar.HOUR;
double minutes = vd * 24 * 60;
if (minutes - Math.floor(minutes) == 0.0)
return Calendar.MINUTE;
double seconds = vd * 24 * 60 * 60;
if (seconds - Math.floor(seconds) == 0.0)
return Calendar.SECOND;
return Calendar.MILLISECOND;
}
}
if ("hours".equalsIgnoreCase(units) || "hour".equalsIgnoreCase(units)) {
if (vd == null || vd == 0.0)
return Calendar.HOUR;
else {
double minutes = vd * 24 * 60;
if (minutes - Math.floor(minutes) == 0.0)
return Calendar.MINUTE;
double seconds = vd * 24 * 60 * 60;
if (seconds - Math.floor(seconds) == 0.0)
return Calendar.SECOND;
return Calendar.MILLISECOND;
}
}
if ("minutes".equalsIgnoreCase(units)) {
if (vd == null || vd == 0.0)
return Calendar.MINUTE;
else {
double seconds = vd * 24 * 60 * 60;
if (seconds - Math.floor(seconds) == 0.0)
return Calendar.SECOND;
return Calendar.MILLISECOND;
}
}
if ("seconds".equalsIgnoreCase(units)) {
if (vd == null || vd == 0.0)
return Calendar.SECOND;
else {
return Calendar.MILLISECOND;
}
}
return -1;
}
/**
*
*/
private static int getTimeSubUnitsValue(String units, Double vd) {
if ("days".equalsIgnoreCase(units)) {
int subUnit = getTimeUnits(units, vd);
if (subUnit == Calendar.HOUR) {
double hours = vd * 24;
return (int) hours;
}
if (subUnit == Calendar.MINUTE) {
double hours = vd * 24 * 60;
return (int) hours;
}
if (subUnit == Calendar.SECOND) {
double hours = vd * 24 * 60 * 60;
return (int) hours;
}
if (subUnit == Calendar.MILLISECOND) {
double hours = vd * 24 * 60 * 60 * 1000;
return (int) hours;
}
return 0;
}
if ("hours".equalsIgnoreCase(units) || "hour".equalsIgnoreCase(units)) {
int subUnit = getTimeUnits(units, vd);
if (subUnit == Calendar.MINUTE) {
double hours = vd * 24 * 60;
return (int) hours;
}
if (subUnit == Calendar.SECOND) {
double hours = vd * 24 * 60 * 60;
return (int) hours;
}
if (subUnit == Calendar.MILLISECOND) {
double hours = vd * 24 * 60 * 60 * 1000;
return (int) hours;
}
return 0;
}
if ("minutes".equalsIgnoreCase(units)) {
int subUnit = getTimeUnits(units, vd);
if (subUnit == Calendar.SECOND) {
double hours = vd * 24 * 60 * 60;
return (int) hours;
}
if (subUnit == Calendar.MILLISECOND) {
double hours = vd * 24 * 60 * 60 * 1000;
return (int) hours;
}
return 0;
}
if ("seconds".equalsIgnoreCase(units)) {
int subUnit = getTimeUnits(units, vd);
if (subUnit == Calendar.MILLISECOND) {
double hours = vd * 24 * 60 * 60 * 1000;
return (int) hours;
}
return 0;
}
return 0;
}
public static int JGREG = 15 + 31 * (10 + 12 * 1582);
// public static double HALFSECOND = 0.5;
public static GregorianCalendar fromJulian(double injulian) {
int jalpha, ja, jb, jc, jd, je, year, month, day;
// double julian = injulian + HALFSECOND / 86400.0;
ja = (int) injulian;
if (ja >= JGREG) {
jalpha = (int) (((ja - 1867216) - 0.25) / 36524.25);
ja = ja + 1 + jalpha - jalpha / 4;
}
jb = ja + 1524;
jc = (int) (6680.0 + ((jb - 2439870) - 122.1) / 365.25);
jd = 365 * jc + jc / 4;
je = (int) ((jb - jd) / 30.6001);
day = jb - jd - (int) (30.6001 * je);
month = je - 1;
if (month > 12)
month = month - 12;
year = jc - 4715;
if (month > 2)
year--;
if (year <= 0)
year--;
// Calendar Months are 0 based
return new GregorianCalendar(year, month - 1, day);
}
/**
* @param origin
*/
public static String checkDateDigits(String origin) {
String digitsCheckedOrigin = "";
if (origin.indexOf("-") > 0) {
String tmp = (origin.indexOf(" ") > 0 ? origin.substring(0, origin.indexOf(" ")) : origin);
String[] originDateParts = tmp.split("-");
for (int l=0; l<originDateParts.length; l++) {
String datePart = originDateParts[l];
while (datePart.length() % 2 != 0) {
datePart = "0" + datePart;
}
digitsCheckedOrigin += datePart;
digitsCheckedOrigin += (l<(originDateParts.length-1) ? "-" : "");
}
}
if (origin.indexOf(":") > 0) {
digitsCheckedOrigin += " ";
String tmp = (origin.indexOf(" ") > 0 ? origin.substring(origin.indexOf(" ")+1) : origin);
String[] originDateParts = tmp.split(":");
for (int l=0; l<originDateParts.length; l++) {
String datePart = originDateParts[l];
while (datePart.length() % 2 != 0) {
datePart = "0" + datePart;
}
digitsCheckedOrigin += datePart;
digitsCheckedOrigin += (l<(originDateParts.length-1) ? ":" : "");
}
}
if (digitsCheckedOrigin.length() > 0)
return digitsCheckedOrigin;
return origin;
}
}