Вычисление ограничивающей рамки на некотором расстоянии от координаты lat/long в Java

Учитывая координату (lat, long), я пытаюсь вычислить квадратную ограничительную рамку, которая является заданным расстоянием (например, на расстоянии 50 км) от координаты. Так как вход у меня есть lat, long и distance, а в качестве вывода я бы хотел две координаты; один из которых - юго-западный (нижний левый) угол, а один - северо-восточный (верхний правый) угол. Я видел пару ответов здесь, которые пытаются решить этот вопрос в Python, но я ищу, в частности, реализацию Java.

Чтобы быть ясным, я намерен использовать алгоритм только на Земле, поэтому мне не нужно размещать переменный радиус.

Он не должен быть очень точным (+/- 20% в порядке), и он будет использоваться только для расчета ограничивающих коробок на малых расстояниях (не более 150 км). Поэтому я рад пожертвовать некоторой точностью для эффективного алгоритма. Любая помощь очень ценится.

Изменить: я должен был быть более ясным, я действительно после квадрата, а не круга. Я понимаю, что расстояние между центром квадрата и различными точками вдоль квадратного периметра не является постоянным значением, как это связано с кругом. Я предполагаю, что я имею в виду квадрат, где, если вы нарисуете линию от центра до любой из четырех точек периметра, которая приводит к прямой, перпендикулярной стороне периметра, то эти 4 линии имеют одинаковую длину.

Ответ 1

Я написал статью о поиске граничных координат:

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

В статье объясняются формулы, а также предоставляется реализация Java. (Он также показывает, почему формула IronMan для минимальной/максимальной долготы неточна.)

Ответ 2

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

Хотя я бы также рекомендовал JTS.

Ответ 3

import com.vividsolutions.jts.geom.Envelope;

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

Теперь env содержит ваш конверт. Это не фактически "квадрат" (что бы это ни значило на поверхности сферы), но он должен это делать.

Следует отметить, что расстояние в градусах будет зависеть от широты центральной точки. На экваторе 1 градус широты составляет около 111 км, но в Нью-Йорке это всего около 75 км.

По-настоящему здорово, что вы можете подбросить все свои очки в com.vividsolutions.jts.index.strtree.STRtree, а затем использовать его для быстрого вычисления точек внутри этого конверта.

Ответ 4

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

Хотя я бы также рекомендовал JTS.

Это вычисляет, но Google Планета Земля не принимает и не отображает 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);


 }

}

Он печатает

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>

Ответ 5

У меня есть PHP script и пример, который делает это. Учитывая начальную точку, он вычисляет углы прямоугольника вокруг него на определенном расстоянии. Это специально для Карт Google, но он может работать для чего угодно:

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

Ответ 6

Все предыдущие ответы являются лишь частично правильными. Специально в регионе, таком как Австралия, они всегда включают полюс и вычисляют очень большой прямоугольник даже для 10 км.

В частности, алгоритм Ян Филип Матучек в http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex включал очень большой прямоугольник из (-37, -90, -180, 180) практически для каждой точки Австралии. Это касается большого числа пользователей в базе данных, и расстояние должно быть рассчитано для всех пользователей почти в половине страны.

Я обнаружил, что алгоритм API Drupal API Earth от Рочестерского технологического института работает лучше как на полюсе, так и в другом месте и намного проще реализовать.

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

Используйте earth_latitude_range и earth_longitude_range из приведенного выше алгоритма для вычисления ограничивающего прямоугольника

Вот реализация 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 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 radius is in km , not meters
      return (1 / (Math.sqrt(x * x + y * y)))/1000;
    }

И используйте формулу расчета расстояния, задокументированную картами google, чтобы рассчитать расстояние

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

Чтобы выполнить поиск по километрам вместо миль, замените 3959 на 6371. Для (Lat, Lng) = (37, -122) и таблицы маркеров с столбцами lat и lng формула:

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;