简体   繁体   中英

Map locations to their nearest location based on latitude and longitude

I have two set of locations say A(20K locations) and B(2k locations). I want to have a map for all locations in set A to their closest location in set B based on their latitude and longitude.

Is there any solution in java or R. Java solution preferred.

As from the answer from @rosscova

Take the points, but as a matrix

pointsA <- as.matrix(data.frame( lat = c( 10, 12, 20 ), lon = c( 12, 17, 10 ) ))
pointsB <- as.matrix(data.frame( lat = c( 11, 15 ), lon = c( 15, 15 ) ))

Then, when dealing with coordinates, you may prefer to use the Great Circle (WGS84 ellipsoid) distance instead of the euclidean. I usually use the spDists function from the sp package

library( sp )
out_Dists <- spDists(x = pointsA, y = pointsB, longlat = TRUE, segments = FALSE, diagonal = FALSE)

and at the end using the apply function with the which.min over the rows to get the nearest pointB to pointA

pointsA[ apply(out_Dists, 1, which.min), ]

I agree with @STaefi in the comments, it's a good idea to show at least a little bit of work on your part before asking others to write code for you. Having said that, your problem has piqued my interest enough to grab a few minutes of my time, so here's a method in R . Note though that this is only considering distance as a sqrt(a^2+b^2) function of the coordinates, which won't be accurate anywhere except at the equator. You'd need to modify this to suit if you need better accuracy there.

Take a small hypothetical dataset:

pointsA <- data.frame( lat = c( 10, 12, 20 ), lon = c( 12, 17, 10 ) )
pointsB <- data.frame( lat = c( 11, 15 ), lon = c( 15, 15 ) )

Write a function for computing the closest point:

closest_point <- function( lat1, lon1, lat2, lon2 ) {
    x_dist <- abs( lon1 - lon2 )
    y_dist <- abs( lat1 - lat2 )
    dist <- sqrt( x_dist ^ 2 + y_dist ^ 2 )
    closest <- data.frame( lat = lat2[ which.min( dist ) ],
                           lon = lon2[ which.min( dist ) ] )
    return( closest )
}

We can execute that function on a single point from pointsA , returning the closest point from pointsB :

closest_point( pointsA[1,"lat"], pointsA[1,"lon"], pointsB[,"lat"], pointsB[,"lon"] )
#   lat lon
# 1  11  15

Or, we can apply it to ALL points from pointsA , returning the closest point from pointsB for each of the points in pointsA

closest.points <- lapply( seq_len( nrow( pointsA ) ),
                          function(x) {
                              closest_point( lat1 = pointsA[x,"lat"],
                                             lon1 = pointsA[x,"lon"],
                                             lat2 = pointsB[,"lat"],
                                             lon2 = pointsB[,"lon"] )
                          } )
closest.points <- do.call( rbind, closest.points )
closest.points
#   lat lon
# 1  11  15
# 2  11  15
# 3  15  15

Here's one in Java. The math is simplified by converting the latitude and longitude to a normalised vector representation, and noting that the closer the two vectors are the larger their dot product will be (approaching one when they are equal).

This assumes that earth is spherical. If you want "perfect" results you'll need to convert your coordinates using a closer approximation (WGS84, for example).

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Math.*;

public class LatLong {
    public static void main(String[] args) throws Exception {
        // A random lat/long
        double[] target = randLatLong();
        // Transform to ECEF vector
        double[] targetv = toEcef(target);
        // 2000 random candidates
        List<double[]> b = Stream.generate(LatLong::randLatLong).limit(2000).collect(Collectors.toList());
        // Transform candidates to ECEF representation
        List<double[]> bv = b.stream().map(LatLong::toEcef).collect(Collectors.toList());

        // Find the closest candidate to the target
        int i = closest(targetv, bv);

        System.out.println("Closest point to " + target[0] + ", " + target[1] + " is " + b.get(i)[0] + ", " + b.get(i)[1]);
    }

    // index of closest vector to target from list of candidates
    public static int closest(double[] target, List<double[]> candidates) {
        double p = Double.MIN_VALUE;
        int closest = -1;
        for (int i = 0; i < candidates.size(); i++) {
            double next = dotProduct(target, candidates.get(i));
            if (next > p) {
                p = next;
                closest = i;
            }
        }
        return closest;
    }

    // dot product of two 3vectors
    public static double dotProduct(double[] v1, double[] v2) {
        return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
    }

    // lat/long in degrees to normalised ECEF vector
    public static double[] toEcef(double[] latLong) {
        return toEcef(toRadians(latLong[0]), toRadians(latLong[1]));
    }

    // lat/long in radians to normalised ECEF vector
    public static double[] toEcef(double φ, double λ) {
        return new double[] {cos(φ) * cos(λ), cos(φ) * sin(λ), sin(φ)};
    }

    // A random lat/long
    public static double[] randLatLong() {
        return new double[] {Math.random() * 180 - 90, Math.random() * 360 - 180};
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM