Создание случайных мест поблизости?


30

Я пытаюсь создать случайные места рядом с моим местоположением. Я хочу создать случайные пары широта / долгота внутри 200-метрового круга, окружающего мое местоположение.

Вот формула, которую я придумал (с помощью людей из StackOverFlow): (Случайное число от -1 до 1) * radius + (старая долгота) = новая долгота в радиусе старой долготы

(Случайное число от -1 до 1) * радиус + (старая широта) = новая широта в пределах радиуса старой широты

Дело в том, что с моей реализацией происходит что-то странное, потому что все случайные местоположения находятся слишком близко от моего центра местоположения, кажется, что формула не охватывает весь радиус.

Есть идеи, что может быть не так с моей формулой?

Отредактировано, чтобы показать текущую реализацию Java:

public static Location getLocation(Location location, int radius) {
    Random random = new Random();

    // Convert radius from meters to degrees
    double radiusInDegrees = radius / METERS_IN_DEGREES;

    double x0 = location.getLongitude() * 1E6;
    double y0 = location.getLatitude() * 1E6;
    double u = random.nextInt(1001) / 1000;
    double v = random.nextInt(1001) / 1000;
    double w = radiusInDegrees * Math.sqrt(u);
    double t = 2 * Math.PI * v;
    double x = w * Math.cos(t);
    double y = w * Math.sin(t);

    // Adjust the x-coordinate for the shrinking of the east-west distances
    double new_x = x / Math.cos(y0);

    // Set the adjusted location
    Location newLocation = new Location("Loc in radius");
    newLocation.setLongitude(new_x + x0);
    newLocation.setLatitude(y + y0);

    return newLocation;
}

Я не уверен, что я делаю неправильно, потому что новые места создаются посреди моря.

Есть идеи?


Как вы реализуете эту формулу? Можете ли вы представить эту часть своего кода? Может быть ваша проблема в генераторе псевдослучайных чисел ?
Алекс Марков

Что касается последнего вопроса, такие процедуры сталкиваются с такими проблемами, потому что (i) расстояния неправильно преобразуются в градусы широты или долготы и (ii) метрическое искажение системы координат не учитывается или учитывается неправильно. Использование проецированной системы координат вместо географической системы координат обычно решает обе эти проблемы. Выполнение этого откроет фундаментальное свойство вашей формулы, которое может быть или не быть желательным: оно создает местоположения внутри прямоугольника вокруг местоположения, а не внутри круга.
whuber

Спасибо Алекс, java-код размещен на stackoverflow: stackoverflow.com/questions/10682743/…
pindleskin

Повторно отредактированный код: (i) random.nextInt(1001)/1000вернет значение больше 1 примерно в 0,1% времени. Почему вы не используете random.nextDoubleили random.nextFloat? (б) Умножение x0и y0на 1E6весьма таинственное; не похоже, что это даст правильные результаты.
whuber

Правда, я отредактировал метод с помощью nextDouble и избавился от 1E6. Теперь все случайно сгенерированные местоположения имеют те же координаты, что и мое местоположение. Спасибо за помощь, кажется, что я собираюсь решить ее в ближайшее время
pindleskin

Ответы:


46

Это сложно по двум причинам: во-первых, ограничение точек на круг вместо квадрата; во-вторых, учет искажений в расчетах расстояния.

Многие ГИС включают возможности, которые автоматически и прозрачно справляются с обоими сложностями. Однако теги здесь предполагают, что ГИС-независимое описание алгоритма может быть желательным.

  1. Чтобы сгенерировать точки равномерно, случайно и независимо внутри круга радиуса r вокруг местоположения (x0, y0), начните с генерирования двух независимых равномерных случайных значений u и v в интервале [0, 1). (Это то, что почти каждый генератор случайных чисел предоставляет вам.) Вычислить

    w = r * sqrt(u)
    t = 2 * Pi * v
    x = w * cos(t) 
    y = w * sin(t)
    

    Желаемая случайная точка находится в местоположении (x + x0, y + y0).

  2. При использовании географических ( широта, долгота ) координат тогда x0 (долгота) и y0 (широта) будут в градусах, но r , скорее всего, будет в метрах (или футах или милях или каком-либо другом линейном измерении). Сначала преобразуйте радиус r в градусы, как если бы вы находились рядом с экватором. Здесь около 111 300 метров в градусе.

    Во-вторых, после генерации x и y, как в шаге (1), отрегулируйте x-координату для сокращения расстояний восток-запад:

    x' = x / cos(y0)

    Желаемая случайная точка находится в местоположении (x '+ x0, y + y0). Это приблизительная процедура. Для малых радиусов (менее нескольких сотен километров), которые не распространяются ни на один из полюсов Земли, обычно это будет настолько точно, что вы не сможете обнаружить какую-либо ошибку даже при генерации десятков тысяч случайных точек вокруг каждого центра (x0, y0) ,


2
Отличное объяснение, вот что мне нужно было знать. Теперь я собираюсь реализовать это. Спасибо
pindleskin

1
Я отредактировал вопрос, чтобы показать некоторую реализацию формулы в java
pindleskin

1
«около 111 300 метров в градусе», просто обратите внимание, запятая используется как разделитель тысяч. radiusInDegrees = radius / 111300
RMalke

2
для широты длинные координаты не следует делать x '= x / cos (y0 * Pi / 180)
Аарон Стейнбэк

2
Сумасшедший @whuber, и это имеет смысл. Другой способ взглянуть на это, я думаю, состоит в том, чтобы представить 55 случайных радиусов, генерируемых для радиуса 20. Допустим, каждый случайный радиус является равномерным и в точности равным 0-20, так что 0, 2, 4, ..., 20 Таким образом, будет 5 точек с радиусами, 5 радиусов из 2 и т. Д. 5 точек с радиусами 2 (вокруг круга радиуса 2) будут выглядеть НАМНОГО ближе друг к другу, чем 5 точек с радиусами. из 20.
Азиз Джавед

11

Реализовано для Javascript:

var r = 100/111300 // = 100 meters
  , y0 = original_lat
  , x0 = original_lng
  , u = Math.random()
  , v = Math.random()
  , w = r * Math.sqrt(u)
  , t = 2 * Math.PI * v
  , x = w * Math.cos(t)
  , y1 = w * Math.sin(t)
  , x1 = x / Math.cos(y0)

newY = y0 + y1
newX = x0 + x1

10

Правильная реализация:

public static void getLocation(double x0, double y0, int radius) {
    Random random = new Random();

    // Convert radius from meters to degrees
    double radiusInDegrees = radius / 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);

    // Adjust the x-coordinate for the shrinking of the east-west distances
    double new_x = x / Math.cos(Math.toRadians(y0));

    double foundLongitude = new_x + x0;
    double foundLatitude = y + y0;
    System.out.println("Longitude: " + foundLongitude + "  Latitude: " + foundLatitude );
}

Я удалил зависимость от внешних библиотек, чтобы сделать ее более доступной.


Предлагаемое редактирование OP В соответствии с этим Q & A в стеке потока в Java Math.cos () ожидает ввод в радианах.
MikeJRamsey56

3

Принятый ответ и производные не работали для меня. Результаты были очень неточными.

Правильная реализация в javascript:

function pointAtDistance(inputCoords, distance) {
    const result = {}
    const coords = toRadians(inputCoords)
    const sinLat =  Math.sin(coords.latitude)
    const cosLat =  Math.cos(coords.latitude)

    /* go a fixed distance in a random direction*/
    const bearing = Math.random() * TWO_PI
    const theta = distance/EARTH_RADIUS
    const sinBearing = Math.sin(bearing)
    const cosBearing =  Math.cos(bearing)
    const sinTheta = Math.sin(theta)
    const cosTheta =    Math.cos(theta)

    result.latitude = Math.asin(sinLat*cosTheta+cosLat*sinTheta*cosBearing);
    result.longitude = coords.longitude + 
        Math.atan2( sinBearing*sinTheta*cosLat, cosTheta-sinLat*Math.sin(result.latitude )
    );
    /* normalize -PI -> +PI radians (-180 - 180 deg)*/
    result.longitude = ((result.longitude+THREE_PI)%TWO_PI)-Math.PI

    return toDegrees(result)
}

function pointInCircle(coord, distance) {
    const rnd =  Math.random()
    /*use square root of random number to avoid high density at the center*/
    const randomDist = Math.sqrt(rnd) * distance
    return pointAtDistance(coord, randomDist)
}

Полная суть здесь

В принятом ответе - я обнаружил, что точки распределены по эллипсу шириной в 1,5 раза больше его высоты (в Панаме) и в 8 раз выше его высоты (на севере Швеции). Если я удалил корректировку координаты х из ответа @ whuber, то эллипс искажается в другую сторону, в 8 раз больше его ширины.

Код в моем ответе был основан на алгоритмах отсюда

Ниже вы можете увидеть два jsfiddles, которые показывают проблему с растягивающимся эллипсом

Правильный алгоритм

Искаженный алгоритм


Ваше описание проблем, с которыми вы столкнулись, говорит о том, что ваша реализация была неверной.
whuber

Вы вполне можете быть правы. Не могли бы вы взглянуть на сделанные мной jsfiddles и сказать, где я ошибся.
Джулиан Манн

Я по сравнению с Java ответ Аток в выше, и сделал это изменение в вашей jsfiddle из искажается алгоритма whuberPointAtDistance(): x1 = (w * Math.cos(t)) / Math.cos(y0 * (Math.PI / 180)).
Мэтт

1
Несмотря на мою коррекцию, у меня были гораздо более точные результаты с Джулианом. Добавив мои исправления в whuberPointAtDistance () и запустив их в суть с отчетом об ошибках, он показал ошибку 0,05% во всех трех сценариях (значительно выше, чем в альтернативном.)
Мэтт

1

В питоне

# Testing simlation of generating random points 
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA

def create_random_point(x0,y0,distance):
    """
            Utility method for simulation of the points
    """   
    r = distance/ 111300
    u = np.random.uniform(0,1)
    v = np.random.uniform(0,1)
    w = r * np.sqrt(u)
    t = 2 * np.pi * v
    x = w * np.cos(t)
    x1 = x / np.cos(y0)
    y = w * np.sin(t)
    return (x0+x1, y0 +y)

fig = plt.figure()
ax = host_subplot(111, axes_class=AA.Axes)

#ax.set_ylim(76,78)
#ax.set_xlim(13,13.1)
ax.set_autoscale_on(True)

latitude1,longitude1 = 13.04738626,77.61946793  
ax.plot(latitude1,longitude1,'ro')

for i in range(1,20):
    x,y = create_random_point(latitude1,longitude1 ,500 )
    ax.plot(x,y,'bo')
    dist = haversine(x,y,latitude1,longitude1)
    print "Distance between points is " ,dist    # a value approxiamtely less than 500 meters   


plt.show()

Выход

Расстояние между точками составляет 0,288044147914 Расстояние между точками составляет 0,409557451806 Расстояние между точками составляет 0,368260305716 Расстояние между точками составляет 0,340720560546 Расстояние между точками составляет 0,453773334731 Расстояние между точками составляет 0,460608754561 Расстояние между точками составляет 0,497188825576 Расстояние между точками составляет 0,603178188859 Расстояние между точками составляет 0,6288 Расстояние между точками составляет 0,703178188859 Расстояние между точками - 0.503691568896 Расстояние между точками - 0.175153349209 Расстояние между точками - 0.195149463735 Расстояние между точками - 0.424094009858 Расстояние между точками - 0.286807741494 Расстояние между точками - 0.558049206307 Расстояние между точками - 0.498612171417 Расстояние между точками - 0.047344718215 Расстояние между точками - 0.48428686

введите описание изображения здесь


0

Вы можете проверить результаты своих расчетов здесь . Прокрутите вниз до раздела «Пункт назначения с учетом расстояния и направления от начальной точки». Внизу есть даже простая формула JavaScript для реализации. Вам все еще нужно будет сгенерировать случайный азимут $ \ theta $ в радианах (измеренный по часовой стрелке с севера), хотя это должно быть довольно прямым. Эти формулы предполагают сферическую землю (хотя она эллипсоидальная), которая достаточно хороша, поскольку дает ошибки до 0,3%.


0

Реализация для Swift

Получение lat и lng из геоэнкодера и передача его этой функции

func generateRandomLocation(lat: CLLocationDegrees, lng: CLLocationDegrees){
    let radius : Double = 100000 // this is in meters so 100 KM
    let radiusInDegrees: Double = radius / 111000
    let u : Double = Double(arc4random_uniform(100)) / 100.0
    let v : Double = Double(arc4random_uniform(100)) / 100.0
    let w : Double = radiusInDegrees * u.squareRoot()
    let t : Double = 2 * Double.pi * v
    let x : Double = w * cos(t)
    let y : Double = w * sin(t)

    // Adjust the x-coordinate for the shrinking of the east-west distances
    //in cos converting degree to radian
    let new_x : Double = x / cos(lat * .pi / 180 )

    processedLat = new_x + lat
    processedLng = y + lng

    print("The Lat are :- ")
    print(processedLat)
    print("The Lng are :- ")
    print(processedLng)
}

В моем примере выше я получаю широту и долготу из гео-кодирования названия страны, так как каждый раз название страны дает одинаковую широту и долготу, что и в середине страны, поэтому мне нужна случайность.


-1

private void drawPolyline (double lat, double lng) {

         double Pi=Math.PI;

         double lt=lat;
         double ln=lng;

        //Earth’s radius, sphere
         double R=6378137;

         double dn = 50;
         double de = 50;

         //Coordinate offsets in radians
         double dLat = dn/R;
         double dLon = de/(R*Math.cos(Pi*lat/180));

        //OffsetPosition, decimal degrees
        double lat2 = lt + dLat * 180/Pi;
        double lon2 = ln + dLon * 180/Pi ;



            //12.987859, 80.231038
            //12.987954, 80.231252

        double lat3 = lt - dLat * 180/Pi;
        double lon3 = ln - dLon * 180/Pi ;

            LatLng origin=new LatLng(lt, lon3);

            LatLng dest=new LatLng(lt, lon2);




          Polyline line = googleMap.addPolyline(new PolylineOptions()
         .add(origin, dest)
         .width(6)
         .color(Color.RED));

5
Не могли бы вы немного рассказать о том, как это решает проблемы OP и кратко объяснить ваш код?
Мартин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.