web-dev-qa-db-fra.com

Calcul du cadre de sélection d'une certaine distance d'une coordonnée lat/long en Java

Étant donné une coordonnée (lat, long), j'essaie de calculer une boîte englobante carrée située à une distance donnée (par exemple, 50 km) de la coordonnée. Donc, comme entrée, j'ai lat, long et distance et comme sortie, j'aimerais deux coordonnées; l'un étant le coin sud-ouest (en bas à gauche) et l'autre, le coin nord-est (en haut à droite). J'ai vu quelques réponses ici qui tentent de répondre à cette question en Python, mais je recherche une implémentation Java en particulier. 

Pour être clair, je compte utiliser l’algorithme sur la Terre uniquement et je n’ai donc pas besoin d’adapter un rayon variable. 

Il n'a pas besoin d'être extrêmement précis (+/- 20%, c'est bien) et il ne sera utilisé que pour calculer les boîtes englobantes sur de petites distances (pas plus de 150 km). Je suis donc heureux de sacrifier un peu de précision pour un algorithme efficace. Toute aide est très appréciée.

Edit: J'aurais dû être plus clair, je suis vraiment après un carré, pas un cercle. Je comprends que la distance entre le centre d'un carré et divers points le long de son périmètre n'est pas une valeur constante, contrairement à ce qu'elle est avec un cercle. Je suppose que ce que je veux dire est un carré dans lequel, si vous tracez une ligne du centre vers l’un des quatre points du périmètre, qui forme une ligne perpendiculaire à un côté du périmètre, ces quatre lignes ont la même longueur.

36
Bryce Thomas

J'ai écrit un article sur la recherche des coordonnées de délimitation:

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

L'article explique les formules et fournit également une implémentation Java. (Cela montre également pourquoi la formule d'IronMan pour la longitude min/max est inexacte.)

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

Bien que je recommande aussi JTS.

14
IronMan
import com.vividsolutions.jts.geom.Envelope;

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

Maintenant env contient votre enveloppe. Ce n'est pas réellement un "carré" (quoi que cela signifie sur la surface d'une sphère), mais cela devrait être.

Notez que la distance en degrés dépend de la latitude du point central. À l'équateur, 1 degré de latitude correspond à environ 111 km, mais à New York, il ne s'agit que d'environ 75 km. 

La chose vraiment cool est que vous pouvez jeter tous vos points dans un com.vividsolutions.jts.index.strtree.STRtree et ensuite l'utiliser pour calculer rapidement des points à l'intérieur de cette enveloppe.

3
novalis

J'ai un script PHP et un exemple qui le fait. À partir d'un point de départ, il calcule les coins d'une boîte autour d'une distance donnée. C'est spécifiquement pour Google Maps, mais cela pourrait fonctionner pour autre chose:

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

1
Richard
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);

Bien que je recommande aussi JTS.

Ceci calcule mais Google Earth n'accepte pas et ne mappe pas le modèle 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);


 }

}

Il imprime

80.30172366789824---|80.30775033210176---|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.30503833321018</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
susheel

Toutes les réponses précédentes ne sont que partiellement correctes. Particulièrement dans des régions comme l’Australie, ils incluent toujours un pôle et calculent un très grand rectangle, même pour 10 km. 

En particulier, l'algorithme de Jan Philip Matuschek sur http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex incluait un très grand rectangle de (-37, -90, -180, 180) pour presque chaque point en Australie. La base de données est touchée par un grand nombre d'utilisateurs et la distance doit être calculée pour tous les utilisateurs dans presque la moitié du pays.

J'ai trouvé que l'algorithme Drupal API Earth du Rochester Institute of Technology fonctionnait mieux autour du pôle qu'ailleurs et était beaucoup plus facile à mettre en œuvre.

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

Utilisez earth_latitude_range et earth_longitude_range à partir de l'algorithme ci-dessus pour calculer le rectangle englobant 

Voici l'implémentation en 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;
    }

Et utilisez la formule de calcul distance documentée par google maps pour calculer la distance

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

Pour effectuer une recherche par kilomètres au lieu de miles, remplacez 3959 par 6371 .Pour (Lat, Lng) = (37, -122) et un tableau Markers avec les colonnes lat et lng, la formule est la suivante:

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;
1
Sacky San

Voici une solution simple que j’utilisais pour générer les coordonnées du cadre de sélection que j’utilise avec GeoNames citieJSON API pour obtenir les grandes villes proches à partir d’une coordonnée décimale gps. 

Ceci est une méthode Java de mon référentiel GitHub: FusionTableModifyJava

J'avais une position GPS décimale et j'avais besoin de trouver la plus grande ville/état "proche" de cette position. J'avais besoin d'un cadre de sélection relativement précis pour passer au service Web citiesJSON GeoNames afin de récupérer la plus grande ville dans ce cadre de sélection. Je passe devant le rayon et le rayon qui m'intéresse (en km) et il me donne les coordonnées décimales nord, sud, est et ouest nécessaires pour passer à citiesJSON.

(J'ai trouvé ces ressources utiles dans mes recherches: 

Calculez la distance, le relèvement et plus encore entre les points de latitude/longitude.

Longitude - Wikipedia )

Ce n'est pas super précis mais assez précis pour ce que je l'utilisais pour:

    // Compute bounding Box coordinates for use with Geonames API.
    class BoundingBox
    {
        public double north, south, east, west;
        public BoundingBox(String location, float km)
        {
             //System.out.println(location + " : "+ km);
            String[] parts = location.replaceAll("\\s","").split(","); //remove spaces and split on ,

            double lat = Double.parseDouble(parts[0]);
            double lng = Double.parseDouble(parts[1]);

            double adjust = .008983112; // 1km in degrees at equator.
            //adjust = 0.008983152770714983; // 1km in degrees at equator.

            //System.out.println("deg: "+(1.0/40075.017)*360.0);


            north = lat + ( km * adjust);
            south = lat - ( km * adjust);

            double lngRatio = 1/Math.cos(Math.toRadians(lat)); //ratio for lng size
            //System.out.println("lngRatio: "+lngRatio);

            east = lng + (km * adjust) * lngRatio;
            west = lng - (km * adjust) * lngRatio;
        }

    }
0
Greg Kendall