package org.esa.snap.timeseries.ui.graph;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.timeseries.core.insitu.InsituSource;
import org.esa.snap.timeseries.core.insitu.csv.InsituRecord;
import org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries;
import org.esa.snap.timeseries.core.timeseries.datamodel.AxisMapping;
import org.esa.snap.timeseries.core.timeseries.datamodel.TimeCoding;
import org.esa.snap.util.ProductUtils;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesDataItem;
import javax.swing.SwingWorker;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutionException;
class TimeSeriesGraphUpdater extends SwingWorker<List<TimeSeries>, Void> {
private final WorkerChainSupport workerChainSupport;
private final Position cursorPosition;
private final PositionSupport positionSupport;
private final TimeSeriesType type;
private final boolean showCursorTimeSeries;
private final int version;
private final AbstractTimeSeries timeSeries;
private final TimeSeriesDataHandler dataHandler;
private final VersionSafeDataSources dataSources;
private final AxisMapping displayAxisMapping;
TimeSeriesGraphUpdater(AbstractTimeSeries timeSeries, VersionSafeDataSources dataSources,
TimeSeriesDataHandler dataHandler, AxisMapping displayAxisMapping,
WorkerChainSupport workerChainSupport, Position cursorPosition,
PositionSupport positionSupport, TimeSeriesType type, boolean showCursorTimeSeries,
int version) {
super();
this.timeSeries = timeSeries;
this.dataHandler = dataHandler;
this.dataSources = dataSources;
this.displayAxisMapping = displayAxisMapping;
this.workerChainSupport = workerChainSupport;
this.cursorPosition = cursorPosition;
this.positionSupport = positionSupport;
this.type = type;
this.showCursorTimeSeries = showCursorTimeSeries;
this.version = version;
}
@Override
protected List<TimeSeries> doInBackground() throws Exception {
if (dataSources.getCurrentVersion() != version) {
return Collections.emptyList();
}
if (type == TimeSeriesType.INSITU) {
return computeInsituTimeSeries();
} else {
return computeRasterTimeSeries();
}
}
@Override
protected void done() {
try {
if (dataSources.getCurrentVersion() != version) {
return;
}
dataHandler.addTimeSeries(get(), type);
} catch (InterruptedException | ExecutionException ignore) {
ignore.printStackTrace();
} finally {
workerChainSupport.removeWorkerAndStartNext(this);
}
}
private List<TimeSeries> computeRasterTimeSeries() {
final List<Position> positionsToDisplay = new ArrayList<>();
final ArrayList<String> positionNames = new ArrayList<>();
if (type.equals(TimeSeriesType.PIN)) {
final Placemark[] pinPositionsToDisplay = dataSources.getPinPositionsToDisplay();
for (Placemark namedGeoPos : pinPositionsToDisplay) {
positionsToDisplay.add(positionSupport.transformGeoPos(namedGeoPos.getGeoPos()));
positionNames.add(namedGeoPos.getLabel());
}
} else if (showCursorTimeSeries && cursorPosition != null) {
positionsToDisplay.add(cursorPosition);
positionNames.add("");
}
final Set<String> aliasNames = displayAxisMapping.getAliasNames();
final List<TimeSeries> rasterTimeSeries = new ArrayList<>();
for (int i = 0, positionsToDisplaySize = positionsToDisplay.size(); i < positionsToDisplaySize; i++) {
final Position position = positionsToDisplay.get(i);
final String positionName = positionNames.get(i);
for (String aliasName : aliasNames) {
final List<String> rasterNames = displayAxisMapping.getRasterNames(aliasName);
for (String rasterName : rasterNames) {
final List<Band> bandsForVariable = timeSeries.getBandsForVariable(rasterName);
final TimeSeries timeSeries = computeSingleTimeSeries(bandsForVariable, position.pixelX, position.pixelY, position.currentLevel, positionName);
rasterTimeSeries.add(dataHandler.getValidatedTimeSeries(timeSeries, rasterName, type));
}
}
}
return rasterTimeSeries;
}
private List<TimeSeries> computeInsituTimeSeries() {
final InsituSource insituSource = timeSeries.getInsituSource();
final List<TimeSeries> insituTimeSeries = new ArrayList<>();
final Set<String> aliasNames = displayAxisMapping.getAliasNames();
final Placemark[] pinPositionsToDisplay = dataSources.getPinPositionsToDisplay();
for (Placemark insituPin : pinPositionsToDisplay) {
for (String aliasName : aliasNames) {
final List<String> insituNames = displayAxisMapping.getInsituNames(aliasName);
for (String insituName : insituNames) {
// todo
final GeoPos insituGeoposFor = timeSeries.getInsituGeoposFor(insituPin);
if (insituGeoposFor != null) {
InsituRecord[] insituRecords = insituSource.getValuesFor(insituName, insituGeoposFor);
final TimeSeries timeSeries = computeSingleTimeSeries(insituRecords, insituName + "_" + insituPin.getLabel());
insituTimeSeries.add(dataHandler.getValidatedTimeSeries(timeSeries, insituName, type));
}
}
}
}
return insituTimeSeries;
}
private TimeSeries computeSingleTimeSeries(InsituRecord[] insituRecords, String insituName) {
TimeSeries timeSeries = new TimeSeries(insituName);
for (InsituRecord insituRecord : insituRecords) {
final ProductData.UTC startTime = ProductData.UTC.create(insituRecord.time, 0);
final Millisecond timePeriod = new Millisecond(startTime.getAsDate(),
ProductData.UTC.UTC_TIME_ZONE,
Locale.getDefault());
timeSeries.addOrUpdate(timePeriod, insituRecord.value);
}
return timeSeries;
}
private TimeSeries computeSingleTimeSeries(final List<Band> bandList, int pixelX, int pixelY, int currentLevel, String positionName) {
final Band firstBand = bandList.get(0);
final String firstBandName = firstBand.getName();
final int lastUnderscore = firstBandName.lastIndexOf("_");
final String suffix = positionName.isEmpty()?positionName: "_" + positionName;
final String timeSeriesName = firstBandName.substring(0, lastUnderscore);
final TimeSeries timeSeries = new TimeSeries(timeSeriesName + suffix);
for (Band band : bandList) {
final TimeCoding timeCoding = this.timeSeries.getRasterTimeMap().get(band);
if (timeCoding != null) {
final ProductData.UTC startTime = timeCoding.getStartTime();
final Millisecond timePeriod = new Millisecond(startTime.getAsDate(),
ProductData.UTC.UTC_TIME_ZONE,
Locale.getDefault());
final double value = getValue(band, pixelX, pixelY, currentLevel);
timeSeries.add(new TimeSeriesDataItem(timePeriod, value));
}
}
return timeSeries;
}
private static double getValue(Band band, int pixelX, int pixelY, int currentLevel) {
final Rectangle pixelRect = new Rectangle(pixelX, pixelY, 1, 1);
if (band.getValidMaskImage() != null) {
final RenderedImage validMask = band.getValidMaskImage().getImage(currentLevel);
final Raster validMaskData = validMask.getData(pixelRect);
if (validMaskData.getSample(pixelX, pixelY, 0) > 0) {
return ProductUtils.getGeophysicalSampleDouble(band, pixelX, pixelY, currentLevel);
} else {
return band.getNoDataValue();
}
} else {
return ProductUtils.getGeophysicalSampleDouble(band, pixelX, pixelY, currentLevel);
}
}
static class Position {
private final int pixelX;
private final int pixelY;
private final int currentLevel;
Position(int pixelX, int pixelY, int currentLevel) {
this.currentLevel = currentLevel;
this.pixelY = pixelY;
this.pixelX = pixelX;
}
}
static interface TimeSeriesDataHandler {
void addTimeSeries(List<TimeSeries> data, TimeSeriesType type);
TimeSeries getValidatedTimeSeries(TimeSeries timeSeries, String dataSourceName, TimeSeriesType type);
}
static interface WorkerChainSupport {
void removeWorkerAndStartNext(TimeSeriesGraphUpdater worker);
}
static abstract class VersionSafeDataSources {
private final Placemark[] pinPositionsToDisplay;
private final int version;
protected VersionSafeDataSources(Placemark[] pinPositionsToDisplay, final int version) {
this.pinPositionsToDisplay = pinPositionsToDisplay;
this.version = version;
}
public Placemark[] getPinPositionsToDisplay() {
if (canReturnValues()) {
return pinPositionsToDisplay;
}
return new Placemark[0];
}
protected abstract int getCurrentVersion();
private boolean canReturnValues() {
return getCurrentVersion() == version;
}
}
static interface PositionSupport {
Position transformGeoPos(GeoPos geoPos);
}
}