Jak obliczyć prostokąt ograniczający dla podanej lokalizacji szerokości / lng?

103

Podałem lokalizację określoną przez szerokość i długość geograficzną. Teraz chcę obliczyć prostokąt ograniczający w odległości np. 10 kilometrów od tego punktu.

Ramka graniczna powinna być zdefiniowana jako latmin, lngmin i latmax, lngmax.

Potrzebuję tych rzeczy, aby korzystać z interfejsu API panoramio .

Czy ktoś zna formułę, jak zdobyć te punkty?

Edycja: Chłopaki, szukam formuły / funkcji, która przyjmuje lat i lng jako dane wejściowe i zwraca ramkę ograniczającą jako latmin i lngmin oraz latmax i latmin. MySQL, php, c #, javascript jest w porządku, ale także pseudokod powinien być w porządku.

Edycja: nie szukam rozwiązania, które pokazuje mi odległość 2 punktów

Michał
źródło
Jeśli korzystasz gdzieś z geobazy, z pewnością mają one zintegrowane obliczanie prostokąta ograniczającego. Możesz nawet sprawdzić, na przykład, źródło PostGIS / GEOS.
Vinko Vrsalovic

Odpowiedzi:

61

Proponuję lokalne przybliżenie powierzchni Ziemi jako kuli o promieniu podanym przez elipsoidę WGS84 na danej szerokości geograficznej. Podejrzewam, że dokładne obliczenia latMin i latMax wymagałyby funkcji eliptycznych i nie przyniosłyby znaczącego wzrostu dokładności (WGS84 sam w sobie jest przybliżeniem).

Moja implementacja jest następująca (jest napisana w Pythonie; nie testowałem jej):

# degrees to radians
def deg2rad(degrees):
    return math.pi*degrees/180.0
# radians to degrees
def rad2deg(radians):
    return 180.0*radians/math.pi

# Semi-axes of WGS-84 geoidal reference
WGS84_a = 6378137.0  # Major semiaxis [m]
WGS84_b = 6356752.3  # Minor semiaxis [m]

# Earth radius at a given latitude, according to the WGS-84 ellipsoid [m]
def WGS84EarthRadius(lat):
    # http://en.wikipedia.org/wiki/Earth_radius
    An = WGS84_a*WGS84_a * math.cos(lat)
    Bn = WGS84_b*WGS84_b * math.sin(lat)
    Ad = WGS84_a * math.cos(lat)
    Bd = WGS84_b * math.sin(lat)
    return math.sqrt( (An*An + Bn*Bn)/(Ad*Ad + Bd*Bd) )

# Bounding box surrounding the point at given coordinates,
# assuming local approximation of Earth surface as a sphere
# of radius given by WGS84
def boundingBox(latitudeInDegrees, longitudeInDegrees, halfSideInKm):
    lat = deg2rad(latitudeInDegrees)
    lon = deg2rad(longitudeInDegrees)
    halfSide = 1000*halfSideInKm

    # Radius of Earth at given latitude
    radius = WGS84EarthRadius(lat)
    # Radius of the parallel at given latitude
    pradius = radius*math.cos(lat)

    latMin = lat - halfSide/radius
    latMax = lat + halfSide/radius
    lonMin = lon - halfSide/pradius
    lonMax = lon + halfSide/pradius

    return (rad2deg(latMin), rad2deg(lonMin), rad2deg(latMax), rad2deg(lonMax))

EDYCJA: Poniższy kod konwertuje (stopnie, liczby pierwsze, sekundy) na stopnie + ułamki stopnia i odwrotnie (nie testowano):

def dps2deg(degrees, primes, seconds):
    return degrees + primes/60.0 + seconds/3600.0

def deg2dps(degrees):
    intdeg = math.floor(degrees)
    primes = (degrees - intdeg)*60.0
    intpri = math.floor(primes)
    seconds = (primes - intpri)*60.0
    intsec = round(seconds)
    return (int(intdeg), int(intpri), int(intsec))
Federico A. Ramponi
źródło
4
Jak wskazano w dokumentacji sugerowanej biblioteki CPAN, ma to sens tylko dla halfSide <= 10 km.
Federico A. Ramponi
1
Czy to działa w pobliżu biegunów? Wydaje się, że tak nie jest, ponieważ wygląda na to, że kończy się na latMin <-pi (dla bieguna południowego) lub latMax> pi (dla bieguna północnego)? Myślę, że kiedy znajdujesz się w połowie boku bieguna, musisz zwrócić ramkę ograniczającą, która zawiera wszystkie długości i szerokości obliczone normalnie dla strony oddalonej od bieguna i dla boku znajdującego się w pobliżu bieguna.
Doug McClean,
1
Oto implementacja PHP ze specyfikacji znalezionej na JanMatuschek.de: github.com/anthonymartin/GeoLocation.class.php
Anthony Martin
2
Dodałem poniżej implementację C # tej odpowiedzi.
Ε Г И І И О
2
@ FedericoA.Ramponi co to jest haldSideinKm tutaj? nie rozumiem ... co muszę przekazać w tych argumentach, promień między dwoma punktami na mapie czy co?
53

Napisałem artykuł o znajdowaniu współrzędnych ograniczających:

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

W artykule wyjaśniono formuły, a także przedstawiono implementację języka Java. (Pokazuje również, dlaczego formuła Federico na minimalną / maksymalną długość geograficzną jest niedokładna).

Jan Philip Matuschek
źródło
4
Utworzyłem port PHP Twojej klasy GeoLocation. Można go znaleźć tutaj: pastie.org/5416584
Anthony Martin
1
Wgrałem to teraz na github: github.com/anthonymartin/GeoLocation.class.php
Anthony Martin
1
Czy to w ogóle odpowiada na pytanie? Jeśli mamy tylko 1 punkt początkowy, nie możemy obliczyć długości ortodromy, tak jak jest to zrobione w tym kodzie, co wymaga dwóch szerokich, długich lokalizacji.
mdoran3844
w twoim wariancie C # jest zły kod, na przykład: public override string ToString()bardzo źle jest przesłonić taką globalną metodę tylko w jednym celu, lepiej po prostu dodać inną metodę, a następnie przesłonić metodę standardową, która może być używana w innych częściach aplikacji, nie dla dokładnego gis ...
Oto zaktualizowany link do portu PHP klasy GeoLocaiton Jana: github.com/anthonymartin/GeoLocation.php
Anthony Martin
34

Tutaj przekonwertowałem odpowiedź Federico A. Ramponiego na C # dla wszystkich zainteresowanych:

public class MapPoint
{
    public double Longitude { get; set; } // In Degrees
    public double Latitude { get; set; } // In Degrees
}

public class BoundingBox
{
    public MapPoint MinPoint { get; set; }
    public MapPoint MaxPoint { get; set; }
}        

// Semi-axes of WGS-84 geoidal reference
private const double WGS84_a = 6378137.0; // Major semiaxis [m]
private const double WGS84_b = 6356752.3; // Minor semiaxis [m]

// 'halfSideInKm' is the half length of the bounding box you want in kilometers.
public static BoundingBox GetBoundingBox(MapPoint point, double halfSideInKm)
{            
    // Bounding box surrounding the point at given coordinates,
    // assuming local approximation of Earth surface as a sphere
    // of radius given by WGS84
    var lat = Deg2rad(point.Latitude);
    var lon = Deg2rad(point.Longitude);
    var halfSide = 1000 * halfSideInKm;

    // Radius of Earth at given latitude
    var radius = WGS84EarthRadius(lat);
    // Radius of the parallel at given latitude
    var pradius = radius * Math.Cos(lat);

    var latMin = lat - halfSide / radius;
    var latMax = lat + halfSide / radius;
    var lonMin = lon - halfSide / pradius;
    var lonMax = lon + halfSide / pradius;

    return new BoundingBox { 
        MinPoint = new MapPoint { Latitude = Rad2deg(latMin), Longitude = Rad2deg(lonMin) },
        MaxPoint = new MapPoint { Latitude = Rad2deg(latMax), Longitude = Rad2deg(lonMax) }
    };            
}

// degrees to radians
private static double Deg2rad(double degrees)
{
    return Math.PI * degrees / 180.0;
}

// radians to degrees
private static double Rad2deg(double radians)
{
    return 180.0 * radians / Math.PI;
}

// Earth radius at a given latitude, according to the WGS-84 ellipsoid [m]
private static double WGS84EarthRadius(double lat)
{
    // http://en.wikipedia.org/wiki/Earth_radius
    var An = WGS84_a * WGS84_a * Math.Cos(lat);
    var Bn = WGS84_b * WGS84_b * Math.Sin(lat);
    var Ad = WGS84_a * Math.Cos(lat);
    var Bd = WGS84_b * Math.Sin(lat);
    return Math.Sqrt((An*An + Bn*Bn) / (Ad*Ad + Bd*Bd));
}
Ε Г И І И О
źródło
1
Dzięki, ta praca dla mnie. Musiałem przetestować kod ręcznie, nie wiedziałem, jak napisać test jednostkowy do tego, ale generuje dokładne wyniki w stopniu dokładności, którego potrzebuję
mdoran3844
co tu jest haldSideinKm? nie rozumiem ... co muszę przekazać w tych argumentach, promień między dwoma punktami na mapie czy co?
@GeloVolro: To połowa długości obwiedni, którą chcesz.
Ε Г И І И О
1
Nie musisz koniecznie pisać własnej klasy MapPoint. W System.Device.Location znajduje się klasa GeoCoordinate, która przyjmuje szerokość i długość geograficzną jako parametry.
Lawyerson
1
To działa pięknie. Naprawdę doceniam port C #.
Tom Larcher
10

Napisałem funkcję JavaScript, która zwraca cztery współrzędne kwadratu ograniczającego, biorąc pod uwagę odległość i parę współrzędnych:

'use strict';

/**
 * @param {number} distance - distance (km) from the point represented by centerPoint
 * @param {array} centerPoint - two-dimensional array containing center coords [latitude, longitude]
 * @description
 *   Computes the bounding coordinates of all points on the surface of a sphere
 *   that has a great circle distance to the point represented by the centerPoint
 *   argument that is less or equal to the distance argument.
 *   Technique from: Jan Matuschek <http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates>
 * @author Alex Salisbury
*/

getBoundingBox = function (centerPoint, distance) {
  var MIN_LAT, MAX_LAT, MIN_LON, MAX_LON, R, radDist, degLat, degLon, radLat, radLon, minLat, maxLat, minLon, maxLon, deltaLon;
  if (distance < 0) {
    return 'Illegal arguments';
  }
  // helper functions (degrees<–>radians)
  Number.prototype.degToRad = function () {
    return this * (Math.PI / 180);
  };
  Number.prototype.radToDeg = function () {
    return (180 * this) / Math.PI;
  };
  // coordinate limits
  MIN_LAT = (-90).degToRad();
  MAX_LAT = (90).degToRad();
  MIN_LON = (-180).degToRad();
  MAX_LON = (180).degToRad();
  // Earth's radius (km)
  R = 6378.1;
  // angular distance in radians on a great circle
  radDist = distance / R;
  // center point coordinates (deg)
  degLat = centerPoint[0];
  degLon = centerPoint[1];
  // center point coordinates (rad)
  radLat = degLat.degToRad();
  radLon = degLon.degToRad();
  // minimum and maximum latitudes for given distance
  minLat = radLat - radDist;
  maxLat = radLat + radDist;
  // minimum and maximum longitudes for given distance
  minLon = void 0;
  maxLon = void 0;
  // define deltaLon to help determine min and max longitudes
  deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat));
  if (minLat > MIN_LAT && maxLat < MAX_LAT) {
    minLon = radLon - deltaLon;
    maxLon = radLon + deltaLon;
    if (minLon < MIN_LON) {
      minLon = minLon + 2 * Math.PI;
    }
    if (maxLon > MAX_LON) {
      maxLon = maxLon - 2 * Math.PI;
    }
  }
  // a pole is within the given distance
  else {
    minLat = Math.max(minLat, MIN_LAT);
    maxLat = Math.min(maxLat, MAX_LAT);
    minLon = MIN_LON;
    maxLon = MAX_LON;
  }
  return [
    minLon.radToDeg(),
    minLat.radToDeg(),
    maxLon.radToDeg(),
    maxLat.radToDeg()
  ];
};
asalisbury
źródło
Ten kod w ogóle nie działa. To znaczy, nawet po utrwaleniu się oczywistych błędów, jak minLon = void 0;i maxLon = MAX_LON;to nadal nie działa.
aroth
1
@aroth, właśnie go przetestowałem i nie miałem problemu. Pamiętaj, że centerPointargument to tablica składająca się z dwóch współrzędnych. Np. getBoundingBox([42.2, 34.5], 50)- void 0jest wyjściem CoffeeScript dla „undefined” i nie będzie miało wpływu na działanie kodu.
asalisbury,
ten kod nie działa. degLat.degToRadnie jest funkcją
user299709
Kod działał w Node i Chrome bez zmian, dopóki nie umieściłem go w projekcie, nad którym pracuję i nie zacząłem otrzymywać degToRadbłędów „nie jest funkcją”. Nigdy nie dowiedziałem się dlaczego, ale Number.prototype.nie jest to dobry pomysł na taką funkcję narzędzia, więc przekonwertowałem je na normalne funkcje lokalne. Należy również zauważyć, że zwracana skrzynka to [LNG, LAT, LNG, LAT] zamiast [LAT, LNG, LAT, LNG]. Zmodyfikowałem funkcję powrotu, gdy jej użyłem, aby uniknąć nieporozumień.
KernelDeimos
9

Ponieważ potrzebowałem bardzo przybliżonego oszacowania, więc aby odfiltrować niepotrzebne dokumenty w zapytaniu elastycznym wyszukiwaniu, zastosowałem poniższą formułę:

Min.lat = Given.Lat - (0.009 x N)
Max.lat = Given.Lat + (0.009 x N)
Min.lon = Given.lon - (0.009 x N)
Max.lon = Given.lon + (0.009 x N)

N = km wymagane od podanej lokalizacji. W twoim przypadku N = 10

Nie dokładne, ale poręczne.

Ajay
źródło
Rzeczywiście, nie jest to dokładne, ale nadal przydatne i bardzo łatwe do wdrożenia.
MV.
6

Szukasz elipsoidalnej formuły.

Najlepsze miejsce do rozpoczęcia kodowania, jakie znalazłem, jest oparte na bibliotece Geo :: Ellipsoid firmy CPAN. Daje podstawę do tworzenia testów i porównywania wyników z ich wynikami. Użyłem go jako podstawy dla podobnej biblioteki dla PHP u mojego poprzedniego pracodawcy.

Geo :: Elipsoida

Spójrz na locationmetodę. Zadzwoń dwa razy i masz swoją skrzynkę pocztową.

Nie opublikowałeś, jakiego języka używasz. Być może dostępna jest już biblioteka geokodowania.

Aha, i jeśli jeszcze tego nie rozgryzłeś, mapy Google używają elipsoidy WGS84.

jcoby
źródło
5

Ilustracja przedstawiająca @Jana Philipa Matuscheka doskonałe wyjaśnienie (proszę o głosowanie w górę jego odpowiedzi, a nie to; dodam to, ponieważ poświęciłem trochę czasu na zrozumienie oryginalnej odpowiedzi)

Technika prostokąta granicznego optymalizacji znajdowania najbliższych sąsiadów wymagałaby wyznaczenia minimalnej i maksymalnej pary szerokości i długości geograficznej dla punktu P w odległości d. Wszystkie punkty leżące poza nimi są zdecydowanie w odległości większej niż d od tego punktu. Należy tu zwrócić uwagę na obliczenie szerokości geograficznej przecięcia, co zostało podkreślone w wyjaśnieniu Jana Philipa Matuscheka. Szerokość geograficzna przecięcia nie jest równa szerokości geograficznej punktu P, ale jest nieco od niej przesunięta. Jest to często pomijana, ale ważna część przy określaniu prawidłowej minimalnej i maksymalnej długości granicznej punktu P dla odległości d. Jest to również przydatne w weryfikacji.

Odległość haversine między (szerokość geograficzna przecięcia, długość geograficzna wzwyż) a (szerokość, długość geograficzna) punktu P jest równa odległości d.

Streszczenie Pythona tutaj https://gist.github.com/alexcpn/f95ae83a7ee0293a5225

wprowadź opis obrazu tutaj

Alex Punnen
źródło
5

Oto prosta implementacja za pomocą javascript, która opiera się na konwersji stopni szerokości geograficznej na km, gdzie 1 degree latitude ~ 111.2 km .

Granice mapy obliczam z podanej szerokości i długości geograficznej na 10km szerokości.

function getBoundsFromLatLng(lat, lng, radiusInKm){
     var lat_change = radiusInKm/111.2;
     var lon_change = Math.abs(Math.cos(lat*(Math.PI/180)));
     var bounds = { 
         lat_min : lat - lat_change,
         lon_min : lng - lon_change,
         lat_max : lat + lat_change,
         lon_max : lng + lon_change
     };
     return bounds;
}
Noushad
źródło
4

Zaadaptowałem skrypt PHP, który znalazłem, aby to zrobić. Możesz go użyć do znalezienia rogów pudełka wokół punktu (powiedzmy 20 km na zewnątrz). Mój konkretny przykład dotyczy interfejsu API Map Google:

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

Richard
źródło
-1 To, czego szuka OP, to: mając punkt odniesienia (szer., Dł.) I odległość, znajdź najmniejszy prostokąt, tak aby wszystkie punkty, które są <= "odległość" od punktu odniesienia, nie znajdowały się poza ramką. Twoje pudełko ma swoje rogi w „odległości” od punktu odniesienia i dlatego jest za małe. Przykład: punkt znajdujący się w „odległości” na północ jest daleko poza Twoim polem.
John Machin
Cóż, przypadkiem właśnie tego potrzebowałem. Więc dziękuję, nawet jeśli nie do końca odpowiada na to pytanie :)
Simon Steinberger
Cóż, cieszę się, że to mogło komuś pomóc!
Richard
1

Pracowałem nad problemem z obwiednią jako pobocznym problemem znalezienia wszystkich punktów w promieniu SrcRad statycznego punktu LAT, LONG. Było sporo obliczeń, które używają

maxLon = $lon + rad2deg($rad/$R/cos(deg2rad($lat)));
minLon = $lon - rad2deg($rad/$R/cos(deg2rad($lat)));

aby obliczyć granice długości geograficznej, ale okazało się, że nie daje to wszystkich potrzebnych odpowiedzi. Ponieważ naprawdę chcesz to zrobić

(SrcRad/RadEarth)/cos(deg2rad(lat))

Wiem, wiem, że odpowiedź powinna być taka sama, ale stwierdziłem, że tak nie było. Okazało się, że nie upewniając się, że wykonuję najpierw (SRCrad / RadEarth), a następnie dzieląc przez część Cos, pomijam niektóre punkty lokalizacji.

Po uzyskaniu wszystkich punktów obwiedni, jeśli masz funkcję, która oblicza odległość między punktami na podstawie szerokości, łatwo jest uzyskać tylko te punkty, które mają określony promień odległości od stałego punktu. Oto co zrobiłem. Wiem, że wymagało to kilku dodatkowych kroków, ale pomogło mi

-- GLOBAL Constants
gc_pi CONSTANT REAL := 3.14159265359;  -- Pi

-- Conversion Factor Constants
gc_rad_to_degs          CONSTANT NUMBER := 180/gc_pi; -- Conversion for Radians to Degrees 180/pi
gc_deg_to_rads          CONSTANT NUMBER := gc_pi/180; --Conversion of Degrees to Radians

lv_stat_lat    -- The static latitude point that I am searching from 
lv_stat_long   -- The static longitude point that I am searching from 

-- Angular radius ratio in radians
lv_ang_radius := lv_search_radius / lv_earth_radius;
lv_bb_maxlat := lv_stat_lat + (gc_rad_to_deg * lv_ang_radius);
lv_bb_minlat := lv_stat_lat - (gc_rad_to_deg * lv_ang_radius);

--Here's the tricky part, accounting for the Longitude getting smaller as we move up the latitiude scale
-- I seperated the parts of the equation to make it easier to debug and understand
-- I may not be a smart man but I know what the right answer is... :-)

lv_int_calc := gc_deg_to_rads * lv_stat_lat;
lv_int_calc := COS(lv_int_calc);
lv_int_calc := lv_ang_radius/lv_int_calc;
lv_int_calc := gc_rad_to_degs*lv_int_calc;

lv_bb_maxlong := lv_stat_long + lv_int_calc;
lv_bb_minlong := lv_stat_long - lv_int_calc;

-- Now select the values from your location datatable 
SELECT *  FROM (
SELECT cityaliasname, city, state, zipcode, latitude, longitude, 
-- The actual distance in miles
spherecos_pnttopntdist(lv_stat_lat, lv_stat_long, latitude, longitude, 'M') as miles_dist    
FROM Location_Table 
WHERE latitude between lv_bb_minlat AND lv_bb_maxlat
AND   longitude between lv_bb_minlong and lv_bb_maxlong)
WHERE miles_dist <= lv_limit_distance_miles
order by miles_dist
;
Greg Beck
źródło
0

To bardzo proste, wystarczy wejść na stronę panoramio, a następnie otworzyć Mapę Świata ze strony panoramio, a następnie udać się do określonej lokalizacji o wymaganej szerokości i długości geograficznej.

Następnie znalazłeś szerokość i długość geograficzną w pasku adresu, na przykład w tym adresie.

http://www.panoramio.com/map#lt=32.739485&ln=70.491211&z=9&k=1&a=1&tab=1&pl=all

lt = 32,739485 => szerokość geograficzna ln = 70,491211 => długość geograficzna

ten widżet Panoramio JavaScript API tworzy ramkę ograniczającą wokół pary szerokości i długości, a następnie zwraca wszystkie zdjęcia w tych granicach.

Inny typ widżetu Panoramio JavaScript API, w którym można również zmienić kolor tła za pomocą przykładu i kodu, znajduje się tutaj .

Nie pojawia się w nastroju kompozycyjnym, po opublikowaniu.

<div dir="ltr" style="text-align: center;" trbidi="on">
<script src="https://ssl.panoramio.com/wapi/wapi.js?v=1&amp;hl=en"></script>
<div id="wapiblock" style="float: right; margin: 10px 15px"></div>
<script type="text/javascript">
var myRequest = {
  'tag': 'kahna',
  'rect': {'sw': {'lat': -30, 'lng': 10.5}, 'ne': {'lat': 50.5, 'lng': 30}}
};
  var myOptions = {
  'width': 300,
  'height': 200
};
var wapiblock = document.getElementById('wapiblock');
var photo_widget = new panoramio.PhotoWidget('wapiblock', myRequest, myOptions);
photo_widget.setPosition(0);
</script>
</div>
kahna9
źródło
0

Tutaj przekonwertowałem odpowiedź Federico A. Ramponiego na PHP, jeśli ktoś jest zainteresowany:

<?php
# deg2rad and rad2deg are already within PHP

# Semi-axes of WGS-84 geoidal reference
$WGS84_a = 6378137.0;  # Major semiaxis [m]
$WGS84_b = 6356752.3;  # Minor semiaxis [m]

# Earth radius at a given latitude, according to the WGS-84 ellipsoid [m]
function WGS84EarthRadius($lat)
{
    global $WGS84_a, $WGS84_b;

    $an = $WGS84_a * $WGS84_a * cos($lat);
    $bn = $WGS84_b * $WGS84_b * sin($lat);
    $ad = $WGS84_a * cos($lat);
    $bd = $WGS84_b * sin($lat);

    return sqrt(($an*$an + $bn*$bn)/($ad*$ad + $bd*$bd));
}

# Bounding box surrounding the point at given coordinates,
# assuming local approximation of Earth surface as a sphere
# of radius given by WGS84
function boundingBox($latitudeInDegrees, $longitudeInDegrees, $halfSideInKm)
{
    $lat = deg2rad($latitudeInDegrees);
    $lon = deg2rad($longitudeInDegrees);
    $halfSide = 1000 * $halfSideInKm;

    # Radius of Earth at given latitude
    $radius = WGS84EarthRadius($lat);
    # Radius of the parallel at given latitude
    $pradius = $radius*cos($lat);

    $latMin = $lat - $halfSide / $radius;
    $latMax = $lat + $halfSide / $radius;
    $lonMin = $lon - $halfSide / $pradius;
    $lonMax = $lon + $halfSide / $pradius;

    return array(rad2deg($latMin), rad2deg($lonMin), rad2deg($latMax), rad2deg($lonMax));
}
?>
Joe Black
źródło
0

Dzięki @Fedrico A. za wdrożenie Phyton, przeniosłem go do klasy kategorii Objective C. Tutaj jest:

#import "LocationService+Bounds.h"

//Semi-axes of WGS-84 geoidal reference
const double WGS84_a = 6378137.0; //Major semiaxis [m]
const double WGS84_b = 6356752.3; //Minor semiaxis [m]

@implementation LocationService (Bounds)

struct BoundsLocation {
    double maxLatitude;
    double minLatitude;
    double maxLongitude;
    double minLongitude;
};

+ (struct BoundsLocation)locationBoundsWithLatitude:(double)aLatitude longitude:(double)aLongitude maxDistanceKm:(NSInteger)aMaxKmDistance {
    return [self boundingBoxWithLatitude:aLatitude longitude:aLongitude halfDistanceKm:aMaxKmDistance/2];
}

#pragma mark - Algorithm 

+ (struct BoundsLocation)boundingBoxWithLatitude:(double)aLatitude longitude:(double)aLongitude halfDistanceKm:(double)aDistanceKm {
    double radianLatitude = [self degreesToRadians:aLatitude];
    double radianLongitude = [self degreesToRadians:aLongitude];
    double halfDistanceMeters = aDistanceKm*1000;


    double earthRadius = [self earthRadiusAtLatitude:radianLatitude];
    double parallelRadius = earthRadius*cosl(radianLatitude);

    double radianMinLatitude = radianLatitude - halfDistanceMeters/earthRadius;
    double radianMaxLatitude = radianLatitude + halfDistanceMeters/earthRadius;
    double radianMinLongitude = radianLongitude - halfDistanceMeters/parallelRadius;
    double radianMaxLongitude = radianLongitude + halfDistanceMeters/parallelRadius;

    struct BoundsLocation bounds;
    bounds.minLatitude = [self radiansToDegrees:radianMinLatitude];
    bounds.maxLatitude = [self radiansToDegrees:radianMaxLatitude];
    bounds.minLongitude = [self radiansToDegrees:radianMinLongitude];
    bounds.maxLongitude = [self radiansToDegrees:radianMaxLongitude];

    return bounds;
}

+ (double)earthRadiusAtLatitude:(double)aRadianLatitude {
    double An = WGS84_a * WGS84_a * cosl(aRadianLatitude);
    double Bn = WGS84_b * WGS84_b * sinl(aRadianLatitude);
    double Ad = WGS84_a * cosl(aRadianLatitude);
    double Bd = WGS84_b * sinl(aRadianLatitude);
    return sqrtl( ((An * An) + (Bn * Bn))/((Ad * Ad) + (Bd * Bd)) );
}

+ (double)degreesToRadians:(double)aDegrees {
    return M_PI*aDegrees/180.0;
}

+ (double)radiansToDegrees:(double)aRadians {
    return 180.0*aRadians/M_PI;
}



@end

Przetestowałem to i wygląda na to, że działa dobrze. Struct BoundsLocation należy zastąpić klasą, użyłem jej tylko do udostępnienia go tutaj.

Jesuslg123
źródło
0

Wszystkie powyższe odpowiedzi są tylko częściowo poprawne . Szczególnie w regionie takim jak Australia, zawsze zawierają biegun i obliczają bardzo duży prostokąt nawet dla 10 km.

W szczególności algorytm Jana Philipa Matuscheka pod adresem http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex obejmował bardzo duży prostokąt z (-37, -90, -180, 180) dla prawie każdego punktu w Australii. Uderza to w dużych użytkowników w bazie danych i trzeba obliczyć odległość dla wszystkich użytkowników w prawie połowie kraju.

Odkryłem, że algorytm Earth Algorithm API Drupala opracowany przez Rochester Institute of Technology działa lepiej zarówno na biegunie, jak i gdzie indziej i jest znacznie łatwiejszy do wdrożenia.

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

Użyj earth_latitude_rangei earth_longitude_rangez powyższego algorytmu do obliczania prostokąta ograniczającego

I użyj wzoru obliczania odległości udokumentowanego przez mapy Google, aby obliczyć odległość

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

Aby wyszukiwać według kilometrów zamiast mil, zamień 3959 na 6371. Dla (Lat, Lng) = (37, -122) i tabeli Znaczniki z kolumnami lat i lng , wzór jest następujący:

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;

Przeczytaj moją szczegółową odpowiedź na https://stackoverflow.com/a/45950426/5076414

Sacky San
źródło
0

Oto odpowiedź Federico Ramponiego w Go. Uwaga: bez sprawdzania błędów :(

import (
    "math"
)

// Semi-axes of WGS-84 geoidal reference
const (
    // Major semiaxis (meters)
    WGS84A = 6378137.0
    // Minor semiaxis (meters)
    WGS84B = 6356752.3
)

// BoundingBox represents the geo-polygon that encompasses the given point and radius
type BoundingBox struct {
    LatMin float64
    LatMax float64
    LonMin float64
    LonMax float64
}

// Convert a degree value to radians
func deg2Rad(deg float64) float64 {
    return math.Pi * deg / 180.0
}

// Convert a radian value to degrees
func rad2Deg(rad float64) float64 {
    return 180.0 * rad / math.Pi
}

// Get the Earth's radius in meters at a given latitude based on the WGS84 ellipsoid
func getWgs84EarthRadius(lat float64) float64 {
    an := WGS84A * WGS84A * math.Cos(lat)
    bn := WGS84B * WGS84B * math.Sin(lat)

    ad := WGS84A * math.Cos(lat)
    bd := WGS84B * math.Sin(lat)

    return math.Sqrt((an*an + bn*bn) / (ad*ad + bd*bd))
}

// GetBoundingBox returns a BoundingBox encompassing the given lat/long point and radius
func GetBoundingBox(latDeg float64, longDeg float64, radiusKm float64) BoundingBox {
    lat := deg2Rad(latDeg)
    lon := deg2Rad(longDeg)
    halfSide := 1000 * radiusKm

    // Radius of Earth at given latitude
    radius := getWgs84EarthRadius(lat)

    pradius := radius * math.Cos(lat)

    latMin := lat - halfSide/radius
    latMax := lat + halfSide/radius
    lonMin := lon - halfSide/pradius
    lonMax := lon + halfSide/pradius

    return BoundingBox{
        LatMin: rad2Deg(latMin),
        LatMax: rad2Deg(latMax),
        LonMin: rad2Deg(lonMin),
        LonMax: rad2Deg(lonMax),
    }
}
sma
źródło