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

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

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

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

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


person Bryce Thomas    schedule 06.11.2009    source источник
comment
Если у вас есть реализация Python, я не понимаю, почему вы не можете преобразовать ее в Java. Алгоритм должен быть таким же.   -  person Alexandru Luchian    schedule 06.11.2009
comment
Лучший пример Python, который я нашел, был отмечен автором как непроверенный. Я полагаю, учитывая, что я ищу что-то довольно простое в отношении точности, может быть также уместен другой алгоритм.   -  person Bryce Thomas    schedule 06.11.2009
comment
Этот вопрос - простая тригонометрия; Я бы предложил удалить теги Java и алгоритма. (Если это вообще возможно.)   -  person Kevin Bourrillion    schedule 07.11.2009
comment
Это просто формула, которая вам нужна; Я уверен, что кто-то предоставит его вам и, надеюсь, даст вам ссылку для справки.   -  person Kevin Bourrillion    schedule 07.11.2009
comment
Необходимо учитывать сферическую природу Земли. Какой ответ вы хотите, если исходное положение - северный полюс?   -  person MarkJ    schedule 09.11.2009


Ответы (6)


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

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

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

person Jan Philip Matuschek    schedule 26.05.2010
comment
Спасибо, что написали это; Я особенно ценю детали реализации SQL. - person Dave Jarvis; 25.06.2010
comment
Меня смущает, почему эти сферические приближения принимаются за стандартное решение. Здесь задействовано огромное количество тригонометрических функций. Кроме того, все устройства GPS выдают координаты широты и долготы на основе эллипсоида WGS84 - вы просто не можете использовать сферическое приближение, если хотите полную точность ... Адаптация этих уравнений в сферических приближениях для учета эллипсоида приведет к невообразимому чудовищу. . Похоже, метод должен заключаться в выполнении вычислений и интегралов длины дуги в трехмерном пространстве. - person Steven Lu; 13.02.2014
comment
@StephanKlein Ограничивающий прямоугольник - это прямоугольник, который наверняка покрывает все координаты на расстоянии, но не все координаты, которые он охватывает, равны или меньше расстояния. Ограничивающая рамка похожа на прямоугольник вокруг круга. Он покрывает все точки внутри круга, но рядом с краями он также покрывает некоторые точки за пределами круга. Но только с прямоугольником (прямоугольником) вы можете выполнять простое сравнение < и > в операторе SQL, поэтому полный выбор SQL, показанный на странице, сначала проверяет, находится ли точка внутри ограничивающего прямоугольника (быстро!), А затем проверяет, находится ли оно действительно равно или меньше желаемого расстояния (медленно!) - person Mecki; 11.12.2015
comment
@StevenLu В чем именно ваша проблема с вычислениями на этой странице? Мне они кажутся очень точными, и нет ничего более тригонометрического, чем в ответах IronMan или Susheel, за исключением одного вызова sin() и одного вызова asin() - все остальное идентично для вычисления ограничивающей рамки. Используемый точный расчет расстояния - это стандартная формула, которую все используют для этого. Так что я действительно не вижу причин, по которым этот ответ не должен быть принятым ответом, в конце концов, он является наиболее точным. - person Mecki; 11.12.2015
comment
@Mecki, извините, я не был полностью ясен, и мне потребовалось немного подумать, чтобы вспомнить первоначальную причину моей жалобы. В основном проблема заключается в том, что с вычислительной точки зрения для компьютера триггерные операции не очень быстрые (по сравнению с более простыми операциями, такими как больше / меньше). Этот вопрос требует базовой проверки ограничивающего прямоугольника. OP даже указывает несколькими предложениями, что ему не нужна максимальная точность, ему нужна скорость. Таким образом, использование дерьмовой загрузки триггеров на каждой контрольной точке для получения точного результата, на котором она находится на заданном расстоянии от другой точки, является излишним. - person Steven Lu; 12.12.2015
comment
Что OP хочет сделать (как и в моем последнем проекте, над которым я работал, в котором мы работали с широтой и долготой), так это применить базовое приближение, которое работает очень быстро: вам нужны две константы, километры в градусе широты и километры в градусе долготы на экваторе. Затем просто используйте это, чтобы выяснить, в каком количестве градусов ваши очки должны пройти проверку. Да, это приближение ухудшается около полюсов. Однако для большинства приложений это не имеет большого значения. Географические достопримечательности обычно находятся очень далеко от полюсов. (на самом деле я сильно упростил это, это немного сложнее) - person Steven Lu; 12.12.2015
comment
это разница между минимальным количеством ложных срабатываний (даже меньше, чем вы ожидали, если отображаемая область экрана прямоугольная, что почти всегда так, на самом деле OP также явно указывает на это) при выполнении проверки AABB для каждой точки по сравнению с нулевым количеством ложных срабатываний, выполняющих 3 или 9 вызовов триггерных функций на точку. сложность заключается в том, что чем ближе вы подходите к полюсам, тем больший диапазон долгот вам нужно проверить, чтобы надежно закрыть свой ящик - person Steven Lu; 12.12.2015
comment
Я скажу это. Пока кто-то действительно читает связанную статью, он должен быть в состоянии оценить, как реализовать это сверхточным способом, а также подумать о том, как настроить его и ослабить условие, чтобы код выполнял меньше триггерных функций. Иногда вы можете гарантировать, что всегда есть достаточно времени для запуска тонны и тонны триггерных функций для каждой точки запроса. - person Steven Lu; 12.12.2015
comment
При тестировании на расстоянии 1 км я получил расстояние между lat_min, lon_min и lat_max_lon_max равным 2,82 км; Почему не 2; Также расстояние между широтой, долгом и этими точками составляет 1,41 вместо того, чтобы приближаться к 1; Вот здесь gist.github.com/alexcpn/f95ae83a7ee0293a5225 - person Alex Punnen; 16.01.2016
comment
Это вычисляет очень большой ограничивающий прямоугольник для координат в Австралии. Это неприемлемо, поскольку у моего приложения тысячи пользователей в Мельбурне. Я обнаружил, что API Drupal Earth от Rochester IOT работает лучше - person Sacky San; 30.08.2017

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.

person IronMan    schedule 10.11.2009
comment
Что означает JTS? - person Gili; 04.12.2014
comment
Java Topology Suite, это API для операций, связанных с геометрией - person prettyvoid; 25.01.2018

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, а затем использовать его для быстрого вычисления точек внутри этого конверта.

person novalis    schedule 06.11.2009

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

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

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

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

И используйте формулу расчета расстояния, задокументированную картами 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;
person Sacky San    schedule 30.08.2017
comment
Обратите внимание на код earth_radius. Убедитесь, что радиус Земли указан в км, а не в метрах: return (1 / (Math.sqrt (x * x + y * y))) / 1000; - person Sacky San; 30.08.2017

На основании ответа IronMan:

/**
 * Calculate the lat and len of a square around a point.
 * @return latMin, latMax, lngMin, lngMax
 */
public static double[] calculateSquareRadius(double lat, double lng, double radius) {
    double R = 6371;  // earth radius in km
    double latMin = lat - Math.toDegrees(radius/R);
    double latMax = lat + Math.toDegrees(radius/R);
    double lngMin = lng - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));
    double lngMax = lng + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));

    return new double[] {latMin, latMax, lngMin, lngMax};
}
person Laurent    schedule 11.05.2020

Вот простое решение, которое я использовал для генерации координат ограничивающего прямоугольника, который я использую с GeoNames citieJSON API, чтобы получить близлежащие крупные города по десятичной системе координат GPS.

Это метод Java из моего репозитория GitHub: FusionTableModifyJava

У меня было десятичное местоположение по GPS, и мне нужно было найти самый большой город / штат «рядом» с этим местоположением. Мне нужна была относительно точная ограничивающая рамка, чтобы передать ее веб-сервису CityJSON GeoNames, чтобы вернуть самый большой город в этой ограничивающей рамке. Я передаю местоположение и интересующий меня "радиус" (в км), и он возвращает десятичные координаты севера, юга, востока и запада, необходимые для передачи в citiesJSON.

(Я нашел эти ресурсы полезными в своих исследованиях:

Рассчитайте расстояние, азимут и другие параметры между точками широты и долготы.

Долгота - Википедия)

Он не очень точный, но достаточно точный для того, для чего я его использовал:

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

    }
person Greg Kendall    schedule 05.06.2018