Алгоритм определения минимального ограничивающего прямоугольника для сбора координат широты / долготы

Есть ли алгоритм для определения минимального ограничивающего прямоугольника вокруг набора координат широты / долготы?

Допустимо предположить, что Земля плоская, поскольку координаты не будут слишком далеко друг от друга. Псевдокод - это нормально, но если бы кто-то сделал это в Objective-C, это было бы даже лучше. Я пытаюсь установить уровень масштабирования карты в зависимости от количества точек, которые будут отображаться на карте.


person Matthew Belk    schedule 19.08.2009    source источник


Ответы (8)


Это метод, который я использую в одном из своих приложений.

- (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];
}
person jessecurry    schedule 11.09.2009
comment
Только что получил сообщение от Бутча Антона - getDistanceFrom: устарело в iPhone OS 3.2. Теперь ваш код должен использовать distanceFromLocation: вместо. - person jessecurry; 22.07.2010

Это найдет наименьшую широту / долготу для вашей верхней левой точки и самую большую широту / долготу для вашей нижней правой точки.

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 );
}
person Muad'Dib    schedule 20.08.2009
comment
Даже если мы точно знаем, что абсолютные значения lat и lon не превысят 900, я думаю, было бы лучше инициировать минимальные и максимальные значения для первой точки списка, а затем попытаться найти лучшие, начиная с второй пункт в списке. - person mbritto; 07.07.2012
comment
Это не соответствует примерам, которые пересекают линию дат: то есть диапазон Lon от -178 до 175. - person Tim Makins; 01.06.2020

Поскольку OP хочет использовать ограничивающий прямоугольник для установки на карте, алгоритм должен учитывать тот факт, что широта и долгота находятся в сферической системе координат, а карта использует двухмерную систему координат. Ни одно из опубликованных на данный момент решений не учитывает это и, таким образом, приводит к неправильному ограничивающему прямоугольнику, но, к счастью, довольно легко создать допустимое решение, используя метод MKMapPointForCoordinate, найденный в этом примере кода из WWDC 2013 «Что нового в MapKit» видео сеанса.

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);
}
person malhal    schedule 20.06.2013

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

Это обработает исключение, если регион пересекает полюса ...

person Noushad    schedule 20.01.2012

То, что написал @malhal, правильно, все ответы здесь неверны, и вот пример:

Возьмите долготы -178, -175, +175, +178. Согласно другим ответам, наименьшая ограничивающая рамка вокруг них будет: -178 (запад): +178 (восток), то есть весь мир. Это неправда, поскольку Земля круглая, если вы посмотрите сзади, вы увидите ограничительную рамку меньшего размера: +175 (запад): -175 (восток).

Эта проблема может возникнуть для долгот, близких к -180 / + 180. Мой мозг болит, пытаясь думать о широтах, но если у них есть проблема, это вокруг полюсов, которые, например, Google Maps не «обходят», так что там это не имеет значения (поскольку это полюса).

Вот пример решения (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
person Oded Niv    schedule 27.08.2015

Для того, что вы хотите сделать, вы, вероятно, могли бы просто найти минимальные и максимальные значения для Lat и Long и использовать их в качестве границ вашего прямоугольника. Для более сложных решений см .:

Вычислить прямоугольник с минимальной площадью для многоугольника

person Mark Bessey    schedule 20.08.2009

Если вы используете Objective-C, вы можете вместо этого использовать Objective-C ++, и в этом случае вы можете использовать STL, чтобы сделать за вас большую часть тяжелой работы:

#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());
person fbrereto    schedule 20.08.2009
comment
Обычный C ++ также имеет библиотеки. Кроме того, как кодирование этой статической идеи является хорошей? - person Foredecker; 20.08.2009
comment
Это стандартный C ++. Статическое заполнение векторов широты и долготы служит просто для примера ... вы можете добавлять элементы в вектор любым удобным для вас способом. - person fbrereto; 20.08.2009
comment
Я не уверен, что вижу большой угол подъема, когда более простой код ObjC легче ... в любом случае, вы смотрите на все значения точек. - person Kendall Helmstetter Gelner; 20.08.2009

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

Если вы дадите своим методам класса lat / long с именами 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.

person Cinder6    schedule 20.08.2009
comment
мой массив - это NSMutableArray объектов MKAnnotation. Мне нужно было подумать о том, как лучше всего реализовать эти методы селектора для сравнения. Это не слишком сильно отличается от простого перебора списка вручную, но это немного более элегантно. - person Matthew Belk; 20.08.2009