Index: src/org/openstreetmap/josm/data/coor/EastNorth.java
===================================================================
--- src/org/openstreetmap/josm/data/coor/EastNorth.java	(revision 17380)
+++ src/org/openstreetmap/josm/data/coor/EastNorth.java	(working copy)
@@ -12,7 +12,17 @@
 
     private static final long serialVersionUID = 1L;
 
+    /** Set on valid when the east north is valid */
+    private static final byte IS_VALID = 1;
+    /** Set on valid when east north is not valid */
+    private static final byte IS_INVALID = 2;
     /**
+     * If {@link #IS_VALID}, return {#code true}. If {@link #IS_INVALID}, return {@code false}.
+     * Otherwise, run {@link #updateValid}. Default value is {@code 0}.
+     */
+    private byte valid;
+
+    /**
      * A zero constant
      */
     public static final EastNorth ZERO = new EastNorth(0, 0);
@@ -153,9 +163,20 @@
      * @return true if east and north are different from Double.NaN and not infinite
      */
     public boolean isValid() {
-        return !Double.isNaN(x) && !Double.isNaN(y) && !Double.isInfinite(x) && !Double.isInfinite(y);
+        /* Most of the time, EastNorth should be valid. When it isn't cached, and not IS_INVALID,
+         * update and cache the validity of the east north. */
+        return this.valid == IS_VALID || (this.valid == 0 && updateValid());
     }
 
+    private boolean updateValid() {
+        if (!Double.isNaN(x) && !Double.isNaN(y) && !Double.isInfinite(x) && !Double.isInfinite(y)) {
+            this.valid = IS_VALID;
+        } else {
+            this.valid = IS_INVALID;
+        }
+        return this.valid == IS_VALID;
+    }
+
     /**
      * Returns an EastNorth representing the this EastNorth rotated around
      * a given EastNorth by a given angle
Index: src/org/openstreetmap/josm/data/gpx/GpxDistance.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxDistance.java	(revision 17380)
+++ src/org/openstreetmap/josm/data/gpx/GpxDistance.java	(working copy)
@@ -1,8 +1,15 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.gpx;
 
-import org.openstreetmap.josm.data.osm.Node;
+import java.util.OptionalDouble;
+
+import org.openstreetmap.josm.data.coor.ILatLon;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.BBox;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.projection.Ellipsoid;
+import org.openstreetmap.josm.data.projection.ProjectionRegistry;
+import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
 import org.openstreetmap.josm.tools.Geometry;
 
 /**
@@ -23,9 +30,38 @@
      * @return The shortest distance
      */
     public static double getLowestDistance(OsmPrimitive p, GpxData gpxData) {
-        return gpxData.getTrackPoints()
-                .mapToDouble(tp -> Geometry.getDistance(p, new Node(tp.getCoor())))
-                .filter(x -> x >= 0)
-                .min().orElse(Double.MAX_VALUE);
+        OptionalDouble min = gpxData.getTrackPoints().map(waypoint -> waypoint.getEastNorth(ProjectionRegistry.getProjection()))
+                .mapToDouble(tp -> Geometry.getDistanceSq(p, tp))
+                .filter(x -> x >= 0).min();
+        return min.isPresent() ? Math.sqrt(min.getAsDouble()) : Double.MAX_VALUE;
     }
+
+    /**
+     * Find the distance between a point and a dataset of surveyed points
+     * @param p OsmPrimitive from which to get the lowest distance to a GPX point
+     * @param gpxData Data from which to get the GPX points
+     * @param maxDistance The maximum distance to look (LatLon distance)
+     * @return The shortest distance
+     */
+    public static double getLowestDistance(OsmPrimitive p, GpxData gpxData, double maxDistance) {
+        BBox onlyInside = p.getBBox();
+        Ellipsoid ellipsoid = WGS84Datum.INSTANCE.getEllipsoid();
+        ILatLon centroid = p.getBBox().getCenter();
+        // This probably isn't the best method, but this should work. TODO
+        double dLat = maxDistance / (111132.954 - 559.822 * Math.cos(2 * centroid.lat()) + 1.175 * Math.cos(4 * centroid.lat()));
+        // This is currently ~4x the actual.
+        double dLong = maxDistance / (Math.PI * ellipsoid.a * Math.cos(centroid.lat())
+                / (180 * Math.sqrt(1 - ellipsoid.e2 * Math.pow(Math.sin(centroid.lat()), 2))));
+        dLong = dLong / 4;
+        LatLon lowerRight = new LatLon(onlyInside.getBottomRightLat() - dLat, onlyInside.getBottomRightLon() + dLong);
+        LatLon upperLeft = new LatLon(onlyInside.getTopLeftLat() + dLat, onlyInside.getTopLeftLon() - dLong);
+        onlyInside.add(lowerRight);
+        onlyInside.add(upperLeft);
+        // TODO if GpxData ever implements OsmData, use the searchNodes method.
+        OptionalDouble min = gpxData.getTrackPoints().filter(onlyInside::bounds)
+                .map(waypoint -> waypoint.getEastNorth(ProjectionRegistry.getProjection()))
+                .mapToDouble(tp -> Geometry.getDistanceSq(p, tp))
+                .filter(x -> x >= 0).min();
+        return min.isPresent() ? Math.sqrt(min.getAsDouble()) : Double.MAX_VALUE;
+    }
 }
Index: src/org/openstreetmap/josm/data/osm/BBox.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/BBox.java	(revision 17380)
+++ src/org/openstreetmap/josm/data/osm/BBox.java	(working copy)
@@ -236,10 +236,22 @@
      * Tests, whether the Point {@code c} lies within the bbox.
      * @param c point
      * @return {@code true} if {@code c} lies within the bbox
+     * @deprecated use {@link #bounds(ILatLon)} instead. Casting may be required. This will eventually become unnecessary.
      */
+    @Deprecated
     public boolean bounds(LatLon c) {
+        return bounds((ILatLon) c);
+    }
+
+    /**
+     * Tests, whether the Point {@code c} lies within the bbox.
+     * @param c point
+     * @return {@code true} if {@code c} lies within the bbox
+     * @since xxx
+     */
+    public boolean bounds(ILatLon c) {
         return xmin <= c.lon() && xmax >= c.lon()
-            && ymin <= c.lat() && ymax >= c.lat();
+                && ymin <= c.lat() && ymax >= c.lat();
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
===================================================================
--- src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java	(revision 17380)
+++ src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java	(working copy)
@@ -513,7 +513,9 @@
      * @param env the environment
      * @return the distance between the object and the closest gpx point or {@code Double.MAX_VALUE}
      * @since 14802
+     * @deprecated Use {@link #gpx_distance(Environment, float)} instead. There is a major performance improvement.
      */
+    @Deprecated
     public static double gpx_distance(final Environment env) { // NO_UCD (unused code)
         if (env.osm instanceof OsmPrimitive) {
             return MainApplication.getLayerManager().getAllGpxData().stream()
@@ -524,6 +526,23 @@
     }
 
     /**
+     * Returns the lowest distance between the OSM object and a GPX point
+     * <p>
+     * @param env the environment
+     * @param maxDistance The maximum distance to consider
+     * @return the distance between the object and the closest gpx point or {@code Double.MAX_VALUE}
+     * @since xxx
+     */
+    public static double gpx_distance(final Environment env, final float maxDistance) { // NO_UCD (unused code)
+        if (env.osm instanceof OsmPrimitive) {
+            return MainApplication.getLayerManager().getAllGpxData().stream()
+                    .mapToDouble(gpx -> GpxDistance.getLowestDistance((OsmPrimitive) env.osm, gpx, maxDistance))
+                    .min().orElse(Double.MAX_VALUE);
+        }
+        return Double.MAX_VALUE;
+    }
+
+    /**
      * Determines whether the object has a tag with the given key.
      * @param env the environment
      * @param key the OSM key
Index: src/org/openstreetmap/josm/tools/Geometry.java
===================================================================
--- src/org/openstreetmap/josm/tools/Geometry.java	(revision 17380)
+++ src/org/openstreetmap/josm/tools/Geometry.java	(working copy)
@@ -1303,7 +1303,7 @@
         double lowestDistance = Double.MAX_VALUE;
         TreeSet<T> closest = new TreeSet<>();
         for (T primitive : primitives) {
-            double distance = getDistance(osm, primitive);
+            double distance = getDistanceSq(osm, primitive);
             if (Double.isNaN(distance)) continue;
             int comp = Double.compare(distance, lowestDistance);
             if (comp < 0) {
@@ -1367,7 +1367,7 @@
         double furthestDistance = Double.NEGATIVE_INFINITY;
         TreeSet<T> furthest = new TreeSet<>();
         for (T primitive : primitives) {
-            double distance = getDistance(osm, primitive);
+            double distance = getDistanceSq(osm, primitive);
             if (Double.isNaN(distance)) continue;
             int comp = Double.compare(distance, furthestDistance);
             if (comp > 0) {
@@ -1394,36 +1394,82 @@
      * @since 15035
      */
     public static double getDistance(OsmPrimitive one, OsmPrimitive two) {
+        double distance = getDistanceSq(one, two);
+        return distance != Double.MAX_VALUE && !Double.isNaN(distance) ? Math.sqrt(distance) : Double.NaN;
+    }
+
+    /**
+     * Get the distance squared between different {@link OsmPrimitive}s
+     * @param one The primitive to get the distance from
+     * @param two The primitive to get the distance to
+     * @return The distance squared between the primitives in meters
+     * (or the unit of the current projection, see {@link Projection}).
+     * May return {@link Double#NaN} if one of the primitives is incomplete.
+     *
+     * Note: The complexity is O(n*m), where (n,m) are the number of child
+     * objects the {@link OsmPrimitive}s have.
+     * @since xxx
+     */
+    public static double getDistanceSq(OsmPrimitive one, OsmPrimitive two) {
         double rValue = Double.MAX_VALUE;
         if (one == null || two == null || one.isIncomplete()
                 || two.isIncomplete()) return Double.NaN;
         if (one instanceof Node && two instanceof Node) {
-            rValue = ((Node) one).getCoor().greatCircleDistance(((Node) two).getCoor());
+            rValue = Math.pow(((Node) one).getCoor().greatCircleDistance(((Node) two).getCoor()), 2);
         } else if (one instanceof Node && two instanceof Way) {
-            rValue = getDistanceWayNode((Way) two, (Node) one);
+            rValue = getDistanceWayEastNorthSq((Way) two, ((Node) one).getEastNorth());
         } else if (one instanceof Way && two instanceof Node) {
-            rValue = getDistanceWayNode((Way) one, (Node) two);
+            rValue = getDistanceWayEastNorthSq((Way) one, ((Node) two).getEastNorth());
         } else if (one instanceof Way && two instanceof Way) {
-            rValue = getDistanceWayWay((Way) one, (Way) two);
+            rValue = getDistanceWayWaySq((Way) one, (Way) two);
         } else if (one instanceof Relation && !(two instanceof Relation)) {
             for (OsmPrimitive osmPrimitive: ((Relation) one).getMemberPrimitives()) {
-                double currentDistance = getDistance(osmPrimitive, two);
+                double currentDistance = getDistanceSq(osmPrimitive, two);
                 if (currentDistance < rValue) rValue = currentDistance;
             }
         } else if (!(one instanceof Relation) && two instanceof Relation) {
-            rValue = getDistance(two, one);
+            rValue = getDistanceSq(two, one);
         } else if (one instanceof Relation && two instanceof Relation) {
             for (OsmPrimitive osmPrimitive1 : ((Relation) one).getMemberPrimitives()) {
                 for (OsmPrimitive osmPrimitive2 : ((Relation) two).getMemberPrimitives()) {
-                    double currentDistance = getDistance(osmPrimitive1, osmPrimitive2);
+                    double currentDistance = getDistanceSq(osmPrimitive1, osmPrimitive2);
                     if (currentDistance < rValue) rValue = currentDistance;
                 }
             }
         }
-        return rValue != Double.MAX_VALUE ? rValue : Double.NaN;
+        return rValue;
     }
 
     /**
+     * Get the distance between a {@link OsmPrimitive} and a position
+     * @param one The primitive to get the distance from
+     * @param two The EastNorth to get the distance to
+     * @return The distance between the objects in meters
+     * (or the unit of the current projection, see {@link Projection}).
+     * May return {@link Double#NaN} if one of the objects is incomplete.
+     *
+     * Note: The complexity is O(n*m), where (n,m) are the number of child
+     * objects the {@link OsmPrimitive} have.
+     * @since xxx
+     */
+    public static double getDistanceSq(OsmPrimitive one, EastNorth two) {
+        double rValue = Double.MAX_VALUE;
+        if (one == null || two == null || one.isIncomplete()
+                || !two.isValid()) return Double.NaN;
+        if (one instanceof Node) {
+            rValue = two.distanceSq(((Node) one).getEastNorth());
+        } else if (one instanceof Way) {
+            rValue = getDistanceWayEastNorthSq((Way) one, two);
+        } else if (one instanceof Relation) {
+            for (OsmPrimitive osmPrimitive: ((Relation) one).getMemberPrimitives()) {
+                double currentDistance = getDistanceSq(osmPrimitive, two);
+                if (currentDistance < rValue) rValue = currentDistance;
+            }
+        }
+        return rValue;
+    }
+
+    /**
      * Get the distance between a way and a node
      * @param way The way to get the distance from
      * @param node The node to get the distance to
@@ -1435,20 +1481,35 @@
     public static double getDistanceWayNode(Way way, Node node) {
         if (way == null || node == null || way.getNodesCount() < 2 || !node.isLatLonKnown())
             return Double.NaN;
+        double distanceSq = getDistanceWayEastNorthSq(way, node.getEastNorth());
+        return distanceSq != Double.MAX_VALUE && !Double.isNaN(distanceSq) ? Math.sqrt(distanceSq) : distanceSq;
+    }
 
+    /**
+     * Get the distance between a way and a node squared
+     * @param way The way to get the distance from
+     * @param eastNorth The EastNorth to get the distance to
+     * @return The distance between the {@code way} and the {@code eastnorth} in
+     * meters (or the unit of the current projection, see {@link Projection}).
+     * May return {@link Double#NaN} if the primitives are incomplete.
+     * @since xxx
+     */
+    public static double getDistanceWayEastNorthSq(Way way, EastNorth eastNorth) {
+        if (way == null || eastNorth == null || way.getNodesCount() < 2 || !eastNorth.isValid())
+            return Double.NaN;
+
         double smallest = Double.MAX_VALUE;
-        EastNorth en0 = node.getEastNorth();
         // go through the nodes as if they were paired
         Iterator<Node> iter = way.getNodes().iterator();
         EastNorth en1 = iter.next().getEastNorth();
         while (iter.hasNext()) {
             EastNorth en2 = iter.next().getEastNorth();
-            double distance = getSegmentNodeDistSq(en1, en2, en0);
+            double distance = getSegmentNodeDistSq(en1, en2, eastNorth);
             if (distance < smallest)
                 smallest = distance;
             en1 = en2;
         }
-        return smallest != Double.MAX_VALUE ? Math.sqrt(smallest) : Double.NaN;
+        return smallest != Double.MAX_VALUE ? smallest : Double.NaN;
     }
 
     /**
@@ -1492,6 +1553,22 @@
      * @since 15035
      */
     public static double getDistanceWayWay(Way w1, Way w2) {
+        double distance = getDistanceWayWaySq(w1, w2);
+        return distance != Double.MAX_VALUE && !Double.isNaN(distance) ? Math.sqrt(distance) : Double.NaN;
+    }
+
+    /**
+     *
+     * Get the distance squared between different ways. Iterates over the nodes of the ways, complexity is O(n*m)
+     * (n,m giving the number of nodes)
+     * @param w1 The first {@link Way}
+     * @param w2 The second {@link Way}
+     * @return The shortest distance squared between the ways in meters
+     * (or the unit of the current projection, see {@link Projection}).
+     * May return {@link Double#NaN}.
+     * @since xxx
+     */
+    public static double getDistanceWayWaySq(Way w1, Way w2) {
         if (w1 == null || w2 == null || w1.getNodesCount() < 2 || w2.getNodesCount() < 2)
             return Double.NaN;
         double rValue = Double.MAX_VALUE;
@@ -1503,7 +1580,7 @@
             Node w2N1 = iter2.next();
             while (iter2.hasNext()) {
                 Node w2N2 = iter2.next();
-                double distance = getDistanceSegmentSegment(w1N1, w1N2, w2N1, w2N2);
+                double distance = getDistanceSegmentSegmentSq(w1N1, w1N2, w2N1, w2N2);
                 if (distance < rValue)
                     rValue = distance;
                 w2N1 = w2N2;
@@ -1538,6 +1615,22 @@
      * @since 15035
      */
     public static double getDistanceSegmentSegment(Node ws1Node1, Node ws1Node2, Node ws2Node1, Node ws2Node2) {
+        double distance = getDistanceSegmentSegmentSq(ws1Node1, ws1Node2, ws2Node1, ws2Node2);
+        return distance != Double.MAX_VALUE && !Double.isNaN(distance) ? Math.sqrt(distance) : Double.NaN;
+    }
+
+    /**
+     * Get the distance squared between different {@link WaySegment}s
+     * @param ws1Node1 The first node of the first WaySegment
+     * @param ws1Node2 The second node of the second WaySegment
+     * @param ws2Node1 The first node of the second WaySegment
+     * @param ws2Node2 The second node of the second WaySegment
+     * @return The distance squared between the two {@link WaySegment}s in meters
+     * (or the unit of the current projection, see {@link Projection}).
+     * May return {@link Double#NaN} or {@link Double#MAX_VALUE}.
+     * @since xxx
+     */
+    public static double getDistanceSegmentSegmentSq(Node ws1Node1, Node ws1Node2, Node ws2Node1, Node ws2Node2) {
         EastNorth enWs1Node1 = ws1Node1.getEastNorth();
         EastNorth enWs1Node2 = ws1Node2.getEastNorth();
         EastNorth enWs2Node1 = ws2Node1.getEastNorth();
@@ -1551,8 +1644,7 @@
         double dist2sq = getSegmentNodeDistSq(enWs1Node1, enWs1Node2, enWs2Node2);
         double dist3sq = getSegmentNodeDistSq(enWs2Node1, enWs2Node2, enWs1Node1);
         double dist4sq = getSegmentNodeDistSq(enWs2Node1, enWs2Node2, enWs1Node2);
-        double smallest = Math.min(Math.min(dist1sq, dist2sq), Math.min(dist3sq, dist4sq));
-        return smallest != Double.MAX_VALUE ? Math.sqrt(smallest) : Double.NaN;
+        return Math.min(Math.min(dist1sq, dist2sq), Math.min(dist3sq, dist4sq));
     }
 
     /**
