package ucar.coord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ucar.nc2.grib.GribUtils;
import ucar.nc2.grib.TimeCoord;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarPeriod;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
/**
* Create union CoordinateTime2D's.
* Will build orthogonal and regular if possible.
* Does not actually depend on T.
*
* @author caron
* @since 11/22/2014
*/
class CoordinateTime2DUnionizer<T> extends CoordinateBuilderImpl<T> {
static private final Logger logger = LoggerFactory.getLogger(CoordinateTime2DUnionizer.class);
boolean isTimeInterval;
boolean makeVals;
CalendarPeriod timeUnit;
int code;
SortedMap<Long, CoordinateTimeAbstract> timeMap = new TreeMap<>();
boolean shown;
public CoordinateTime2DUnionizer(boolean isTimeInterval, CalendarPeriod timeUnit, int code, boolean makeVals) {
this.isTimeInterval = isTimeInterval;
this.timeUnit = timeUnit;
this.code = code;
this.makeVals = makeVals;
}
@Override
public void addAll(Coordinate coord) {
CoordinateTime2D coordT2D = (CoordinateTime2D) coord;
for (int runIdx = 0; runIdx < coordT2D.getNruns(); runIdx++) { // possible duplicate runtimes from different partitions
CoordinateTimeAbstract times = coordT2D.getTimeCoordinate(runIdx);
CoordinateTimeAbstract timesPrev = timeMap.get(coordT2D.getRuntime(runIdx));
if (timesPrev != null && !shown) {
logger.warn("CoordinateTime2DUnionizer duplicate runtimes from different partitions {}",
GribUtils.stackTraceToString(Thread.currentThread().getStackTrace()));
shown = true;
}
timeMap.put(coordT2D.getRuntime(runIdx), times); // later partitions will override LOOK could check how many times there are and choose larger
}
}
@Override
public Object extract(T gr) {
throw new RuntimeException();
}
// set the list of runtime coordinates; add any that are not already present, and make an empty CoordinateTimeAbstract for it
public void setRuntimeCoords(CoordinateRuntime runtimes) {
for (int idx=0; idx<runtimes.getSize(); idx++) {
CalendarDate cd = runtimes.getRuntimeDate(idx);
long runtime = runtimes.getRuntime(idx);
CoordinateTimeAbstract time = timeMap.get(runtime);
if (time == null) {
time = isTimeInterval ? new CoordinateTimeIntv(this.code, this.timeUnit, cd, new ArrayList<TimeCoord.Tinv>(0), null) :
new CoordinateTime(this.code, this.timeUnit, cd, new ArrayList<Integer>(0), null);
timeMap.put(runtime, time);
}
}
}
@Override
public Coordinate makeCoordinate(List<Object> values) {
// the set of unique runtimes, sorted
List<Long> runtimes = new ArrayList<>();
List<Coordinate> times = new ArrayList<>(); // the corresponding time coordinate for each runtime
List<CoordinateTime2D.Time2D> allVals = new ArrayList<>(); // optionally all Time2D coordinates
for (long runtime : timeMap.keySet()) {
runtimes.add(runtime);
CoordinateTimeAbstract time = timeMap.get(runtime);
times.add(time);
if (makeVals) {
CalendarDate cd = CalendarDate.of(runtime);
for (Object timeVal : time.getValues())
allVals.add( isTimeInterval ? new CoordinateTime2D.Time2D(cd, null, (TimeCoord.Tinv) timeVal) : new CoordinateTime2D.Time2D(cd, (Integer) timeVal, null));
}
}
Collections.sort(allVals);
CoordinateTimeAbstract maxCoord = testOrthogonal(timeMap.values());
if (maxCoord != null)
return new CoordinateTime2D(code, timeUnit, allVals, new CoordinateRuntime(runtimes, timeUnit), maxCoord, times);
List<Coordinate> regCoords = testIsRegular();
if (regCoords != null)
return new CoordinateTime2D(code, timeUnit, allVals, new CoordinateRuntime(runtimes, timeUnit), regCoords, times);
return new CoordinateTime2D(code, timeUnit, allVals, new CoordinateRuntime(runtimes, timeUnit), times);
}
// regular means that all the times for each offset from 0Z can be made into a single time coordinate (FMRC algo)
private List<Coordinate> testIsRegular() {
// group time coords by offset hour
Map<Integer, List<CoordinateTimeAbstract>> hourMap = new TreeMap<>();
for (CoordinateTimeAbstract coord : timeMap.values()) {
CalendarDate runDate = coord.getRefDate();
int hour = runDate.getHourOfDay();
List<CoordinateTimeAbstract> hg = hourMap.get(hour);
if (hg == null) {
hg = new ArrayList<>();
hourMap.put(hour, hg);
}
hg.add(coord);
}
// see if each offset hour is orthogonal
List<Coordinate> result = new ArrayList<>();
for (int hour : hourMap.keySet()) {
List<CoordinateTimeAbstract> hg = hourMap.get(hour);
Coordinate maxCoord = testOrthogonal(hg);
if (maxCoord == null) return null;
result.add(maxCoord);
}
return result;
}
// check if the coordinate with maximum # values includes all of the time in the collection
// if so, we can store time2D as orthogonal
// LOOK not right I think, consider one coordinate every 6 hours, and one every 24; should not be merged.
static public CoordinateTimeAbstract testOrthogonal(Collection<CoordinateTimeAbstract> times) {
CoordinateTimeAbstract maxCoord = null;
Set<Object> result = new HashSet<>(100);
int max = 0;
for (CoordinateTimeAbstract coord : times) {
if (max < coord.getSize()) {
maxCoord = coord;
max = coord.getSize();
}
for (Object val : coord.getValues())
result.add(val);
}
// is the set of all values the same as the component times?
// this means we can use the "orthogonal representation" of the time2D
int totalMax = result.size();
return totalMax == max ? maxCoord : null;
}
} // Time2DUnionBuilder