Gerando locais aleatórios nas proximidades?

30

Estou tentando criar locais aleatórios próximos ao meu local. O que eu quero é criar pares aleatórios de latitude / longitude dentro de um círculo de 200 metros ao redor da minha localização.

Esta é a fórmula que eu criei (com a ajuda de pessoas no StackOverFlow): (Número aleatório entre -1 e 1) * raio + (longitude antiga) = nova longitude dentro do raio da longitude antiga

(Número aleatório entre -1 e 1) * raio + (latitude antiga) = nova latitude dentro do raio da latitude antiga

O problema é que algo estranho está acontecendo com minha implementação, porque todos os locais aleatórios estão muito próximos do meu centro de localização, parece que a fórmula não cobre todo o raio.

Alguma idéia do que poderia estar errado com a minha fórmula?

Editado para mostrar a implementação java atual:

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;
}

Não tenho certeza do que estou fazendo de errado, porque os novos locais são criados no meio do mar.

Qualquer ideia?

pele de porco
fonte
Como você implementa essa fórmula? Você pode apresentar esta parte do seu código? Pode ser seu problema no gerador de números Pseudoaleatórios ?
Alex Markov
No que diz respeito à pergunta final, procedimentos como esse encontram tais problemas porque (i) as distâncias são incorretamente convertidas em graus de latitude ou longitude e (ii) a distorção métrica do sistema de coordenadas não é contabilizada ou incorretamente. O uso de um sistema de coordenadas projetado em vez de um sistema de coordenadas geográficas geralmente contorna esses dois problemas. Isso expõe uma propriedade fundamental da sua fórmula, que pode ou não ser desejável: gera locais dentro de um retângulo ao redor de um local, não dentro de um círculo.
whuber
Graças Alex, o código java está afixado em stackoverflow: stackoverflow.com/questions/10682743/...
Pindleskin
No código editado: (i) random.nextInt(1001)/1000retornará um valor maior que 1 em cerca de 0,1% das vezes. Por que você não está usando random.nextDoubleou random.nextFloat? (ii) Multiplicar x0e y0por 1E6é bastante misterioso; não parece produzir resultados corretos.
whuber
É verdade que editei o método usando nextDouble e me livrei do 1E6. Agora, todos os locais gerados aleatoriamente têm as mesmas coordenadas que o meu local. Obrigado pela ajuda, parece que eu estou indo para resolvê-lo tão cedo
Pindleskin

Respostas:

46

Isso é complicado por duas razões: primeiro, limitando os pontos a um círculo, em vez de um quadrado; segundo, contabilizando distorções nos cálculos de distância.

Muitos GISs incluem recursos que lidam com ambas as complicações de forma automática e transparente. No entanto, as tags aqui sugerem que uma descrição independente de GIS de um algoritmo pode ser desejável.

  1. Para gerar pontos de maneira uniforme, aleatória e independente dentro de um círculo de raio r em torno de um local (x0, y0), comece gerando dois valores aleatórios uniformes independentes u e v no intervalo [0, 1). (Isso é o que quase todo gerador de números aleatórios fornece.)

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

    O ponto aleatório desejado está no local (x + x0, y + y0).

  2. Ao usar coordenadas geográficas (lat, lon), x0 (longitude) e y0 (latitude) estarão em graus, mas r provavelmente estará em metros (ou pés ou milhas ou alguma outra medida linear). Primeiro, converta o raio r em graus como se estivesse localizado perto do equador. Aqui, existem cerca de 111.300 metros em um grau.

    Em segundo lugar, após a geração de x e y , como no passo (1), ajustar a coordenada x para o encolhimento das distâncias leste-oeste:

    x' = x / cos(y0)

    O ponto aleatório desejado está no local (x '+ x0, y + y0). Este é um procedimento aproximado. Para raios pequenos (menos de algumas centenas de quilômetros) que não se estendem por nenhum dos pólos da Terra, geralmente será tão preciso que você não poderá detectar nenhum erro, mesmo ao gerar dezenas de milhares de pontos aleatórios ao redor de cada centro (x0, y0) .

whuber
fonte
2
Ótima explicação whuber, é isso que eu precisava saber. Agora eu vou implementá-lo. Obrigado
pindleskin
11
Eu editei a pergunta para mostrar alguma aplicação java da fórmula
Pindleskin
11
"existem cerca de 111.300 metros em um grau" apenas para observar que a vírgula é usada como separador de milhar. radiusInDegrees = radius / 111300
RMalke
2
para lat, long coordenadas que você não deve fazer x'= x / cos (y0 * Pi / 180)
Aaron Stainback
2
Mente soprada @whuber, e faz sentido. Outra maneira de ver isso, eu acho, é imaginar 55 raios aleatórios sendo gerados para um raio de 20. Digamos que cada raio aleatório seja uniforme e exatamente igual a 0 a 20, então 0, 2, 4, ..., 20 Portanto, haverá 5 pontos com um raio de, 5 de raio de 2, etc. Os 5 pontos com um raio de 2 (em torno de um círculo de raio 2) parecerão MUITO mais próximos um do outro do que os 5 pontos com um raio. de 20.
Aziz Javed
11

Implementado para 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
Lyra
fonte
10

A implementação correta é:

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 );
}

Eu removi a dependência de bibliotecas externas para torná-lo mais acessível.

atok
fonte
Editar proposta de OP Como por este stackoverflow Q & A, em Java Math.cos () espera entradas em radianos.
MikeJRamsey56
3

Resposta aceita e derivados não funcionaram para mim. Os resultados foram muito imprecisos.

Implementação correta em 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)
}

Essência completa aqui

Na resposta aceita, descobri que os pontos são distribuídos em uma elipse com largura 1,5 vezes a altura (no Panamá) e 8 vezes a altura (no norte da Suécia). Se eu removi o ajuste x coord da resposta do @ whuber, a elipse é distorcida para o outro lado, 8 vezes maior que a sua largura.

O código na minha resposta foi baseado em algoritmos daqui

Abaixo, você pode ver dois jsfiddles que mostram o problema com a elipse de alongamento

Algoritmo correto

Algoritmo distorcido

Julian Mann
fonte
Sua descrição dos problemas que você teve sugere que sua implementação estava incorreta.
whuber
Você bem pode estar certo. Gostaria de dar uma olhada nas brigas que eu fiz e me dizer onde errei.
Julian Mann
I comparado com a resposta Java de ATOK acima, e fez esta alteração no seu jsFiddle do algoritmo Distored em whuberPointAtDistance(): x1 = (w * Math.cos(t)) / Math.cos(y0 * (Math.PI / 180)).
Matt
11
Apesar da minha correção, tive resultados muito mais precisos com a essência de Julian. Adicionando minhas correções para whuberPointAtDistance () e executá-los na essência com o relatório de erro, mostrou 0,05% de erro em todos os três cenários (significativamente mais elevados do que a alternativa.)
Matt
1

Em Python

# 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()

Saída

A distância entre os pontos é 0.288044147914 A distância entre os pontos é 0.409557451806 A distância entre os pontos é 0.368260305716 A distância entre os pontos é 0.340720560546 A distância entre os pontos é 0.453773334731 A distância entre os pontos é 0.460608754561 A distância entre os pontos é 0.497188825576 A distância entre os pontos é 0.497188825576 A distância entre pontos é 0.503691568896 A distância entre pontos é 0.175153349209 A distância entre pontos é 0.195149463735 A distância entre pontos é 0.424094009858 A distância entre pontos é 0.286807741494 A distância entre pontos é 0.558049206307 A distância entre pontos é 0.558049206307 A distância entre pontos é 0.498612171417 A distância entre pontos é 0.0473447471821532

insira a descrição da imagem aqui

Alex Punnen
fonte
0

Você pode verificar os resultados de seus cálculos aqui . Role para baixo até a seção chamada "Ponto de destino, dada a distância e o rumo do ponto inicial". Existe até uma fórmula JavaScript simples na parte inferior para implementar isso. Você ainda precisará gerar um rolamento aleatório de $ \ theta $ em radianos (medido no sentido horário a partir do norte), embora isso deva ser bastante direto. Essas fórmulas assumem uma terra esférica (embora seja elipsoidal), que é boa o suficiente, pois produz erros de até 0,3%.

Dimitriy V. Masterov
fonte
0

Implementação para Swift

Obtendo o lat e o lng do geoencoder e passando para esta função

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)
}

No meu exemplo acima, obtenho a latitude e longitude da codificação geográfica do nome do país, pois cada vez que o nome do país fornece a mesma latitude e longitude, também no meio do país, então eu precisava de aleatoriedade.

Pulkit
fonte
-1

private void drawPolyline (duplo lat, duplo 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));
Jeeva
fonte
5
Você poderia expandir um pouco a maneira como isso resolve os problemas dos OPs e explicar seu código com facilidade?
Martin Martin