How to calculate distance from different markers in a map and then pick up the least one

后端 未结 9 1027
野趣味
野趣味 2020-12-05 08:53

I have to get distance from different markers on the map to the current location of the device and the pick up the shortest one. I have the lat and long for the markers and

9条回答
  •  佛祖请我去吃肉
    2020-12-05 09:40

    I thought it should not be too difficult to extend my KDTree (see my other answer) also to a 3 dimensional version, and here is the result. But as I do not use this version myself so far, take it with care. I added a unit-test, which shows that it works at least for your example.

    /** 3 dimensional implementation of a KDTree for LatLng coordinates. */
    public class KDTree {
    
    private XYZComparator xComparator = new XYZComparator(0);
    private XYZComparator yComparator = new XYZComparator(1);
    private XYZComparator zComparator = new XYZComparator(2);
    private XYZComparator[] comparators = { xComparator, yComparator,
            zComparator };
    
    /**
     * Create a KDTree from a list of lat/lon coordinates. Returns the root-node
     * of the tree.
     */
    public KDTNode createTree(List recList) {
        List xyzList = convertTo3Dimensions(recList);
        return createTreeRecursive(0, xyzList);
    }
    
    /**
     * Traverse the tree and find the point nearest to wp.
     */
    static public LatLng findNearestWp(KDTNode root, LatLng wp) {
        KDTResult result = new KDTResult();
        XYZ xyz = convertTo3Dimensions(wp);
        findNearestWpRecursive(root, xyz, result);
        return result.nearestWp;
    }
    
    /** Convert lat/lon coordinates into a 3 dimensional xyz system. */
    private static XYZ convertTo3Dimensions(LatLng wp) {
        // See e.g.
        // http://stackoverflow.com/questions/8981943/lat-long-to-x-y-z-position-in-js-not-working
        double cosLat = Math.cos(wp.latitude * Math.PI / 180.0);
        double sinLat = Math.sin(wp.latitude * Math.PI / 180.0);
        double cosLon = Math.cos(wp.longitude * Math.PI / 180.0);
        double sinLon = Math.sin(wp.longitude * Math.PI / 180.0);
        double rad = 6378137.0;
        double f = 1.0 / 298.257224;
        double C = 1.0 / Math.sqrt(cosLat * cosLat + (1 - f) * (1 - f) * sinLat
                * sinLat);
        double S = (1.0 - f) * (1.0 - f) * C;
        XYZ result = new XYZ();
        result.x = (rad * C) * cosLat * cosLon;
        result.y = (rad * C) * cosLat * sinLon;
        result.z = (rad * S) * sinLat;
        result.wp = wp;
        return result;
    }
    
    private List convertTo3Dimensions(List recList) {
        List result = new ArrayList();
        for (LatLng latLng : recList) {
            XYZ xyz = convertTo3Dimensions(latLng);
            result.add(xyz);
        }
        return result;
    }
    
    private static void findNearestWpRecursive(KDTNode node, XYZ wp,
            KDTResult result) {
        /* If a leaf node, calculate distance and return. */
        if (node.isLeaf) {
            double xDiff = node.xyz.x - wp.x;
            double yDiff = node.xyz.y - wp.y;
            double zDiff = node.xyz.z - wp.z;
            double squareDist = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff;
            // Replace a previously found nearestDest only if the new one is
            // nearer.
            if (result.nearestWp == null || result.squareDistance > squareDist) {
                result.nearestWp = node.xyz.wp;
                result.squareDistance = squareDist;
            }
            return;
        }
        int devidedByDimension = node.depth % 3;
        boolean goLeft;
        /* Check whether left or right is more promising. */
        if (devidedByDimension == 0) {
            goLeft = wp.x < node.splitValue;
        } else if (devidedByDimension == 1) {
            goLeft = wp.y < node.splitValue;
        } else {
            goLeft = wp.z < node.splitValue;
        }
        KDTNode child = goLeft ? node.left : node.right;
        findNearestWpRecursive(child, wp, result);
        /*
         * Check whether result needs to be checked also against the less
         * promising side.
         */
        if (result.squareDistance > node.minSquareDistance) {
            KDTNode otherChild = goLeft ? node.right : node.left;
            findNearestWpRecursive(otherChild, wp, result);
        }
    
    }
    
    private KDTNode createTreeRecursive(int depth, List recList) {
        KDTNode node = new KDTNode();
        node.depth = depth;
        if (recList.size() == 1) {
            // Leafnode found
            node.isLeaf = true;
            node.xyz = recList.get(0);
            return node;
        }
        int dimension = node.depth % 3;
        sortWayPointListByDimension(recList, dimension);
        List leftList = getHalfOf(recList, true);
        List rightList = getHalfOf(recList, false);
        // Get split point and distance to last left and first right point.
        XYZ lastLeft = leftList.get(leftList.size() - 1);
        XYZ firstRight = rightList.get(0);
        double minDistanceToSplitValue;
        double splitValue;
        if (dimension == 0) {
            minDistanceToSplitValue = (firstRight.x - lastLeft.x) / 2;
            splitValue = lastLeft.x + Math.abs(minDistanceToSplitValue);
        } else if (dimension == 1) {
            minDistanceToSplitValue = (firstRight.y - lastLeft.y) / 2;
            splitValue = lastLeft.y + Math.abs(minDistanceToSplitValue);
        } else {
            minDistanceToSplitValue = (firstRight.z - lastLeft.z) / 2;
            splitValue = lastLeft.z + Math.abs(minDistanceToSplitValue);
        }
        node.splitValue = splitValue;
        node.minSquareDistance = minDistanceToSplitValue
                * minDistanceToSplitValue;
        /** Call next level */
        depth++;
        node.left = createTreeRecursive(depth, leftList);
        node.right = createTreeRecursive(depth, rightList);
        return node;
    }
    
    /**
     * Return a sublist representing the left or right half of a List. Size of
     * recList must be at least 2 !
     * 
     * IMPORTANT !!!!! Note: The original list must not be modified after
     * extracting this sublist, as the returned subList is still backed by the
     * original list.
     */
    List getHalfOf(List xyzList, boolean leftHalf) {
        int mid = xyzList.size() / 2;
        if (leftHalf) {
            return xyzList.subList(0, mid);
        } else {
            return xyzList.subList(mid, xyzList.size());
        }
    }
    
    private void sortWayPointListByDimension(List wayPointList, int sortBy) {
        XYZComparator comparator = comparators[sortBy];
        Collections.sort(wayPointList, comparator);
    }
    
    class XYZComparator implements Comparator {
        private int sortBy;
    
        public XYZComparator(int sortBy) {
            this.sortBy = sortBy;
        }
    
        @Override
        public int compare(XYZ lhs, XYZ rhs) {
            double diff;
            if (sortBy == 0) {
                diff = lhs.x - rhs.x;
            } else if (sortBy == 1) {
                diff = lhs.y - rhs.y;
            } else {
                diff = lhs.z - rhs.z;
            }
            if (diff > 0) {
                return 1;
            } else if (diff < 0) {
                return -1;
            } else {
                return 0;
            }
        }
    
    }
    
    /** 3 Dimensional coordinates of a waypoint. */
    static class XYZ {
        double x;
        double y;
        double z;
        // Keep also the original waypoint
        LatLng wp;
    }
    
    /** Node of the KDTree */
    public static class KDTNode {
    
        KDTNode left;
        KDTNode right;
        boolean isLeaf;
        /** latitude or longitude of the nodes division line. */
        double splitValue;
        /** Distance between division line and first point. */
        double minSquareDistance;
        /**
         * Depth of the node in the tree. Depth 0,3,6.. devides the tree in the
         * x-axis, depth 1,4,7,.. devides the tree in the y-axis and depth
         * 2,5,8... devides the tree in the z axis.
         */
        int depth;
        /** The Waypoint in case the node is a leaf node. */
        XYZ xyz;
    
    }
    
    /** Holds the result of a tree traversal. */
    static class KDTResult {
        LatLng nearestWp;
        // We use the square of the distance to avoid square-root operations.
        double squareDistance;
    }
    }
    

    And here is the unit test:

    public void testSOExample() {
        KDTree tree = new KDTree();
        LatLng Bangalore = new LatLng(12.971599, 77.594563);
        LatLng Delhi = new LatLng(28.635308, 77.224960);
        LatLng Mumbai = new LatLng(19.075984, 72.877656);
        LatLng Chennai = new LatLng(13.052414, 80.250825);
        LatLng Kolkata = new LatLng(22.572646, 88.363895);
        List cities = Arrays.asList(new LatLng[] { Bangalore, Delhi,
                Mumbai, Chennai, Kolkata });
    
        KDTree.KDTNode root = tree.createTree(cities);
        LatLng Hyderabad = new LatLng(17.385044, 78.486671);
        LatLng nearestWp = tree.findNearestWp(root, Hyderabad);
    
        assertEquals(nearestWp, Bangalore);
    }
    

提交回复
热议问题