fmiquerytest.Coordinates.java Source code

Java tutorial

Introduction

Here is the source code for fmiquerytest.Coordinates.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package fmiquerytest;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Font;
import java.net.URL;
import java.net.URLConnection;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.lang.Double.NaN;
import static java.lang.Integer.parseInt;
import java.net.MalformedURLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.text.DecimalFormat;
import static oracle.jrockit.jfr.events.Bits.intValue;
import static java.lang.Integer.parseInt;
import java.text.DecimalFormatSymbols;
import java.util.Scanner;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYAreaRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Minute;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.ui.RefineryUtilities;
import static java.lang.Integer.parseInt;
import static java.lang.Integer.parseInt;
import static java.lang.Integer.parseInt;
import static java.lang.Integer.parseInt;
import static java.lang.Integer.parseInt;
import static java.lang.Integer.parseInt;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.title.TextTitle;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.VerticalAlignment;

class Coordinates implements Serializable {
    public double Lat;
    public double Lon;

    // constructor
    public Coordinates(double Lat, double Lon) {
        this.Lat = Lat;
        this.Lon = Lon;
    }
}

class distance implements Comparable<distance> {
    public int stationNum;
    public double stationDistance;

    @Override
    public int compareTo(distance o) {
        return Double.compare(this.stationDistance, o.stationDistance);
    }

    public distance(int stationNum, double stationDistance) {
        this.stationNum = stationNum;
        this.stationDistance = stationDistance;
    }
}

class routeStep implements Serializable {
    public Coordinates StartLocation;
    public Coordinates EndLocation;
    public int Distance;
    public int Duration;
    public String Polyline;
    public String Instructions;

    // constructor
    public routeStep(Coordinates StartLocation, Coordinates EndLocation, int Distance, int Duration,
            String Polyline, String Instructions) {
        this.StartLocation = StartLocation;
        this.EndLocation = EndLocation;
        this.Distance = Distance;
        this.Duration = Duration;
        this.Polyline = Polyline;
        this.Instructions = Instructions;
    }
}

class routePoint {
    public Coordinates Coords;
    public int nearestStation;

    // constructor
    public routePoint(Coordinates Coords, int nearestStation) {
        this.Coords = Coords;
        this.nearestStation = nearestStation;
    }
}

class weatherData implements Serializable {
    public String isoTime;
    public String parameterName;
    public String parameterValue;

    // constructor
    public weatherData(String isoTime, String parameterName, String parameterValue) {
        this.isoTime = isoTime;
        this.parameterName = parameterName;
        this.parameterValue = parameterValue;
    }
}

class stationData implements Serializable, Comparable<stationData> {
    public Coordinates stationLocation;
    public String stationName;
    public List<weatherData> weatherData;

    @Override
    public int compareTo(stationData o) {
        return this.stationName.compareTo(o.stationName);
    }

    // constructor
    public stationData(Coordinates stationLocation, String stationName, List<weatherData> weatherData) {
        this.stationLocation = stationLocation;
        this.stationName = stationName;
        this.weatherData = weatherData;
    }
}

class stepWeather {
    public Coordinates StartLocation;
    public Coordinates EndLocation;
    public int Distance;
    public int Duration;
    public int totalDuration;
    public double timeAsDouble;
    public int nearestStation;
    public String stationName;
    public List<weatherData> weatherData;
    public List<String> Warnings;
    public double sunRise;
    public double sunSet;

    // constructor
    public stepWeather(Coordinates StartLocation, Coordinates EndLocation, int Distance, int Duration,
            int totalDuration, double timeAsDouble, int nearestStation, String stationName,
            List<weatherData> weatherData, List<String> Warnings, double sunRise, double sunSet) {
        this.StartLocation = StartLocation;
        this.EndLocation = EndLocation;
        this.Distance = Distance;
        this.Duration = Duration;
        this.totalDuration = totalDuration;
        this.timeAsDouble = timeAsDouble;
        this.nearestStation = nearestStation;
        this.stationName = stationName;
        this.weatherData = weatherData;
        this.Warnings = Warnings;
        this.sunRise = sunRise;
        this.sunSet = sunSet;
    }
}

class RouteTools {
    //    interpolate_step(step,curr_step,curr_coords)
    //    start_point = curr_step
    //    end_point = curr_step + step.duration
    //    for i in (start_point:end_point)
    //        lat = curr_coords[1]+(step.end_location[1]-curr_coords[1])/(step.duration)*(i-curr_step)
    //        lon = curr_coords[2]+(step.end_location[2]-curr_coords[2])/(step.duration)*(i-curr_step)
    //    end for
    static Coordinates interpolateOnePoint(routeStep step, int currPoint, int startPoint) {
        Coordinates currCoor = step.StartLocation;
        double lat = currCoor.Lat
                + (step.EndLocation.Lat - currCoor.Lat) / step.Duration * (currPoint - startPoint);
        double lon = currCoor.Lon
                + (step.EndLocation.Lon - currCoor.Lon) / step.Duration * (currPoint - startPoint);
        Coordinates corrNow = new Coordinates(lat, lon);
        return corrNow;
    }

    //    current_step = 0
    //    current_coordinates = start_coordinates
    //    for i in length(steps)
    //    append(steps, interpolate_step(steps[i]),current_step,current_coordinates)
    //    current_step = current_step + steps[i].duration
    //    current_coordinates = steps[i].end_location
    //    end for
    //    Loop the points of one step
    //    for(int j=startPoint; j < endPoint;j++){
    //        //System.out.println("j: "+j);
    //        routeTemp[j] = new routePoint(RouteTools.interpolateOnePoint(gSteps.get(i), j, 
    //                startPoint, currentCoords), 0);
    //    }
    static List<routeStep> stepsToRoute(List<routeStep> gSteps, Coordinates routeStart, int routeDur,
            int refreshInterval) {
        List<routeStep> routeTemp = new ArrayList<>();
        //System.out.println("routeTemp.length: "+routeTemp.size());
        int currentTime = 0;
        Coordinates currentCoords = routeStart;
        // Loop the gSteps
        for (int i = 0; i < gSteps.size(); i++) {
            int startPoint = currentTime;
            int endPoint = currentTime + gSteps.get(i).Duration;
            double fractionOfRoute = ((double) refreshInterval / (double) gSteps.get(i).Duration);
            int oneIntervalDistance = intValue(gSteps.get(i).Distance * (fractionOfRoute));

            //If current step is longer than 5km, we follow the polyline
            List<Coordinates> points = new ArrayList<>();
            List<Coordinates> polyCoords = new ArrayList<>();
            double polylineLength = 0, polyMultiplier = 0, oneIntervalDisplacement = 0;
            if (gSteps.get(i).Distance > oneIntervalDistance) {
                points = decodePolyLine(gSteps.get(i).Polyline);
                for (int j = 0; j < points.size() - 1; ++j) {
                    polylineLength += euclideanDistance(points.get(j), points.get(j + 1));
                }
                polyMultiplier = polylineLength / gSteps.get(i).Distance;
                oneIntervalDisplacement = oneIntervalDistance * polyMultiplier;
                polyCoords = followPolyLine(points, oneIntervalDisplacement);
            }
            int totalDistance = 0, index = 0;
            while ((currentTime + refreshInterval) < endPoint) {
                currentTime += refreshInterval;
                Coordinates startCoords = currentCoords;
                if (polyCoords.size() > 0) {
                    currentCoords = polyCoords.get(index++);
                } else {
                    currentCoords = RouteTools.interpolateOnePoint(gSteps.get(i), currentTime, startPoint);
                }
                routeTemp.add(
                        new routeStep(startCoords, currentCoords, oneIntervalDistance, refreshInterval, "", ""));
                totalDistance += oneIntervalDistance;
            }
            routeTemp.add(
                    new routeStep(currentCoords, gSteps.get(i).EndLocation, gSteps.get(i).Distance - totalDistance,
                            gSteps.get(i).Duration - (currentTime - startPoint), "", ""));
            currentTime = endPoint;
            currentCoords = gSteps.get(i).EndLocation;
            //System.out.format("%-7s%-10s%-5s%-10s%-7s%-12s %-12s%n",
            //        "Start: ",startPoint,"End: ",endPoint,"Coord: ",currentCoords.Lat,currentCoords.Lon);
        }
        return routeTemp;
    }

    static double euclideanDistance(Coordinates targetCoordinates, Coordinates stationCoordinates) {
        double distance = sqrt(pow((targetCoordinates.Lat - stationCoordinates.Lat), 2)
                + pow((targetCoordinates.Lon - stationCoordinates.Lon), 2));
        return distance;
    }

    static distance[] calculateStationDistances(Coordinates currentPoint, List<stationData> allStations) {
        distance[] stationDistances = new distance[allStations.size()];
        for (int i = 0; i < allStations.size(); i++) {
            stationDistances[i] = new distance(i,
                    euclideanDistance(currentPoint, allStations.get(i).stationLocation));
            //            System.out.format("%-12s%-4s%-10s%-10s%n",
            //                   "stationNum: ",stationDistances[i].stationNum,
            //                   "distance: ",stationDistances[i].stationDistance);
        }
        Arrays.sort(stationDistances);
        return stationDistances;
    }

    static int findNearestStation(Coordinates currentPoint, List<stationData> allStations) {
        //max stationDistance from origin (current) coordinates is 2x stationDistance travelled + nearest station in the otigin
        //double maxDistance = 2*euclideanDistance(target_coordinates,origin_coordinates)+station_distances[1].stationDistance
        //# this takes into account for example the situation where the route is so short that the station does not change
        //# for long trip (Helsinki-Ivalo) it may be good idea to trigger the station stationDistance table calculation again
        //# (to produce new station list where the more relevant stations are on the top)
        //# take all points that are closer than max stationDistance
        //near_stations = station_distances LESS_THAN max_distance
        //if (length(near_stations MORE_THAN 30){
        //# NOTE. Change values that are stored in the main program level
        //# These will be used later
        //calculate_station_distances(station_distances,target_coordinates)
        //}
        //else {
        //# calculate current distances to those stations
        //results = calculate_station_distances(near_stations,target_coordinates)
        //nearest = sort(results,ASCENDING)[1]
        //}
        //return nearest
        return 0;
    }

    static List<routeStep> compileRoute(List<routeStep> gSteps, Integer refreshInterval) {
        Coordinates routeStart = gSteps.get(0).StartLocation;
        System.out.format("%-12s%-12s%-12s%-12s%n", "Start Lat: ", routeStart.Lat, "Start Lon: ", routeStart.Lon);
        Coordinates routeEnd = gSteps.get(gSteps.size() - 1).EndLocation;
        System.out.format("%-12s%-12s%-12s%-12s%n", "End Lat: ", routeEnd.Lat, "End Lon: ", routeEnd.Lon);
        int routeDist = 0;
        for (int i = 0; i < gSteps.size(); i++)
            routeDist += gSteps.get(i).Distance;
        System.out.println("Google route distance: " + routeDist + " (m)");
        //int routeDur = 0;
        for (int i = 0; i < gSteps.size(); i++)
            FmiQueryTest.routeDur += gSteps.get(i).Duration;
        System.out.println("Google route duration: " + FmiQueryTest.routeDur + " (s)");
        List<routeStep> routeData = new ArrayList<>();
        routeData = RouteTools.stepsToRoute(gSteps, routeStart, FmiQueryTest.routeDur, refreshInterval);

        //Add endpoint to Google steps
        routeData.add(new routeStep(routeEnd, routeEnd, 0, 0, "", ""));

        return routeData;
    }

    static List<stepWeather> combineRouteDatabase(List<routeStep> routeData, List<Integer> neededStations,
            List<stationData> allStations) {
        List<stepWeather> temp = new ArrayList<>();
        int totalDuration = 0;
        double zenithAngleOfficial = 90 + ((double) 50 / 60);
        double startTime = DayLightTime.stringTimeToDouble(
                FmiQueryTest.df_short.format(new Date(System.currentTimeMillis()))) + FmiQueryTest.startTimeOffset;
        for (int i = 0; i < routeData.size(); ++i) {
            routeStep step = routeData.get(i);
            List<weatherData> weatherPoint = new ArrayList<>();
            weatherPoint.add(0, new weatherData("", "", ""));
            List<String> warnings = new ArrayList<>();
            warnings.add("");
            double timeNow = startTime + (double) totalDuration / 60 / 60;
            //If the trip is going over the change of day, we add 1 to getSunTime offSet
            int offSet = intValue(timeNow / 24);
            double sunRise = DayLightTime.getSunTime(TRUE, offSet, step.StartLocation.Lat, step.StartLocation.Lon,
                    zenithAngleOfficial);
            ;
            double sunSet = DayLightTime.getSunTime(FALSE, offSet, step.StartLocation.Lat, step.StartLocation.Lon,
                    zenithAngleOfficial);
            ;
            temp.add(new stepWeather(step.StartLocation, step.EndLocation, step.Distance, step.Duration,
                    totalDuration, timeNow, neededStations.get(i),
                    allStations.get(neededStations.get(i)).stationName, weatherPoint, warnings, sunRise, sunSet));
            //Finally add the duration to the total
            totalDuration += step.Duration;
        }
        return temp;
    }

    static Coordinates findLocationAtTime(double time, List<stepWeather> stepDataBase) {
        Coordinates loc = new Coordinates(0, 0);
        for (int i = 0; i < stepDataBase.size() - 1; ++i) {
            stepWeather step = stepDataBase.get(i);
            stepWeather nextStep = stepDataBase.get(i + 1);
            //If the time we are looking for is during the next step:
            if (time > step.timeAsDouble && time < nextStep.timeAsDouble) {
                //Interpolate the location approximately
                loc.Lat = step.StartLocation.Lat + (((time - step.timeAsDouble) / ((double) step.Duration / 3600))
                        * (nextStep.StartLocation.Lat - step.StartLocation.Lat));
                loc.Lon = step.StartLocation.Lon + (((time - step.timeAsDouble) / ((double) step.Duration / 3600))
                        * (nextStep.StartLocation.Lon - step.StartLocation.Lon));
            }
        }
        return loc;
    }

    // http://stackoverflow.com/questions/15924834/decoding-polyline-with-new-google-maps-api
    private static List<Coordinates> decodePolyLine(String Polyline) {
        List<Coordinates> temp = new ArrayList<>();

        int index = 0, len = Polyline.length();
        int lat = 0, lng = 0;

        while (index < len) {
            int b, shift = 0, result = 0;
            do {
                b = Polyline.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do {
                b = Polyline.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            Coordinates p = new Coordinates((((double) lat / 1E5)), (((double) lng / 1E5)));
            temp.add(p);
        }
        return temp;
    }

    private static List<Coordinates> followPolyLine(List<Coordinates> points, double oneIntervalDisplacement) {
        List<Coordinates> temp = new ArrayList<>();
        double distanceFollowed = 0;
        for (int i = 0; i < points.size() - 1; ++i) {
            double distNow = euclideanDistance(points.get(i), points.get(i + 1));
            distanceFollowed += distNow;
            if (distanceFollowed > oneIntervalDisplacement) {
                distanceFollowed -= oneIntervalDisplacement;
                double fractionFromStart = (distNow - distanceFollowed) / distNow;
                temp.add(new Coordinates(
                        points.get(i).Lat + (points.get(i + 1).Lat - points.get(i).Lat) * fractionFromStart,
                        points.get(i).Lon + (points.get(i + 1).Lon - points.get(i).Lon) * fractionFromStart));
            }
        }
        return temp;
    }
}

class ToolBox {
    // Not used?
    static String getURLContent(String p_sURL) {
        URL oURL;
        URLConnection oConnection;
        BufferedReader oReader;
        String sLine;
        StringBuilder sbResponse;
        String sResponse = null;

        try {
            oURL = new URL(p_sURL);
            oConnection = oURL.openConnection();
            oReader = new BufferedReader(new InputStreamReader(oConnection.getInputStream()));
            sbResponse = new StringBuilder();
            while ((sLine = oReader.readLine()) != null) {
                sbResponse.append(sLine);
            }
            sResponse = sbResponse.toString();
        } catch (MalformedURLException ex) {
            Logger.getLogger(ToolBox.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ToolBox.class.getName()).log(Level.SEVERE, null, ex);
        }
        return sResponse;
    }

    static ArrayList<stationData> getUniqueStations(List<stationData> stations) {
        ArrayList<stationData> temp = new ArrayList<>();
        Set<String> uniques = new HashSet<>();

        for (stationData station : stations) {
            String addedAsString = new StringBuilder(String.valueOf(station.stationLocation.Lat))
                    .append(String.valueOf(station.stationLocation.Lon)).toString();
            if (!uniques.contains(addedAsString)) {
                uniques.add(addedAsString);
                temp.add(new stationData(station.stationLocation, station.stationName, station.weatherData));
            }
        }
        return temp;
    }
}

class Parser {
    private static List<String> getValueList(Document doc, XPath xpath, String expression) {
        List<String> list = new ArrayList<>();
        try {
            //create XPathExpression object
            XPathExpression expr = xpath.compile(expression);
            //evaluate expression result on XML document
            NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

            //            NodeList nodeList = doc.getElementsByTagName("*");
            //            for (int i = 0; i < nodeList.getLength(); i++) {
            //                Node node = nodeList.item(i);
            //                if (node.getNodeType() == Node.ELEMENT_NODE) {
            //                    // do something with the current element
            //                    System.out.println(node.getNodeName());
            //                }
            //            }

            for (int i = 0; i < nodes.getLength(); i++) {
                //System.out.println(nodes.item(i).getTextContent().trim());
                list.add(nodes.item(i).getTextContent().trim());
            }
        } catch (XPathExpressionException ex) {
            Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
        }
        return list;
    }

    static List<routeStep> getSteps(String gQuery) {
        List<routeStep> list = new ArrayList<>();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(gQuery);

            XPathFactory xPathfactory = XPathFactory.newInstance();
            XPath xpath = xPathfactory.newXPath();
            String expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/start_location/lat/text()";
            // System.out.println(expr);
            List<String> start_lats = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/start_location/lng/text()";
            // System.out.println(expr);
            List<String> start_lngs = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/end_location/lat/text()";
            List<String> end_lats = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/end_location/lng/text()";
            List<String> end_lngs = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/distance/value/text()";
            List<String> dists = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/duration/value/text()";
            List<String> durs = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/polyline/points/text()";
            List<String> polys = Parser.getValueList(doc, xpath, expr);
            expr = "/DirectionsResponse/route[" + (FmiQueryTest.gRouteOption + 1)
                    + "]/leg/step/html_instructions/text()";
            List<String> insts = Parser.getValueList(doc, xpath, expr);

            for (int i = 0; i < start_lats.size(); i++) {
                double slat = Double.parseDouble(start_lats.get(i));
                double slon = Double.parseDouble(start_lngs.get(i));
                Coordinates start = new Coordinates(slat, slon);
                double elat = Double.parseDouble(end_lats.get(i));
                double elon = Double.parseDouble(end_lngs.get(i));
                Coordinates end = new Coordinates(elat, elon);
                int dist = parseInt(dists.get(i));
                int dur = parseInt(durs.get(i));
                String poly = polys.get(i);
                String inst = insts.get(i);

                list.add(new routeStep(start, end, dist, dur, poly, inst));
            }
        } catch (ParserConfigurationException | SAXException | IOException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return list;
    }

    static List<stationData> getStations(String fmiQuery) {
        List<stationData> list = new ArrayList<>();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(fmiQuery);

            NodeList nodeList = doc.getElementsByTagName("BsWfs:BsWfsElement");
            for (int i = 0; i < nodeList.getLength(); i++) {
                //System.out.println(nodeList.item(i).getNodeName());
                NodeList nodeData = nodeList.item(i).getChildNodes();

                Coordinates stationCoords = null;
                List<weatherData> weatherPoint = new ArrayList<>();
                weatherPoint.add(0, new weatherData("", "", ""));
                for (int j = 0; j < nodeData.getLength(); j++) {
                    switch (nodeData.item(j).getNodeName()) {
                    case "BsWfs:Location":
                        String Coords = nodeData.item(j).getTextContent().trim();
                        //System.out.println(Coords);
                        String[] splitCoords = Coords.split("[ ]");
                        stationCoords = new Coordinates(Double.parseDouble(splitCoords[0]),
                                Double.parseDouble(splitCoords[1]));
                        break;
                    case "BsWfs:Time":
                        weatherPoint.get(0).isoTime = nodeData.item(j).getTextContent().trim();
                        break;
                    case "BsWfs:ParameterName":
                        weatherPoint.get(0).parameterName = nodeData.item(j).getTextContent().trim();
                        break;
                    case "BsWfs:ParameterValue":
                        weatherPoint.get(0).parameterValue = nodeData.item(j).getTextContent().trim();
                        break;
                    }
                }
                list.add(new stationData(stationCoords, "", weatherPoint));
            }
        } catch (SAXException | IOException | ParserConfigurationException ex) {
            Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
        }
        return list;
    }

    static String getStationName(String geoQuery) {
        String name = new String();
        String backupName = new String();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(geoQuery);

            NodeList statusCheck = doc.getElementsByTagName("status");
            //System.out.println(statusCheck.item(0).getTextContent().trim());
            if (statusCheck.item(0).getTextContent().trim().equals("OK")) {
                NodeList nodeList = doc.getElementsByTagName("address_component");
                for (int i = 0; i < nodeList.getLength(); i++) {
                    //System.out.println(nodeList.item(i).getNodeName());
                    NodeList nodeData = nodeList.item(i).getChildNodes();
                    Boolean nameFound = FALSE;
                    for (int j = 0; j < nodeData.getLength(); j++) {
                        //System.out.println(nodeData.item(j).getTextContent().trim());
                        switch (nodeData.item(j).getNodeName()) {
                        case "long_name":
                            name = nodeData.item(j).getTextContent().trim();
                            //System.out.println("Name changed to: "+name);
                        case "type":
                            if (nodeData.item(j).getTextContent().trim().equals("locality") || nodeData.item(j)
                                    .getTextContent().trim().equals("administrative_area_level_3")) {
                                nameFound = TRUE;
                                System.out.println("Locality or Adm.area3 found. Saving name: " + name);
                            } else if (nodeData.item(j).getTextContent().trim()
                                    .equals("administrative_area_level_2")) {
                                backupName = name;
                                System.out.println("backupName set to: " + backupName);
                            }
                        }
                    }
                    if (nameFound) {
                        return name;
                    }
                }
            }
        } catch (SAXException | IOException | ParserConfigurationException ex) {
            Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("End of file, returning backupName: " + backupName);
        return backupName;
    }

    static String getQueryFromShare(String gShare) {
        StringBuilder queryTemp = new StringBuilder(FmiQueryTest.gDirectionQuery);
        //System.out.println(gShare);
        if (gShare.contains(FmiQueryTest.gWebShareBase)) {
            String data = gShare.substring(FmiQueryTest.gWebShareBase.length(), gShare.indexOf("/@"));
            data = data.replaceAll("%C5|%C3%85", "");
            data = data.replaceAll("%C4|%C3%84", "");
            data = data.replaceAll("%D6|%C3%96", "");
            data = data.replaceAll("%E5|%C3%A5", "");
            data = data.replaceAll("%E4|%C3%A4", "");
            data = data.replaceAll("%F6|%C3%B6", "");
            String[] wayPoints = data.split("/");
            if (!wayPoints[0].isEmpty()) {
                FmiQueryTest.gOrigin = wayPoints[0];
            }
            if (!wayPoints[wayPoints.length - 1].isEmpty()) {
                FmiQueryTest.gDestination = wayPoints[wayPoints.length - 1];
            }
            String wayPointTemp = "";
            if (wayPoints.length > 2) {
                for (int i = 1; i < wayPoints.length - 1; ++i) {
                    wayPointTemp += wayPoints[i] + "|";
                }
                wayPointTemp = wayPointTemp.substring(0, wayPointTemp.length() - 1);
            }
            FmiQueryTest.gWaypoints = wayPointTemp;
            if (gShare.contains(FmiQueryTest.gWebShareRouteOptPar)) {
                int found = gShare.indexOf(FmiQueryTest.gWebShareRouteOptPar)
                        + FmiQueryTest.gWebShareRouteOptPar.length();
                int routeOption = parseInt(gShare.substring(found, found + 1));
                FmiQueryTest.gRouteOption = routeOption;
            }
        }
        queryTemp.append("origin=").append(FmiQueryTest.gOrigin);
        queryTemp.append("&destination=").append(FmiQueryTest.gDestination);
        queryTemp.append("&key=").append(FmiQueryTest.gKey);
        queryTemp.append("&mode=").append(FmiQueryTest.gMode);
        queryTemp.append("&waypoints=").append(FmiQueryTest.gWaypoints);
        queryTemp.append("&alternatives=true");
        return queryTemp.toString();
    }
}

class FileSystemTools {
    static void saveObjectToFile(Object object, File fileName) {
        System.out.println("Saving object to file: " + fileName);
        try {
            FileOutputStream fout = new FileOutputStream(fileName);
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject(object);
            fout.close();
            oos.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("Saved file: " + fileName);
    }

    static List<stationData> loadStationsFromFile(File stationFileName) {
        List<stationData> savedStations = new ArrayList<>();
        try {
            FileInputStream fin = new FileInputStream(stationFileName);
            ObjectInputStream ois = new ObjectInputStream(fin);
            savedStations = (List<stationData>) ois.readObject();
            fin.close();
            ois.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return savedStations;
    }

    static Boolean isSavedRoute(String gQuery) {
        if (FmiQueryTest.routeFileName.exists()) {
            gQuery = gQuery.replace(FmiQueryTest.gDirectionQuery, "");
            gQuery = gQuery.replace(FmiQueryTest.gKey, "");
            gQuery = gQuery.replace("&key=", "");
            //Find gQuery from the file
            List<String> queries = readLinesFromFile(FmiQueryTest.routeFileName);
            for (String query : queries) {
                if (query.equals(gQuery)) {
                    //                    System.out.println("Route found from saved list.");
                    return TRUE;
                }
            }
        }
        return FALSE;
    }

    static List<routeStep> loadSavedRoute(String gQuery) {
        List<routeStep> loadedRoute = new ArrayList<>();
        gQuery = gQuery.replace(FmiQueryTest.gDirectionQuery, "");
        gQuery = gQuery.replace(FmiQueryTest.gKey, "");
        gQuery = gQuery.replace("&key=", "");
        File savedFileName = new File(gQuery.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        //Load route
        try {
            FileInputStream fin = new FileInputStream(savedFileName);
            ObjectInputStream ois = new ObjectInputStream(fin);
            loadedRoute = (List<routeStep>) ois.readObject();
            fin.close();
            ois.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(FmiQueryTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return loadedRoute;
    }

    static void saveRoute(String gQuery, List<routeStep> gSteps) {
        gQuery = gQuery.replace(FmiQueryTest.gDirectionQuery, "");
        gQuery = gQuery.replace(FmiQueryTest.gKey, "");
        gQuery = gQuery.replace("&key=", "");
        //Save gQuery
        FileSystemTools.appendLineToFile(gQuery, FmiQueryTest.routeFileName);
        //Save gSteps
        File savedFileName = new File(gQuery.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        FileSystemTools.saveObjectToFile(gSteps, savedFileName);
    }

    static void appendLineToFile(String gQuery, File file) {
        try {
            // if file doesnt exists, then create it
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fw = new FileWriter(file.getAbsoluteFile(), true);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(gQuery + FmiQueryTest.newLine);
            bw.close();

            System.out.println("Saved route to " + file.getName());
        } catch (IOException ex) {
            Logger.getLogger(FileSystemTools.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    static List<String> readLinesFromFile(File file) {
        List<String> lines = new ArrayList<String>();
        try {
            FileReader fileReader;
            fileReader = new FileReader(file);
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                lines.add(line);
            }
            bufferedReader.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FileSystemTools.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(FileSystemTools.class.getName()).log(Level.SEVERE, null, ex);
        }
        return lines;
    }

    static List<stationData> extractHourOfWeatherData(String savedFileTime, List<stationData> fmiData) {
        List<stationData> thisHourData = new ArrayList<>();
        System.out.println("savedFileTime: " + savedFileTime);
        for (stationData line : fmiData) {
            for (weatherData data : line.weatherData) {
                List<weatherData> weatherPoint = new ArrayList<>();
                if (data.isoTime.equals(savedFileTime)) {
                    weatherPoint.add(0, data);
                    thisHourData.add(new stationData(line.stationLocation, line.stationName, weatherPoint));
                }
            }
        }
        return thisHourData;
    }

    static void cleanupOldWeatherData(int daysToStoreWeatherData) {
        int cleanUpTime = parseInt(FmiQueryTest.df_daycode
                .format(new Date(System.currentTimeMillis() - ((daysToStoreWeatherData * 24 * 3600) * 1000))));
        File dir = new File(".");
        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.startsWith("weather");
            }
        };
        String[] weatherFiles = dir.list(filter);
        System.out.println("Cleaning up old weather data from directory:");
        for (String file : weatherFiles) {
            System.out.print(file);
            int currentDayCode = parseInt(file.replaceAll("weather", "").substring(0, 8));
            if (currentDayCode <= cleanUpTime) {
                try {
                    Files.delete(FileSystems.getDefault().getPath(file));
                } catch (IOException ex) {
                    Logger.getLogger(FileSystemTools.class.getName()).log(Level.SEVERE, null, ex);
                }
                System.out.println(" - was removed.");
            } else {
                System.out.println(" - was kept.");
            }
        }
    }
}

class WeatherTools {
    //    Data structure
    //    hmap (keys: String isoTime-parameterName)
    //      parameterValue
    static Map extractNeededStations(Set<Integer> uniqueEntries, List<stationData> fmiData,
            List<stationData> allStations) {
        //Prepare returned HashMap
        HashMap<String, String> hmap = new HashMap<>();
        //Prepare HashMap for Coordinates-Integer pairs
        HashMap<String, Integer> idMap = new HashMap<>();

        //Prepare inner HashMap for every station (keys: isotimes)
        for (Integer stationNum : uniqueEntries) {
            //Save needed stations as Coordinates
            String coor = allStations.get(stationNum).stationLocation.Lat + "-"
                    + allStations.get(stationNum).stationLocation.Lon;
            idMap.put(coor, stationNum);
        }
        //Save all needed weather data
        for (stationData station : fmiData) {
            //Use idMap to check if current station is needed
            String coor = station.stationLocation.Lat + "-" + station.stationLocation.Lon;
            //System.out.println(coor);
            if (idMap.containsKey(coor)) {
                //If it is, save any weather data found here
                Integer stationNum = idMap.get(coor);
                for (weatherData weather : station.weatherData) {
                    //System.out.format("%-25s%-35s%-20s%n",weather.isoTime,weather.parameterName,weather.parameterValue);
                    hmap.put(stationNum + "-" + weather.isoTime + "-" + weather.parameterName,
                            weather.parameterValue);
                }
            }
        }
        return hmap;
    }

    static void makeResultHtml(List<stepWeather> stepDataBase, List<stationData> allStations, Map routeWeather,
            List<String> sunEvents) {
        try {
            // if file doesnt exists, then create it
            if (!FmiQueryTest.resultFileName.exists()) {
                FmiQueryTest.resultFileName.createNewFile();
            }
            FileWriter fw = new FileWriter(FmiQueryTest.resultFileName.getAbsoluteFile(), false);
            BufferedWriter bw = new BufferedWriter(fw);

            bw.write("<!DOCTYPE html>\n" + "<html>\n" + "  <head>\n"
                    + "    <meta name=\"viewport\" content=\"initial-scale=1.0, user-scalable=no\">\n"
                    + "    <meta charset=\"utf-8\">\n" + "    <title>Weather en Route</title>\n" + "    <style>\n"
                    + "      html, body {\n" + "        height: 100%;\n" + "        margin: 0;\n"
                    + "        padding: 0;\n" + "      }\n" + "      #map {\n" + "        height: 100%;\n"
                    + "      }\n"
                    + "      #map > div > div:nth-child(1) > div:nth-child(3) > div:nth-child(4) > div > div:nth-child(1) > div:nth-child(3){display:none;}\n"
                    + "      #map > div > div:nth-child(1) > div:nth-child(3) > div:nth-child(4) > div > div:nth-child(1) > div:nth-child(1){display:none;}\n"
                    + "    </style>\n" + "  </head>\n" + "  <body>\n" + "    <div id=\"map\"></div>\n"
                    + "    <script>\n" + "  function initMap() {\n");
            bw.write("  var siikalatva = {lat: 64.179265, lng: 25.804715};\n"
                    + "  var map = new google.maps.Map(document.getElementById('map'), {\n" + "    zoom: 6,\n"
                    + "    center: siikalatva,\n" + "    zoomControl: true,\n" + "    scaleControl: true,\n"
                    + "    streetViewControl: true,\n" + "    mapTypeControl: true,\n"
                    + "    mapTypeControlOptions: {\n"
                    + "      style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,\n" + "      mapTypeIds: [\n"
                    + "        google.maps.MapTypeId.ROADMAP,\n" + "        google.maps.MapTypeId.TERRAIN\n"
                    + "      ]\n" + "    }\n" + "  });\n");
            bw.write("  var directionsService = new google.maps.DirectionsService;\n"
                    + "  var directionsDisplay = new google.maps.DirectionsRenderer({\n" + "    draggable: false,\n"
                    + "    map: map\n" + "  });");
            bw.write("  displayRoute('" + FmiQueryTest.gOrigin + "', '" + FmiQueryTest.gDestination
                    + "', directionsService,\n" + "      directionsDisplay);");

            bw.write(makeWeatherStationBoxes(stepDataBase, allStations));
            if (!sunEvents.isEmpty()) {
                bw.write(makeSunEventBoxes(stepDataBase, sunEvents));
            }
            bw.write(makeRouteWeatherBoxes(stepDataBase, routeWeather));
            bw.write(makeChartBoxes(stepDataBase, routeWeather));

            //http://en.marnoto.com/2014/09/5-formas-de-personalizar-infowindow.html

            bw.write("  }\n");
            bw.write("function displayRoute(origin, destination, service, display) {\n" + "  service.route({\n"
                    + "    origin: origin,\n" + "    destination: destination,\n");
            if (!FmiQueryTest.gWaypoints.isEmpty()) {
                bw.write("    waypoints: [");
                String[] locs = FmiQueryTest.gWaypoints.split("\\|");
                for (int i = 0; i < locs.length; ++i) {
                    bw.write("{location: '" + locs[i] + "'}");
                    if (i < locs.length - 1) {
                        bw.write(", ");
                    }
                }
                bw.write("],\n");
            }
            bw.write("    provideRouteAlternatives: true,\n" + "    travelMode: google.maps.TravelMode.DRIVING\n"
                    + "  }, function(response, status) {\n"
                    + "    if (status === google.maps.DirectionsStatus.OK) {\n"
                    + "      display.setDirections(response);\n" + "      display.setRouteIndex("
                    + FmiQueryTest.gRouteOption + ");\n" + "    } else {\n"
                    + "      alert('Could not display directions due to: ' + status);\n" + "    }\n" + "  });\n"
                    + "}");
            bw.write("    </script>\n" + "    <script async defer\n"
                    + "        src=\"https://maps.googleapis.com/maps/api/js?key=AIzaSyDvB2p97Ee-USxdOsOTyCa50HnW8H4PlRQ&signed_in=true&callback=initMap\"></script>\n"
                    + "  </body>\n" + "</html>");
            bw.close();
        } catch (IOException ex) {
            System.err.println("Something went wrong: " + ex.getMessage());
        }
        //        try {
        //            //Open the file
        //            Desktop.getDesktop().browse(FmiQueryTest.resultFileName.toURI());
        //        } catch (IOException ex) {
        //            Logger.getLogger(WeatherTools.class.getName()).log(Level.SEVERE, null, ex);
        //        }
    }

    private static String makeWeatherStationBoxes(List<stepWeather> stepDataBase, List<stationData> allStations) {
        String markers = "";
        List<Integer> neededStations = new ArrayList<>();
        for (int i = 0; i < stepDataBase.size(); ++i) {
            neededStations.add(stepDataBase.get(i).nearestStation);
        }
        Set<Integer> uniqueSet = new HashSet<>(neededStations);
        List<Integer> uniqueEntries = new ArrayList<>();
        for (Integer a : uniqueSet) {
            uniqueEntries.add(a);
        }
        for (int i = 0; i < uniqueEntries.size(); ++i) {
            Integer nearestStation = uniqueEntries.get(i);
            markers += ("  var station" + i + " = '<div id=\"content" + i + "\"><pre>'+") + FmiQueryTest.newLine;
            markers += ("'Station:     " + allStations.get(nearestStation).stationName + "<br>'+")
                    + FmiQueryTest.newLine;
            markers += ("'Coordinates: "
                    + FmiQueryTest.df_fiveDecimal.format(allStations.get(nearestStation).stationLocation.Lat) + " "
                    + FmiQueryTest.df_fiveDecimal.format(allStations.get(nearestStation).stationLocation.Lon)
                    + "<br>'+") + FmiQueryTest.newLine;
            markers += ("'Time(s):     ");
            for (int j = 0; j < stepDataBase.size(); ++j) {
                if (stepDataBase.get(j).nearestStation == uniqueEntries.get(i)) {
                    markers += DayLightTime.doubleTimeToString(stepDataBase.get(j).timeAsDouble) + " ";
                }
            }
            markers += ("<br>'+") + FmiQueryTest.newLine;
            markers += ("'</pre></div>';") + FmiQueryTest.newLine;
            markers += ("  var station_infowindow" + i + " = new google.maps.InfoWindow({\n"
                    + "    content: station" + i + ",\n" + "    maxWidth: 400,\n"
                    + "    pixelOffset: new google.maps.Size(-210, 25)\n" + "  });");
            markers += ("  var station_position" + i + " = {lat: "
                    + allStations.get(nearestStation).stationLocation.Lat + ", lng: "
                    + allStations.get(nearestStation).stationLocation.Lon + "};");
            markers += ("  var station_marker" + i + " = new google.maps.Marker({\n"
                    + "    position: station_position" + i + ",\n" + "    map: map,\n");
            markers += ("    icon: { url: 'http://mt.google.com/vt/icon?color=ff004C13&name=icons/spotlight/spotlight-waypoint-blue.png' },\n");
            markers += ("    title: '" + allStations.get(nearestStation).stationName + "'\n" + "  });");
            markers += ("  station_marker" + i + ".addListener('click', function() {\n" + "    station_infowindow"
                    + i + ".open(map, station_marker" + i + ");\n" + "  });");
            //This makes the info box visible when the map opens
            //markers += ("station_infowindow"+i+".open(map, station_marker"+i+");\n");
        }
        return markers;
    }

    private static String makeSunEventBoxes(List<stepWeather> stepDataBase, List<String> sunEvents) {
        String markers = "";
        markers += ("  var sun_events = '<div id=\"sun_content\"><pre>'+") + FmiQueryTest.newLine;
        for (String s : sunEvents) {
            markers += ("'" + s.replaceAll(",", ".") + "<br>'+") + FmiQueryTest.newLine;
        }
        markers += ("'</pre></div>';") + FmiQueryTest.newLine;
        markers += ("  var sun_infowindow = new google.maps.InfoWindow({\n" + "    content: sun_events,\n"
                + "    pixelOffset: new google.maps.Size(250, 25)\n" + "  });");
        markers += ("  var sun_position = {lat: " + (stepDataBase.get(0).StartLocation.Lat - 0.05) + ", lng: "
                + (stepDataBase.get(0).StartLocation.Lon + 0.05) + "};");
        markers += ("  var sun_marker = new google.maps.Marker({\n" + "    position: sun_position,\n"
                + "    map: map,\n" + "    draggable:true,\n");
        markers += ("    icon: { url: 'http://maps.google.com/mapfiles/ms/icons/yellow-dot.png' },\n");
        markers += ("    title: 'Sun events'\n" + "  });");
        markers += ("  sun_marker.addListener('click', function() {\n"
                + "    sun_infowindow.open(map, sun_marker);\n" + "  });");
        //This makes the info box visible when the map opens
        markers += ("sun_infowindow.open(map, sun_marker);\n");
        //Skip the first sunEvent, which is trip start time
        for (int i = 1; i < sunEvents.size(); ++i) {
            String coords = sunEvents.get(i).substring(sunEvents.get(i).indexOf("(") + 1,
                    sunEvents.get(i).indexOf(")"));
            String[] parts = coords.split(" ");
            //System.out.println(parts[1].trim()); // STRING_VALUES
            markers += ("  var sun_events" + i + " = '<div id=\"sun_content" + i + "\"><pre>'+")
                    + FmiQueryTest.newLine;
            markers += ("'" + sunEvents.get(i).substring(0, sunEvents.get(i).indexOf("(")) + "<br>'+");
            markers += ("'</pre></div>';") + FmiQueryTest.newLine;
            markers += ("  var sun_infowindow" + i + " = new google.maps.InfoWindow({\n" + "    content: sun_events"
                    + i + ",\n" + "    pixelOffset: new google.maps.Size(180, 25)\n" + "  });");
            markers += ("  var sun_position" + i + " = {lat: " + parts[0].replaceAll(",", ".") + ", lng: "
                    + parts[1].replaceAll(",", ".") + "};");
            markers += ("  var sun_marker" + i + " = new google.maps.Marker({\n" + "    position: sun_position" + i
                    + ",\n" + "    map: map,\n");
            markers += ("    icon: { url: 'http://maps.google.com/mapfiles/ms/icons/yellow-dot.png' },\n");
            markers += ("    title: 'Sun event " + i + "'\n" + "  });");
            markers += ("  sun_marker" + i + ".addListener('click', function() {\n" + "    sun_infowindow" + i
                    + ".open(map, sun_marker" + i + ");\n" + "  });");
            //This makes the info box visible when the map opens
            markers += ("sun_infowindow" + i + ".open(map, sun_marker" + i + ");\n");
        }
        return markers;
    }

    private static String makeRouteWeatherBoxes(List<stepWeather> stepDataBase, Map routeWeather) {
        String markers = "";
        //        List<Integer> neededStations = new ArrayList<>();
        //        for (int i=0;i<stepDataBase.size();++i){neededStations.add(stepDataBase.get(i).nearestStation);}
        //        Set<Integer> uniqueSet = new HashSet<>(neededStations);
        //        List<Integer> uniqueEntries = new ArrayList<>();
        //        for (Integer a : uniqueSet){uniqueEntries.add(a);}
        for (int i = 0; i < stepDataBase.size(); ++i) {
            //            Integer nearestStation = uniqueEntries.get(i);
            markers += ("  var weatherpoint" + i + " = '<div id=\"weathercontent" + i + "\"><pre>'+")
                    + FmiQueryTest.newLine;
            //            markers += ("'Station:     "+allStations.get(nearestStation).stationName+"<br>'+")+FmiQueryTest.newLine;
            //            markers += ("'Coordinates: "+allStations.get(nearestStation).stationLocation.Lat
            //                    +   " "+allStations.get(nearestStation).stationLocation.Lon+"<br>'+")+FmiQueryTest.newLine;
            markers += ("'" + DayLightTime.doubleTimeToString(stepDataBase.get(i).timeAsDouble) + "<br>'+");
            //            for (int j=0;j<stepDataBase.size();++j){
            //                if (stepDataBase.get(j).nearestStation==uniqueEntries.get(i)){
            //                    markers += DayLightTime.doubleTimeToString(stepDataBase.get(j).timeAsDouble)+" ";
            //                }
            //            }
            markers += (extractWeatherAtStep(stepDataBase, i, routeWeather)) + FmiQueryTest.newLine;
            //            markers += ("'<br>'+")+FmiQueryTest.newLine;
            markers += ("'</pre></div>';") + FmiQueryTest.newLine;
            markers += ("  var weather_infowindow" + i + " = new google.maps.InfoWindow({\n"
                    + "    content: weatherpoint" + i + ",\n" + "    maxWidth: 400,\n"
                    + "    pixelOffset: new google.maps.Size(-125, 25)\n" + "  });");
            markers += ("  var weather_position" + i + " = {lat: " + stepDataBase.get(i).StartLocation.Lat
                    + ", lng: " + stepDataBase.get(i).StartLocation.Lon + "};");
            markers += ("  var weather_marker" + i + " = new google.maps.Marker({\n"
                    + "    position: weather_position" + i + ",\n" + "    map: map,\n");
            markers += ("    icon: { url: 'http://mt.google.com/vt/icon?psize=30&font=fonts/arialuni_t.ttf&color=ff304C13&name=icons/spotlight/spotlight-waypoint-a.png&ax=43&ay=48&text=%E2%80%A2' },\n");
            markers += ("    title: 'Weather point " + i + "'\n" + "  });");
            markers += ("  weather_marker" + i + ".addListener('click', function() {\n" + "    weather_infowindow"
                    + i + ".open(map, weather_marker" + i + ");\n" + "  });");
            //This makes the info box visible when the map opens
            //markers += ("weather_infowindow"+i+".open(map, weather_marker"+i+");\n");
        }
        return markers;
    }

    private static String extractWeatherAtStep(List<stepWeather> stepDataBase, int i, Map routeWeather) {
        String weatherBoxContent = "";
        stepWeather step = stepDataBase.get(i);
        stepWeather nextstep = step;
        if (i < stepDataBase.size() - 1) {
            nextstep = stepDataBase.get(i + 1);
        }
        String isoTimeNowBegin = FmiQueryTest.df_iso
                .format(new Date(FmiQueryTest.startTimeMillis + step.totalDuration * 1000));

        List<String> paramsToShow = new ArrayList<>();
        paramsToShow.add("Temperature");
        paramsToShow.add("Pressure");
        paramsToShow.add("Humidity");
        paramsToShow.add("DewPoint");
        paramsToShow.add("Precipitation1h");
        paramsToShow.add("WindSpeedMS");
        paramsToShow.add("WindDirection");
        paramsToShow.add("TotalCloudCover");
        //WeatherSymbol3, Pressure, DewPoint, CloudSymbol(null), TotalCloudCover
        //MaximumWind(null), WindVMS, WindUMS, PrecipitationType(NaN), WindDirection
        //WindSpeedMS, Precipitation1h, Humidity, Temperature
        for (String param : paramsToShow) {
            double result = getParameterAverage(routeWeather, step, nextstep, param);
            weatherBoxContent += String.format("'%-16s%s", param, FmiQueryTest.df_oneDecimal.format(result));
            switch (param) {
            case "Temperature":
                weatherBoxContent += " C";
                break;
            case "DewPoint":
                weatherBoxContent += " C";
                break;
            case "WindSpeedMS":
                weatherBoxContent += " m/s";
                break;
            case "Pressure":
                weatherBoxContent += " mbar";
                break;
            case "Humidity":
                weatherBoxContent += " %";
                break;
            case "TotalCloudCover":
                weatherBoxContent += " %";
                break;
            case "WindDirection":
                weatherBoxContent += " " + directionToCardinal(result);
                break;
            case "Precipitation1h":
                weatherBoxContent += " mm";
                break;
            default:
                break;
            }
            weatherBoxContent += "<br>'+";
        }
        try {
            int weatherSymbol = intValue(Double.parseDouble(
                    routeWeather.get(step.nearestStation + "-" + isoTimeNowBegin + "-WeatherSymbol3").toString()));
            weatherBoxContent += String.format("'%-16s%s", "WeatherSymbol3",
                    getWeatherSymbolDescription(weatherSymbol)) + "<br>'+";
        } catch (NullPointerException ex) {
            //weatherBoxContent += "'WeatherSymbol3  : NA<br>'+";
        }
        return weatherBoxContent;
    }

    private static double getParameterAverage(Map routeWeather, stepWeather step, stepWeather nextstep,
            String param) {
        double result = NaN;
        try {
            String isoTimeNowBegin = FmiQueryTest.df_iso
                    .format(new Date(FmiQueryTest.startTimeMillis + step.totalDuration * 1000));
            String isoTimeNowEnd = FmiQueryTest.df_iso
                    .format(new Date(FmiQueryTest.startTimeMillis + nextstep.totalDuration * 1000));
            String isoTimeNextBegin = FmiQueryTest.df_iso
                    .format(new Date(FmiQueryTest.startTimeMillis + (step.totalDuration + 3600) * 1000));
            String isoTimeNextEnd = FmiQueryTest.df_iso
                    .format(new Date(FmiQueryTest.startTimeMillis + (step.totalDuration + 3600) * 1000));

            //System.out.println(routeWeather.get(step.nearestStation+"-"+isoTimeNowBegin+"-"+param).toString());
            double beginNow = Double.parseDouble(
                    routeWeather.get(step.nearestStation + "-" + isoTimeNowBegin + "-" + param).toString());
            //System.out.println(routeWeather.get(step.nearestStation+"-"+isoTimeNextBegin+"-"+param).toString());
            double beginNext = Double.parseDouble(
                    routeWeather.get(step.nearestStation + "-" + isoTimeNextBegin + "-" + param).toString());
            double beginInterp = beginNow + step.timeAsDouble % 1 * (beginNext - beginNow);
            //System.out.println(step.timeAsDouble%1);
            //System.out.println(beginInterp);
            double endNow = Double.parseDouble(
                    routeWeather.get(nextstep.nearestStation + "-" + isoTimeNowEnd + "-" + param).toString());
            double endNext = Double.parseDouble(
                    routeWeather.get(nextstep.nearestStation + "-" + isoTimeNextEnd + "-" + param).toString());
            double endInterp = endNow + nextstep.timeAsDouble % 1 * (endNext - endNow);
            //System.out.println(endInterp);
            result = (beginInterp + endInterp) / 2;
            //System.out.println(result);
        } catch (NullPointerException ex) {
            //obsolete
            //weatherBoxContent += "'"+param+": NA<br>'+";
        }
        return result;
    }

    private static String directionToCardinal(double result) {
        String cardinal = "";
        if (result >= 0 && result < 11.25) {
            cardinal = "N";
        } else if (result >= 11.25 && result < 33.75) {
            cardinal = "NNE";
        } else if (result >= 33.75 && result < 56.25) {
            cardinal = "NE";
        } else if (result >= 56.25 && result < 78.75) {
            cardinal = "ENE";
        } else if (result >= 78.75 && result < 101.25) {
            cardinal = "E";
        } else if (result >= 101.25 && result < 123.75) {
            cardinal = "ESE";
        } else if (result >= 123.75 && result < 146.25) {
            cardinal = "SE";
        } else if (result >= 146.25 && result < 168.75) {
            cardinal = "SSE";
        } else if (result >= 168.75 && result < 191.25) {
            cardinal = "S";
        } else if (result >= 191.25 && result < 213.75) {
            cardinal = "SSW";
        } else if (result >= 213.75 && result < 236.25) {
            cardinal = "SW";
        } else if (result >= 236.25 && result < 258.75) {
            cardinal = "WSW";
        } else if (result >= 258.75 && result < 281.25) {
            cardinal = "W";
        } else if (result >= 281.25 && result < 303.75) {
            cardinal = "WNW";
        } else if (result >= 303.75 && result < 326.25) {
            cardinal = "NW";
        } else if (result >= 326.25 && result < 348.75) {
            cardinal = "NNW";
        } else if (result >= 348.75 && result <= 360) {
            cardinal = "N";
        } else {
            cardinal = "-";
        }
        return cardinal;
    }

    //case 1  : symbol += "selke"; break;
    //case 2  : symbol += "puolipilvist"; break;
    //case 21 : symbol += "heikkoja sadekuuroja"; break;
    //case 22 : symbol += "sadekuuroja"; break;
    //case 23 : symbol += "voimakkaita sadekuuroja"; break;
    //case 3  : symbol += "pilvist"; break;
    //case 31 : symbol += "heikkoa vesisadetta"; break;
    //case 32 : symbol += "vesisadetta"; break;
    //case 33 : symbol += "voimakasta vesisadetta"; break;
    //case 41 : symbol += "heikkoja lumikuuroja"; break;
    //case 42 : symbol += "lumikuuroja"; break;
    //case 43 : symbol += "voimakkaita lumikuuroja"; break;
    //case 51 : symbol += "heikkoa lumisadetta"; break;
    //case 52 : symbol += "lumisadetta"; break;
    //case 53 : symbol += "voimakasta lumisadetta"; break;
    //case 61 : symbol += "ukkoskuuroja"; break;
    //case 62 : symbol += "voimakkaita ukkoskuuroja"; break;
    //case 63 : symbol += "ukkosta"; break;
    //case 64 : symbol += "voimakasta ukkosta"; break;
    //case 71 : symbol += "heikkoja rntkuuroja"; break;
    //case 72 : symbol += "rntkuuroja"; break;
    //case 73 : symbol += "voimakkaita rntkuuroja"; break;
    //case 81 : symbol += "heikkoa rntsadetta"; break;
    //case 82 : symbol += "rntsadetta"; break;
    //case 83 : symbol += "voimakasta rntsadetta"; break;
    //case 91 : symbol += "utua"; break;
    //case 92 : symbol += "sumua"; break;
    private static String getWeatherSymbolDescription(int number) {
        String symbol = "";
        switch (number) {
        case 1:
            symbol += "Clear";
            break;
        case 2:
            symbol += "Partly cloudy";
            break;
        case 21:
            symbol += "Light rain showers";
            break;
        case 22:
            symbol += "Rain showers";
            break;
        case 23:
            symbol += "Heavy rain showers";
            break;
        case 3:
            symbol += "Cloudy";
            break;
        case 31:
            symbol += "Light rain";
            break;
        case 32:
            symbol += "Rain";
            break;
        case 33:
            symbol += "Heavy rain";
            break;
        case 41:
            symbol += "Light snow showers";
            break;
        case 42:
            symbol += "Snow showers";
            break;
        case 43:
            symbol += "Heavy snow showers";
            break;
        case 51:
            symbol += "Light snow";
            break;
        case 52:
            symbol += "Snow";
            break;
        case 53:
            symbol += "Heavy snow";
            break;
        case 61:
            symbol += "Thunder showers";
            break;
        case 62:
            symbol += "Heavy thunder showers";
            break;
        case 63:
            symbol += "Thunder";
            break;
        case 64:
            symbol += "Heavy thunder";
            break;
        case 71:
            symbol += "Light sleet showers";
            break;
        case 72:
            symbol += "Sleet showers";
            break;
        case 73:
            symbol += "Heavy sleet showers";
            break;
        case 81:
            symbol += "Light sleet";
            break;
        case 82:
            symbol += "Sleet";
            break;
        case 83:
            symbol += "Heavy sleet";
            break;
        case 91:
            symbol += "Mist";
            break;
        case 92:
            symbol += "Fogg";
            break;
        default:
            symbol += "-";
            break;
        }
        return symbol;
    }

    // http://www.java2s.com/Code/Java/Chart/JFreeChartCompassFormatDemo.htm
    //        int count = 100;
    //        for (int i = 0; i < count; i++) {
    //            double temperature = (Math.random() - 0.5) * 20;
    //            s1.add(start, temperature);
    //            start = start.next();
    //        }
    //        start = new Minute();
    //        for (int i = 0; i < count; i++) {
    //            double rain = Math.random();
    //            s2.add(start, rain);
    //            start = start.next();
    //        }
    static String drawTempRainChart(List<stepWeather> stepDataBase, Map routeWeather) {
        //First, fetch the real data
        List<Double> times = new ArrayList<>();
        List<Double> temps = new ArrayList<>();
        List<Double> rains = new ArrayList<>();
        List<Double> lights = new ArrayList<>();
        for (int i = 0; i < stepDataBase.size(); ++i) {
            stepWeather step = stepDataBase.get(i);
            stepWeather nextstep = step;
            if (i < stepDataBase.size() - 1) {
                nextstep = stepDataBase.get(i + 1);
            }
            times.add(step.timeAsDouble);
            temps.add(getParameterAverage(routeWeather, step, nextstep, "Temperature"));
            rains.add(getParameterAverage(routeWeather, step, nextstep, "Precipitation1h"));
            lights.add(Math.pow(Math.abs(step.timeAsDouble % 24 - ((step.sunRise + step.sunSet) / 2))
                    / ((step.sunSet - step.sunRise) / 2), 5));
        }

        double zeroPoint = times.get(0);
        // Make first dataset
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        final TimeSeries s1 = new TimeSeries("Temperature C", Minute.class);
        // Make second dataset
        final TimeSeriesCollection dataset2 = new TimeSeriesCollection();
        final TimeSeries s2 = new TimeSeries("Rain (mm)", Minute.class);
        // Make third dataset
        //final TimeSeriesCollection dataset3 = new TimeSeriesCollection();
        final TimeSeries s3 = new TimeSeries("Darkness", Minute.class);
        // Show data per minute
        RegularTimePeriod start = new Minute(new Date(FmiQueryTest.startTimeMillis));
        for (int i = 0; i < times.size() - 1; ++i) {
            double time = times.get(i);
            double nexttime = times.get(i + 1);
            int firstMinute = intValue((time - zeroPoint) * 60);
            int currentMinute = firstMinute;
            int lastMinute = intValue((nexttime - zeroPoint) * 60);
            while (currentMinute < lastMinute) {
                s1.add(start, temps.get(i) + ((double) currentMinute - firstMinute) / (lastMinute - firstMinute)
                        * (temps.get(i + 1) - temps.get(i)));
                s2.add(start, (rains.get(i) + rains.get(i + 1)) / 2);
                //+((double)currentMinute-firstMinute)/(lastMinute-firstMinute)*(rains.get(i+1)-rains.get(i)));
                s3.add(start, lights.get(i) + ((double) currentMinute - firstMinute) / (lastMinute - firstMinute)
                        * (lights.get(i + 1) - lights.get(i)));
                ++currentMinute;
                start = start.next();
            }
        }
        dataset.addSeries(s1);
        dataset2.addSeries(s3);
        dataset2.addSeries(s2);

        // Initialize chart
        final JFreeChart chart = ChartFactory.createTimeSeriesChart("Temperature and rain", "Time",
                "Temperature C", dataset, true, // legend? 
                true, // tooltips? 
                false // URLs? 
        );
        final XYPlot plot = chart.getXYPlot();
        plot.getDomainAxis().setLowerMargin(0.0);
        plot.getDomainAxis().setUpperMargin(0.0);

        // configure the range axis to display first dataset...
        final ValueAxis rangeAxis = (ValueAxis) plot.getRangeAxis();
        rangeAxis.setRange(-30, 30);
        final TickUnits units = new TickUnits();
        units.add(new NumberTickUnit(10.0));
        units.add(new NumberTickUnit(1.0));
        units.add(new NumberTickUnit(0.1));
        rangeAxis.setStandardTickUnits(units);
        //rangeAxis.setTickLabelFont(new Font("SansSerif", Font.BOLD, 16));

        // add the secondary dataset/renderer/axis
        plot.setRangeAxis(rangeAxis);
        final XYItemRenderer renderer2 = new XYAreaRenderer();
        final ValueAxis axis2 = new NumberAxis("Rain (mm) ");
        axis2.setRange(0, 2);
        axis2.setStandardTickUnits(units);
        //axis2.setTickLabelFont(new Font("SansSerif", Font.BOLD, 16));
        renderer2.setSeriesPaint(0, new Color(0, 0, 255, 128));
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, renderer2);
        plot.setRangeAxis(1, axis2);
        plot.mapDatasetToRangeAxis(1, 1);
        final XYItemRenderer renderer3 = new XYAreaRenderer();
        renderer3.setSeriesPaint(0, new Color(0, 0, 0, 64));
        //plot.setDataset(1, dataset2);
        plot.setRenderer(1, renderer3);
        plot.setRangeAxis(1, axis2);
        plot.mapDatasetToRangeAxis(1, 1);

        chart.setBackgroundPaint(Color.white);
        //plot.setBackgroundPaint(Color.lightGray);
        plot.setBackgroundPaint(Color.white);
        plot.setDomainGridlinesVisible(true);
        plot.setDomainGridlinePaint(Color.black);
        plot.setRangeGridlinesVisible(true);
        plot.setRangeGridlinePaint(Color.black);

        //        final TextTitle subtitle = new TextTitle("An area chart demonstration.  We use this "
        //            + "subtitle as an example of what happens when you get a really long title or "
        //            + "subtitle.");
        //        subtitle.setFont(new Font("SansSerif", Font.PLAIN, 12));
        //        subtitle.setPosition(RectangleEdge.TOP);
        ////        subtitle.setSpacer(new Spacer(Spacer.RELATIVE, 0.05, 0.05, 0.05, 0.05));
        //        subtitle.setVerticalAlignment(VerticalAlignment.BOTTOM);
        //        chart.addSubtitle(subtitle);

        // Produce chart
        //        ChartFrame frame = new ChartFrame("Tamperature and rain", chart);
        //        frame.pack();
        //        RefineryUtilities.centerFrameOnScreen(frame);
        //        frame.setVisible(true);
        ByteArrayOutputStream bas = new ByteArrayOutputStream();
        try {
            ChartUtilities.writeChartAsPNG(bas, chart, 600, 400);
        } catch (IOException ex) {
            Logger.getLogger(WeatherTools.class.getName()).log(Level.SEVERE, null, ex);
        }
        byte[] byteArray = bas.toByteArray();
        String baseCode = Base64.encode(byteArray);
        return "<img width=\"480\" alt=\"Temperature and rain\" src=\"data:image/png;base64," + baseCode.trim()
                + "\" />";
    }

    private static String makeChartBoxes(List<stepWeather> stepDataBase, Map routeWeather) {
        String markers = "";
        markers += ("  var chart = '<div id=\"chart_content\">'+") + FmiQueryTest.newLine;
        markers += ("  '" + WeatherTools.drawTempRainChart(stepDataBase, routeWeather) + "'+")
                + FmiQueryTest.newLine;
        markers += ("  '</div>';") + FmiQueryTest.newLine;
        markers += ("  var chart_infowindow = new google.maps.InfoWindow({\n" + "    content: chart,\n"
                + "    pixelOffset: new google.maps.Size(-280, 25)\n" + "  });");
        markers += ("  var chart_position = {lat: " + (stepDataBase.get(0).StartLocation.Lat - 0.05) + ", lng: "
                + (stepDataBase.get(0).StartLocation.Lon - 0.05) + "};");
        markers += ("  var chart_marker = new google.maps.Marker({\n" + "    position: chart_position,\n"
                + "    map: map,\n" + "    draggable:true,\n");
        markers += ("    icon: { url: 'http://maps.google.com/mapfiles/ms/icons/orange-dot.png' },\n");
        markers += ("    title: 'Temperature and rain'\n" + "  });");
        markers += ("  chart_marker.addListener('click', function() {\n"
                + "    chart_infowindow.open(map, chart_marker);\n" + "  });");
        //This makes the info box visible when the map opens
        markers += ("chart_infowindow.open(map, chart_marker);\n");
        return markers;
    }
}

public class FmiQueryTest {
    //INPUTS AND SETTINGS
    static String gKey = "AIzaSyBceB0at3czeTt5BWnL7FQvmf_AEBxLjys";
    static String gDirectionQuery = "https://maps.googleapis.com/maps/api/directions/xml?";
    static String gOrigin = "Helsinki";
    static String gDestination = "Tampere";
    static String gMode = "driving";
    static String gWaypoints = "";
    static int gRouteOption = 0;
    //    static String gShare = "https://www.google.fi/maps/dir/Helsinki/Ilomantsi/Kemi/Utsjoki/@64.7725729,18.7906882,5z/am=t/data=!4m26!4m25!1m5!1m1!1s0x46920bc796210691:0xcd4ebd843be2f763!2m2!1d24.9410248!2d60.1733244!1m5!1m1!1s0x469c17bfcf5576db:0x40146d63c75ada0!2m2!1d30.9394911!2d62.6678853!1m5!1m1!1s0x442ab2b34f9628f7:0x40146d63c75b0f0!2m2!1d24.5643979!2d65.7362771!1m5!1m1!1s0x45cc09e00b8b9557:0x744946de9aa7ade0!2m2!1d27.0285297!2d69.9090465!3e0?hl=en";
    //    static String gShare = "https://www.google.fi/maps/dir/Helsinki/Ilomantsi/Kemi/Utsjoki/@64.7809149,18.7736186,5z/am=t/data=!3m1!4b1!4m26!4m25!1m5!1m1!1s0x46920bc796210691:0xcd4ebd843be2f763!2m2!1d24.9410248!2d60.1733244!1m5!1m1!1s0x469c17bfcf5576db:0x40146d63c75ada0!2m2!1d30.9394911!2d62.6678853!1m5!1m1!1s0x442ab2b34f9628f7:0x40146d63c75b0f0!2m2!1d24.5643979!2d65.7362771!1m5!1m1!1s0x45cc09e00b8b9557:0x744946de9aa7ade0!2m2!1d27.0285297!2d69.9090465!3e0?hl=en";
    static String gShare = "";
    static String gWebShareBase = "https://www.google.fi/maps/dir/";
    static String gWebShareRouteOptPar = "!5i";
    static String fmiKey = "2dcc4c13-ee27-4471-b5b1-73623eb933e4";
    static String fmiBase = "http://data.fmi.fi/fmi-apikey/";
    static String fmiMiddle = "/wfs?request=getFeature&storedquery_id=";
    static String fmiQueryCities = "fmi::forecast::hirlam::surface::cities::simple";
    static String fmiQueryObsStations = "fmi::forecast::hirlam::surface::obsstations::simple";
    static String newLine = System.getProperty("line.separator");//This will retrieve line separator dependent on OS.
    static File stationFileName = new File("stationnames.txt");
    static File routeFileName = new File("savedroutes.txt");
    static Integer routeDur = 0;
    static DateFormat df_short = new SimpleDateFormat("HH:mm");
    static DateFormat df_iso = new SimpleDateFormat("yyyy-MM-dd'T'HH:00:00'Z'");
    static DateFormat df_daycode = new SimpleDateFormat("yyyyMMdd");
    static DecimalFormat df_oneDecimal = new DecimalFormat("0.0");
    static DecimalFormat df_fiveDecimal = new DecimalFormat("0.00000");
    static TimeZone tz = TimeZone.getTimeZone("GMT+2");
    static int daysToStoreWeatherData = 1;
    static double startTimeOffset = 0;
    static long startTimeMillis = System.currentTimeMillis()
            + (intValue(Math.floor(startTimeOffset * 60 * 60 * 1000)));
    static int refreshInterval = 15 * 60; //seconds
    static File resultFileName = new File(
            "/home/navinoob/public-www/result_" + System.currentTimeMillis() + ".html");

    public static void main(String[] args) {
        df_short.setTimeZone(tz);
        df_iso.setTimeZone(tz);
        df_daycode.setTimeZone(tz);
        DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols();
        otherSymbols.setDecimalSeparator('.');
        df_fiveDecimal.setDecimalFormatSymbols(otherSymbols);
        String startTime = df_short.format(new Date(startTimeMillis));
        System.out.println("startTime: " + startTime);

        //Clean up old weather data 
        //**********************************************************************
        FileSystemTools.cleanupOldWeatherData(daysToStoreWeatherData);

        //Google query
        //**********************************************************************
        if (gShare.equals("")) {
            Scanner input = new Scanner(System.in);
            System.out.println("Paste Google Directions Share:");
            gShare = input.nextLine();
        }
        String gQuery = Parser.getQueryFromShare(gShare);
        System.out.println("Google query URL: " + gQuery);

        //Check if we already have this route
        //Valid only if the route option is 0 (default)
        //Because otherwise we cannot be sure we already have the optional route
        List<routeStep> gSteps = new ArrayList<>();
        if (FileSystemTools.isSavedRoute(gQuery) && gRouteOption == 0) {
            System.out.println("Route found from saved list. Loading.");
            gSteps = FileSystemTools.loadSavedRoute(gQuery);
        } else {
            gSteps = Parser.getSteps(gQuery);
            if (gRouteOption == 0) {
                System.out.println("Saving new route to list.");
                FileSystemTools.saveRoute(gQuery, gSteps);
            }
        }

        //Compile route table with current settings
        //**********************************************************************
        List<routeStep> routeData = RouteTools.compileRoute(gSteps, refreshInterval);
        String endTime = df_short.format(new Date(startTimeMillis + routeDur * 1000));
        System.out.println("endTime: " + endTime);
        //Forecast from FMI is only for 48h - warning if we are going over
        //Or is it 54h? http://ilmatieteenlaitos.fi/avoin-data-saaennustedata-hirlam
        if (((startTimeMillis + routeDur * 1000) - System.currentTimeMillis()) / (1000 * 60 * 60) > 48) {
            System.out.println("**************************************************" + newLine + "WARNING:" + newLine
                    + "Weather forecast available only for 48 hours" + newLine
                    + "**************************************************");
        }

        //Prepare time and file variables
        //**********************************************************************
        String nowAsISO = df_iso.format(new Date());
        System.out.println("Start ISO time: " + nowAsISO);
        double timeMarginal = routeDur * 1.2 + 3600;
        String endTimeForFmi = df_iso.format(new Date(startTimeMillis + (intValue(timeMarginal)) * 1000));
        String endTimeForFile = df_iso.format(new Date(startTimeMillis + (intValue(routeDur + 3600)) * 1000));
        System.out.println("End ISO time:   " + endTimeForFmi);
        String fmiParam = new StringBuilder("&starttime=").append(nowAsISO).append("&endtime=")
                .append(endTimeForFmi).toString();
        File weatherDataFileNameFirst = new File("weather" + nowAsISO.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        File weatherDataFileNameLast = new File("weather" + endTimeForFmi.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        File weatherDataFileNameStart = new File(
                "weather" + (df_iso.format(new Date(startTimeMillis))).replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        File weatherDataFileNameEnd = new File("weather" + endTimeForFile.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
        List<stationData> allStations = new ArrayList<>();
        List<stationData> fmiData = new ArrayList<>();
        List<String> savedFileTimes = new ArrayList<>();

        //**********************************************************************
        //Check if we already have the weather data
        //**********************************************************************
        if (!weatherDataFileNameStart.exists() || !weatherDataFileNameEnd.exists()) {
            //FMI query
            //**********************************************************************
            String fmiCities = new StringBuilder(fmiBase).append(fmiKey).append(fmiMiddle).append(fmiQueryCities)
                    .append(fmiParam).toString();
            String fmiObsStations = new StringBuilder(fmiBase).append(fmiKey).append(fmiMiddle)
                    .append(fmiQueryObsStations).append(fmiParam).toString();
            //System.out.println("FMI cities URL: "+fmiCities);
            //System.out.println("FMI obsstations URL: "+fmiObsStations);

            //Collect weather data from FMI
            //**********************************************************************
            System.out.print("FMI data:" + newLine + fmiCities + newLine + "Loading and processing...");
            fmiData.addAll(Parser.getStations(fmiCities));
            System.out.println("SUCCESS.");
            System.out.print("FMI data:" + newLine + fmiObsStations + newLine + "Loading and processing...");
            fmiData.addAll(Parser.getStations(fmiObsStations));
            System.out.println("SUCCESS.");

            //Get unique stations
            //**********************************************************************
            List<stationData> uniqueStations = ToolBox.getUniqueStations(fmiData);
            System.out.println("Parsed stations count: " + uniqueStations.size());

            //Save or load stations
            //**********************************************************************
            List<stationData> savedStations = new ArrayList<>();
            if (!stationFileName.exists()) {
                //Save current parsed stations to file
                FileSystemTools.saveObjectToFile(uniqueStations, stationFileName);
            } else {
                //Or if the stations were already saved, load them
                System.out.println("Station information file found: " + stationFileName);
                System.out.print("Loading...");
                savedStations = FileSystemTools.loadStationsFromFile(stationFileName);
                System.out.println("DONE.");
                System.out.println("Loaded stations count: " + savedStations.size());
            }

            //Merge station information
            //**********************************************************************
            System.out.println("Merging station information.");
            savedStations.addAll(uniqueStations);
            allStations = ToolBox.getUniqueStations(savedStations);
            System.out.println("Merged stations count: " + allStations.size());

            //Find names for stations
            //**********************************************************************
            String gMapsGeoCode = "https://maps.googleapis.com/maps/api/geocode/xml?latlng=";
            //for (stationData station : allStations){
            for (int i = 0; i < allStations.size(); i++) {
                if (allStations.get(i).stationName.equals("")) {
                    gQuery = new StringBuilder(gMapsGeoCode).append(allStations.get(i).stationLocation.Lat)
                            .append(",").append(allStations.get(i).stationLocation.Lon).append("&key=").append(gKey)
                            .toString();
                    System.out.println("Google query URL: " + gQuery);

                    allStations.get(i).stationName = Parser.getStationName(gQuery);
                }
            }
            //System.out.println("Station names parsed.");
            Collections.sort(allStations);

            //Print stations and separate them for saving
            //**********************************************************************
            List<stationData> onlyStations = new ArrayList<>();
            //int indeksi = 0;
            List<weatherData> weatherPoint = new ArrayList<>();
            weatherPoint.add(0, new weatherData("", "", ""));
            for (stationData station : allStations) {
                //System.out.format("%-4s%-30s%-10s%-10s%n",
                //                    indeksi,station.stationName,station.stationLocation.Lat,station.stationLocation.Lon);
                //++indeksi;
                onlyStations.add(new stationData(station.stationLocation, station.stationName, weatherPoint));
            }

            //Save station names
            //**********************************************************************
            System.out.println("Saving station names.");
            FileSystemTools.saveObjectToFile(onlyStations, stationFileName);

            //Save weather dataset
            //**********************************************************************
            //Compute file names between start and end
            System.out.println("Saving weather data...");
            long currentTimeAsDouble = System.currentTimeMillis();
            int hoursPassed = intValue(Math.floor(currentTimeAsDouble - startTimeMillis) / 1000 / 60 / 60);
            File weatherDataFileNameTemp = weatherDataFileNameFirst;
            while (!weatherDataFileNameTemp.equals(weatherDataFileNameLast)) {
                String savedFileTime = df_iso.format(new Date(startTimeMillis + ((hoursPassed * 3600) * 1000)));
                savedFileTimes.add(savedFileTime);
                weatherDataFileNameTemp = new File(
                        "weather" + savedFileTime.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
                //System.out.println("Weather data file: "+weatherDataFileNameTemp);
                //This if we don't actually maybe want
                //if (!weatherDataFileNameTemp.exists()){
                List<stationData> thisHourWeather = FileSystemTools.extractHourOfWeatherData(savedFileTime,
                        fmiData);
                //System.out.println("Saving: "+weatherDataFileNameTemp);
                FileSystemTools.saveObjectToFile(thisHourWeather, weatherDataFileNameTemp);
                //}
                ++hoursPassed;
            }
        }
        //If we have weather data saved, definitely we have the stations also
        //**********************************************************************
        else {
            System.out.println("Loading weather data...");
            File weatherDataFileNameTemp = weatherDataFileNameStart;
            int hoursPassed = 0;
            while (!weatherDataFileNameTemp.equals(weatherDataFileNameEnd)) {
                String savedFileTime = df_iso.format(new Date(startTimeMillis + ((hoursPassed * 3600) * 1000)));
                savedFileTimes.add(savedFileTime);
                weatherDataFileNameTemp = new File(
                        "weather" + savedFileTime.replaceAll("[^A-Za-z0-9 ]", "") + ".txt");
                System.out.println("Weather data file: " + weatherDataFileNameTemp);
                if (weatherDataFileNameTemp.exists()) {
                    fmiData.addAll(FileSystemTools.loadStationsFromFile(weatherDataFileNameTemp));
                }
                ++hoursPassed;
            }
            allStations = FileSystemTools.loadStationsFromFile(stationFileName);
            System.out.println("DONE.");
        }

        //Find closest weather stations in route points and extract their data
        //**********************************************************************
        System.out.println("Calculating nearest stations in route points:");
        List<Integer> neededStations = new ArrayList<>();
        for (routeStep step : routeData) {
            distance[] stationDistances = RouteTools.calculateStationDistances(step.StartLocation, allStations);
            System.out.format("%-6s%.5f, %.5f  ", "Step: ", step.StartLocation.Lat, step.StartLocation.Lon);
            for (int i = 0; i < 1; i++) {
                System.out.format("%-9s%-5s%-20s%.5f%n", "Station: ", stationDistances[i].stationNum,
                        allStations.get(stationDistances[i].stationNum).stationName,
                        stationDistances[i].stationDistance);
            }
            neededStations.add(stationDistances[0].stationNum);
        }
        System.out.println("Needed stations: " + neededStations.toString().trim());
        //Remove duplicates from needed stations list
        Set<Integer> uniqueEntries = new HashSet<Integer>(neededStations);
        //Extract weather data from needed stations
        Map routeWeather = Collections.synchronizedMap(new HashMap());
        routeWeather = WeatherTools.extractNeededStations(uniqueEntries, fmiData, allStations);

        //Find what fields we have
        List<String> allParameters = new ArrayList<>();
        for (int i = 0; i < fmiData.size(); ++i) {
            allParameters.add(fmiData.get(i).weatherData.get(0).parameterName);
        }
        Set<String> uniqueParameters = new HashSet<String>(allParameters);
        for (String par : uniqueParameters) {
            for (Integer num : uniqueEntries) {
                for (String time : savedFileTimes) {
                    //System.out.format("%-5s%-25s%-35s%s%n",num,time,par,routeWeather.get(num+"-"+time+"-"+par));
                }
            }
        }

        // Build the final data table
        //**********************************************************************
        List<stepWeather> stepDataBase = new ArrayList<>();
        stepDataBase = RouteTools.combineRouteDatabase(routeData, neededStations, allStations);

        //Find sunrise and sunset times during the route
        //**********************************************************************
        List<String> sunEvents = DayLightTime.calculateSunEvents(stepDataBase);
        for (String s : sunEvents) {
            System.out.println(s.replaceAll(",", "."));
        }

        //Make a webpage to show the weather data
        //**********************************************************************
        WeatherTools.makeResultHtml(stepDataBase, allStations, routeWeather, sunEvents);
    }
}