在半径内随机生成latlng会产生一个超出范围的点

时间:2016-04-28 04:41:03

标签: java android

我试图在半径内生成一个点并且我得到的值不正确。有人介意看一看,告诉我自己在经度上做错了什么?这是一个在不同问题上发布的公式化方法......

  public static Location generateLocationWithinRadius(Location myCurrentLocation) {
    return getLocationInLatLngRad(1000, myCurrentLocation);
}

protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
    double x0 = currentLocation.getLatitude();
    double y0 = currentLocation.getLongitude();

    Random random = new Random();

    // Convert radius from meters to degrees
    double radiusInDegrees = radiusInMeters / 111000f;

    double u = random.nextDouble();
    double v = random.nextDouble();
    double w = radiusInDegrees * Math.sqrt(u);
    double t = 2 * Math.PI * v;
    double x = w * Math.cos(t);
    double y = w * Math.sin(t);

    double new_x = x / Math.cos(y0);
    double new_y = y / Math.cos(x0);
    double foundLatitude;
    double foundLongitude;
    boolean shouldAddOrSubtractLat = random.nextBoolean();
    boolean shouldAddOrSubtractLon = random.nextBoolean();
    if (shouldAddOrSubtractLat) {
        foundLatitude = new_x + x0;
    } else {
        foundLatitude = x0 - new_x;
    }
    if (shouldAddOrSubtractLon) {
        foundLongitude = new_y + y0;
    } else {
        foundLongitude = y0 - new_y;
    }
    Location copy = new Location(currentLocation);
    copy.setLatitude(foundLatitude);
    copy.setLongitude(foundLongitude);
    return copy;
}

我还应该说,出于某种原因,有效点在查看它们时会产生统一的坐标线。

我认为纬度正确处理,而经度则没有。

4 个答案:

答案 0 :(得分:8)

您的代码似乎或多或少基于一个想法 which is presented at gis.stackexchange.com 并在this discussion中讨论了更多内容 并在this discussion

如果我们根据这些讨论仔细研究一下,那么它可能会更有意义。

为了轻松地将值限制为圆,它使用随机化方向和距离的方法。首先,我们得到两个在0.0 ... 1.0之间的随机双精度值:

double u = random.nextDouble();
double v = random.nextDouble();

由于半径以米为单位并且计算需要度数,因此将其转换为:

double radiusInDegrees = radiusInMeters / 111000f;

此处使用赤道的度数与米的比率。 (维基百科建议111320米。)

为了使随机点的分布更均匀,距离用平方根补偿:

w = r * sqrt(u)

否则,中心附近与远离中心的点数量将存在统计偏差。 1的平方根为0,当然为0,所以 将随机双精度的根乘以预期的最大值。 radius始终给出介于0和半径之间的值。

然后另一个随机双倍乘以2 * pi,因为整圆中有2 * pi弧度:

t = 2 * Pi * v

我们现在的角度介于0 ... 2 * pi之间,即0 ... 360度。

然后使用随机距离和随机角度,使用基本三角函数计算随机x和y坐标增量:

x = w * cos(t) 
y = w * sin(t)

[x,y]然后将一些随机距离w指向远离原始坐标的方向t

然后用三角法补偿经度线之间的变化距离(y0是中心的y坐标):

x' = x / cos(y0)

如果y0期望角度为弧度,则cos()以上需要转换为弧度。在Java中它确实。

然后建议将这些增量值添加到原始坐标中。 cossin对于整圆的角度的一半是负的,所以只需添加即可。一些随机点将从格林威治向西,从赤道向南。没有必要随机化 是否应该进行加法或减法。

因此,随机点将位于(x'+x0, y+y0)

我不知道为什么你的代码有:

double new_y = y / Math.cos(x0);

如上所述,我们可以忽略shouldAddOrSubtractLatshouldAddOrSubtractLon

在我看来x指的是从左到右或从西到东的东西。即使经度线从南到北,经度值也会增长。因此,我们使用x作为经度,y作为纬度。

那么剩下的是什么?类似的东西:

protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
    double x0 = currentLocation.getLongitude();
    double y0 = currentLocation.getLatitude();

    Random random = new Random();

    // Convert radius from meters to degrees.
    double radiusInDegrees = radiusInMeters / 111320f;

    // Get a random distance and a random angle.
    double u = random.nextDouble();
    double v = random.nextDouble();
    double w = radiusInDegrees * Math.sqrt(u);
    double t = 2 * Math.PI * v;
    // Get the x and y delta values.
    double x = w * Math.cos(t);
    double y = w * Math.sin(t);

    // Compensate the x value.
    double new_x = x / Math.cos(Math.toRadians(y0));

    double foundLatitude;
    double foundLongitude;

    foundLatitude = y0 + y;
    foundLongitude = x0 + new_x;

    Location copy = new Location(currentLocation);
    copy.setLatitude(foundLatitude);
    copy.setLongitude(foundLongitude);
    return copy;
}

答案 1 :(得分:1)

我很难为您提供纯Android解决方案,因为我从未使用过这些API。但是,我确信您可以轻松地调整此解决方案,以便从现有点生成给定半径内的随机点。

问题在二维空间中得到解决,但也很容易扩展到支撑高度。

请查看下面的代码。它为您提供LocationGenerator以及我自己的Location实施方案以及证明其有效的单元测试。

我的解决方案基于求解圆方程(x-a)^2 + (y-b)^2 = r^2

package my.test.pkg;

import org.junit.Test;

import java.util.Random;

import static org.junit.Assert.assertTrue;

public class LocationGeneratorTest {
    private class Location {
        double longitude;
        double latitude;

        public Location(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }
    }

    private class LocationGenerator {
        private final Random random = new Random();

        Location generateLocationWithinRadius(Location currentLocation, double radius) {
            double a = currentLocation.longitude;
            double b = currentLocation.latitude;
            double r = radius;

            // x must be in (a-r, a + r) range
            double xMin = a - r;
            double xMax = a + r;
            double xRange = xMax - xMin;

            // get a random x within the range
            double x = xMin + random.nextDouble() * xRange;

            // circle equation is (y-b)^2 + (x-a)^2 = r^2
            // based on the above work out the range for y
            double yDelta = Math.sqrt(Math.pow(r,  2) - Math.pow((x - a), 2));
            double yMax = b + yDelta;
            double yMin = b - yDelta;
            double yRange = yMax - yMin;
            // Get a random y within its range
            double y = yMin + random.nextDouble() * yRange;

            // And finally return the location
            return new Location(x, y);
        }
    }

    @Test
    public void shoulRandomlyGeneratePointWithinRadius () throws Exception {
        LocationGenerator locationGenerator = new LocationGenerator();
        Location currentLocation = new Location(20., 10.);
        double radius = 5.;
        for (int i=0; i < 1000000; i++) {
            Location randomLocation = locationGenerator.generateLocationWithinRadius(currentLocation, radius);
            try {
                assertTrue(Math.pow(randomLocation.latitude - currentLocation.latitude, 2) + Math.pow(randomLocation.longitude - currentLocation.longitude, 2) < Math.pow(radius, 2));
            } catch (Throwable e) {
                System.out.println("i= " + i + ", x=" + randomLocation.longitude + ", y=" + randomLocation.latitude);
                throw new Exception(e);
            }
        }

    }
}

注意: 这只是一个通用的解决方案,用于获得圆内的随机点,其中心位于(a,b),半径为r,可用于解决您的问题,而不是您可以使用的直接解决方案。您很可能需要根据您的使用情况进行调整。

我相信这是一个自然的解决方案。

此致

答案 2 :(得分:1)

Markus Kauppinen答案的科特琳版

fun Location.getRandomLocation(radius: Double): Location {
    val x0: Double = longitude
    val y0: Double = latitude

    // Convert radius from meters to degrees.

    // Convert radius from meters to degrees.
    val radiusInDegrees: Double = radius / 111320f

    // Get a random distance and a random angle.

    // Get a random distance and a random angle.
    val u = Random.nextDouble()
    val v = Random.nextDouble()
    val w = radiusInDegrees * sqrt(u)
    val t = 2 * Math.PI * v
    // Get the x and y delta values.
    // Get the x and y delta values.
    val x = w * cos(t)
    val y = w * sin(t)

    // Compensate the x value.

    // Compensate the x value.
    val newX = x / cos(Math.toRadians(y0))

    val foundLatitude: Double
    val foundLongitude: Double

    foundLatitude = y0 + y
    foundLongitude = x0 + newX

    val copy = Location(this)
    copy.latitude = foundLatitude
    copy.longitude = foundLongitude
    return copy
}

答案 3 :(得分:0)

经度和纬度使用椭圆坐标,因此对于大半径(百米),使用此方法的误差将变得重要。一个可能的技巧是转换为笛卡尔坐标,进行半径随机化,然后再转换回long-lat的椭圆坐标。我使用来自ibm的this java library测试了几公里并取得了巨大的成功。比这更长的可能会起作用,但最终半径会随着地球显示其球形性质而下降。