/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.ncwms;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Logger;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.Response;
import org.geoserver.ows.util.OwsUtils;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetFeatureInfoRequest;
import org.geoserver.wms.WMS;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.util.logging.Logging;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.ProjectedCRS;
import org.springframework.util.Assert;
import com.vividsolutions.jts.geom.Coordinate;
import net.opengis.wfs.FeatureCollectionType;
/**
* Formats the output of a GetTimeSeries response as a JPG or PNG chart or as a CSV file.
*/
public class GetTimeSeriesResponse extends Response {
private static final Logger LOGGER = Logging.getLogger(GetTimeSeriesResponse.class);
protected static final Set<String> outputFormats = new HashSet<String>();
static {
outputFormats.add("text/csv");
outputFormats.add("image/png");
outputFormats.add("image/jpg");
outputFormats.add("image/jpeg");
}
private WMS wms;
private final static String ISO8601_2000_UTC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
private final static int IMAGE_HEIGHT = 600, IMAGE_WIDTH = 700;
public GetTimeSeriesResponse(final WMS wms) {
super(FeatureCollectionType.class, outputFormats);
this.wms = wms;
}
/**
* @see org.geoserver.ows.Response#canHandle(org.geoserver.platform.Operation)
*/
@Override
public boolean canHandle(Operation operation) {
return "GetTimeSeries".equalsIgnoreCase(operation.getId());
}
@Override
public String getMimeType(Object value, Operation operation) throws ServiceException {
GetFeatureInfoRequest request = (GetFeatureInfoRequest) OwsUtils
.parameter(operation.getParameters(), GetFeatureInfoRequest.class);
String infoFormat = (String) request.getRawKvp().get("INFO_FORMAT");
if (infoFormat != null && outputFormats.contains(infoFormat.toLowerCase())) {
return infoFormat;
}
// default
return "text/csv";
}
@Override
public void write(Object value, OutputStream output, Operation operation)
throws IOException, ServiceException {
Assert.notNull(value, "value is null");
Assert.notNull(operation, "operation is null");
Assert.isTrue(value instanceof FeatureCollectionType, "unrecognized result type:");
Assert.isTrue(operation.getParameters() != null && operation.getParameters().length == 1
&& operation.getParameters()[0] instanceof GetFeatureInfoRequest);
GetFeatureInfoRequest request = (GetFeatureInfoRequest) operation.getParameters()[0];
FeatureCollectionType results = (FeatureCollectionType) value;
String mime = getMimeType(value, operation);
if (mime.startsWith("image/")) {
writeChart(request, results, output, mime);
} else {
writeCsv(request, results, output);
}
}
@SuppressWarnings("rawtypes")
private void writeChart(GetFeatureInfoRequest request, FeatureCollectionType results,
OutputStream output, String mimeType) throws IOException {
final TimeSeries series = new TimeSeries("time", Millisecond.class);
String valueAxisLabel = "Value";
String title = "Time series";
final String timeaxisLabel = "Date / time";
final List collections = results.getFeature();
if (collections.size() > 0) {
SimpleFeatureCollection fc = (SimpleFeatureCollection) collections.get(0);
title += " of " + fc.getSchema().getName().getLocalPart();
valueAxisLabel = fc.getSchema().getDescription().toString();
try (SimpleFeatureIterator fi = fc.features()) {
while (fi.hasNext()) {
SimpleFeature f = fi.next();
Date date = (Date) f.getAttribute("date");
Double value = (Double) f.getAttribute("value");
series.add(new Millisecond(date), value);
}
}
}
XYDataset dataset = new TimeSeriesCollection(series);
JFreeChart chart = ChartFactory.createTimeSeriesChart(title, timeaxisLabel, valueAxisLabel,
dataset, false, false, false);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setRenderer(new XYLineAndShapeRenderer());
if (mimeType.startsWith("image/png")) {
ChartUtilities.writeChartAsPNG(output, chart, IMAGE_WIDTH, IMAGE_HEIGHT);
} else if (mimeType.equals("image/jpg") || mimeType.equals("image/jpeg")) {
ChartUtilities.writeChartAsJPEG(output, chart, IMAGE_WIDTH, IMAGE_HEIGHT);
}
}
@SuppressWarnings("rawtypes")
private void writeCsv(GetFeatureInfoRequest request, FeatureCollectionType results,
OutputStream output) {
Charset charSet = wms.getCharSet();
OutputStreamWriter osw = new OutputStreamWriter(output, charSet);
PrintWriter writer = new PrintWriter(osw);
CoordinateReferenceSystem crs = request.getGetMapRequest().getCrs();
final Coordinate middle = WMS.pixelToWorld(request.getXPixel(), request.getYPixel(),
new ReferencedEnvelope(request.getGetMapRequest().getBbox(), crs),
request.getGetMapRequest().getWidth(), request.getGetMapRequest().getHeight());
if (crs instanceof ProjectedCRS) {
writer.println("# X: " + middle.y);
writer.println("# Y: " + middle.x);
} else {
writer.println("# Latitude: " + middle.y);
writer.println("# Longitude: " + middle.x);
}
final List collections = results.getFeature();
if (collections.size() > 0) {
SimpleFeatureCollection fc = (SimpleFeatureCollection) collections.get(0);
writer.println("Time (UTC)," + fc.getSchema().getDescription().toString());
DateFormat isoFormatter = new SimpleDateFormat(ISO8601_2000_UTC_PATTERN);
isoFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
try (SimpleFeatureIterator fi = fc.features()) {
while (fi.hasNext()) {
SimpleFeature f = fi.next();
Date date = (Date) f.getAttribute("date");
Double value = (Double) f.getAttribute("value");
writer.println(isoFormatter.format(date) + "," + value);
}
}
}
writer.flush();
}
@Override
public String getAttachmentFileName(Object value, Operation operation) {
Request request = Dispatcher.REQUEST.get();
if (request != null && request.getRawKvp() != null
&& request.getRawKvp().get("QUERY_LAYERS") != null
&& request.getRawKvp().get("INFO_FORMAT") != null) {
String filename = null;
String layers = ((String) request.getRawKvp().get("QUERY_LAYERS")).trim();
if (layers.length() > 0) {
filename = layers.replace(",", "_").replace(":", "-");
String format = (String) request.getRawKvp().get("INFO_FORMAT");
String[] splitted = format.split("/");
if (splitted.length > 0) {
filename = filename += "." + splitted[1];
}
return filename;
}
}
// fallback on the default behavior otherwise
return super.getAttachmentFileName(value, operation);
}
}