org.matsim.pt.counts.obsolete.PtCountSimComparisonKMLWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.matsim.pt.counts.obsolete.PtCountSimComparisonKMLWriter.java

Source

/* *********************************************************************** *
 * project: org.matsim.*
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 * 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.pt.counts.obsolete;

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.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.Count;
import org.matsim.counts.CountSimComparison;
import org.matsim.counts.Counts;
import org.matsim.counts.algorithms.graphs.CountsGraph;
import org.matsim.counts.algorithms.graphs.CountsLoadCurveGraph;
import org.matsim.pt.counts.PtCountsLoadCurveGraphCreator;
import org.matsim.pt.counts.obsolete.PtCountSimComparisonWriter.PtCountsType;
import org.matsim.vis.kml.KMZWriter;
import org.matsim.vis.kml.MatsimKMLLogo;
import org.matsim.vis.kml.NetworkFeatureFactory;

@Deprecated // we should try to rather make CountSimComparisonKMLWriter more general. kai, dec'13
public final class PtCountSimComparisonKMLWriter extends PtCountSimComparisonWriter {
    // yyyyyy I think that this class is no longer used, and can thus be deleted.  kai, sep'16

    /**
     * constant for the name of the stops
     */
    private static final String STOP = "Stop: ";
    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";
    /**
     * constant for the file name of the CountsSimRealPerHourGraphs
     */
    private static final String SIMREALGRAPHNAME = "countsSimRealPerHour_";

    // private final Network network;
    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 stopids to filenames in the kmz
     */
    private Map<String, String> boardCountsLoadCurveGraphMap, alightCountsLoadCurveGraphMap,
            occupancyCountsLoadCurveGraphMap;
    private Counts boardCounts, alightCounts, occupancyCounts;

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

    /**
     * Sets the data to the fields of this class
     *
     * @param countSimCompList
     * @param qnetwork
     * @param coordTransform
     */
    @Deprecated // we should try to rather make CountSimComparisonKMLWriter more general. kai, dec'13
    public PtCountSimComparisonKMLWriter(final List<CountSimComparison> boardCountSimCompList,
            final List<CountSimComparison> alightCountSimCompList,
            final List<CountSimComparison> occupancyCountSimCompList,
            // final Network network,
            final CoordinateTransformation coordTransform, final Counts boradCounts, final Counts alightCounts,
            final Counts occupancyCounts) {
        super(boardCountSimCompList, alightCountSimCompList, occupancyCountSimCompList);
        // this.network = network;
        this.coordTransform = coordTransform;
        this.boardCounts = boradCounts;
        this.alightCounts = alightCounts;
        this.occupancyCounts = occupancyCounts;
    }

    /**
     * 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);

            if (styleType == this.redCrossStyle || styleType == this.redMinusStyle) {
                icon.setScale(ICONSCALE); // yyyy kai: make configurable!
            }

            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
    @Deprecated // we should try to rather make CountSimComparisonKMLWriter more general. kai, dec'13
    public void writeFile(final String 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.iter);
        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 - board
        ScreenOverlayType errorGraphBoard = createBiasErrorGraph(PtCountsType.Boarding, filename);
        errorGraphBoard.setVisibility(Boolean.TRUE);
        this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(errorGraphBoard));
        // error graphs and awtv graph - alight
        ScreenOverlayType errorGraphAlight = createBiasErrorGraph(PtCountsType.Alighting, filename);
        errorGraphAlight.setVisibility(Boolean.TRUE);
        this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(errorGraphAlight));
        // error graphs and awtv graph - occupancy
        ScreenOverlayType errorGraphOccupancy = createBiasErrorGraph(PtCountsType.Occupancy, filename);
        errorGraphOccupancy.setVisibility(Boolean.TRUE);
        this.mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(errorGraphOccupancy));

        // stop 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));

            if (this.boardCountComparisonFilter != null) {
                writeStopData(this.boardCountComparisonFilter.getCountsForHour(Integer.valueOf(h)), subfolder,
                        PtCountsType.Boarding);
            }
            if (this.alightCountComparisonFilter != null) {
                writeStopData(this.alightCountComparisonFilter.getCountsForHour(Integer.valueOf(h)), subfolder,
                        PtCountsType.Alighting);
            }
            if (this.occupancyCountComparisonFilter != null) {
                writeStopData(this.occupancyCountComparisonFilter.getCountsForHour(Integer.valueOf(h)), subfolder,
                        PtCountsType.Occupancy);
            }
        }
        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 stopid
     * @param csc
     * @param relativeError
     * @param timestep
     * @return the Placemark instance with description and name set
     */
    private PlacemarkType createPlacemark(final String stopid, final CountSimComparison csc,
            final double relativeError, final int timestep, PtCountsType type) {
        StringBuilder stringBuffer = new StringBuilder();
        PlacemarkType placemark = kmlObjectFactory.createPlacemarkType();
        stringBuffer.delete(0, stringBuffer.length());
        stringBuffer.append(STOP);
        stringBuffer.append(stopid);
        //      placemark.setName(stringBuffer.toString()) ; // adds "name" to icon in image.  not so useful
        placemark.setDescription(createPlacemarkDescription(stopid, csc, relativeError, timestep, type));
        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.
     * @param counts
     */
    private void writeStopData(final List<CountSimComparison> countSimComparisonList, final FolderType folder,
            PtCountsType type) {
        Id<Link> stopid;
        PlacemarkType placemark;
        double relativeError;
        Coord coord;
        PointType point;
        for (CountSimComparison csc : countSimComparisonList) {
            stopid = csc.getId();
            // link = this.network.getLinks().get(stopid);
            Count count;
            switch (type) {
            case Boarding:
                count = this.boardCounts.getCount(stopid);
                //            continue ; // kai: this is where these icons can be disabled.
                break;
            case Alighting:
                count = this.alightCounts.getCount(stopid);
                //            continue ; // kai: this is where these icons can be disabled.
                break;
            default:
                count = this.occupancyCounts.getCount(stopid);
            }
            // yyyy If I see this correctly, the above only works because it relies on the convention that the id in the CountSimComparison
            // data structure is not the link id, as the documentation implies, but in fact the stopId.  We need to stay away from such
            // things.  kai, dec'13
            // (Actually, it seems to be locId = location id in the original design, so it might be a mis-documentation in the javadoc.???)

            coord = this.coordTransform.transform(count.getCoord());
            relativeError = csc.calculateRelativeError();
            // build placemark
            placemark = createPlacemark(stopid.toString(), csc, relativeError, csc.getHour(), type);
            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 CoordImpl(coordFrom.getX() + (xDiff * scale / length),
    // coordFrom.getY() + (yDiff * scale / length));
    // return vec;
    // }

    /**
     *
     * @param stopid
     * @param csc
     * @param relativeError
     * @param timestep
     * @param type
     *            board or alight
     * @return A String containing the description for each placemark
     */
    private String createPlacemarkDescription(final String stopid, final CountSimComparison csc,
            final double relativeError, final int timestep, PtCountsType type) {
        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(STOP);
        buffer.append(stopid).append("\t").append(type).append("ing");
        buffer.append(NetworkFeatureFactory.ENDH2);
        buffer.append(NetworkFeatureFactory.STARTH3);
        buffer.append(H24OVERVIEW);
        buffer.append(NetworkFeatureFactory.ENDH3);
        buffer.append(NetworkFeatureFactory.STARTP);

        buffer.append(IMG);
        switch (type) {
        case Boarding:
            buffer.append(this.boardCountsLoadCurveGraphMap.get(stopid + "b"));
            break;
        case Alighting:
            buffer.append(this.alightCountsLoadCurveGraphMap.get(stopid + "a"));
            break;
        default/* occupancy */:
            buffer.append(this.occupancyCountsLoadCurveGraphMap.get(stopid + "o"));
        }
        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);
    }

    private String getSimRealGraphName(PtCountsType type, int timestep) {
        StringBuilder filename = new StringBuilder(type.toString());
        filename.append('-');
        filename.append(SIMREALGRAPHNAME);
        filename.append(timestep);
        filename.append(PNG);
        return filename.toString();
    }

    /**
     * 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) {
        try {
            // add the file to the kmz
            String filenameBoard = getSimRealGraphName(PtCountsType.Boarding, timestep), //
                    filenameAlight = getSimRealGraphName(PtCountsType.Alighting, timestep), //
                    filenameOccupancy = getSimRealGraphName(PtCountsType.Occupancy, timestep);

            PtCountsSimRealPerHourGraph graphBoard = new PtCountsSimRealPerHourGraph(
                    this.boardCountComparisonFilter.getCountsForHour(null), this.iter, filenameBoard,
                    PtCountsType.Boarding), //
                    graphAlight = new PtCountsSimRealPerHourGraph(
                            this.alightCountComparisonFilter.getCountsForHour(null), this.iter, filenameAlight,
                            PtCountsType.Alighting), //
                    graphOccupancy = new PtCountsSimRealPerHourGraph(
                            this.occupancyCountComparisonFilter.getCountsForHour(null), this.iter,
                            filenameOccupancy, PtCountsType.Occupancy);
            // ------------------Boarding--------------------
            graphBoard.createChart(timestep);
            this.writeChartToKmz(filenameBoard, graphBoard.getChart());
            addGraph2Screen(filenameBoard, graphBoard.getChartTitle(), timespan, folder, 1, 1, 0.98, 0.98);
            // ------------------Alighting--------------------
            graphAlight.createChart(timestep);
            this.writeChartToKmz(filenameAlight, graphAlight.getChart());
            addGraph2Screen(filenameAlight, graphAlight.getChartTitle(), timespan, folder, 1, 0.75, 0.98, 0.73);
            // ------------------Occupancy---------------------
            graphOccupancy.createChart(timestep);
            this.writeChartToKmz(filenameOccupancy, graphOccupancy.getChart());
            addGraph2Screen(filenameOccupancy, graphOccupancy.getChartTitle(), timespan, folder, 1, 0.5, 0.98,
                    0.48);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void addGraph2Screen(String graphFilename, String chartTitle, TimeSpanType timespan, FolderType folder,
            double overlayX, double overlayY, double screenX, double screenY) {
        // and link with the overlay
        ScreenOverlayType overlay = kmlObjectFactory.createScreenOverlayType();
        LinkType icon = kmlObjectFactory.createLinkType();
        icon.setHref("./" + graphFilename);
        overlay.setIcon(icon);
        overlay.setName(chartTitle);

        // place the image somewhere
        Vec2Type overlayXY = kmlObjectFactory.createVec2Type();
        overlayXY.setX(overlayX);
        overlayXY.setY(overlayY);
        overlayXY.setXunits(UnitsEnumType.FRACTION);
        overlayXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setOverlayXY(overlayXY);

        Vec2Type screenXY = kmlObjectFactory.createVec2Type();
        screenXY.setX(screenX);
        screenXY.setY(screenY);
        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));
    }

    private void createCountsLoadCurveGraphs(PtCountsType type, PtCountsLoadCurveGraphCreator cgc) {
        List<CountsGraph> graphs;
        switch (type) {
        case Boarding:
            graphs = cgc.createGraphs(this.boardCountComparisonFilter.getCountsForHour(null), this.iter);
            this.boardCountsLoadCurveGraphMap = new HashMap<String, String>(graphs.size());
            break;
        case Alighting:
            graphs = cgc.createGraphs(this.alightCountComparisonFilter.getCountsForHour(null), this.iter);
            this.alightCountsLoadCurveGraphMap = new HashMap<String, String>(graphs.size());
            break;
        default:
            graphs = cgc.createGraphs(this.occupancyCountComparisonFilter.getCountsForHour(null), this.iter);
            this.occupancyCountsLoadCurveGraphMap = new HashMap<String, String>(graphs.size());
        }

        String postfix;
        switch (type) {
        case Boarding:
            postfix = "b";
            break;
        case Alighting:
            postfix = "a";
            break;
        default:
            postfix = "o";
        }
        for (CountsGraph cg : graphs)
            try {
                String stopId = ((CountsLoadCurveGraph) cg).getLinkId(), filename = stopId + postfix + PNG;
                writeChartToKmz(filename, cg.getChart());
                switch (type) {
                case Boarding:
                    this.boardCountsLoadCurveGraphMap.put(stopId + postfix, filename);
                    break;
                case Alighting:
                    this.alightCountsLoadCurveGraphMap.put(stopId + postfix, filename);
                    break;
                default:
                    this.occupancyCountsLoadCurveGraphMap.put(stopId + postfix, filename);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

    /**
     * Creates CountsLoadCurveGraphs for each stop and puts them in the kmz as
     * pngs
     */
    private void createCountsLoadCurveGraphs() {
        PtCountsLoadCurveGraphCreator cgc = new PtCountsLoadCurveGraphCreator("");
        createCountsLoadCurveGraphs(PtCountsType.Boarding, cgc);
        createCountsLoadCurveGraphs(PtCountsType.Alighting, cgc);
        createCountsLoadCurveGraphs(PtCountsType.Occupancy, cgc);
    }

    /**
     * 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 kmlFilename
     *            the filename of the kml file
     * @param visible
     *            true if initially visible
     * @return the ScreenOverlay Feature
     */

    private ScreenOverlayType createBiasErrorGraph(PtCountsType type, String kmlFilename) {
        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");
        }
        // ------------------------------------------------------------------------------

        List<CountSimComparison> countComparisonFilter;
        switch (type) {
        case Boarding:
            countComparisonFilter = this.boardCountComparisonFilter.getCountsForHour(null);
            break;
        case Alighting:
            countComparisonFilter = this.alightCountComparisonFilter.getCountsForHour(null);
            break;
        default:
            countComparisonFilter = this.occupancyCountComparisonFilter.getCountsForHour(null);
        }
        PtBiasErrorGraph pbeg = new PtBiasErrorGraph(countComparisonFilter, this.iter, null,
                "error graph - " + type.name());
        pbeg.createChart(0);

        double[] meanError = pbeg.getMeanRelError();
        double[] meanBias = pbeg.getMeanAbsBias();

        String file = outdir + "biasErrorGraphData" + type.name() + ".txt";
        log.info("writing chart data to " + new File(file).getAbsolutePath());
        try {
            BufferedWriter bwriter = IOUtils.getBufferedWriter(file);
            StringBuilder buffer = new StringBuilder(100);
            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 chartFilename = "errorGraphErrorBias" + type.name() + ".png";
        try {
            writeChartToKmz(chartFilename, pbeg.getChart());
            return createOverlayBottomRight(chartFilename, "Error Graph [Error/Bias]");
        } 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();
    }
}