Làm thế nào để tính toán hộp giới hạn cho một vị trí vĩ độ / lng nhất định?


101

Tôi đã đưa ra một vị trí được xác định theo vĩ độ và kinh độ. Bây giờ tôi muốn tính một hộp giới hạn trong phạm vi ví dụ 10 km từ điểm đó.

Hộp giới hạn phải được xác định là latmin, lngmin và latmax, lngmax.

Tôi cần công cụ này để sử dụng API panoramio .

Có ai đó biết công thức làm thế nào để có được điểm thos?

Chỉnh sửa: Các bạn ơi, tôi đang tìm một công thức / hàm lấy lat & lng làm đầu vào và trả về một hộp giới hạn là latmin & lngmin và latmax & latmin. Mysql, php, c #, javascript cũng được nhưng mã giả cũng được.

Chỉnh sửa: Tôi không tìm kiếm giải pháp cho tôi biết khoảng cách của 2 điểm


Nếu bạn đang sử dụng cơ sở dữ liệu địa lý ở đâu đó, chúng chắc chắn có tích hợp tính toán hộp giới hạn. Bạn thậm chí có thể kiểm tra nguồn của PostGIS / GEOS chẳng hạn.
Vinko Vrsalovic

Câu trả lời:


60

Tôi đề nghị nên tính gần đúng cục bộ bề mặt Trái đất như một hình cầu có bán kính được cho bởi ellipsoid WGS84 ở vĩ độ nhất định. Tôi nghi ngờ rằng việc tính toán chính xác latMin và latMax sẽ yêu cầu các hàm elliptic và sẽ không mang lại sự gia tăng đáng kể về độ chính xác (bản thân WGS84 là một ước tính).

Cách triển khai của tôi như sau (Nó được viết bằng Python; tôi chưa thử nghiệm nó):

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

CHỈNH SỬA: Đoạn mã sau chuyển đổi (độ, số nguyên tố, giây) thành độ + phân số của độ và ngược lại (không được kiểm tra):

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

4
Như đã chỉ ra trong tài liệu của thư viện CPAN được đề xuất, điều này chỉ có ý nghĩa đối với HalfSide <= 10km.
Federico A. Ramponi

1
Điều này có hoạt động gần các cực không? Có vẻ như không, vì có vẻ như nó kết thúc với latMin <-pi (cho cực nam) hoặc latMax> pi (cho cực bắc)? Tôi nghĩ rằng khi bạn ở trong một nửa Mặt của một cực, bạn cần trả về một hộp giới hạn bao gồm tất cả các kinh độ và vĩ độ được tính thông thường cho phía xa cực và tại cực ở phía gần cực.
Doug McClean

1
Đây là một triển khai PHP từ đặc tả được tìm thấy tại JanMatuschek.de: github.com/anthonymartin/GeoLocation.class.php
Anthony Martin

2
Tôi đã thêm một triển khai C # cho câu trả lời này ở bên dưới.
Ε Г И І И О

2
@ FedericoA.Ramponi haldSideinKm ở đây là gì? không hiểu ... những gì tôi phải vượt qua trong argyments này, bán kính giữa hai điểm trong bản đồ hay gì?

53

Tôi đã viết một bài báo về việc tìm tọa độ giới hạn:

http://JanMatuschek.de/LatitudeLongitudeBoundsCoferences

Bài viết giải thích các công thức và cũng cung cấp một triển khai Java. (Nó cũng cho thấy lý do tại sao công thức của Federico cho kinh độ tối thiểu / tối đa là không chính xác.)


4
Tôi đã tạo một cổng PHP của lớp GeoLocation của bạn. Nó có thể được tìm thấy ở đây: pastie.org/5416584
Anthony Martin

1
Tôi đã tải nó lên github ngay bây giờ: github.com/anthonymartin/GeoLocation.class.php
Anthony Martin

1
Điều này thậm chí có trả lời câu hỏi? Nếu chúng ta chỉ có 1 điểm bắt đầu, chúng ta không thể tính khoảng cách vòng tròn lớn như được thực hiện trong mã này, điều đó yêu cầu hai vị trí vĩ độ, dài.
mdoran3844

có một mã không hợp lệ trong biến thể C # của bạn, ví dụ:, public override string ToString()rất tệ khi ghi đè một phương thức toàn cục như vậy chỉ cho một mục đích, tốt hơn là chỉ cần thêm một phương thức khác, sau đó ghi đè phương thức chuẩn, có thể được sử dụng trong các phần khác của ứng dụng, không cho GIS chính xác ...

Đây là liên kết cập nhật tới cổng PHP của lớp GeoLocaiton của Jan: github.com/anthonymartin/GeoLocation.php
Anthony Martin

34

Ở đây tôi đã chuyển đổi câu trả lời của Federico A. Ramponi thành C # cho bất kỳ ai quan tâm:

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

1
Cảm ơn, công việc này cho tôi. Phải kiểm tra mã bằng tay, không biết làm thế nào để viết một bài kiểm tra đơn vị này, nhưng nó tạo ra kết quả chính xác đến mức độ chính xác tôi cần
mdoran3844

haldSideinKm ở đây là gì? không hiểu ... những gì tôi phải vượt qua trong argyments này, bán kính giữa hai điểm trong bản đồ hay gì?

@GeloVolro: Đó là nửa chiều dài của hộp giới hạn mà bạn muốn.
Ε Г И І И О

1
Bạn không nhất thiết phải viết lớp MapPoint của riêng mình. Có một lớp Địa lý trong System.Device.Location lấy Vĩ độ và Kinh độ làm tham số.
Lawyerson

1
Điều này hoạt động tuyệt vời. Tôi thực sự đánh giá cao cổng C #.
Tom Larcher

10

Tôi đã viết một hàm JavaScript trả về bốn tọa độ của một hộp giới hạn hình vuông, cho trước một khoảng cách và một cặp tọa độ:

'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()
  ];
};

Mã này hoàn toàn không hoạt động. Ý tôi là, ngay cả sau khi sửa chữa lên các lỗi hiển nhiên như minLon = void 0;maxLon = MAX_LON;vẫn không làm việc.
aroth

1
@aroth, tôi vừa thử nghiệm nó và không có vấn đề gì. Hãy nhớ rằng centerPointđối số là một mảng bao gồm hai tọa độ. Ví dụ, getBoundingBox([42.2, 34.5], 50)- void 0là đầu ra CoffeeScript cho "không xác định" và sẽ không ảnh hưởng đến khả năng chạy mã.
asalisbury

mã này không hoạt động. degLat.degToRadkhông phải là một chức năng
user299709

Mã đã hoạt động trong Node và Chrome như hiện tại, cho đến khi tôi đặt nó vào bên trong một dự án mà tôi đang thực hiện và bắt đầu nhận được lỗi " degToRadkhông phải là một chức năng". Không bao giờ tìm ra lý do tại sao nhưng Number.prototype.không phải là một ý tưởng hay cho một hàm tiện ích như thế này, vì vậy tôi đã chuyển đổi chúng thành các hàm cục bộ bình thường. Ngoài ra, điều quan trọng cần lưu ý là hộp được trả lại là [LNG, LAT, LNG, LAT] thay vì [LAT, LNG, LAT, LNG]. Tôi đã sửa đổi hàm trả về khi sử dụng nó để tránh nhầm lẫn.
KernelDeimos

9

Vì tôi cần một ước tính rất sơ bộ, vì vậy để lọc ra một số tài liệu không cần thiết trong truy vấn tìm kiếm đàn hồi, tôi đã sử dụng công thức dưới đây:

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 cần thiết tạo thành vị trí đã cho. Đối với trường hợp của bạn N = 10

Không chính xác nhưng tiện dụng.


Quả thực, không chính xác nhưng vẫn hữu ích và rất dễ thực hiện.
MV.

6

Bạn đang tìm kiếm một công thức ellipsoid.

Nơi tốt nhất tôi đã tìm thấy để bắt đầu viết mã là dựa trên thư viện Geo :: Ellipsoid từ CPAN. Nó cung cấp cho bạn một đường cơ sở để tạo các thử nghiệm của bạn và so sánh kết quả của bạn với kết quả của nó. Tôi đã sử dụng nó làm cơ sở cho một thư viện tương tự cho PHP tại công ty trước đây của tôi.

Địa lý :: Ellipsoid

Hãy xem locationphương pháp. Gọi nó hai lần và bạn đã có bbox của mình.

Bạn đã không đăng ngôn ngữ bạn đang sử dụng. Có thể đã có một thư viện mã hóa địa lý dành cho bạn.

Ồ, và nếu bây giờ bạn vẫn chưa tìm ra, thì bản đồ của Google sử dụng ellipsoid WGS84.


5

Hình minh họa về lời giải thích xuất sắc của @Jan Philip Matuschek. (Vui lòng bỏ phiếu cho câu trả lời của anh ấy, không phải câu trả lời này; tôi đang thêm điều này vì tôi đã mất một chút thời gian để hiểu câu trả lời ban đầu)

Kỹ thuật hộp giới hạn để tối ưu hóa việc tìm kiếm các láng giềng gần nhất sẽ cần tính toán các cặp kinh độ, vĩ độ tối thiểu và tối đa, cho một điểm P ở khoảng cách d. Tất cả các điểm nằm ngoài các điểm này chắc chắn cách điểm đó một khoảng lớn hơn d. Một điều cần lưu ý ở đây là việc tính toán vĩ độ của giao lộ như được nhấn mạnh trong giải thích của Jan Philip Matuschek. Giao điểm có vĩ độ không nằm ở vĩ độ của điểm P mà hơi lệch so với nó. Đây là phần thường bị bỏ sót nhưng quan trọng trong việc xác định chính xác kinh độ giới hạn nhỏ nhất và lớn nhất cho điểm P trong khoảng cách d. Điều này cũng hữu ích trong việc xác minh.

Khoảng cách hasrsine giữa (vĩ độ giao nhau, kinh độ cao) đến (vĩ độ, kinh độ) của P bằng khoảng cách d.

Ý chính của Python tại đây https://gist.github.com/alexcpn/f95ae83a7ee0293a5225

nhập mô tả hình ảnh ở đây


5

Đây là một triển khai đơn giản bằng cách sử dụng javascript dựa trên việc chuyển đổi độ vĩ độ sang km ở đâu 1 degree latitude ~ 111.2 km.

Tôi đang tính toán các giới hạn của bản đồ từ một vĩ độ và kinh độ nhất định với chiều rộng 10km.

function getBoundsFromLatLng(lat, lng){
     var lat_change = 10/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;
}

4

Tôi đã điều chỉnh một tập lệnh PHP mà tôi tìm thấy để làm điều này. Bạn có thể sử dụng nó để tìm các góc của hộp xung quanh một điểm (giả sử cách đó 20 km). Ví dụ cụ thể của tôi dành cho API Google Maps:

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


-1 Điều OP đang tìm là: cho trước một điểm tham chiếu (vĩ độ, vĩ độ) và một khoảng cách, hãy tìm hộp nhỏ nhất sao cho tất cả các điểm cách xa điểm tham chiếu <= "khoảng cách" đều không nằm ngoài hộp. Hộp của bạn có "khoảng cách" các góc so với điểm tham chiếu và do đó quá nhỏ. Ví dụ: điểm có "khoảng cách" do phía bắc nằm ngoài hộp của bạn.
John Machin

Thật tình cờ, đó chính xác là thứ tôi cần. Vì vậy, cảm ơn bạn, ngay cả khi nó không hoàn toàn trả lời câu hỏi này :)
Simon Steinberger

Chà, tôi rất vui vì nó có thể giúp được ai đó!
Richard

1

Tôi đang giải quyết vấn đề hộp giới hạn như một vấn đề phụ để tìm tất cả các điểm trong bán kính SrcRad của một điểm LAT, LONG tĩnh. Đã có khá nhiều phép tính sử dụng

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

để tính toán giới hạn kinh độ, nhưng tôi thấy điều này không đưa ra tất cả các câu trả lời cần thiết. Bởi vì những gì bạn thực sự muốn làm là

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

Tôi biết, tôi biết câu trả lời phải giống nhau, nhưng tôi thấy rằng không phải. Có vẻ như do không chắc chắn rằng tôi đang thực hiện (SRCrad / RadEarth) Đầu tiên và sau đó chia cho phần Cos, tôi đã bỏ qua một số điểm vị trí.

Sau khi bạn nhận được tất cả các điểm hộp giới hạn của mình, nếu bạn có một chức năng tính toán Khoảng cách từ điểm đến điểm đã cho, thì rất dễ dàng để chỉ lấy những điểm có bán kính khoảng cách nhất định từ điểm cố định. Đây là những gì tôi đã làm. Tôi biết phải mất thêm một vài bước nhưng nó đã giúp tôi

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

0

Nó rất đơn giản chỉ cần truy cập trang web panoramio và sau đó mở Bản đồ thế giới từ trang web panoramio, sau đó đi đến vị trí cụ thể theo vĩ độ và kinh độ cần thiết.

Sau đó, bạn tìm thấy vĩ độ và kinh độ trong thanh địa chỉ, ví dụ trong địa chỉ này.

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

lt = 32,739485 => vĩ độ ln = 70,491211 => kinh độ

tiện ích API Panoramio JavaScript này tạo một hộp giới hạn xung quanh một cặp vĩ độ / kinh độ và sau đó trả về tất cả các ảnh có trong các giới hạn đó.

Một loại tiện ích con Panoramio JavaScript API khác, trong đó bạn cũng có thể thay đổi màu nền với ví dụ và mã ở đây .

Nó không hiển thị trong tâm trạng sáng tác mà hiển thị sau khi xuất bản.

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

0

Ở đây tôi đã chuyển đổi câu trả lời của Federico A. Ramponi sang PHP nếu ai đó quan tâm:

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

0

Cảm ơn @Fedrico A. về việc triển khai Phyton, tôi đã chuyển nó vào một lớp thể loại Objective C. Đây là:

#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

Tôi đã thử nghiệm nó và có vẻ hoạt động tốt. Struct BoundsLocation nên được thay thế bằng một lớp, tôi đã sử dụng nó chỉ để chia sẻ nó ở đây.


0

Tất cả câu trả lời trên chỉ đúng một phần . Đặc biệt ở khu vực như Úc, họ luôn bao gồm cực và tính toán một hình chữ nhật rất lớn dù chỉ 10kms.

Đặc biệt, thuật toán của Jan Philip Matuschek tại địa chỉ http://janmatuschek.de/LatitudeLongitudeBoundsCoferences#UsingIndex bao gồm một hình chữ nhật rất lớn từ (-37, -90, -180, 180) cho hầu hết mọi điểm ở Úc. Điều này đánh vào một lượng lớn người dùng trong cơ sở dữ liệu và khoảng cách phải được tính cho tất cả người dùng ở gần một nửa quốc gia.

Tôi nhận thấy rằng Thuật toán Trái đất Drupal API của Học viện Công nghệ Rochester hoạt động tốt hơn xung quanh cực cũng như các nơi khác và dễ thực hiện hơn nhiều.

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

Sử dụng earth_latitude_rangeearth_longitude_rangetừ thuật toán trên để tính toán hình chữ nhật giới hạn

Và sử dụng công thức tính khoảng cách do google maps ghi lại lại để tính khoảng cách

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

Để tìm kiếm theo km thay vì dặm, thay thế 3959 với 6371. Đối với (LAT, LNG) = (37, -122) và một bảng với các cột Markers lat và lng , công thức là:

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;

Đọc câu trả lời chi tiết của tôi tại https://stackoverflow.com/a/45950426/5076414


0

Đây là câu trả lời của Federico Ramponi trong cờ vây. Lưu ý: không kiểm tra lỗi :(

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),
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.