All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.gpx.GpxConversions Maven / Gradle / Ivy

There is a newer version: 10.0
Show newest version
/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.graphhopper.gpx;

import com.graphhopper.jackson.Gpx;
import com.graphhopper.matching.Observation;
import com.graphhopper.util.*;
import com.graphhopper.util.shapes.GHPoint;
import com.graphhopper.util.shapes.GHPoint3D;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;

public class GpxConversions {

    private static final AngleCalc AC = AngleCalc.ANGLE_CALC;
    private static final Pattern XML_ESCAPE_PATTERN = Pattern.compile("[\\<\\>]");

    static String simpleXMLEscape(String str) {
        // We could even use the 'more flexible' CDATA section but for now do the following:
        // The 'and' could be important sometimes but remove others
        return XML_ESCAPE_PATTERN.matcher(str.replace("&", "&")).replaceAll("_");
    }

    public static List createGPXList(InstructionList instructions) {
        List gpxList = new ArrayList<>();
        long timeOffset = 0;
        for (Instruction instruction : instructions) {
            int i = 0;
            for (GHPoint3D point : instruction.getPoints()) {
                GPXEntry gpxEntry;
                if (i == 0) {
                    gpxEntry = new GPXEntry(point, timeOffset);
                } else {
                    // We don't have timestamps for pillar nodes
                    gpxEntry = new GPXEntry(point);
                }
                gpxList.add(gpxEntry);
                i++;
            }
            timeOffset = timeOffset + instruction.getTime();
        }
        return gpxList;
    }

    private static void createWayPointBlock(StringBuilder output, Instruction instruction, DecimalFormat decimalFormat, Translation tr) {
        output.append("\n");
        String name;
        if (instruction.getName().isEmpty())
            name = instruction.getTurnDescription(tr);
        else
            name = instruction.getName();

        output.append(" ").append(simpleXMLEscape(name)).append("");
        output.append("");
    }

    public static String createGPX(InstructionList instructions, String trackName, long startTimeMillis, boolean includeElevation, boolean withRoute, boolean withTrack, boolean withWayPoints, String version, Translation tr) {
        DateFormat formatter = Helper.createFormatter();

        DecimalFormat decimalFormat = new DecimalFormat("#", DecimalFormatSymbols.getInstance(Locale.ROOT));
        decimalFormat.setMinimumFractionDigits(1);
        decimalFormat.setMaximumFractionDigits(6);
        decimalFormat.setMinimumIntegerDigits(1);

        String header = ""
                + ""
                + "\n"
                + ""
                + ""
                + "GraphHopper GPX"
                + ""
                + ""
                + "";
        StringBuilder gpxOutput = new StringBuilder(header);
        if (!instructions.isEmpty()) {
            if (withWayPoints) {
                createWayPointBlock(gpxOutput, instructions.get(0), decimalFormat, tr);   // Start
                for (Instruction currInstr : instructions) {
                    if ((currInstr.getSign() == Instruction.REACHED_VIA) // Via
                            || (currInstr.getSign() == Instruction.FINISH)) // End
                    {
                        createWayPointBlock(gpxOutput, currInstr, decimalFormat, tr);
                    }
                }
            }
            if (withRoute) {
                gpxOutput.append("\n");
                Instruction nextInstr = null;
                for (Instruction currInstr : instructions) {
                    if (null != nextInstr)
                        createRteptBlock(gpxOutput, nextInstr, currInstr, decimalFormat, tr);

                    nextInstr = currInstr;
                }
                createRteptBlock(gpxOutput, nextInstr, null, decimalFormat, tr);
                gpxOutput.append("\n");
            }
        }
        if (withTrack) {
            gpxOutput.append("\n").append(trackName).append("");

            gpxOutput.append("");
            for (GPXEntry entry : createGPXList(instructions)) {
                gpxOutput.append("\n");
                if (includeElevation)
                    gpxOutput.append("").append(Helper.round2(((GHPoint3D) entry.getPoint()).getEle())).append("");
                if (entry.getTime() != null)
                    gpxOutput.append("");
                gpxOutput.append("");
            }
            gpxOutput.append("\n");
            gpxOutput.append("\n");
        }

        // we could now use 'wpt' for via points
        gpxOutput.append("\n");
        return gpxOutput.toString();
    }

    private static void createRteptBlock(StringBuilder output, Instruction instruction, Instruction nextI, DecimalFormat decimalFormat, Translation tr) {
        output.append("\n");

        if (!instruction.getName().isEmpty())
            output.append("").append(simpleXMLEscape(instruction.getTurnDescription(tr))).append("");

        output.append("");
        output.append("").append(Helper.round(instruction.getDistance(), 1)).append("");
        output.append("").append(instruction.getTime()).append("");

        String direction = calcDirection(instruction, nextI);
        if (!direction.isEmpty())
            output.append("").append(direction).append("");

        double azimuth = calcAzimuth(instruction, nextI);
        if (!Double.isNaN(azimuth))
            output.append("").append(Helper.round2(azimuth)).append("");

        if (instruction instanceof RoundaboutInstruction) {
            RoundaboutInstruction ri = (RoundaboutInstruction) instruction;

            output.append("").append(ri.getExitNumber()).append("");
        }

        output.append("").append(instruction.getSign()).append("");
        output.append("");
        output.append("");
    }

    /**
     * Return the direction like 'NE' based on the first tracksegment of the instruction. If
     * Instruction does not contain enough coordinate points, an empty string will be returned.
     */
    public static String calcDirection(Instruction instruction, Instruction nextI) {
        double azimuth = calcAzimuth(instruction, nextI);
        if (Double.isNaN(azimuth))
            return "";

        return AC.azimuth2compassPoint(azimuth);
    }

    /**
     * Return the azimuth in degree based on the first tracksegment of this instruction. If this
     * instruction contains less than 2 points then NaN will be returned or the specified
     * instruction will be used if that is the finish instruction.
     */
    public static double calcAzimuth(Instruction instruction, Instruction nextI) {
        double nextLat;
        double nextLon;

        if (instruction.getPoints().size() >= 2) {
            nextLat = instruction.getPoints().getLat(1);
            nextLon = instruction.getPoints().getLon(1);
        } else if (nextI != null && instruction.getPoints().size() == 1) {
            nextLat = nextI.getPoints().getLat(0);
            nextLon = nextI.getPoints().getLon(0);
        } else {
            return Double.NaN;
        }

        double lat = instruction.getPoints().getLat(0);
        double lon = instruction.getPoints().getLon(0);
        return AC.calcAzimuth(lat, lon, nextLat, nextLon);
    }

    public static List getEntries(Gpx.Trk trk) {
        ArrayList gpxEntries = new ArrayList<>();
        for (Gpx.Trkseg t : trk.trkseg) {
            for (Gpx.Trkpt trkpt : t.trkpt) {
                gpxEntries.add(new Observation(new GHPoint3D(trkpt.lat, trkpt.lon, trkpt.ele)));
            }
        }
        return gpxEntries;
    }

    /**
     * @author Peter Karich
     */
    public static class GPXEntry {
        private GHPoint point;
        private Long time;

        public GPXEntry(GHPoint p) {
            this.point = p;
        }

        public GPXEntry(GHPoint p, long time) {
            this.point = p;
            this.time = time;
        }

        public Long getTime() {
            return time;
        }

        public GHPoint getPoint() {
            return point;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            GPXEntry gpxEntry = (GPXEntry) o;
            return Objects.equals(point, gpxEntry.point) &&
                    Objects.equals(time, gpxEntry.time);
        }

        @Override
        public int hashCode() {
            return Objects.hash(point, time);
        }

        @Override
        public String toString() {
            return "GPXEntry{" +
                    "point=" + point +
                    ", time=" + time +
                    '}';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy