33

Dada una coordenada (lat, long), estoy intentando calcular un cuadro delimitador cuadrado que está a una distancia dada (por ejemplo, 50 km) de la coordenada. Así que como entrada tengo lat, long y distance y como salida me gustaría dos coordenadas; una es la esquina suroeste (esquina inferior izquierda) y una esquina noreste (esquina superior derecha). He visto un par de respuestas aquí que intentan abordar esta cuestión en Python, pero estoy buscando una implementación de Java en particular.Cálculo del cuadro delimitador a cierta distancia de una coordenada lat/long en Java

Para que quede claro, tengo la intención de usar el algoritmo solo en la Tierra y por lo tanto no necesito acomodar un radio variable.

No tiene que ser muy preciso (+/- 20% es bueno) y solo se usará para calcular cajas delimitadoras en distancias pequeñas (no más de 150 km). Así que estoy feliz de sacrificar algo de precisión por un algoritmo eficiente. Cualquier ayuda es muy apreciada.

Editar: Debería haber sido más claro, realmente estoy después de un cuadrado, no un círculo. Entiendo que la distancia entre el centro de un cuadrado y varios puntos a lo largo del perímetro del cuadrado no es un valor constante como lo es con un círculo. Supongo que lo que quiero decir es un cuadrado donde si dibujas una línea desde el centro hacia cualquiera de los cuatro puntos del perímetro que da como resultado una línea perpendicular a un lado del perímetro, esas 4 líneas tienen la misma longitud.

+0

Esta pregunta es solo trigonometría directa; Sugeriría eliminar las etiquetas Java y Algoritmo. (Si eso es posible). –

+0

oh, lo siento, hice esto ahora. –

+0

La naturaleza esférica de la tierra necesita ser considerada. ¿Qué respuesta quieres si la posición de entrada es el Polo Norte? – MarkJ

Respuesta

51

escribí un artículo sobre la búsqueda de la delimitación Coordenadas:

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

El artículo explica las fórmulas y también proporciona una implementación de Java. (También muestra por qué la fórmula de Ironman por la longitud min/max está incorrecto.)

+0

Gracias por escribir esto; Aprecio especialmente los detalles de implementación de SQL. –

+2

Estoy confundido por qué estas aproximaciones esféricas se toman como la solución estándar. Aquí hay una cantidad abominable de funciones trigonométricas. Además, todos los dispositivos GPS producen coordenadas lat/long basadas en el elipsoide WGS84. Simplemente no puedes usar una aproximación esférica si quieres una precisión completa ... Adaptar esas ecuaciones en las aproximaciones esféricas para dar cuenta del elipsoide dará como resultado una monstruosidad inimaginable . El método debe ser realizar cálculos e integraciones de longitud de arco en el espacio 3D, parece. –

+0

@StephanKlein El cuadro delimitador es una casilla que con seguridad cubre todas las coordenadas de distancia, pero no todas las coordenadas que cubre son iguales o inferiores a la distancia. El cuadro delimitador es como un rectángulo alrededor de un círculo.Cubre todos los puntos dentro del círculo, pero cerca de los bordes también cubre algunos puntos más allá del círculo. Pero solo con un cuadro (rect) puede hacer una simple comparación '<' and '>' dentro de una declaración de SQL por eso la selección completa de SQL que se muestra en la página primero prueba si un punto está dentro del cuadro delimitador (¡rápido!) Y luego verifica si realmente es igual o inferior a la distancia deseada (¡lento!) – Mecki

3
import com.vividsolutions.jts.geom.Envelope; 

... 
Envelope env = new Envelope(centerPoint.getCoordinate()); 
env.expandBy(distance_in_degrees); 
... 

Ahora env contiene su sobre. En realidad no es un "cuadrado" (lo que sea que eso signifique en la superficie de una esfera), pero debería funcionar.

Debe tener en cuenta que la distancia en grados dependerá de la latitud del punto central. En el ecuador, 1 grado de latitud es de aproximadamente 111 km, pero en Nueva York, solo hay unos 75 km.

Lo realmente genial es que puedes tirar todos tus puntos en un com.vividsolutions.jts.index.strtree.STRtree y luego usarlo para calcular rápidamente los puntos dentro de ese sobre.

12
double R = 6371; // earth radius in km 

double radius = 50; // km 

double x1 = lon - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat))); 

double x2 = lon + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat))); 

double y1 = lat + Math.toDegrees(radius/R); 

double y2 = lat - Math.toDegrees(radius/R); 

Aunque también recomendaría JTS.

+7

¿Qué significa 'JTS'? – Gili

+0

Java Topology Suite, es una API para operaciones relacionadas con la geometría – prettyvoid

1
double R = 6371; // earth radius in km 
double radius = 50; // km 
double x1 = lon - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat))); 
double x2 = lon + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat))); 
double y1 = lat + Math.toDegrees(radius/R); 
double y2 = lat - Math.toDegrees(radius/R); 

Aunque También recomendaría JTS.

Esto calcula pero Google Earth no acepta y no asigna el modelo 3D.

/* 
* To change this template, choose Tools | Templates 
* and open the template in the editor. 
*/ 

package assetmap; 




public class Main { 

public double degrees; 
public double pi= 3.1416; 
public static double lon=80.304737; 
public static double lat=26.447521; 
public static double x1,x2,y1,y2; 


public static void main(String[] args) { 

double R = 6371; // earth radius in km 26.447521 

double radius = 0.300; // km 

x1 = (lon - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)))); 

x2 = (lon + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)))); 

y1 = (lat + Math.toDegrees(radius/R)); 

y2 = (lat - Math.toDegrees(radius/R)); 


System.out.println(x1+"---|"+x2+"---|"+y1+"|---|"+y2); 


} 

} 

Imprime

80.30172366789824---|80.176---|26.450218964817754|---|26.444823035182242 

KML:

<?xml version="1.0" encoding="UTF-8"?> 
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> 
<Placemark> 
    <name>United Nations Headquarters</name> 
    <Region> 
     <LatLonAltBox> 
      <north>26.447251203518224</north> 
      <south>26.447790796481772</south> 
      <east>80.18</east> 
      <west>80.30443566678983</west> 
      <minAltitude>0</minAltitude> 
      <maxAltitude>30</maxAltitude> 
      <altitudeMode>absolute</altitudeMode> 
     </LatLonAltBox> 
     <Lod> 
      <minLodPixels>128</minLodPixels> 
      <maxLodPixels>-1</maxLodPixels> 
      <minFadeExtent>0</minFadeExtent> 
      <maxFadeExtent>0</maxFadeExtent> 
     </Lod> 
    </Region> 
    <Model id="model_1"> 
     <altitudeMode>absolute</altitudeMode> 
     <Location> 
      <longitude>80.304737</longitude> 
      <latitude>26.447521</latitude> 
      <altitude>0.406173708576</altitude> 
     </Location> 
     <Orientation> 
      <heading>0</heading> 
      <tilt>0</tilt> 
      <roll>0</roll> 
     </Orientation> 
     <Scale> 
      <x>10</x> 
      <y>10</y> 
      <z>10</z> 
     </Scale> 
     <Link> 
      <href>un.dae</href> 
     </Link> 
     <ResourceMap> 
      <Alias> 
       <targetHref>_01.jpg</targetHref> 
       <sourceHref>../images/_01.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_02.jpg</targetHref> 
       <sourceHref>../images/_02.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_04.jpg</targetHref> 
       <sourceHref>../images/_04.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_05.jpg</targetHref> 
       <sourceHref>../images/_05.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_06.jpg</targetHref> 
       <sourceHref>../images/_06.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_07.jpg</targetHref> 
       <sourceHref>../images/_07.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_08.jpg</targetHref> 
       <sourceHref>../images/_08.jpg</sourceHref> 
      </Alias> 
      <Alias> 
       <targetHref>_09.jpg</targetHref> 
       <sourceHref>../images/_09.jpg</sourceHref> 
      </Alias> 
     </ResourceMap> 
    </Model> 
</Placemark> 
</kml> 
1

Tengo un script PHP y el ejemplo que hace esto. Dado un punto de partida, calcula las esquinas de una caja a una distancia determinada.Es específicamente para Google Maps, pero podría funcionar para otra cosa:

http://www.richardpeacock.com/blog/2011/11/draw-box-around-coordinate-google-maps-based-miles-or-kilometers

+0

Sé que esta es una respuesta increíblemente antigua, pero ayudó muchísimo, ¡muchas gracias! – acupajoe

+1

¡Me alegra que haya ayudado a alguien! – Richard

1

Todas las respuestas anteriores son sólo parcialmente correcta. Especialmente en regiones como Australia, siempre incluyen polos y calculan un rectángulo muy grande incluso para 10kms.

Especialmente el algoritmo de Jan Philip Matuschek en http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex incluía un rectángulo muy grande de (-37, -90, -180, 180) para casi todos los puntos en Australia. Esto afecta a los usuarios grandes en la base de datos y la distancia debe calcularse para todos los usuarios en casi la mitad del país.

Encontré que el Drupal API Earth Algorithm por Rochester Institute of Technology funciona mejor alrededor de la pértiga y en otros lugares y es mucho más fácil de implementar.

https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54

Uso earth_latitude_range y earth_longitude_range del algoritmo anterior para calcular rectángulo de delimitación

Aquí está la aplicación es Java

/** 
* Get bouding rectangle using Drupal Earth Algorithm 
* @see https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54 
* @param lat 
* @param lng 
* @param distance 
* @return 
*/ 
default BoundingRectangle getBoundingRectangleDrupalEarthAlgo(double lat, double lng, int distance) { 
    lng = Math.toRadians(lng); 
    lat = Math.toRadians(lat); 
    double radius = earth_radius(lat); 
    List<Double> retLats = earth_latitude_range(lat, radius, distance); 
    List<Double> retLngs = earth_longitude_range(lat, lng, radius, distance); 
    return new BoundingRectangle(retLats.get(0), retLats.get(1), retLngs.get(0), retLngs.get(1)); 
} 


/** 
* Calculate latitude range based on earths radius at a given point 
* @param latitude 
* @param longitude 
* @param distance 
* @return 
*/ 
default List<Double> earth_latitude_range(double lat, double radius, double distance) { 
     // Estimate the min and max latitudes within distance of a given location. 

     double angle = distance/radius; 
     double minlat = lat - angle; 
     double maxlat = lat + angle; 
     double rightangle = Math.PI/2; 
     // Wrapped around the south pole. 
     if (minlat < -rightangle) { 
     double overshoot = -minlat - rightangle; 
     minlat = -rightangle + overshoot; 
     if (minlat > maxlat) { 
      maxlat = minlat; 
     } 
     minlat = -rightangle; 
     } 
     // Wrapped around the north pole. 
     if (maxlat > rightangle) { 
     double overshoot = maxlat - rightangle; 
     maxlat = rightangle - overshoot; 
     if (maxlat < minlat) { 
      minlat = maxlat; 
     } 
     maxlat = rightangle; 
     } 
     List<Double> ret = new ArrayList<>(); 
     ret.add((minlat)); 
     ret.add((maxlat)); 
     return ret; 
    } 

/** 
* Calculate longitude range based on earths radius at a given point 
* @param lat 
* @param lng 
* @param earth_radius 
* @param distance 
* @return 
*/ 
default List<Double> earth_longitude_range(double lat, double lng, double earth_radius, int distance) { 
     // Estimate the min and max longitudes within distance of a given location. 
     double radius = earth_radius * Math.cos(lat); 

     double angle; 
     if (radius > 0) { 
     angle = Math.abs(distance/radius); 
     angle = Math.min(angle, Math.PI); 
     } 
     else { 
     angle = Math.PI; 
     } 
     double minlong = lng - angle; 
     double maxlong = lng + angle; 
     if (minlong < -Math.PI) { 
     minlong = minlong + Math.PI * 2; 
     } 
     if (maxlong > Math.PI) { 
     maxlong = maxlong - Math.PI * 2; 
     } 

     List<Double> ret = new ArrayList<>(); 
     ret.add((minlong)); 
     ret.add((maxlong)); 
     return ret; 
    } 

/** 
* Calculate earth radius at given latitude 
* @param latitude 
* @return 
*/ 
default Double earth_radius(double latitude) { 
     // Estimate the Earth's radius at a given latitude. 
     // Default to an approximate average radius for the United States. 
     double lat = Math.toRadians(latitude); 

     double x = Math.cos(lat)/6378137.0; 
     double y = Math.sin(lat)/(6378137.0 * (1 - (1/298.257223563))); 

     //Make sure earth's radius is in km , not meters 
     return (1/(Math.sqrt(x * x + y * y)))/1000; 
    } 

y utilizar la fórmula de cálculo distancia documentado por Google Maps para calcular la distancia

https://developers.google.com/maps/solutions/store-locator/clothing-store-locator#outputting-data-as-xml-using-php

Para buscar por kilómetros en lugar de millas, reemplace 3959 con 6371. Para (latitud, la longitud) = (37, -122) y una mesa de marcadores con columnas lat y lng, la fórmula es:

SELECT id, (3959 * acos(cos(radians(37)) * cos(radians(lat)) * cos(radians(lng) - radians(-122)) + sin(radians(37)) * sin(radians(lat)))) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20; 
+0

Observe el código earth_radius. Asegúrese de que el radio de la Tierra esté en km, no en metros: retorno (1/(Math.sqrt (x * x + y * y)))/1000; –

Cuestiones relacionadas