package ucar.coord;
import net.jcip.annotations.Immutable;
import ucar.nc2.grib.GribUtils;
import ucar.nc2.grib.grib1.Grib1ParamTime;
import ucar.nc2.grib.grib1.Grib1Record;
import ucar.nc2.grib.grib1.Grib1SectionProductDefinition;
import ucar.nc2.grib.grib1.tables.Grib1Customizer;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.grib.TimeCoord;
import ucar.nc2.grib.grib2.Grib2Pds;
import ucar.nc2.grib.grib2.Grib2Record;
import ucar.nc2.grib.grib2.table.Grib2Customizer;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarPeriod;
import ucar.nc2.util.Indent;
import ucar.nc2.util.Misc;
import java.util.*;
/**
* Time coordinates that are intervals.
*
* @author John
* @since 11/28/13
*/
@Immutable
public class CoordinateTimeIntv extends CoordinateTimeAbstract implements Coordinate {
private final List<TimeCoord.Tinv> timeIntervals;
public CoordinateTimeIntv(int code, CalendarPeriod timeUnit, CalendarDate refDate, List<TimeCoord.Tinv> timeIntervals, int[] time2runtime) {
super(code, timeUnit, refDate, time2runtime);
this.timeIntervals = Collections.unmodifiableList(timeIntervals);
}
CoordinateTimeIntv(CoordinateTimeIntv org, CalendarDate refDate) {
super(org.code, org.timeUnit, refDate, null);
this.timeIntervals = org.getTimeIntervals();
}
public List<TimeCoord.Tinv> getTimeIntervals() {
return timeIntervals;
}
@Override
public List<? extends Object> getValues() {
return timeIntervals;
}
@Override
public Object getValue(int idx) {
return timeIntervals.get(idx);
}
@Override
public int getIndex(Object val) {
return Collections.binarySearch(timeIntervals, (TimeCoord.Tinv) val);
}
@Override
public int getSize() {
return timeIntervals.size();
}
@Override
public int estMemorySize() {
return 616 + getSize() * (24); // LOOK wrong
}
@Override
public Type getType() {
return Type.timeIntv;
}
/**
* Check if we all time intervals have the same length.
* @return time interval name or MIXED_INTERVALS
*/
public String getTimeIntervalName() {
// are they the same length ?
int firstValue = -1;
for (TimeCoord.Tinv tinv : timeIntervals) {
int value = (tinv.getBounds2() - tinv.getBounds1());
if (firstValue < 0) firstValue = value;
else if (value != firstValue) return MIXED_INTERVALS;
}
firstValue = (int) (firstValue * getTimeUnitScale());
return firstValue + "_" + timeUnit.getField().toString();
}
/**
* Make calendar date range, using the first and last ending bounds
* @param cal optional calendar, may be null
* @return calendar date range
*/
@Override
public CalendarDateRange makeCalendarDateRange(ucar.nc2.time.Calendar cal) {
CalendarDateUnit cdu = CalendarDateUnit.of(cal, timeUnit.getField(), refDate);
CalendarDate start = cdu.makeCalendarDate(timeIntervals.get(0).getBounds2());
CalendarDate end = cdu.makeCalendarDate(timeIntervals.get(getSize()-1).getBounds2());
return CalendarDateRange.of(start, end);
}
@Override
public void showInfo(Formatter info, Indent indent) {
info.format("%s%s:", indent, getType());
for (TimeCoord.Tinv cd : timeIntervals)
info.format(" %s,", cd);
info.format(" (%d) %n", timeIntervals.size());
if (time2runtime != null)
info.format("%stime2runtime: %s", indent, Misc.showInts(time2runtime));
}
@Override
public void showCoords(Formatter info) {
info.format("Time Interval offsets: (%s) ref=%s%n", getUnit(), getRefDate());
for (TimeCoord.Tinv cd : timeIntervals)
info.format(" (%3d - %3d) %d%n", cd.getBounds1(), cd.getBounds2(), cd.getBounds2() - cd.getBounds1());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CoordinateTimeIntv that = (CoordinateTimeIntv) o;
if (code != that.code) return false;
if (!timeIntervals.equals(that.timeIntervals)) return false;
return true;
}
@Override
public int hashCode() {
int result = code;
result = 31 * result + timeIntervals.hashCode();
return result;
}
////////////////////////////////////////
protected CoordinateTimeIntv makeBestFromComplete(int[] best, int n) {
List<TimeCoord.Tinv> timeIntervalsBest = new ArrayList<>(timeIntervals.size());
int[] time2runtimeBest = new int[n];
int count = 0;
for (int i=0; i<best.length; i++) {
int time = best[i];
if (time >= 0) {
time2runtimeBest[count] = time;
timeIntervalsBest.add(timeIntervals.get(i));
count++;
}
}
return new CoordinateTimeIntv(code, timeUnit, refDate, timeIntervalsBest, time2runtimeBest);
}
/* make the union of all the offsets from base date
public CoordinateTimeIntv makeBestTimeCoordinate(List<Double> runOffsets) {
Set<TimeCoord.Tinv> values = new HashSet<>();
for (double runOffset : runOffsets) {
for (TimeCoord.Tinv val : getTimeIntervals())
values.add( val.offset(runOffset)); // LOOK possible roundoff
}
List<TimeCoord.Tinv> offsetSorted = new ArrayList<>(values.size());
for (Object val : values) offsetSorted.add( (TimeCoord.Tinv) val);
Collections.sort(offsetSorted);
return new CoordinateTimeIntv(getCode(), getTimeUnit(), refDate, offsetSorted);
}
public int[] makeTime2RuntimeMap(List<Double> runOffsets, CoordinateTimeIntv coordBest, TwoDTimeInventory twot) {
int[] result = new int[ coordBest.getSize()];
Map<TimeCoord.Tinv, Integer> map = new HashMap<>(); // lookup coord val to index
int count = 0;
for (TimeCoord.Tinv val : coordBest.getTimeIntervals()) map.put(val, count++);
int runIdx = 0;
for (double runOffset : runOffsets) {
int timeIdx = 0;
for (TimeCoord.Tinv val : getTimeIntervals()) {
if (twot == null || twot.getCount(runIdx, timeIdx) > 0) { // skip missing;
TimeCoord.Tinv bestVal = val.offset(runOffset);
Integer bestValIdx = map.get(bestVal);
if (bestValIdx == null) throw new IllegalStateException();
result[bestValIdx] = runIdx+1; // use this partition; later ones override; one based so 0 = missing
}
timeIdx++;
}
runIdx++;
}
return result;
} */
///////////////////////////////////////////////////////////
static public class Builder2 extends CoordinateBuilderImpl<Grib2Record> {
private final Grib2Customizer cust;
private final int code; // pdsFirst.getTimeUnit()
private final CalendarPeriod timeUnit;
private final CalendarDate refDate;
public Builder2(Grib2Customizer cust, int code, CalendarPeriod timeUnit, CalendarDate refDate) {
this.cust = cust;
this.code = code;
this.timeUnit = timeUnit;
this.refDate = refDate;
}
@Override
public Object extract(Grib2Record gr) {
TimeCoord.Tinv tinv;
Grib2Pds pds = gr.getPDS();
int tuInRecord = pds.getTimeUnit();
if (tuInRecord == code) {
int[] intv = cust.getForecastTimeIntervalOffset(gr);
tinv = new TimeCoord.Tinv(intv[0], intv[1]);
} else {
// int unit = cust.convertTimeUnit(tu2); // not used
TimeCoord.TinvDate tinvd = cust.getForecastTimeInterval(gr); // converts to calendar date
tinv = tinvd.convertReferenceDate(refDate, timeUnit);
}
return tinv;
}
@Override
public Coordinate makeCoordinate(List<Object> values) {
List<TimeCoord.Tinv> offsetSorted = new ArrayList<>(values.size());
for (Object val : values) offsetSorted.add( (TimeCoord.Tinv) val);
Collections.sort(offsetSorted);
return new CoordinateTimeIntv(code, timeUnit, refDate, offsetSorted, null);
}
}
static public class Builder1 extends CoordinateBuilderImpl<Grib1Record> {
private final Grib1Customizer cust;
private final int code; // pdsFirst.getTimeUnit()
private final CalendarPeriod timeUnit;
private final CalendarDate refDate;
public Builder1(Grib1Customizer cust, int code, CalendarPeriod timeUnit, CalendarDate refDate) {
this.cust = cust;
this.code = code;
this.timeUnit = timeUnit;
this.refDate = refDate;
}
@Override
public Object extract(Grib1Record gr) {
Grib1SectionProductDefinition pds = gr.getPDSsection();
Grib1ParamTime ptime = gr.getParamTime(cust);
int tuInRecord = pds.getTimeUnit();
int[] intv = ptime.getInterval();
TimeCoord.Tinv tinv = new TimeCoord.Tinv(intv[0], intv[1]);
if (tuInRecord != code) {
CalendarPeriod unitInRecord = GribUtils.getCalendarPeriod(tuInRecord);
tinv = tinv.convertReferenceDate(gr.getReferenceDate(), unitInRecord, refDate, timeUnit);
}
return tinv;
}
@Override
public Coordinate makeCoordinate(List<Object> values) {
List<TimeCoord.Tinv> offsetSorted = new ArrayList<>(values.size());
for (Object val : values) offsetSorted.add( (TimeCoord.Tinv) val);
Collections.sort(offsetSorted);
return new CoordinateTimeIntv(code, timeUnit, refDate, offsetSorted, null);
}
}
}