org.matsim.counts.algorithms.CountSimComparisonKMLWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.matsim.counts.algorithms.CountSimComparisonKMLWriter.java

Source

/* *********************************************************************** *
 * project: org.matsim.*
 * CountSimComparisonKMLWriter.java
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2007 by the members listed in the COPYING,        *
 *                   LICENSE and WARRANTY file.                            *
 * email           : info at matsim dot org                                *
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *   See also COPYING, LICENSE and WARRANTY file                           *
 *                                                                         *
 * *********************************************************************** */

package org.matsim.counts.algorithms;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.opengis.kml._2.DocumentType;
import net.opengis.kml._2.FolderType;
import net.opengis.kml._2.IconStyleType;
import net.opengis.kml._2.KmlType;
import net.opengis.kml._2.LinkType;
import net.opengis.kml._2.ObjectFactory;
import net.opengis.kml._2.PlacemarkType;
import net.opengis.kml._2.PointType;
import net.opengis.kml._2.ScreenOverlayType;
import net.opengis.kml._2.StyleType;
import net.opengis.kml._2.TimeSpanType;
import net.opengis.kml._2.UnitsEnumType;
import net.opengis.kml._2.Vec2Type;

import org.apache.log4j.Logger;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.gbl.MatsimResource;
import org.matsim.core.utils.geometry.CoordinateTransformation;
import org.matsim.core.utils.io.IOUtils;
import org.matsim.core.utils.misc.Time;
import org.matsim.counts.CountSimComparison;
import org.matsim.counts.Counts;
import org.matsim.counts.algorithms.graphs.BiasErrorGraph;
import org.matsim.counts.algorithms.graphs.BoxPlotErrorGraph;
import org.matsim.counts.algorithms.graphs.CountsGraph;
import org.matsim.counts.algorithms.graphs.CountsLoadCurveGraph;
import org.matsim.counts.algorithms.graphs.CountsLoadCurveGraphCreator;
import org.matsim.counts.algorithms.graphs.CountsSimReal24Graph;
import org.matsim.counts.algorithms.graphs.CountsSimRealPerHourGraph;
import org.matsim.vis.kml.KMZWriter;
import org.matsim.vis.kml.MatsimKMLLogo;
import org.matsim.vis.kml.NetworkFeatureFactory;

/**
 * @author dgrether
 */
public class CountSimComparisonKMLWriter extends CountSimComparisonWriter {
    /**
     * constant for the name of the links
     */
    private static final String LINK = "Link: ";

    private static final String COUNTVALUE = "Count Value: ";
    private static final String MATSIMVALUE = "MATSim Value: ";
    private static final String RELERROR = "Relative Error: ";
    private static final String IMG = "<img src=\"../";
    private static final String IMGEND = "\">";
    private static final String H24OVERVIEW = "24 h overview";
    private static final String DETAILSFROM = "Details from ";
    private static final String OCLOCKTO = " o'clock to ";
    private static final String OCLOCK = " o'clock";
    private static final String ZERO = "0";

    /*
     * the icons
     */
    private static final String CROSSICON = "icons/plus.png";
    private static final String MINUSICON = "icons/minus.png";

    /**
     * the scale for the icons
     */
    private static final Double ICONSCALE = Double.valueOf(0.5);
    /**
     * height of the charts
     */
    private static final int CHARTHEIGHT = 300;
    /**
     * width of the  charts
     */
    private static final int CHARTWIDTH = 400;
    /**
     * constant for the file suffix of graphs
     */
    private static final String PNG = ".png";
    private final String graphname;

    private final Network network;
    private final Counts counts;

    private CoordinateTransformation coordTransform = null;
    private ObjectFactory kmlObjectFactory = new ObjectFactory();
    /**
     * main kml, doc and folder
     */
    private KmlType mainKml = null;

    private DocumentType mainDoc = null;
    private FolderType mainFolder = null;
    private KMZWriter writer = null;

    private StyleType redCrossStyle;
    private StyleType redMinusStyle;
    private StyleType yellowCrossStyle;
    private StyleType yellowMinusStyle;
    private StyleType greenMinusStyle;
    private StyleType greenCrossStyle;
    private StyleType greyCrossStyle;
    private StyleType greyMinusStyle;
    /**
     * maps linkids to filenames in the kmz
     */
    private Map<String, String> countsLoadCurveGraphMap;

    /** The logging object for this class. */
    private static final Logger log = Logger.getLogger(CountSimComparisonKMLWriter.class);

    /**
     * Sets the data to the fields of this class.  We either accept "network" or "counts" to localize the counting stations.
     */
    public CountSimComparisonKMLWriter(final List<CountSimComparison> countSimCompList, final Network network,
            final CoordinateTransformation coordTransform) {
        super(countSimCompList);
        this.network = network;
        this.counts = null;
        this.coordTransform = coordTransform;
        this.graphname = "countsSimRealPerHour_";
    }

    /**
     * Sets the data to the fields of this class.  We either accept "network" or "counts" to localize the counting stations.
     * @param graphname TODO
     */
    public CountSimComparisonKMLWriter(final List<CountSimComparison> countSimCompList, final Counts counts,
            final CoordinateTransformation coordTransform, String graphname) {
        super(countSimCompList);
        this.network = null;
        this.counts = counts;
        this.coordTransform = coordTransform;
        this.graphname = graphname;
    }

    /**
     * This method initializes the styles for the different icons used.
     */
    private void createStyles() {

        this.redCrossStyle = kmlObjectFactory.createStyleType();
        this.redCrossStyle.setId("redCrossStyle");
        this.redMinusStyle = kmlObjectFactory.createStyleType();
        this.redMinusStyle.setId("redMinusStyle");
        this.yellowCrossStyle = kmlObjectFactory.createStyleType();
        this.yellowCrossStyle.setId("yellowCrossStyle");
        this.yellowMinusStyle = kmlObjectFactory.createStyleType();
        this.yellowMinusStyle.setId("yellowMinusStyle");
        this.greenCrossStyle = kmlObjectFactory.createStyleType();
        this.greenCrossStyle.setId("greenCrossStyle");
        this.greenMinusStyle = kmlObjectFactory.createStyleType();
        this.greenMinusStyle.setId("greenMinusStyle");
        this.greyCrossStyle = kmlObjectFactory.createStyleType();
        this.greyCrossStyle.setId("greyCrossStyle");
        this.greyMinusStyle = kmlObjectFactory.createStyleType();
        this.greyMinusStyle.setId("greyMinusStyle");

        byte[] red = new byte[] { (byte) 0xFF, (byte) 0x0F, (byte) 0x0F, (byte) 0xBE };
        byte[] green = new byte[] { (byte) 0xFF, (byte) 0x14, (byte) 0xDC, (byte) 0x0A };
        byte[] yellow = new byte[] { (byte) 0xFF, (byte) 0x14, (byte) 0xE6, (byte) 0xE6 };
        byte[] grey = new byte[] { (byte) 0xFF, (byte) 0x42, (byte) 0x42, (byte) 0x42 };

        HashMap<StyleType, byte[]> colors = new HashMap<StyleType, byte[]>();
        colors.put(this.redCrossStyle, red);
        colors.put(this.redMinusStyle, red);
        colors.put(this.yellowCrossStyle, yellow);
        colors.put(this.yellowMinusStyle, yellow);
        colors.put(this.greenCrossStyle, green);
        colors.put(this.greenMinusStyle, green);
        colors.put(this.greyCrossStyle, grey);
        colors.put(this.greyMinusStyle, grey);

        HashMap<StyleType, String> hrefs = new HashMap<StyleType, String>();
        hrefs.put(this.redCrossStyle, CROSSICON);
        hrefs.put(this.redMinusStyle, MINUSICON);
        hrefs.put(this.yellowCrossStyle, CROSSICON);
        hrefs.put(this.yellowMinusStyle, MINUSICON);
        hrefs.put(this.greenCrossStyle, CROSSICON);
        hrefs.put(this.greenMinusStyle, MINUSICON);
        hrefs.put(this.greyCrossStyle, CROSSICON);
        hrefs.put(this.greyMinusStyle, MINUSICON);

        for (StyleType styleType : new StyleType[] { this.redCrossStyle, this.redMinusStyle, this.yellowCrossStyle,
                this.yellowMinusStyle, this.greenCrossStyle, this.greenMinusStyle, this.greyCrossStyle,
                this.greyMinusStyle }) {

            IconStyleType icon = kmlObjectFactory.createIconStyleType();
            icon.setColor(new byte[] { colors.get(styleType)[0], colors.get(styleType)[1], colors.get(styleType)[2],
                    colors.get(styleType)[3] });
            icon.setScale(ICONSCALE);

            LinkType link = kmlObjectFactory.createLinkType();
            link.setHref(hrefs.get(styleType));
            icon.setIcon(link);

            styleType.setIconStyle(icon);

            this.mainDoc.getAbstractStyleSelectorGroup().add(kmlObjectFactory.createStyle(styleType));
        }
    }

    /**
     * Writes the data to the file at the path given as String
     *
     * @param filename
     */
    @Override
    public void writeFile(final String filename) {
        log.info("Writing google earth file to " + filename);
        // init kml
        this.mainKml = kmlObjectFactory.createKmlType();
        this.mainDoc = kmlObjectFactory.createDocumentType();
        this.mainKml.setAbstractFeatureGroup(kmlObjectFactory.createDocument(mainDoc));

        // create the styles and the folders
        createStyles();
        // create a folder
        this.mainFolder = kmlObjectFactory.createFolderType();
        this.mainFolder.setName("Comparison, Iteration " + this.iterationNumber);
        this.mainDoc.getAbstractFeatureGroup().add(kmlObjectFactory.createFolder(this.mainFolder));
        // the writer
        this.writer = new KMZWriter(filename);

        try {
            //try to create the legend
            this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(createLegend()));
        } catch (IOException e) {
            log.error("Cannot add legend to the KMZ file.", e);
        }
        try {
            //add the matsim logo to the kml
            this.mainFolder.getAbstractFeatureGroup()
                    .add(kmlObjectFactory.createScreenOverlay(MatsimKMLLogo.writeMatsimKMLLogo(writer)));
        } catch (IOException e) {
            log.error("Cannot add logo to the KMZ file.", e);
        }

        try {
            // copy required icons to the kmz
            this.writer.addNonKMLFile(MatsimResource.getAsInputStream("icons/plus.png"), CROSSICON);
            this.writer.addNonKMLFile(MatsimResource.getAsInputStream("icons/minus.png"), MINUSICON);
        } catch (IOException e) {
            log.error("Could not copy copy plus-/minus-icons to the KMZ.", e);
        }

        // prepare folders for simRealPerHour-Graphs (top-left, xy-plots)
        FolderType simRealFolder = kmlObjectFactory.createFolderType();
        simRealFolder.setName("XY Comparison Plots");
        this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createFolder(simRealFolder));

        // error graphs and awtv graph
        {
            ScreenOverlayType errorGraph = createBiasErrorGraph(filename);
            errorGraph.setVisibility(Boolean.TRUE);
            this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(errorGraph));
        }
        {
            ScreenOverlayType errorGraph = createBoxPlotErrorGraph();
            if (errorGraph != null) {
                errorGraph.setVisibility(Boolean.FALSE);
                this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(errorGraph));
            }
        }
        {
            ScreenOverlayType awtv = null;
            try {
                awtv = this.createAWTVGraph();
            } catch (Exception ee) {
                log.warn(
                        "generating awtv (average weekday traffic volumes) graph failed; printing stacktrace but continuing anyways ...");
                for (int ii = 0; ii < ee.getStackTrace().length; ii++) {
                    log.info(ee.getStackTrace()[ii].toString());
                }
            }
            if (awtv != null) {
                awtv.setVisibility(Boolean.FALSE);
                this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(awtv));
            }
        }

        // link graphs
        this.createCountsLoadCurveGraphs();

        // hourly data...
        for (int h = 1; h < 25; h++) {
            // the timespan for this hour
            TimeSpanType timespan = kmlObjectFactory.createTimeSpanType();
            timespan.setBegin("1999-01-01T" + Time.writeTime(((h - 1) * 3600)));
            timespan.setEnd("1999-01-01T" + Time.writeTime((h * 3600)));

            // first add the xyplot ("SimRealPerHourGraph") as overlay
            this.addCountsSimRealPerHourGraphs(simRealFolder, h, timespan);

            // add the placemarks for the links in this hour
            FolderType subfolder = kmlObjectFactory.createFolderType();
            subfolder.setName(createFolderName(h));
            subfolder.setAbstractTimePrimitiveGroup(kmlObjectFactory.createTimeSpan(timespan));
            this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createFolder(subfolder));

            writeLinkData(this.countComparisonFilter.getCountsForHour(Integer.valueOf(h)), subfolder);
        }
        finish();
    }

    /**
     * Creates the string for the foldername
     * @param timestep
     * @return a timestep specific standard string
     */
    private String createFolderName(final int timestep) {
        StringBuilder buffer = new StringBuilder(30);
        buffer.append("Traffic from ");
        buffer.append(this.timestepToString(timestep - 1));
        buffer.append(" to ");
        buffer.append(this.timestepToString(timestep));
        buffer.append(" o'clock");
        return buffer.toString();
    }

    /**
     * Creates a legend
     * @return a ScreenOverlay read from a file
     * @throws IOException
     */
    private ScreenOverlayType createLegend() throws IOException {

        this.writer.addNonKMLFile(MatsimResource.getAsInputStream("countsKml/countsLegend240x300.png"),
                "countsLegend.png");
        ScreenOverlayType overlay = kmlObjectFactory.createScreenOverlayType();
        LinkType icon = kmlObjectFactory.createLinkType();
        icon.setHref("./countsLegend.png");
        overlay.setIcon(icon);
        overlay.setName("Legend");
        // place the image bottom left
        Vec2Type overlayXY = kmlObjectFactory.createVec2Type();
        overlayXY.setX(0.0);
        overlayXY.setY(0.0);
        overlayXY.setXunits(UnitsEnumType.FRACTION);
        overlayXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setOverlayXY(overlayXY);
        Vec2Type screenXY = kmlObjectFactory.createVec2Type();
        screenXY.setX(0.02);
        screenXY.setY(0.07);
        screenXY.setXunits(UnitsEnumType.FRACTION);
        screenXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setScreenXY(screenXY);
        return overlay;

    }

    /**
     * Creates a placemark
     *
     * @param linkid
     * @param csc
     * @param relativeError
     * @param timestep
     * @return the Placemark instance with description and name set
     */
    private PlacemarkType createPlacemark(final String linkid, final CountSimComparison csc,
            final double relativeError, final int timestep) {
        StringBuilder stringBuffer = new StringBuilder();
        PlacemarkType placemark = kmlObjectFactory.createPlacemarkType();
        stringBuffer.delete(0, stringBuffer.length());
        stringBuffer.append(LINK);
        stringBuffer.append(linkid);
        placemark.setDescription(createPlacemarkDescription(linkid, csc, relativeError, timestep));
        return placemark;
    }

    /**
     * This method writes all the data for each of the links/counts to the kml
     * document.
     *
     * @param countSimComparisonList provides "the data"
     * @param folder The folder to which to add the data in the kml-file.
     */
    private void writeLinkData(final List<CountSimComparison> countSimComparisonList, final FolderType folder) {
        PlacemarkType placemark;
        double relativeError;
        PointType point;
        for (CountSimComparison csc : countSimComparisonList) {
            Id itemId = csc.getId();
            Coord coord = null;

            if (counts == null) {
                Link link = this.network.getLinks().get(itemId);
                coord = this.coordTransform.transform(calculatePlacemarkPosition(link));
            } else {
                coord = this.coordTransform.transform(counts.getCount(itemId).getCoord());
            }
            relativeError = csc.calculateRelativeError();
            // build placemark
            placemark = createPlacemark(itemId.toString(), csc, relativeError, csc.getHour());
            point = kmlObjectFactory.createPointType();
            point.getCoordinates()
                    .add(Double.toString(coord.getX()) + "," + Double.toString(coord.getY()) + ",0.0");
            placemark.setAbstractGeometryGroup(kmlObjectFactory.createPoint(point));
            // cross
            if (csc.getSimulationValue() > csc.getCountValue()) {
                if (csc.getSimulationValue() < csc.getCountValue() * 1.5) {
                    placemark.setStyleUrl(this.greenCrossStyle.getId());
                } else if (csc.getSimulationValue() < csc.getCountValue() * 2) {
                    placemark.setStyleUrl(this.yellowCrossStyle.getId());
                } else {
                    placemark.setStyleUrl(this.redCrossStyle.getId());
                }
            }
            // minus
            else {
                if (csc.getSimulationValue() > csc.getCountValue() * 0.75) {
                    placemark.setStyleUrl("#greenMinusStyle");
                } else if (csc.getSimulationValue() > csc.getCountValue() * 0.5) {
                    placemark.setStyleUrl("#yellowMinusStyle");
                } else {
                    placemark.setStyleUrl("#redMinusStyle");
                }
            }
            folder.getAbstractFeatureGroup().add(kmlObjectFactory.createPlacemark(placemark));
        }
    }

    /**
     * Calculates the position of a placemark in a way that it is 40 % of the link
     * length away from the node where the link starts.
     *
     * @param l
     * @return the CoordI instance
     */
    private Coord calculatePlacemarkPosition(final Link l) {
        Coord coordFrom = l.getFromNode().getCoord();
        Coord coordTo = l.getToNode().getCoord();
        double xDiff = coordTo.getX() - coordFrom.getX();
        double yDiff = coordTo.getY() - coordFrom.getY();
        double length = Math.sqrt((xDiff * xDiff) + (yDiff * yDiff));
        double scale = 0.4;
        scale = l.getLength() * scale;
        Coord vec = new Coord(coordFrom.getX() + (xDiff * scale / length),
                coordFrom.getY() + (yDiff * scale / length));
        return vec;
    }

    /**
     *
     * @param linkid
     * @param csc
     * @param relativeError
     * @param timestep
     * @return A String containing the description for each placemark
     */
    private String createPlacemarkDescription(final String linkid, final CountSimComparison csc,
            final double relativeError, final int timestep) {
        StringBuilder buffer = new StringBuilder(100);
        //      buffer.append(NetworkFeatureFactory.STARTCDATA);
        //      buffer.append(STARTH1);
        //      buffer.append(LINK);
        //      buffer.append(linkid);
        //      buffer.append(ENDH1);
        buffer.append(NetworkFeatureFactory.STARTH2);
        buffer.append(LINK);
        buffer.append(linkid);
        buffer.append(NetworkFeatureFactory.ENDH2);
        buffer.append(NetworkFeatureFactory.STARTH3);
        buffer.append(H24OVERVIEW);
        buffer.append(NetworkFeatureFactory.ENDH3);
        buffer.append(NetworkFeatureFactory.STARTP);
        buffer.append(IMG);
        buffer.append(this.countsLoadCurveGraphMap.get(linkid));
        buffer.append(IMGEND);
        buffer.append(NetworkFeatureFactory.ENDP);
        buffer.append(NetworkFeatureFactory.STARTH3);
        buffer.append(DETAILSFROM);
        buffer.append((this.timestepToString(timestep - 1)));
        buffer.append(OCLOCKTO);
        buffer.append(this.timestepToString(timestep));
        buffer.append(OCLOCK);
        buffer.append(NetworkFeatureFactory.ENDH3);
        buffer.append(NetworkFeatureFactory.STARTP);
        buffer.append(COUNTVALUE);
        buffer.append(csc.getCountValue());
        buffer.append(NetworkFeatureFactory.ENDP);
        buffer.append(NetworkFeatureFactory.STARTP);
        buffer.append(MATSIMVALUE);
        buffer.append(csc.getSimulationValue());
        buffer.append(NetworkFeatureFactory.ENDP);
        buffer.append(NetworkFeatureFactory.STARTP);
        buffer.append(RELERROR);
        buffer.append(relativeError);
        buffer.append(NetworkFeatureFactory.ENDP);
        //      buffer.append(NetworkFeatureFactory.ENDCDATA);
        return buffer.toString();
    }

    /**
     * @param timestep
     * @return A two digits string containing the given timestep
     */
    private String timestepToString(final int timestep) {
        if (timestep < 10) {
            StringBuilder buffer = new StringBuilder();
            buffer.append(ZERO);
            buffer.append(Integer.toString(timestep));
            return buffer.toString();
        }
        return Integer.toString(timestep);
    }

    /**
     * Creates CountsSimRealPerHourGraphs and adds them to the kmz in the given folder. The creation of the graphs is
     * only done if the map attribute of this class for CountsSimRealPerHourGraphs is null.
     *
     * @param folder
     * @param timestep
     * @param timespan
     */
    private void addCountsSimRealPerHourGraphs(final FolderType folder, final int timestep,
            final TimeSpanType timespan) {
        StringBuffer filename;
        ScreenOverlayType overlay;

        try {
            //add the file to the kmz
            filename = new StringBuffer(graphname);
            filename.append(Integer.toString(timestep));
            filename.append(PNG);

            CountsSimRealPerHourGraph graph = new CountsSimRealPerHourGraph(
                    this.countComparisonFilter.getCountsForHour(null), this.iterationNumber, filename.toString());
            graph.createChart(timestep);

            this.writeChartToKmz(filename.toString(), graph.getChart());
            //and link with the overlay
            overlay = kmlObjectFactory.createScreenOverlayType();
            LinkType icon = kmlObjectFactory.createLinkType();
            icon.setHref("./" + filename.toString());
            overlay.setIcon(icon);
            overlay.setName(graph.getChartTitle());
            // place the image top left
            Vec2Type overlayXY = kmlObjectFactory.createVec2Type();
            overlayXY.setX(1.0);
            overlayXY.setY(1.0);
            overlayXY.setXunits(UnitsEnumType.FRACTION);
            overlayXY.setYunits(UnitsEnumType.FRACTION);
            overlay.setOverlayXY(overlayXY);
            Vec2Type screenXY = kmlObjectFactory.createVec2Type();
            screenXY.setX(0.98);
            screenXY.setY(0.98);
            screenXY.setXunits(UnitsEnumType.FRACTION);
            screenXY.setYunits(UnitsEnumType.FRACTION);
            overlay.setScreenXY(screenXY);
            overlay.setAbstractTimePrimitiveGroup(kmlObjectFactory.createTimeSpan(timespan));
            //add the overlay to the folder
            folder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(overlay));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates CountsLoadCurveGraphs for each link and puts them in the kmz as pngs
     */
    private void createCountsLoadCurveGraphs() {
        CountsLoadCurveGraphCreator cgc = new CountsLoadCurveGraphCreator("");
        List<CountsGraph> graphs = cgc.createGraphs(this.countComparisonFilter.getCountsForHour(null),
                this.iterationNumber);

        this.countsLoadCurveGraphMap = new HashMap<String, String>(graphs.size());
        String linkid;
        StringBuffer filename;
        for (CountsGraph cg : graphs) {
            try {
                filename = new StringBuffer();
                linkid = ((CountsLoadCurveGraph) cg).getLinkId();
                filename.append(linkid);
                filename.append(PNG);
                writeChartToKmz(filename.toString(), cg.getChart());
                this.countsLoadCurveGraphMap.put(linkid, filename.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Writes the given JFreeChart to the kmz file specified for the kmz writer attribute of this class.
     * @param filename the filename to use in the kmz
     * @param chart
     * @throws IOException
     */
    private void writeChartToKmz(final String filename, final JFreeChart chart) throws IOException {
        byte[] img;
        img = ChartUtilities.encodeAsPNG(chart.createBufferedImage(CHARTWIDTH, CHARTHEIGHT));
        this.writer.addNonKMLFile(img, filename);
    }

    /**
     * Creates the CountsErrorGraph for all the data
     * @param visible true if initially visible
     * @return the ScreenOverlay Feature
     */
    private ScreenOverlayType createBoxPlotErrorGraph() {

        CountsGraph ep;
        try {
            ep = new BoxPlotErrorGraph(this.countComparisonFilter.getCountsForHour(null), this.iterationNumber,
                    null, "error graph");
            ep.createChart(0);
        } catch (IllegalArgumentException e) {
            log.error("Could not create BoxPlot-ErrorGraph.", e);
            return null;
        }

        String filename = "errorGraphBoxPlot.png";
        try {
            writeChartToKmz(filename, ep.getChart());
            return createOverlayBottomRight(filename, "Error Graph [Box-Plot]");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Creates the CountsErrorGraph for all the data
     * @param kmlFilename the filename of the kml file
     * @param visible true if initially visible
     * @return the ScreenOverlay Feature
     */
    private ScreenOverlayType createBiasErrorGraph(String kmlFilename) {
        BiasErrorGraph ep = new BiasErrorGraph(this.countComparisonFilter.getCountsForHour(null),
                this.iterationNumber, null, "error graph");
        ep.createChart(0);

        double[] meanError = ep.getMeanRelError();
        double[] meanBias = ep.getMeanAbsBias();
        int index = kmlFilename.lastIndexOf(System.getProperty("file.separator"));
        if (index == -1) {
            index = kmlFilename.lastIndexOf('/');
        }
        String outdir;
        if (index == -1) {
            outdir = "";
        } else {
            outdir = kmlFilename.substring(0, index) + System.getProperty("file.separator");
        }
        String file = outdir + "biasErrorGraphData.txt";
        log.info("writing chart data to " + new File(file).getAbsolutePath());
        try {
            BufferedWriter bwriter = IOUtils.getBufferedWriter(file);
            StringBuilder buffer = new StringBuilder(200);
            buffer.append("hour \t mean relative error \t mean absolute bias");
            bwriter.write(buffer.toString());
            bwriter.newLine();
            for (int i = 0; i < meanError.length; i++) {
                buffer.delete(0, buffer.length());
                buffer.append(i + 1);
                buffer.append('\t');
                buffer.append(meanError[i]);
                buffer.append('\t');
                buffer.append(meanBias[i]);
                bwriter.write(buffer.toString());
                bwriter.newLine();
            }
            bwriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        String filename = "errorGraphErrorBias.png";
        try {
            writeChartToKmz(filename, ep.getChart());
            return createOverlayBottomRight(filename, "Error Graph [Error/Bias]");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Creates the CountsSimReal24Graph for all the data. AWTV = average weekday traffic volumes.
     * <br><br>
     * Notes:<ul>
     * <li> I think that "weekday" means "day-of-week", i.e. the method does not care when it is sunday. kai, sep'16
     * </ul>
     * @param visible true if initially visible
     * @return the ScreenOverlay Feature
     */
    private ScreenOverlayType createAWTVGraph() {
        CountsGraph awtv = new CountsSimReal24Graph(this.countComparisonFilter.getCountsForHour(null),
                this.iterationNumber, "awtv graph");
        awtv.createChart(0);

        String filename = "awtv.png";
        try {
            writeChartToKmz(filename, awtv.getChart());
            return createOverlayBottomRight("./" + filename, "AWTV");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private ScreenOverlayType createOverlayBottomRight(final String fileName, final String overlayName) {
        ScreenOverlayType overlay = kmlObjectFactory.createScreenOverlayType();
        LinkType icon1 = kmlObjectFactory.createLinkType();
        icon1.setHref("./" + fileName);
        overlay.setIcon(icon1);
        overlay.setName(overlayName);
        // place the image bottom right
        Vec2Type overlayXY = kmlObjectFactory.createVec2Type();
        overlayXY.setX(1.0);
        overlayXY.setY(0.0);
        overlayXY.setXunits(UnitsEnumType.FRACTION);
        overlayXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setOverlayXY(overlayXY);
        Vec2Type screenXY = kmlObjectFactory.createVec2Type();
        screenXY.setX(0.98);
        screenXY.setY(0.1);
        screenXY.setXunits(UnitsEnumType.FRACTION);
        screenXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setScreenXY(screenXY);
        return overlay;
    }

    /**
     * to be called when the kml stream shall be closed.
     */
    private void finish() {
        this.writer.writeMainKml(this.mainKml);
        this.writer.close();
        log.info("DONE with writing kml file.");
    }
}