可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Given a coordinate (lat, long), I am trying to calculate a square bounding box that is a given distance (e.g. 50km) away from the coordinate. So as input I have lat, long and distance and as output I would like two coordinates; one being the south-west (bottom-left) corner and one being the north-east (top-right) corner. I have seen a couple of answers on here that try to address this question in Python, but I am looking for a Java implementation in particular.
Just to be clear, I intend on using the algorithm on Earth only and so I don't need to accommodate a variable radius.
It doesn't have to be hugely accurate (+/-20% is fine) and it'll only be used to calculate bounding boxes over small distances (no more than 150km). So I'm happy to sacrifice some accuracy for an efficient algorithm. Any help is much appreciated.
Edit: I should have been clearer, I really am after a square, not a circle. I understand that the distance between the center of a square and various points along the square's perimeter is not a constant value like it is with a circle. I guess what I mean is a square where if you draw a line from the center to any one of the four points on the perimeter that results in a line perpendicular to a side of the perimeter, then those 4 lines have the same length.
回答1:
I wrote an article about finding the bounding coordinates:
http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates
The article explains the formulae and also provides a Java implementation. (It also shows why IronMan's formula for the min/max longitude is inaccurate.)
回答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);
Although I would also recommend JTS.
回答3:
import com.vividsolutions.jts.geom.Envelope;
...
Envelope env = new Envelope(centerPoint.getCoordinate());
env.expandBy(distance_in_degrees);
...
Now env contains your envelope. It's not actually a "square" (whatever that means on the surface of a sphere), but it should do.
You should note that the distance in degrees will depend on the latitude of the center point. At the equator, 1 degree of latitude is about 111km, but in New York, it's only about 75km.
The really cool thing is that you can toss all your points into a com.vividsolutions.jts.index.strtree.STRtree
and then use it to quickly calculate points inside that Envelope.
回答4:
I have a PHP script and example which does this. Given a starting point, it calculates the corners of a box around it out to a particular distance. It is specifically for Google Maps, but it could work for anything else:
http://www.richardpeacock.com/blog/2011/11/draw-box-around-coordinate-google-maps-based-miles-or-kilometers
回答5:
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);
Although I would also recommend JTS.
This calculates but Google Earth does not accept and do not map the 3D model.
/*
* 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);
}
}
It prints
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>
回答6:
All of the previous answers are only partially correct. Specially in region like Australia, they always include pole and calculate a very large rectangle even for 10kms.
Specially the algorithm by Jan Philip Matuschek at http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex included a very large rectangle from (-37, -90, -180, 180) for almost every point in Australia. This hits a large users in database and distance have to be calculated for all of the users in almost half the country.
I found that the Drupal API Earth Algorithm by Rochester Institute of Technology works better around pole as well as elsewhere and is much easier to implement.
https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54
Use earth_latitude_range
and earth_longitude_range
from the above algorithm for calculating bounding rectangle
Here is the implementation is 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;
}
And use the distance calculation formula documented by google maps to calculate distance
https://developers.google.com/maps/solutions/store-locator/clothing-store-locator#outputting-data-as-xml-using-php
To search by kilometers instead of miles, replace 3959 with 6371.
For (Lat, Lng) = (37, -122) and a Markers table with columns lat and lng, the formula is:
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;
回答7:
Here is a simple solution that I used to generate bounding box coordinates that I use with GeoNames citieJSON API to get nearby big cities from a gps decimal coordinate.
This is a Java method from my GitHub repository: FusionTableModifyJava
I had a decimal GPS location and I needed to find the biggest city/state "near" that location. I needed a relatively accurate bounding box to pass to the citiesJSON GeoNames webservice to get back the biggest city in that bounding box. I pass the location and the "radius" I am interested in (in km) and it gives back the north, south, east, west decimal coordinates needed to pass to citiesJSON.
(I found these resources useful in doing my research:
Calculate distance, bearing and more between Latitude/Longitude points.
Longitude - Wikipedia)
It is not super accurate but accurate enough for what I was using it for:
// 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;
}
}