001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.gpx;
003
004import java.util.ArrayList;
005import java.util.List;
006
007import org.openstreetmap.josm.data.coor.EastNorth;
008import org.openstreetmap.josm.data.coor.LatLon;
009import org.openstreetmap.josm.data.osm.Node;
010import org.openstreetmap.josm.data.osm.OsmPrimitive;
011import org.openstreetmap.josm.data.osm.Relation;
012import org.openstreetmap.josm.data.osm.Way;
013import org.openstreetmap.josm.tools.Geometry;
014
015/**
016 * A class to find the distance between an {@link OsmPrimitive} and a GPX point.
017 *
018 * @author Taylor Smock
019 * @since 14802
020 */
021public final class GpxDistance {
022    private GpxDistance() {
023        // This class should not be instantiated
024    }
025
026    /**
027     * Find the distance between a point and a dataset of surveyed points
028     * @param p OsmPrimitive from which to get the lowest distance to a GPX point
029     * @param gpxData Data from which to get the GPX points
030     * @return The shortest distance
031     */
032    public static double getLowestDistance(OsmPrimitive p, GpxData gpxData) {
033        return gpxData.getTrackPoints()
034                .mapToDouble(tp -> getDistance(p, tp))
035                .filter(x -> x >= 0)
036                .min().orElse(Double.MAX_VALUE);
037    }
038
039    /**
040     * Get the distance between an object and a waypoint
041     * @param p OsmPrimitive to get the distance to the WayPoint
042     * @param waypoint WayPoint to get the distance from
043     * @return The shortest distance between p and waypoint
044     */
045    public static double getDistance(OsmPrimitive p, WayPoint waypoint) {
046        if (p instanceof Node) {
047            return getDistanceNode((Node) p, waypoint);
048        } else if (p instanceof Way) {
049            return getDistanceWay((Way) p, waypoint);
050        } else if (p instanceof Relation) {
051            return getDistanceRelation((Relation) p, waypoint);
052        }
053        return Double.MAX_VALUE;
054    }
055
056    /**
057     * Get the shortest distance between a relation and a waypoint
058     * @param relation Relation to get the distance from
059     * @param waypoint WayPoint to get the distance to
060     * @return The distance between the relation and the waypoint
061     */
062    public static double getDistanceRelation(Relation relation, WayPoint waypoint) {
063        double shortestDistance = Double.MAX_VALUE;
064        List<Node> nodes = new ArrayList<>(relation.getMemberPrimitives(Node.class));
065        List<Way> ways = new ArrayList<>(relation.getMemberPrimitives(Way.class));
066        List<Relation> relations = new ArrayList<>(relation.getMemberPrimitives(Relation.class));
067        if (nodes.isEmpty() && ways.isEmpty() && relations.isEmpty()) return Double.MAX_VALUE;
068        for (Relation nrelation : relations) {
069            double distance = getDistanceRelation(nrelation, waypoint);
070            if (distance < shortestDistance) shortestDistance = distance;
071        }
072        for (Way way : ways) {
073            double distance = getDistanceWay(way, waypoint);
074            if (distance < shortestDistance) shortestDistance = distance;
075        }
076        for (Node node : nodes) {
077            double distance = getDistanceNode(node, waypoint);
078            if (distance < shortestDistance) shortestDistance = distance;
079        }
080        return shortestDistance;
081    }
082
083    /**
084     * Get the shortest distance between a way and a waypoint
085     * @param way Way to get the distance from
086     * @param waypoint WayPoint to get the distance to
087     * @return The distance between the way and the waypoint
088     */
089    public static double getDistanceWay(Way way, WayPoint waypoint) {
090        double shortestDistance = Double.MAX_VALUE;
091        if (way == null || waypoint == null) return shortestDistance;
092        LatLon llwaypoint = waypoint.getCoor();
093        EastNorth enwaypoint = new EastNorth(llwaypoint.getY(), llwaypoint.getX());
094        for (int i = 0; i < way.getNodesCount() - 1; i++) {
095            double distance = Double.MAX_VALUE;
096            LatLon llfirst = way.getNode(i).getCoor();
097            LatLon llsecond = way.getNode(i + 1).getCoor();
098            EastNorth first = new EastNorth(llfirst.getY(), llfirst.getX());
099            EastNorth second = new EastNorth(llsecond.getY(), llsecond.getX());
100            if (first.isValid() && second.isValid()) {
101                EastNorth closestPoint = Geometry.closestPointToSegment(first, second, enwaypoint);
102                distance = llwaypoint.greatCircleDistance(new LatLon(closestPoint.getX(), closestPoint.getY()));
103            } else if (first.isValid() && !second.isValid()) {
104                distance = getDistanceEastNorth(first, waypoint);
105            } else if (!first.isValid() && second.isValid()) {
106                distance = getDistanceEastNorth(second, waypoint);
107            } else if (!first.isValid() && !second.isValid()) {
108                distance = Double.MAX_VALUE;
109            }
110            if (distance < shortestDistance) shortestDistance = distance;
111
112        }
113        return shortestDistance;
114    }
115
116    /**
117     * Get the distance between a node and a waypoint
118     * @param node Node to get the distance from
119     * @param waypoint WayPoint to get the distance to
120     * @return The distance between the two points
121     */
122    public static double getDistanceNode(Node node, WayPoint waypoint) {
123        if (node == null) return Double.MAX_VALUE;
124        return getDistanceLatLon(node.getCoor(), waypoint);
125    }
126
127    /**
128     * Get the distance between coordinates (provided by EastNorth) and a waypoint
129     * @param en The EastNorth to get the distance to
130     * @param waypoint WayPoint to get the distance to
131     * @return The distance between the two points
132     */
133    public static double getDistanceEastNorth(EastNorth en, WayPoint waypoint) {
134        if (en == null || !en.isValid()) return Double.MAX_VALUE;
135        return getDistanceLatLon(new LatLon(en.getY(), en.getX()), waypoint);
136    }
137
138    /**
139     * Get the distance between coordinates (latitude longitude) and a waypoint
140     * @param latlon LatLon to get the distance from
141     * @param waypoint WayPoint to get the distance to
142     * @return The distance between the two points
143     */
144    public static double getDistanceLatLon(LatLon latlon, WayPoint waypoint) {
145        if (latlon == null || waypoint == null || waypoint.getCoor() == null) return Double.MAX_VALUE;
146        return waypoint.getCoor().greatCircleDistance(latlon);
147    }
148}