playground.christoph.evacuation.analysis.EvacuationTimePictureWriter.java Source code

Java tutorial

Introduction

Here is the source code for playground.christoph.evacuation.analysis.EvacuationTimePictureWriter.java

Source

/* *********************************************************************** *
 * project: org.matsim.*
 * EvacuationTimePictureWriter.java
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2010 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 playground.christoph.evacuation.analysis;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import javax.imageio.ImageIO;

import net.opengis.kml._2.DocumentType;
import net.opengis.kml._2.FolderType;
import net.opengis.kml._2.IconStyleType;
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.UnitsEnumType;
import net.opengis.kml._2.Vec2Type;

import org.apache.log4j.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.data.statistics.HistogramType;
import org.matsim.api.core.v01.BasicLocation;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.network.Link;
import org.matsim.core.gbl.MatsimResource;
import org.matsim.core.utils.geometry.CoordImpl;
import org.matsim.core.utils.geometry.CoordinateTransformation;
import org.matsim.facilities.Facility;
import org.matsim.vis.kml.KMZWriter;
import org.matsim.vis.kml.MatsimKMLLogo;
import org.matsim.vis.kml.NetworkFeatureFactory;

import playground.christoph.evacuation.analysis.EvacuationTimePicture.AgentInfo;
import playground.christoph.evacuation.config.EvacuationConfig;

public class EvacuationTimePictureWriter {

    private static final Logger log = Logger.getLogger(EvacuationTimePictureWriter.class);

    /*
     * Strings
     */
    private static final String TITLE = "Evacation Travel Times";
    private static final String MEANEVACUATIONTIME = "mean evacuation time: ";
    private static final String TRIPS = "trips (valid/invalid): ";
    private static final String SECONDS = "s";

    private static final String IMG = "<img src=\"./";
    private static final String IMGEND = "\">";

    private static final String LINKLOCATION = "Link_";
    private static final String FACILITYLOCATION = "Facility_";
    private static final String HISTOGRAM = "_Histogram";
    private static final String BOXPLOT = "_Boxplot";
    private static final String LEGENDHEADER = "mean evacuation time\nfrom location by ";

    /*
     * Icons
     */
    private static final String SPACER = "spacer.png";
    private static final String LEGEND = "_legend.png";
    private static final String OVERALLHISTROGRAM = "_overallhistogram.png";
    private static final String DEFAULTNODEICON = "node.png";
    private static final String DEFAULTNODEICONRESOURCE = "icon18.png";
    private static final Double ICONSCALE = Double.valueOf(0.5);

    /*
     * height of the charts
     */
    private static final int OVERALLHISTOGRAMHEIGHT = 200;
    private static final int HISTOGRAMHEIGHT = 250;
    private static final int BOXPLOTHEIGHT = 250;
    /*
     * width of the  charts
     */
    private static final int OVERALLHISTOGRAMWIDTH = 300;
    private static final int HISTOGRAMWIDTH = 400;
    private static final int BOXPLOTWIDTH = 100;
    /*
     * constant for the file suffix of graphs
     */
    private static final String PNG = ".png";

    /*
     * Color Scale:
     * RGB
     * 255   0   0
     * 255 127 0
     * 255 205 0
     * 255 255 0
     * 205 230 0
     * 127 230 0
     * 0 255 0
     * 0 255 153
     * 0 255 255
     * 0 205 255
     * 0 127 255
     * 0 0 255
     */

    // ALPHA - B - G - R
    /*package*/ static final byte[][] colorScale = new byte[][] { { (byte) 255, (byte) 255, (byte) 0, (byte) 0 },
            { (byte) 255, (byte) 255, (byte) 127, (byte) 0 }, { (byte) 255, (byte) 255, (byte) 205, (byte) 0 },
            { (byte) 255, (byte) 255, (byte) 255, (byte) 0 }, { (byte) 255, (byte) 205, (byte) 230, (byte) 0 },
            { (byte) 255, (byte) 127, (byte) 230, (byte) 0 }, { (byte) 255, (byte) 0, (byte) 255, (byte) 0 },
            { (byte) 255, (byte) 0, (byte) 255, (byte) 153 }, { (byte) 255, (byte) 0, (byte) 255, (byte) 255 },
            { (byte) 255, (byte) 0, (byte) 205, (byte) 255 }, { (byte) 255, (byte) 0, (byte) 127, (byte) 255 },
            { (byte) 255, (byte) 0, (byte) 0, (byte) 255 } };

    private static final byte[] colorBlack = new byte[] { (byte) 255, (byte) 0, (byte) 0, (byte) 0 };

    //   private static final byte[] MATSIMRED = new byte[]{(byte) 255, (byte) 15, (byte) 15, (byte) 190};
    //   private static final byte[] MATSIMGREEN = new byte[]{(byte) 255, (byte) 15, (byte) 190, (byte) 15};
    //   private static final byte[] MATSIMWHITE = new byte[]{(byte) 230, (byte) 230, (byte) 230, (byte) 230};

    private Scenario scenario;
    private KMZWriter kmzWriter;
    private DocumentType document;

    private ObjectFactory kmlObjectFactory = new ObjectFactory();

    private StyleType[] colorBarStyles;
    private StyleType blackStyle;

    private CoordinateTransformation coordTransform;

    private double minEvacuationTime = 0.0;
    private double maxEvacuationTime = Double.MIN_VALUE;
    private double meanEvacuationTime = 0.0;
    private double standardDeviation = 0.0;
    private boolean limitMaxEvacuationTime = true;
    private double evacuationTimeCutOffFactor = 3; // cut off values > mean + 3 * standard deviation

    private boolean doClustering = true;
    private double clusterFactor = 5.0;
    private int clusterIterations = 100;

    public EvacuationTimePictureWriter(Scenario scenario, CoordinateTransformation coordTransform,
            KMZWriter kmzWriter, DocumentType document) throws IOException {
        this.scenario = scenario;
        this.coordTransform = coordTransform;
        this.kmzWriter = kmzWriter;
        this.document = document;

        createDefaultStyles();
    }

    private void calcMaxEvacuationTime(Map<Id, Double> evacuationTime) {
        double sum = 0.0;
        for (double d : evacuationTime.values()) {
            if (d > maxEvacuationTime)
                maxEvacuationTime = d;

            sum = sum + d;
        }

        int count = evacuationTime.size();
        if (count == 0)
            return;

        meanEvacuationTime = sum / count;
        double sumSquares = 0.0;
        for (double d : evacuationTime.values()) {
            sumSquares = sumSquares + Math.pow(meanEvacuationTime - d, 2);
        }
        double variance = (1.0 / (count - 1)) * sumSquares;
        standardDeviation = Math.sqrt(variance);

        log.info("Mean evacuation time: " + (int) meanEvacuationTime);
        log.info("Standard deviation: " + (int) standardDeviation);

        int cuttedValues = 0;
        double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
        for (double d : evacuationTime.values()) {
            if (d > cutOffValue)
                cuttedValues++;
        }
        log.info("Persons using transport mode: " + evacuationTime.values().size());
        log.info("Travel times above cut off travel time: " + cuttedValues);
    }

    private int getColorIndex(double value) {
        double step;
        if (limitMaxEvacuationTime) {
            double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
            step = (cutOffValue - minEvacuationTime) / colorScale.length;
            // if the value is > than the cut off Value -> return the highest index
            if (value > cutOffValue)
                return colorScale.length - 1;
        } else {
            step = (maxEvacuationTime - minEvacuationTime) / colorScale.length;
        }
        return (int) Math.floor((value - minEvacuationTime) / step);
    }

    // Ids are personIds!
    public FolderType getLinkFolder(Map<Id, BasicLocation> locations,
            Map<Id, BasicLocation> positionAtEvacuationStart, Map<Id, AgentInfo> agentInfos) throws IOException {

        /*
         * Create basic structures
         */

        // create main folder
        FolderType mainFolder = this.kmlObjectFactory.createFolderType();
        mainFolder.setName("Evacuation Times");

        // add the MATSim logo to the kml
        mainFolder.getAbstractFeatureGroup()
                .add(kmlObjectFactory.createScreenOverlay(MatsimKMLLogo.writeMatsimKMLLogo(kmzWriter)));

        /*
         * Identify all utilized modes
         */
        Set<String> modes = new HashSet<String>();
        for (AgentInfo agentInfo : agentInfos.values())
            modes.addAll(agentInfo.transportModes);
        Set<String> orderedModes = new TreeSet<String>(modes);

        /*
         * for every transportMode
         */

        for (String transportMode : orderedModes) {
            Map<Id, Double> times = new HashMap<Id, Double>();

            for (Entry<Id, BasicLocation> entry : positionAtEvacuationStart.entrySet()) {
                AgentInfo agentInfo = agentInfos.get(entry.getKey());
                if (agentInfo.transportModes.size() == 1 && agentInfo.transportModes.contains(transportMode)) {
                    times.put(entry.getKey(), agentInfo.leftArea - EvacuationConfig.evacuationTime);
                }
            }

            /*
             * If no agents uses the current transport mode we can skip it. 
             */
            if (times.size() == 0)
                continue;

            /*
             * Filter locations - only those are needed, which contain agents of the
             * current transport mode.
             */
            Map<Id, BasicLocation> transportModeLocations = new HashMap<Id, BasicLocation>();
            for (Id id : times.keySet()) {
                transportModeLocations.put(id, locations.get(id));
            }
            FolderType transportModeFolder = getLinkFolder(transportMode, transportModeLocations, times);
            //         FolderType transportModeFolder = getLinkFolder(transportMode, locations, times);
            mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createFolder(transportModeFolder));
        }

        return mainFolder;
    }

    // Ids are personIds and not Link/Facility Ids!
    public FolderType getLinkFolder(String transportMode, Map<Id, BasicLocation> locations,
            Map<Id, Double> evacuationTimes) throws IOException {

        calcMaxEvacuationTime(evacuationTimes);

        /*
         * create Folders and connect them
         */
        FolderType mainFolder = this.kmlObjectFactory.createFolderType();
        FolderType linkFolder = this.kmlObjectFactory.createFolderType();
        FolderType facilityFolder = this.kmlObjectFactory.createFolderType();
        FolderType linkFolderA = this.kmlObjectFactory.createFolderType(); // 0 .. 10 valid Trips
        FolderType linkFolderB = this.kmlObjectFactory.createFolderType(); // 10 .. 100 valid Trips
        FolderType linkFolderC = this.kmlObjectFactory.createFolderType(); // 100 .. 1000 valid Trips
        FolderType linkFolderD = this.kmlObjectFactory.createFolderType(); // 1000 and more valid Trips
        FolderType facilityFolderA = this.kmlObjectFactory.createFolderType(); // 0 .. 10 valid Trips
        FolderType facilityFolderB = this.kmlObjectFactory.createFolderType(); // 10 .. 100 valid Trips
        FolderType facilityFolderC = this.kmlObjectFactory.createFolderType(); // 100 .. 1000 valid Trips
        FolderType facilityFolderD = this.kmlObjectFactory.createFolderType(); // 1000 and more valid Trips

        mainFolder.setName("Evacuation Times " + transportMode);
        linkFolder.setName("Links");
        facilityFolder.setName("Facilities");
        linkFolderA.setName("Links with 0..9 valid Trips");
        linkFolderB.setName("Links with 10..99 valid Trips");
        linkFolderC.setName("Links with 100..9 valid Trips");
        linkFolderD.setName("Links with 1000 and more valid Trips");
        facilityFolderA.setName("Facilities with 0..9 valid Trips");
        facilityFolderB.setName("Facilities with 10..99 valid Trips");
        facilityFolderC.setName("Facilities with 100..9 valid Trips");
        facilityFolderD.setName("Facilities with 1000 and more valid Trips");

        mainFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(linkFolder));
        mainFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(facilityFolder));
        linkFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(linkFolderA));
        linkFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(linkFolderB));
        linkFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(linkFolderC));
        linkFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(linkFolderD));
        facilityFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(facilityFolderA));
        facilityFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(facilityFolderB));
        facilityFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(facilityFolderC));
        facilityFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createFolder(facilityFolderD));

        /*
         * create overall histogram and add it to the kmz file
         */
        ScreenOverlayType histogram = createHistogram(transportMode, evacuationTimes);
        mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(histogram));

        /*
         * create legend and add it to the kmz file
         */
        ScreenOverlayType legend = createLegend(transportMode);
        mainFolder.getAbstractFeatureGroup().add(kmlObjectFactory.createScreenOverlay(legend));

        Map<BasicLocation, List<Double>> locationMap = new HashMap<BasicLocation, List<Double>>();

        for (Entry<Id, BasicLocation> entry : locations.entrySet()) {
            Id id = entry.getKey();
            BasicLocation location = entry.getValue();

            List<Double> list = locationMap.get(location);
            if (list == null) {
                list = new ArrayList<Double>();
                locationMap.put(location, list);
            }

            Double value = evacuationTimes.get(id);
            if (value == null)
                value = Double.NaN;
            list.add(value);
        }

        log.info("Number of different start locations found: " + locationMap.size());

        if (doClustering) {
            EvacuationTimeClusterer clusterer = new EvacuationTimeClusterer(scenario.getNetwork(), locationMap,
                    scenario.getConfig().global().getNumberOfThreads());
            int numClusters = (int) Math.ceil(locationMap.size() / clusterFactor);
            locationMap = clusterer.buildCluster(numClusters, clusterIterations);
        }

        for (Entry<BasicLocation, List<Double>> entry : locationMap.entrySet()) {
            BasicLocation location = entry.getKey();
            List<Double> list = entry.getValue();

            int valid = 0;
            int invalid = 0;

            /*
             * Remove NaN entries from the List
             */
            List<Double> listWithoutNaN = new ArrayList<Double>();
            for (Double d : list) {
                if (d.isNaN()) {
                    invalid++;
                } else
                    listWithoutNaN.add(d);
            }

            /*
             * If trip with significant to high evacuation times should be cut off
             */
            if (limitMaxEvacuationTime) {
                double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
                ListIterator<Double> iter = listWithoutNaN.listIterator();
                while (iter.hasNext()) {
                    double value = iter.next();
                    if (value > cutOffValue) {
                        iter.remove();
                        invalid++;
                    }
                }
            }
            valid = list.size() - invalid;

            double mean = 0.0;
            for (Double d : list) {
                mean = mean + d;
            }
            mean = mean / list.size();

            // if at least one valid entry found - otherwise it would result in a divide by zero error
            if (listWithoutNaN.size() == 0)
                continue;
            //         if (invalid < list.size()) mean = mean / (list.size() - invalid);
            //         else continue;

            int index = getColorIndex(mean);

            StyleType styleType = colorBarStyles[index];

            PlacemarkType placemark = createPlacemark(transportMode, location, mean, valid, invalid);
            placemark.setStyleUrl(styleType.getId());
            if (location instanceof Facility) {
                if (valid < 10)
                    facilityFolderA.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else if (valid < 100)
                    facilityFolderB.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else if (valid < 1000)
                    facilityFolderC.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else
                    facilityFolderD.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));

            } else if (location instanceof Link) {
                if (valid < 10)
                    linkFolderA.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else if (valid < 100)
                    linkFolderB.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else if (valid < 1000)
                    linkFolderC.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
                else
                    linkFolderD.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
            } else {
                mainFolder.getAbstractFeatureGroup().add(this.kmlObjectFactory.createPlacemark(placemark));
            }

            histogramToKMZ(transportMode, location, listWithoutNaN);
            boxplotToKMZ(transportMode, location, listWithoutNaN);
        }

        return mainFolder;
    }

    private void histogramToKMZ(String transportMode, BasicLocation location, List<Double> listWithoutNaN)
            throws IOException {
        String filename = createFilenameFromLocation(location, "_" + transportMode + HISTOGRAM);
        if (filename == null)
            return;

        double[] array = new double[listWithoutNaN.size()];
        int i = 0;
        ListIterator<Double> iter = listWithoutNaN.listIterator();
        while (iter.hasNext()) {
            array[i] = iter.next();
            i++;
        }

        // if no valid travel times exist -> create an empty histogram
        if (array.length == 0) {
            array = new double[1];
            array[0] = 0.0;
        }

        writeChartToKmz(filename, createHistogramChart(transportMode, array), HISTOGRAMWIDTH, HISTOGRAMHEIGHT);
    }

    private void boxplotToKMZ(String transportMode, BasicLocation location, List<Double> listWithoutNaN)
            throws IOException {
        String filename = createFilenameFromLocation(location, "_" + transportMode + BOXPLOT);
        if (filename == null)
            return;

        //      List<Double> listWithoutNaN = new ArrayList<Double>();
        //      for (Double d : list) if (!d.isNaN()) listWithoutNaN.add(d);

        // if no valid travel times exist -> create an empty histogram
        if (listWithoutNaN.size() == 0)
            listWithoutNaN.add(0.0);

        writeChartToKmz(filename, createBoxplotChart(listWithoutNaN), BOXPLOTWIDTH, BOXPLOTHEIGHT);
    }

    private String createFilenameFromLocation(BasicLocation location, String suffix) {
        String prefix;
        Id id;

        if (location instanceof Link) {
            prefix = LINKLOCATION;
            id = ((Link) location).getId();
        } else if (location instanceof Facility) {
            prefix = FACILITYLOCATION;
            id = ((Facility) location).getId();
        } else {
            log.warn("Unkwown location type: " + location.getClass().toString() + ". Skipping it!");
            return null;
        }

        StringBuffer filename = new StringBuffer();
        filename.append(prefix);
        filename.append(id.toString());
        filename.append(suffix);
        filename.append(PNG);
        return filename.toString();
    }

    private PlacemarkType createPlacemark(String transportMode, BasicLocation location, double mean, int valid,
            int invalid) {

        Coord coord = null;
        if (location instanceof Link) {
            coord = getShiftedLinkCoord((Link) location);
        } else
            coord = location.getCoord();

        Coord transformedCoord = this.coordTransform.transform(coord);
        String histogram = createFilenameFromLocation(location, "_" + transportMode + HISTOGRAM);
        String boxplot = createFilenameFromLocation(location, "_" + transportMode + BOXPLOT);

        PlacemarkType placemark = kmlObjectFactory.createPlacemarkType();
        placemark.setDescription(createPlacemarkDescription(mean, valid, invalid, histogram, boxplot));

        PointType point;
        point = kmlObjectFactory.createPointType();
        point.getCoordinates().add(
                Double.toString(transformedCoord.getX()) + "," + Double.toString(transformedCoord.getY()) + ",0.0");
        placemark.setAbstractGeometryGroup(kmlObjectFactory.createPoint(point));
        return placemark;
    }

    private String createPlacemarkDescription(double mean, int valid, int invalid, String histogram,
            String boxplot) {
        StringBuffer buffer = new StringBuffer(100);
        buffer.append(NetworkFeatureFactory.STARTH2);
        buffer.append(TITLE);
        buffer.append(NetworkFeatureFactory.ENDH2);

        buffer.append(NetworkFeatureFactory.STARTH3);
        buffer.append(MEANEVACUATIONTIME);
        buffer.append((int) mean);
        buffer.append(SECONDS);
        buffer.append(NetworkFeatureFactory.ENDH3);

        buffer.append(NetworkFeatureFactory.STARTH3);
        buffer.append(TRIPS);
        buffer.append(valid);
        buffer.append(" / ");
        buffer.append(invalid);
        buffer.append(NetworkFeatureFactory.ENDH3);

        buffer.append(IMG);
        buffer.append(histogram);
        buffer.append(IMGEND);

        buffer.append(IMG);
        buffer.append(boxplot);
        buffer.append(IMGEND);

        /*
         * Add a spacer image which has the same width as all other
         * images together - doing so should ensure that they are
         * displayed side by side.
         */
        buffer.append(IMG);
        buffer.append(SPACER);
        buffer.append(IMGEND);

        return buffer.toString();

        //      NumberFormat formatter = new DecimalFormat("0.0");
        //      formatter.format(mean);
    }

    private JFreeChart createHistogramChart(String transportMode, double[] travelTimes) {

        HistogramDataset dataset = new HistogramDataset();
        dataset.setType(HistogramType.RELATIVE_FREQUENCY);
        dataset.addSeries("evacuation travel time " + transportMode, travelTimes, 20);

        JFreeChart chart = ChartFactory.createHistogram(null, null, "frequency", dataset, PlotOrientation.VERTICAL,
                true, false, false);
        chart.getXYPlot().setForegroundAlpha(0.75f);

        /*
         * set x-axis range 
         */
        double min = 0.0;
        double max = maxEvacuationTime;
        if (limitMaxEvacuationTime) {
            double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
            max = cutOffValue;
        }
        chart.getXYPlot().getDomainAxis().setLowerBound(min);
        chart.getXYPlot().getDomainAxis().setUpperBound(max);

        return chart;
    }

    private JFreeChart createBoxplotChart(List<Double> travelTimes) {

        DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset();
        dataset.add(travelTimes, "Series", "");

        JFreeChart chart = ChartFactory.createBoxAndWhiskerChart(null, null, "evacuation travel time", dataset,
                false);
        chart.getCategoryPlot().setForegroundAlpha(0.75f);
        return chart;
    }

    private void createDefaultStyles() throws IOException {

        LinkType iconLink = kmlObjectFactory.createLinkType();
        iconLink.setHref(DEFAULTNODEICON);
        this.kmzWriter.addNonKMLFile(MatsimResource.getAsInputStream(DEFAULTNODEICONRESOURCE), DEFAULTNODEICON);

        colorBarStyles = new StyleType[colorScale.length];
        for (int i = 0; i < colorScale.length; i++) {
            byte[] color = colorScale[i];
            this.colorBarStyles[i] = kmlObjectFactory.createStyleType();
            this.colorBarStyles[i].setId("Color" + i);
            IconStyleType iStyle = kmlObjectFactory.createIconStyleType();
            iStyle.setIcon(iconLink);
            iStyle.setColor(color);
            iStyle.setScale(EvacuationTimePictureWriter.ICONSCALE);
            this.colorBarStyles[i].setIconStyle(iStyle);
            this.document.getAbstractStyleSelectorGroup().add(kmlObjectFactory.createStyle(colorBarStyles[i]));
        }

        this.blackStyle = kmlObjectFactory.createStyleType();
        this.blackStyle.setId("blackStyle");
        IconStyleType iStyle = kmlObjectFactory.createIconStyleType();
        iStyle.setIcon(iconLink);
        iStyle.setColor(EvacuationTimePictureWriter.colorBlack);
        iStyle.setScale(EvacuationTimePictureWriter.ICONSCALE);
        this.blackStyle.setIconStyle(iStyle);
        this.document.getAbstractStyleSelectorGroup().add(kmlObjectFactory.createStyle(this.blackStyle));

        createSpacer(HISTOGRAMWIDTH + BOXPLOTWIDTH, 1);
    }

    private void createSpacer(int width, int height) throws IOException {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        byte[] imageBytes = bufferedImageToByteArray(image);
        this.kmzWriter.addNonKMLFile(imageBytes, SPACER);
    }

    private ScreenOverlayType createHistogram(String transportMode, Map<Id, Double> evacuationTimes)
            throws IOException {

        /*
         * Remove NaN entries from the List
         */
        List<Double> listWithoutNaN = new ArrayList<Double>();
        for (Double d : evacuationTimes.values())
            if (!d.isNaN())
                listWithoutNaN.add(d);

        /*
         * If trip with significant to high evacuation times should be cut off
         */
        if (limitMaxEvacuationTime) {
            double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
            ListIterator<Double> iter = listWithoutNaN.listIterator();
            while (iter.hasNext()) {
                double value = iter.next();
                if (value > cutOffValue)
                    iter.remove();
            }
        }

        double[] array = new double[listWithoutNaN.size()];
        int i = 0;
        for (double d : listWithoutNaN)
            array[i++] = d;

        JFreeChart chart = createHistogramChart(transportMode, array);
        BufferedImage chartImage = chart.createBufferedImage(OVERALLHISTOGRAMWIDTH, OVERALLHISTOGRAMHEIGHT);
        BufferedImage image = new BufferedImage(OVERALLHISTOGRAMWIDTH, OVERALLHISTOGRAMHEIGHT,
                BufferedImage.TYPE_4BYTE_ABGR);

        // clone image and set alpha value
        for (int x = 0; x < OVERALLHISTOGRAMWIDTH; x++) {
            for (int y = 0; y < OVERALLHISTOGRAMHEIGHT; y++) {
                int rgb = chartImage.getRGB(x, y);
                Color c = new Color(rgb);
                int r = c.getRed();
                int b = c.getBlue();
                int g = c.getGreen();
                int argb = 225 << 24 | r << 16 | g << 8 | b; // 225 as transparency value
                image.setRGB(x, y, argb);
            }
        }

        byte[] imageBytes = bufferedImageToByteArray(image);
        this.kmzWriter.addNonKMLFile(imageBytes, transportMode + OVERALLHISTROGRAM);

        ScreenOverlayType overlay = kmlObjectFactory.createScreenOverlayType();
        LinkType icon = kmlObjectFactory.createLinkType();
        icon.setHref(transportMode + OVERALLHISTROGRAM);
        overlay.setIcon(icon);
        overlay.setName("Histogram " + transportMode);
        // place the image top right
        Vec2Type overlayXY = kmlObjectFactory.createVec2Type();
        overlayXY.setX(0.0);
        overlayXY.setY(1.0);
        overlayXY.setXunits(UnitsEnumType.FRACTION);
        overlayXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setOverlayXY(overlayXY);
        Vec2Type screenXY = kmlObjectFactory.createVec2Type();
        screenXY.setX(0.02);
        screenXY.setY(0.98);
        screenXY.setXunits(UnitsEnumType.FRACTION);
        screenXY.setYunits(UnitsEnumType.FRACTION);
        overlay.setScreenXY(screenXY);
        return overlay;
    }

    private ScreenOverlayType createLegend(String transportMode) throws IOException {
        double step = 0.0;

        if (limitMaxEvacuationTime) {
            double cutOffValue = meanEvacuationTime + standardDeviation * evacuationTimeCutOffFactor;
            step = (cutOffValue - minEvacuationTime) / colorScale.length;
        } else
            step = (maxEvacuationTime - minEvacuationTime) / colorScale.length;

        String[] legendTexts = new String[colorScale.length];

        double from = minEvacuationTime;
        double to = step;

        for (int i = 0; i < legendTexts.length; i++) {
            StringBuffer sb = new StringBuffer();
            sb.append((int) from);
            sb.append(SECONDS);
            sb.append(" - ");
            sb.append((int) to);
            sb.append(SECONDS);
            legendTexts[i] = sb.toString();

            from = to;
            to = to + step;
        }

        /*
         * If we limit the max evacuation time, then the last legend entry is not
         * "from - to" but "from"
         */
        if (limitMaxEvacuationTime) {
            StringBuffer sb = new StringBuffer();
            sb.append((int) from);
            sb.append(SECONDS);
            sb.append(" - ...");
            legendTexts[legendTexts.length - 1] = sb.toString();
        }

        BufferedImage image = new EvacuationTimeLegend().createLegend(LEGENDHEADER + transportMode, legendTexts);
        byte[] imageBytes = bufferedImageToByteArray(image);
        this.kmzWriter.addNonKMLFile(imageBytes, transportMode + LEGEND);

        ScreenOverlayType overlay = kmlObjectFactory.createScreenOverlayType();
        LinkType icon = kmlObjectFactory.createLinkType();
        icon.setHref(transportMode + LEGEND);
        overlay.setIcon(icon);
        overlay.setName("Legend " + transportMode);
        // 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;
    }

    /**
     * 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, int height, int width)
            throws IOException {
        byte[] img;
        img = ChartUtilities.encodeAsPNG(chart.createBufferedImage(height, width));
        this.kmzWriter.addNonKMLFile(img, filename);
    }

    private byte[] bufferedImageToByteArray(BufferedImage image) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(image, "PNG", out);
        byte[] imageBytes = out.toByteArray();
        return imageBytes;
    }

    /**
     * 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. Requires that the coordinates
     * as well as the link length use the same SI units!
     *
     * @param l
     * @return the Coord instance
     */
    private Coord getShiftedLinkCoord(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;
    }
}