是否有一种算法可以确定一组纬度/经度坐标周围的最小边界矩形?
假设地球是平的,因为这些坐标不会相距太远。伪代码可以使用,但如果有人用Objective-C完成了这个功能,那就更好了。我要做的是根据将在地图上显示的点数设置地图的缩放级别。
是否有一种算法可以确定一组纬度/经度坐标周围的最小边界矩形?
假设地球是平的,因为这些坐标不会相距太远。伪代码可以使用,但如果有人用Objective-C完成了这个功能,那就更好了。我要做的是根据将在地图上显示的点数设置地图的缩放级别。
- (void)centerMapAroundAnnotations
{
// if we have no annotations we can skip all of this
if ( [[myMapView annotations] count] == 0 )
return;
// then run through each annotation in the list to find the
// minimum and maximum latitude and longitude values
CLLocationCoordinate2D min;
CLLocationCoordinate2D max;
BOOL minMaxInitialized = NO;
NSUInteger numberOfValidAnnotations = 0;
for ( id<MKAnnotation> a in [myMapView annotations] )
{
// only use annotations that are of our own custom type
// in the event that the user is browsing from a location far away
// you can omit this if you want the user's location to be included in the region
if ( [a isKindOfClass: [ECAnnotation class]] )
{
// if we haven't grabbed the first good value, do so now
if ( !minMaxInitialized )
{
min = a.coordinate;
max = a.coordinate;
minMaxInitialized = YES;
}
else // otherwise compare with the current value
{
min.latitude = MIN( min.latitude, a.coordinate.latitude );
min.longitude = MIN( min.longitude, a.coordinate.longitude );
max.latitude = MAX( max.latitude, a.coordinate.latitude );
max.longitude = MAX( max.longitude, a.coordinate.longitude );
}
++numberOfValidAnnotations;
}
}
// If we don't have any valid annotations we can leave now,
// this will happen in the event that there is only the user location
if ( numberOfValidAnnotations == 0 )
return;
// Now that we have a min and max lat/lon create locations for the
// three points in a right triangle
CLLocation* locSouthWest = [[CLLocation alloc]
initWithLatitude: min.latitude
longitude: min.longitude];
CLLocation* locSouthEast = [[CLLocation alloc]
initWithLatitude: min.latitude
longitude: max.longitude];
CLLocation* locNorthEast = [[CLLocation alloc]
initWithLatitude: max.latitude
longitude: max.longitude];
// Create a region centered at the midpoint of our hypotenuse
CLLocationCoordinate2D regionCenter;
regionCenter.latitude = (min.latitude + max.latitude) / 2.0;
regionCenter.longitude = (min.longitude + max.longitude) / 2.0;
// Use the locations that we just created to calculate the distance
// between each of the points in meters.
CLLocationDistance latMeters = [locSouthEast getDistanceFrom: locNorthEast];
CLLocationDistance lonMeters = [locSouthEast getDistanceFrom: locSouthWest];
MKCoordinateRegion region;
region = MKCoordinateRegionMakeWithDistance( regionCenter, latMeters, lonMeters );
MKCoordinateRegion fitRegion = [myMapView regionThatFits: region];
[myMapView setRegion: fitRegion animated: YES];
// Clean up
[locSouthWest release];
[locSouthEast release];
[locNorthEast release];
}
这将找到您左上角点的最小纬度/经度和您右下角点的最大纬度/经度。
double minLat = 900;
double minLon = 900;
double maxLat = -900;
double maxLon = -900;
foreach(Point point in latloncollection )
{
minLat = Math.min( minLat, point.lat );
minLon = Math.min( minLon, point.lon );
maxLat = Math.max( maxLat, point.lat );
maxLon = Math.max( maxLon, point.lon );
}
由于OP希望使用边界矩形在地图上设置,所以算法需要考虑到纬度和经度是在一个球面坐标系统中,而地图使用的是二维坐标系统。目前为止发布的解决方案都没有考虑到这一点,因此会得出错误的边界矩形,但幸运的是,可以很容易地通过使用从WWDC 2013“MapKit中的新功能”会话视频中找到的MKMapPointForCoordinate方法来创建有效的解决方案。
MKMapRect MapRectBoundingMapPoints(MKMapPoint points[], NSInteger pointCount){
double minX = INFINITY, maxX = -INFINITY, minY = INFINITY, maxY = -INFINITY;
NSInteger i;
for(i = -; i< pointCount; i++){
MKMapPoint p = points[i];
minX = MIN(p.x,minX);
minY = MIN(p.y,minY);
maxX = MAX(p.x,maxX);
maxY = MAX(p.y,maxY);
}
return MKMapRectMake(minX,minY,maxX - minX,maxY-minY);
}
CLLocationCoordinate2D london = CLLocationCoordinate2DMake(51.500756,-0.124661);
CLLocationCoordinate2D paris = CLLocationCoordinate2DMake(48.855228,2.34523);
MKMapPoint points[] = {MKMapPointForCoordinate(london),MKMapPointForCoordinate(paris)};
MKMapRect rect = MapRectBoundingMapPoints(points,2);
rect = MKMapRectInset(rect,
-rect.size.width * 0.05,
-rect.size.height * 0.05);
MKCoordinateRegion coordinateRegion = MKCoordinateRegionForMapRect(rect);
如果你愿意,你可以轻松更改方法使其适用于注释的NSArray。例如,以下是我在应用程序中使用的方法:
- (MKCoordinateRegion)regionForAnnotations:(NSArray*)anns{
MKCoordinateRegion r;
if ([anns count] == 0){
return r;
}
double minX = INFINITY, maxX = -INFINITY, minY = INFINITY, maxY = -INFINITY;
for(id<MKAnnotation> a in anns){
MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
minX = MIN(p.x,minX);
minY = MIN(p.y,minY);
maxX = MAX(p.x,maxX);
maxY = MAX(p.y,maxY);
}
MKMapRect rect = MKMapRectMake(minX,minY,maxX - minX,maxY-minY);
rect = MKMapRectInset(rect,
-rect.size.width * 0.05,
-rect.size.height * 0.05);
return MKCoordinateRegionForMapRect(rect);
}
public BoundingRectangle calculateBoundingRectangle()
{
Coordinate bndRectTopLeft = new Coordinate();
Coordinate bndRectBtRight = new Coordinate();
// Initialize bounding rectangle with first point
Coordinate firstPoint = getVertices().get(0);
bndRectTopLeft.setLongitude(firstPoint.getLongitude());
bndRectTopLeft.setLatitude(firstPoint.getLatitude());
bndRectBtRight.setLongitude(firstPoint.getLongitude());
bndRectBtRight.setLatitude(firstPoint.getLatitude());
double tempLong;
double tempLat;
// Iterate through all the points
for (int i = 0; i < getVertices().size(); i++)
{
Coordinate curNode = getVertices().get(i);
tempLong = curNode.getLongitude();
tempLat = curNode.getLatitude();
if (bndRectTopLeft.getLongitude() > tempLong) bndRectTopLeft.setLongitude(tempLong);
if (bndRectTopLeft.getLatitude() < tempLat) bndRectTopLeft.setLatitude(tempLat);
if (bndRectBtRight.getLongitude() < tempLong) bndRectBtRight.setLongitude(tempLong);
if (bndRectBtRight.getLatitude() > tempLat) bndRectBtRight.setLatitude(tempLat);
}
bndRectTopLeft.setLatitude(bndRectTopLeft.getLatitude());
bndRectBtRight.setLatitude(bndRectBtRight.getLatitude());
// Throw an error if boundaries contains poles
if ((Math.toRadians(topLeft.getLatitude()) >= (Math.PI / 2)) || (Math.toRadians(bottomRight.getLatitude()) <= -(Math.PI / 2)))
{
// Error
throw new Exception("boundaries contains poles");
}
// Now calculate bounding x coordinates
// Calculate it along latitude circle for the latitude closure to the
// pole
// (either north or south). For the other end the loitering distance
// will be slightly higher
double tempLat1 = bndRectTopLeft.getLatitude();
if (bndRectBtRight.getLatitude() < 0)
{
if (tempLat1 < (-bndRectBtRight.getLatitude()))
{
tempLat1 = (-bndRectBtRight.getLatitude());
}
}
bndRectTopLeft.setLongitude(bndRectTopLeft.getLongitude());
bndRectBtRight.setLongitude(bndRectBtRight.getLongitude());
// What if international date line is coming in between ?
// It will not affect any calculation but the range for x coordinate for the bounding rectangle will be -2.PI to +2.PI
// But the bounding rectangle should not cross itself
if ((Math.toRadians(bottomRight.getLongitude()) - Math.toRadians(topLeft.getLongitude())) >= (2 * Math.PI))
{
// Throw some error
throw new Exception("Bounding Rectangle crossing itself");
}
return new BoundingRectangle(bndRectTopLeft, bndRectBtRight);
}
如果跨越极点,这将处理异常...
@malhal所写的是正确的,这里所有的答案都是错误的,这里有一个例子:
取经度为-178、-175、+175、+178。根据其他答案,围绕它们的最小边界框将是:-178(西):+178(东),这是整个世界。这不是真的,因为地球是圆的,如果你从后面看它,你会有一个更小的边界框:+175(西):-175(东)。
这个问题会发生在接近-180 / +180的经度上。我的大脑试图思考纬度,但如果它们有问题,那就是在极地周围,例如谷歌地图不会“环绕”,所以在那里没有关系(因为是极点)。
这是一个解决方案的示例(CoffeeScript):
# This is the object that keeps the mins/maxes
corners =
latitude:
south: undefined
north: undefined
longitude:
normal:
west: undefined
east: undefined
# This keeps the min/max longitude after adding +360 to negative ones
reverse:
west: undefined
east: undefined
points.forEach (point) ->
latitude = point.latitude
longitude = point.longitude
# Setting latitude corners
corners.latitude.south = latitude if not corners.latitude.south? or latitude < corners.latitude.south
corners.latitude.north = latitude if not corners.latitude.north? or latitude > corners.latitude.north
# Setting normal longitude corners
corners.longitude.normal.west = longitude if not corners.longitude.normal.west? or longitude < corners.longitude.normal.west
corners.longitude.normal.east = longitude if not corners.longitude.normal.east? or longitude > corners.longitude.normal.east
# Setting reverse longitude corners (when looking from the other side)
longitude = if longitude < 0 then longitude + 360 else longitude
corners.longitude.reverse.west = longitude if not corners.longitude.reverse.west? or longitude < corners.longitude.reverse.west
corners.longitude.reverse.east = longitude if not corners.longitude.reverse.east? or longitude > corners.longitude.reverse.east
# Choosing the closest corners
# Extreme examples:
# Same: -174 - -178 = +186 - +182 (both eastgtive)
# Better normal: +2 - -4 < 176 - +2 (around the front)
# Better reverse: +182 - +178 < +178 - -178 (around the back)
if corners.longitude.normal.east - corners.longitude.normal.west < corners.longitude.reverse.east - corners.longitude.reverse.west
corners.longitude = corners.longitude.normal
else
corners.longitude = corners.longitude.reverse
corners.longitude.west = corners.longitude.west - 360 if corners.longitude.west > 180
corners.longitude.east = corners.longitude.east - 360 if corners.longitude.east > 180
# Now:
# SW corner at: corners.latitude.south / corners.longitude.west
# NE corner at: corners.latitude.north / corners.longitude.east
#include <vector>
#include <algorithm>
std::vector<float> latitude_set;
std::vector<float> longitude_set;
latitude_set.push_back(latitude_a);
latitude_set.push_back(latitude_b);
latitude_set.push_back(latitude_c);
latitude_set.push_back(latitude_d);
latitude_set.push_back(latitude_e);
longitude_set.push_back(longitude_a);
longitude_set.push_back(longitude_b);
longitude_set.push_back(longitude_c);
longitude_set.push_back(longitude_d);
longitude_set.push_back(longitude_e);
float min_latitude = *std::min_element(latitude_set.begin(), latitude_set.end());
float max_latitude = *std::max_element(latitude_set.begin(), latitude_set.end());
float min_longitude = *std::min_element(longitude_set.begin(), longitude_set.end());
float max_longitude = *std::max_element(longitude_set.begin(), longitude_set.end());
你所需要做的就是获取最左、最上、最右和最下的值。通过排序可以很容易地实现这一点,只要集合不太大,它就不会太昂贵。
如果你给你的纬度/经度类方法命名为compareLatitude:
和compareLongitude:
,那么它将更加容易。
CGFloat north, west, east, south;
[latLongCollection sortUsingSelector:@selector(compareLongitude:)];
west = [[latLongCollection objectAtIndex:0] longitude];
east = [[latLongCollection lastObject] longitude];
[latLongCollection sortUsingSelector:@selector(compareLatitude:)];
south = [[latLongCollection objectAtIndex:0] latitude];
north = [[latLongCollection lastObject] latitude];
假设你的坐标集合是NSMutableArray,那么类似这样的东西应该可以工作。