2009-08-19 7 views
15

Esiste un algoritmo per determinare rettangolo minimo intorno a un insieme di coordinate di latitudine/longitudine?algoritmo per determinare minima rettangolo di delimitazione per la raccolta di latitudine/longitudine coordina

È OK assumere una terra piatta poiché le coordinate non saranno troppo distanti. Lo pseudocodice è OK, ma se qualcuno lo ha fatto in Objective-C, sarebbe ancora meglio. Quello che sto cercando di fare è impostare il livello di zoom di una mappa in base al numero di punti che verranno visualizzati sulla mappa.

+1

Sono sicuro che questo non è più di grande preoccupazione per voi (2 anni più tardi), ma si deve sapere che è possibile - almeno in teoria - per tutte le risposte elencate fallire in modo spettacolare. Considera uno o più poligoni sul Pacifico con una X in basso a sinistra di +170 e una X in alto a destra di -170. Ti scioglierà il cervello cercando di adattarsi alla tua scatola di delimitazione. Questo articolo: http://www.stonybrook.edu/libmap/coordinates/seriesa/no2/a2.htm (sezione Global Gotchas) implica che il problema non può e non dovrebbe essere risolto. – tomfumb

risposta

11

Questo troverà il più piccolo latitudine/longitudine per il punto in alto a sinistra e il più grande latitudine/longitudine per il punto in basso a destra.

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

Anche se sappiamo con certezza che i valori assoluti di lat e lon non supereranno i 900, penso che sarebbe meglio iniziare i valori min e max al primo punto della lista, e quindi provare a trovare quelli migliori a partire da il secondo elemento nell'elenco. – mbritto

0

Per quello che si vuole fare, probabilmente si potrebbe trovare solo i valori minimi e massimi per Lat e Long e usare quelli come i limiti del rettangolo. Per soluzioni più sofisticate, si veda:

Calculate minimum area rectangle for a polygon

0

Se siete in Objective-C, allora si potrebbe essere in grado di utilizzare Objective-C++, invece, nel qual caso è possibile utilizzare l'STL per fare un sacco di sollevamento di carichi pesanti per voi:

#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()); 
+0

Il C++ regolare ha anche delle librerie. Inoltre, come è la codifica di questa staticamente una buona idea? – Foredecker

+0

Questo è C++ conforme allo standard. Il riempimento statico dei vettori di latitudine e longitudine serve solo per l'esempio ... puoi aggiungere elementi al vettore come preferisci. – fbrereto

+0

Non sono sicuro di vedere l'angolo di sollevamento più pesante quando il codice ObjC più semplice è più leggero ... in qualsiasi modo lo tagli, stai guardando tutti i valori dei punti. –

0

Tutto quello che devi fare è ottenere più a sinistra, più in alto, più a destra, e in basso la maggior parte dei valori. È possibile farlo abbastanza facilmente ordinando, e finché il set non è troppo grande, non sarebbe molto costoso.

Se si forniscono i metodi di classe lat/long chiamati compareLatitude: e compareLongitude:, sarebbe ancora più semplice.

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

Qualcosa del genere dovrebbe funzionare, supponendo che la tua collezione di coordinate è un NSMutableArray.

+0

il mio array è NSMutableArray di oggetti MKAnnotation. Dovrei pensare al modo migliore per implementare questi metodi di selezione per fare il confronto. Non è molto diverso dal semplice scorrere l'elenco manualmente, ma questo è un po 'più "elegante". –

10

questo è il metodo che uso in una delle mie applicazioni.

- (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]; 
} 
+0

Magnifico frammento di codice, grazie per averlo condiviso. – Goles

+1

Appena ricevuto un messaggio da Butch Anton - getDistanceFrom: è stato dichiarato obsoleto a partire da iPhone OS 3.2. Il tuo codice dovrebbe ora usare distanceFromLocation: invece. – jessecurry

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

Questo gestirà un'eccezione se pali regione di attraversamento ...

2

Dal momento che l'OP vuole utilizzare il rettangolo per impostare sulla mappa, l'algoritmo deve tener conto del fatto che la latitudine e le longitudini sono in un sistema di coordinate sferiche e la mappa utilizza un sistema di coordinate bidimensionale. Nessuna delle soluzioni postate finora tenerne conto e quindi finire con un torto rettangolo ma per fortuna è abbastanza facile per creare una valida soluzione utilizzando il metodo MKMapPointForCoordinate trovato in questo codice di esempio dal WWDC 2013 "Che cosa è nuovo in MapKit" video di sessione.

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

Si può facilmente cambiare il metodo di lavorare su un NSArray delle annotazioni, se si preferisce. Per esempio. qui è il metodo che sto usando nella mia domanda:

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

Cosa @malhal scritto è corretto, tutte le risposte qui sono sbagliate e qui un esempio:

Prendere le longitudini -178, -175, + 175, +178. Secondo le altre risposte, il riquadro più piccolo intorno a loro sarebbe: -178 (ovest): +178 (est), che è il mondo intero.Questo non è vero, poiché la terra è rotonda se guardi da dietro avrai un riquadro di limitazione più piccolo di: +175 (ovest): -175 (est).

Questo problema si verificherebbe per le longitudini vicine a -180/+ 180. Il mio cervello fa male a pensare alle latitudini, ma se hanno un problema è intorno ai poli che Google Maps per esempio non "gira intorno", quindi non importa lì (dal momento che è i poli).

Ecco un esempio di soluzione (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 
Problemi correlati