org.n52.io.type.quantity.handler.img.ChartIoHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.n52.io.type.quantity.handler.img.ChartIoHandler.java

Source

/*
 * Copyright (C) 2013-2019 52North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * If the program is linked with libraries which are licensed under one of
 * the following licenses, the combination of the program with the linked
 * library is not considered a "derivative work" of the program:
 *
 *     - Apache License, version 2.0
 *     - Apache Software License, version 1.0
 *     - GNU Lesser General Public License, version 3
 *     - Mozilla Public License, versions 1.0, 1.1 and 2.0
 *     - Common Development and Distribution License (CDDL), version 1.0
 *
 * Therefore the distribution of the program linked with libraries licensed
 * under the aforementioned licenses, is permitted by the copyright holders
 * if the distribution is compliant with both the GNU General Public License
 * version 2 and the aforementioned licenses.
 *
 * This program 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 General Public License
 * for more details.
 */
package org.n52.io.type.quantity.handler.img;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

import javax.imageio.ImageIO;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.TextTitle;
import org.jfree.chart.ui.HorizontalAlignment;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.chart.ui.VerticalAlignment;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.n52.io.Constants;
import org.n52.io.IntervalWithTimeZone;
import org.n52.io.IoParseException;
import org.n52.io.IoStyleContext;
import org.n52.io.IoStyleContext.StyleMetadata;
import org.n52.io.handler.IoHandler;
import org.n52.io.handler.IoProcessChain;
import org.n52.io.request.IoParameters;
import org.n52.io.request.Parameters;
import org.n52.io.request.StyleProperties;
import org.n52.io.response.ParameterOutput;
import org.n52.io.response.dataset.Data;
import org.n52.io.response.dataset.DataCollection;
import org.n52.io.response.dataset.DatasetOutput;
import org.n52.io.response.dataset.DatasetParameters;
import org.n52.io.response.dataset.quantity.QuantityValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class ChartIoHandler extends IoHandler<Data<QuantityValue>> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ChartIoHandler.class);

    // TODO refactor prerendering to that it can be moved to io module
    private static final String RENDERING_TRIGGER_PRERENDERING = "prerendering";

    // TODO refactor prerendering to that it can be moved to io module
    private static final String PARAMETER_PRERENDERING_TITLE = "title";

    private final IoStyleContext context;

    private final XYPlot xyPlot;

    private Constants.MimeType mimeType;

    private JFreeChart jFreeChart;

    public ChartIoHandler(IoParameters parameters, IoProcessChain<Data<QuantityValue>> processChain,
            IoStyleContext context) {
        super(parameters, processChain);
        this.context = context;
        this.xyPlot = createChart(context);
    }

    public abstract void writeDataToChart(DataCollection<Data<QuantityValue>> data) throws IoParseException;

    @Override
    public void encodeAndWriteTo(DataCollection<Data<QuantityValue>> data, OutputStream stream)
            throws IoParseException {
        try {
            writeDataToChart(data);
            ImageIO.write(createImage(), mimeType.getFormatName(), stream);
        } catch (IOException e) {
            throw new IoParseException("Could not write image to output stream.", e);
        }
    }

    private BufferedImage createImage() {
        IoParameters parameters = getParameters();
        int width = parameters.getWidth();
        int height = parameters.getHeight();
        BufferedImage chartImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D chartGraphics = chartImage.createGraphics();
        chartGraphics.fillRect(0, 0, width, height);
        chartGraphics.setColor(Color.WHITE);

        jFreeChart.setTextAntiAlias(true);
        jFreeChart.setAntiAlias(true);
        if (jFreeChart.getLegend() != null) {
            jFreeChart.getLegend().setFrame(BlockBorder.NONE);
        }
        jFreeChart.draw(chartGraphics, new Rectangle2D.Float(0, 0, width, height));
        return chartImage;
    }

    public XYPlot getXYPlot() {
        return xyPlot;
    }

    public IoStyleContext getRenderingContext() {
        return context;
    }

    public void setMimeType(Constants.MimeType mimeType) {
        this.mimeType = mimeType;
    }

    private XYPlot createChart(IoStyleContext styleContext) {
        String timespan = getTimespan();
        DateTime end = new DateTime(timespan.split("/")[1]);
        String zoneName = getTimezone().getShortName(end.getMillis(), i18n.getLocale());
        StringBuilder domainAxisLabel = new StringBuilder(i18n.get("msg.io.chart.time"));
        domainAxisLabel.append(" (").append(zoneName).append(")");

        IoParameters parameters = getParameters();
        boolean showLegend = parameters.isLegend();
        jFreeChart = ChartFactory.createTimeSeriesChart(null, domainAxisLabel.toString(),
                i18n.get("msg.io.chart.value"), null, showLegend, false, true);
        return createPlotArea(jFreeChart);
    }

    private XYPlot createPlotArea(JFreeChart chart) {
        XYPlot plot = chart.getXYPlot();
        plot.setBackgroundPaint(Color.WHITE);
        plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
        plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
        plot.setAxisOffset(new RectangleInsets(2.0, 2.0, 2.0, 2.0));
        showCrosshairsOnAxes(plot);
        configureDomainAxis(plot);
        showGridlinesOnChart(plot);
        configureTimeAxis(plot);
        configureTitle(chart);
        addNotice(chart);
        return plot;
    }

    private void addNotice(JFreeChart chart) {
        TextTitle notice = new TextTitle();
        String msg = i18n.get("msg.io.chart.notice");
        if (msg != null && !msg.isEmpty()) {
            notice.setText(msg);
            notice.setPaint(Color.BLACK);
            notice.setFont(LabelConstants.FONT_LABEL_SMALL);
            notice.setPosition(RectangleEdge.BOTTOM);
            notice.setHorizontalAlignment(HorizontalAlignment.RIGHT);
            notice.setVerticalAlignment(VerticalAlignment.BOTTOM);
            notice.setPadding(new RectangleInsets(0, 0, 20, 20));
            chart.addSubtitle(notice);
        }
    }

    private void configureDomainAxis(XYPlot plot) {
        ValueAxis domainAxis = plot.getDomainAxis();
        domainAxis.setTickLabelFont(LabelConstants.FONT_LABEL);
        domainAxis.setLabelFont(LabelConstants.FONT_LABEL);
        domainAxis.setTickLabelPaint(LabelConstants.COLOR);
        domainAxis.setLabelPaint(LabelConstants.COLOR);
    }

    private void showCrosshairsOnAxes(XYPlot plot) {
        plot.setDomainCrosshairVisible(true);
        plot.setRangeCrosshairVisible(true);
    }

    private void showGridlinesOnChart(XYPlot plot) {
        IoParameters parameters = getParameters();
        boolean showGrid = parameters.isGrid();
        plot.setDomainGridlinesVisible(showGrid);
        plot.setRangeGridlinesVisible(showGrid);
    }

    private void configureTimeAxis(XYPlot plot) {
        DateAxis timeAxis = (DateAxis) plot.getDomainAxis();
        final Date start = getStartTime(getTimespan());
        final Date end = getEndTime(getTimespan());
        timeAxis.setRange(start, end);

        final Locale locale = i18n.getLocale();
        IoParameters parameters = getParameters();
        String timeformat = parameters.getTimeFormat();
        DateFormat requestTimeFormat = new SimpleDateFormat(timeformat, locale);
        final DateTimeZone timezone = getTimezone();
        requestTimeFormat.setTimeZone(timezone.toTimeZone());
        timeAxis.setDateFormatOverride(requestTimeFormat);
        timeAxis.setTimeZone(timezone.toTimeZone());
    }

    private String getTimespan() {
        IoParameters parameters = getParameters();
        IntervalWithTimeZone timespan = parameters.getTimespan();
        return timespan.toString();
    }

    private DateTimeZone getTimezone() {
        IoParameters parameters = getParameters();
        return DateTimeZone.forID(parameters.getOutputTimezone());
    }

    public ValueAxis createRangeAxis(DatasetOutput metadata) {
        NumberAxis axis = new NumberAxis(createRangeLabel(metadata));
        axis.setTickLabelFont(LabelConstants.FONT_LABEL);
        axis.setLabelFont(LabelConstants.FONT_LABEL);
        axis.setTickLabelPaint(LabelConstants.COLOR);
        axis.setLabelPaint(LabelConstants.COLOR);
        return axis;
    }

    protected String createRangeLabel(DatasetOutput output) {
        DatasetParameters parameters = output.getDatasetParameters();
        ParameterOutput phenomenon = parameters.getPhenomenon();
        StringBuilder uom = new StringBuilder();
        uom.append(phenomenon.getLabel());
        String uomLabel = output.getUom();
        if (uomLabel != null && !uomLabel.isEmpty()) {
            uom.append(" [").append(uomLabel).append("]");
        }
        return uom.toString();
    }

    private void configureTitle(JFreeChart chart) {
        IoParameters parameters = getParameters();
        if (parameters.containsParameter(PARAMETER_PRERENDERING_TITLE)) {
            String title = parameters.getAsString(PARAMETER_PRERENDERING_TITLE);
            if (parameters.containsParameter(Parameters.RENDERING_TRIGGER)) {
                String trigger = parameters.getAsString(Parameters.RENDERING_TRIGGER);
                title = RENDERING_TRIGGER_PRERENDERING.equalsIgnoreCase(trigger)
                        ? getTitleForSingle(parameters, title)
                        : title;
            }
            chart.setTitle(title);
        }
    }

    private String getTitleForSingle(IoParameters parameters, String template) {
        Set<String> datasets = parameters.getDatasets();
        if (!datasets.isEmpty()) {
            Iterator<String> iterator = datasets.iterator();
            DatasetOutput metadata = getTimeseriesMetadataOutput(iterator.next());
            if (metadata != null) {
                return formatTitle(metadata, template);
            }
        }
        return template;
    }

    protected String formatTitle(DatasetOutput metadata, String title) {
        DatasetParameters parameters = metadata.getDatasetParameters();
        Object[] varargs = {
                // index important to reference in config!
                parameters.getPlatform().getLabel(), parameters.getPhenomenon().getLabel(),
                parameters.getProcedure().getLabel(), parameters.getCategory().getLabel(),
                parameters.getOffering().getLabel(), metadata.getFeature().getLabel(),
                parameters.getService().getLabel(), metadata.getUom() };
        try {
            return String.format(title, varargs);
        } catch (Exception e) {
            String datasetId = metadata.getId();
            LOGGER.info("Couldn't format title while prerendering dataset '{}'", datasetId, e);
            // return template as fallback
            return title;
        }
    }

    private DatasetOutput<?> getTimeseriesMetadataOutput(String datasetId) {
        for (DatasetOutput<?> output : getMetadataOutputs()) {
            if (output.getId().equals(datasetId)) {
                return output;
            }
        }
        return null;
    }

    protected List<? extends DatasetOutput<?>> getMetadataOutputs() {
        return context.getAllDatasetMetadatas();
    }

    protected StyleProperties getDatasetStyleFor(String datasetId) {
        Optional<StyleMetadata> optional = context.getStyleMetadataFor(datasetId);
        StyleMetadata styleMetadata = optional.get();
        return styleMetadata.getStyleProperties();
    }

    protected StyleProperties getTimeseriesStyleFor(String datasetId, String referenceValueDatasetId) {
        return context.getReferenceDatasetStyleOptions(datasetId, referenceValueDatasetId);
    }

    protected boolean isLineStyle(StyleProperties properties) {
        return LineRenderer.LINE_CHART_TYPE.equals(properties.getChartType()) || isLineStyleDefault(properties);
    }

    protected boolean isBarStyle(StyleProperties properties) {
        return BarRenderer.BAR_CHART_TYPE.equals(properties.getChartType()) && !isLineStyleDefault(properties);
    }

    private boolean isLineStyleDefault(StyleProperties properties) {
        return properties == null;
    }

    protected Date getStartTime(String timespan) {
        Interval interval = Interval.parse(timespan);
        return interval.getStart().toDate();
    }

    protected Date getEndTime(String timespan) {
        Interval interval = Interval.parse(timespan);
        return interval.getEnd().toDate();
    }

    static class LabelConstants {
        static final Color COLOR = Color.BLACK;
        static final int FONT_SIZE = 12;
        static final int FONT_SIZE_SMALL = 9;
        static final int FONT_SIZE_TICKS = 10;
        static final String LOGICAL_FONT = "Sans-serif";
        static final Font FONT_LABEL = new Font(LOGICAL_FONT, Font.BOLD, FONT_SIZE);
        static final Font FONT_DOMAIN = new Font(LOGICAL_FONT, Font.PLAIN, FONT_SIZE_TICKS);
        static final Font FONT_LABEL_SMALL = new Font(LOGICAL_FONT, Font.PLAIN, FONT_SIZE_SMALL);
    }

}