2012-05-11 25 views
16

Ich muss überprüfen, ob der Benutzerstandort zu der MKCoordinateRegion gehört. Ich war überrascht, keine einfache Funktion dafür zu finden, etwas wie: CGRectContainsCGPoint (rect, Punkt).Wie kann überprüft werden, ob MKCoordinateRegion CLLocationCoordinate2D enthält, ohne MKMapView zu verwenden?

fand ich folgendes Stück Code:

CLLocationCoordinate2D topLeftCoordinate = 
    CLLocationCoordinate2DMake(region.center.latitude 
           + (region.span.latitudeDelta/2.0), 
           region.center.longitude 
           - (region.span.longitudeDelta/2.0)); 


    CLLocationCoordinate2D bottomRightCoordinate = 
    CLLocationCoordinate2DMake(region.center.latitude 
           - (region.span.latitudeDelta/2.0), 
           region.center.longitude 
           + (region.span.longitudeDelta/2.0)); 

     if (location.latitude < topLeftCoordinate.latitude || location.latitude > bottomRightCoordinate.latitude || location.longitude < bottomRightCoordinate.longitude || location.longitude > bottomRightCoordinate.longitude) { 

    // Coordinate fits into the region 

    } 

Aber ich bin nicht sicher, ob es als Dokumentation genau ist nicht genau angeben, wie die Region Rechteck berechnet wird.

Es muss einen einfacheren Weg geben, es zu tun. Habe ich eine Funktion in der MapKit Framework-Dokumentation übersehen?

Antwort

16

Falls es jemand anderes mit Breiten und longitues verwirrt, hier getestet, Arbeitslösung:

MKCoordinateRegion region = self.mapView.region; 

CLLocationCoordinate2D location = user.gpsposition.coordinate; 
CLLocationCoordinate2D center = region.center; 
CLLocationCoordinate2D northWestCorner, southEastCorner; 

northWestCorner.latitude = center.latitude - (region.span.latitudeDelta/2.0); 
northWestCorner.longitude = center.longitude - (region.span.longitudeDelta/2.0); 
southEastCorner.latitude = center.latitude + (region.span.latitudeDelta/2.0); 
southEastCorner.longitude = center.longitude + (region.span.longitudeDelta/2.0); 

if (
    location.latitude >= northWestCorner.latitude && 
    location.latitude <= southEastCorner.latitude && 

    location.longitude >= northWestCorner.longitude && 
    location.longitude <= southEastCorner.longitude 
    ) 
{ 
    // User location (location) in the region - OK :-) 
    NSLog(@"Center (%f, %f) span (%f, %f) user: (%f, %f)| IN!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude); 

}else { 

    // User location (location) out of the region - NOT ok :-(
    NSLog(@"Center (%f, %f) span (%f, %f) user: (%f, %f)| OUT!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude); 
} 
+1

Ich bezweifle, dass das funktionieren würde: 1. warum sollte location.latitude> = northWestCorner.latitude? Sollte es nicht so gut sein? 2. Was passiert, wenn die berechnete minimale Länge -2.0, die maximale Länge 2.0 und Ihre Position.longitude 359.0 ist? – lichen19853

+1

@ lichen19853 ist richtig, dass es bei einem Test um 360 Grad fehlschlagen wird. Siehe meine Antwort unten für eine etwas korrektere Lösung. – MarekR

15

Sie können Ihren Standort in einen Punkt mit MKMapPointForCoordinate konvertieren, dann verwenden Sie MKMapRectContainsPoint auf dem Mapview visibleMapRect. Das ist völlig über meinem Kopf. Lass mich wissen ob es funktioniert.

+0

Es fühlt sich total überwältigend an, das ganze MKMapView zu initialisieren und es nur für solch eine einfache Überprüfung einzurichten. Ich muss das außerhalb jedes View-Controllers berechnen. – Lukasz

+0

Entschuldigung, ich dachte du arbeitest bereits mit einem Mapview. Wenn Sie nur diese Region haben, müssen Sie sich darauf verlassen, dass sie genau ist. Warum denkst du, dass die Region nicht gut ist? Woher hast du die Region? –

+0

Die Region ist in Ordnung. Ich bin mir nur nicht sicher, ob ich es richtig überprüfe. In der Dokumentation von MKCoordinateRegion wird nicht genau angegeben, wie der Breiten- und Längengrad das Bereichsrechteck bildet. – Lukasz

13

Ich poste diese Antwort als die akzeptierte Lösung nicht gültig ist meiner Meinung nach. Diese Antwort ist auch nicht perfekt, aber es behandelt den Fall, wenn Koordinaten um 360 Grad Grenzen herumgehen, was ausreichend ist, um in meiner Situation geeignet zu sein.

+ (BOOL)coordinate:(CLLocationCoordinate2D)coord inRegion:(MKCoordinateRegion)region 
{ 
    CLLocationCoordinate2D center = region.center; 
    MKCoordinateSpan span = region.span; 

    BOOL result = YES; 
    result &= cos((center.latitude - coord.latitude)*M_PI/180.0) > cos(span.latitudeDelta/2.0*M_PI/180.0); 
    result &= cos((center.longitude - coord.longitude)*M_PI/180.0) > cos(span.longitudeDelta/2.0*M_PI/180.0); 
    return result; 
} 
+0

Dies sollte die akzeptierte Lösung sein. Die Funktion cos() kümmert sich um das 0 bis 360 Grad Problem. Obwohl es eine nicht-lineare Skala auf der Entfernung durchführt, wird es mit einem gleich skalierten Delta verglichen, so dass es wie ein Zauber wirkt. – Brainware

+0

Dies funktionierte für mich für eine kleine, gut definierte rechteckige Region in meiner Stadt. Ich kann den allgemeinen Fall nicht bestätigen. – Verticon

5

ich diesen Code verwendet habe, um zu bestimmen, ob ein in einem kreisförmigen Bereich Koordinate (eine Koordinate mit einem Radius herum).

- (BOOL)location:(CLLocation *)location isNearCoordinate:(CLLocationCoordinate2D)coordinate withRadius:(CLLocationDistance)radius 
{ 
    CLCircularRegion *circularRegion = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:radius identifier:@"radiusCheck"]; 

    return [circularRegion containsCoordinate:coordinate]; 
} 
6

Die anderen Antworten haben alle Fehler. Die angenommene Antwort ist ein wenig ausführlich und scheitert in der Nähe der internationalen Datumsgrenze. Die Cosinus-Antwort ist praktikabel, scheitert jedoch an sehr kleinen Regionen (weil Delta-Cosinus ein Sinus ist, der gegen Null geht, dh kleinere Winkelabweichungen erwarten wir keine Änderung). Diese Antwort sollte für alle Situationen korrekt funktionieren und ist einfacher.

Swift:

/* Standardises and angle to [-180 to 180] degrees */ 
class func standardAngle(var angle: CLLocationDegrees) -> CLLocationDegrees { 
    angle %= 360 
    return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle 
} 

/* confirms that a region contains a location */ 
class func regionContains(region: MKCoordinateRegion, location: CLLocation) -> Bool { 
    let deltaLat = abs(standardAngle(region.center.latitude - location.coordinate.latitude)) 
    let deltalong = abs(standardAngle(region.center.longitude - location.coordinate.longitude)) 
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong 
} 

Objective C:

/* Standardises and angle to [-180 to 180] degrees */ 
+ (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle { 
    angle %= 360 
    return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle 
} 

/* confirms that a region contains a location */ 
+ (BOOL)region:(MKCoordinateRegion*)region containsLocation:(CLLocation*)location { 
    CLLocationDegrees deltaLat = fabs(standardAngle(region.center.latitude - location.coordinate.latitude)) 
    CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude)) 
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong 
} 

Diese Methode für die Regionen, die entweder nicht pole obwohl umfassen, aber dann das Koordinatensystem selbst an den Polen ausfällt. Für die meisten Anwendungen sollte diese Lösung ausreichen. (Beachten Sie, nicht auf Objective C getestet)

+0

yep objc code kompiliert nicht –

+1

In der standardAngle -Methode, für den Fall, dass der normalisierte Winkel> 180: sollte nicht der Rückgabewert 360 - Winkel statt 360 - 180 sein? – Verticon

1

Owen Godfrey, das Ziel-C-Code nicht funktioniert, ist dies der guten Code ist: schlägt auf Objective-C, das ist die gute Code:

/* Standardises and angle to [-180 to 180] degrees */ 
- (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle { 
    angle=fmod(angle,360); 
    return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle; 
} 

-(BOOL)thisRegion:(MKCoordinateRegion)region containsLocation:(CLLocation *)location{ 
    CLLocationDegrees deltaLat =fabs([self standardAngle:(region.center.latitude-location.coordinate.latitude)]); 
    CLLocationDegrees deltaLong =fabs([self standardAngle:(region.center.longitude-location.coordinate.longitude)]); 
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >=deltaLong; 
} 
    CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude)); 
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong; 
} 

Danke!

0

Basierend auf Lukasz Lösung, aber in Swift, jemand im Fall kann von Swift machen: mit gleichen Berechnungen

func isInRegion (region : MKCoordinateRegion, coordinate : CLLocationCoordinate2D) -> Bool { 

    let center = region.center; 
    let northWestCorner = CLLocationCoordinate2D(latitude: center.latitude - (region.span.latitudeDelta/2.0), longitude: center.longitude - (region.span.longitudeDelta/2.0)) 
    let southEastCorner = CLLocationCoordinate2D(latitude: center.latitude + (region.span.latitudeDelta/2.0), longitude: center.longitude + (region.span.longitudeDelta/2.0)) 

    return (
     coordinate.latitude >= northWestCorner.latitude && 
     coordinate.latitude <= southEastCorner.latitude && 

     coordinate.longitude >= northWestCorner.longitude && 
     coordinate.longitude <= southEastCorner.longitude 
    ) 
} 
0

Ich hatte Problem.Ich mag Konzeption vorgeschlagen von Owen Godfrey here, Brötchen sogar Fernando here verpasste die Tatsache, dass der Breitengrad anders gepolstert ist als die Länge und hat unterschiedliche Reichweite. Um meinen Vorschlag zu verdeutlichen, poste ich es mit Tests, damit Sie es selbst ausprobieren können.

import XCTest 
import MapKit 

// MARK - The Solution 

extension CLLocationDegrees { 

    enum WrapingDimension: Double { 
     case latitude = 180 
     case longitude = 360 
    } 

    /// Standardises and angle to [-180 to 180] or [-90 to 90] degrees 
    func wrapped(diemension: WrapingDimension) -> CLLocationDegrees { 
     let length = diemension.rawValue 
     let halfLenght = length/2.0 
     let angle = self.truncatingRemainder(dividingBy: length) 
     switch diemension { 
     case .longitude: 
      //  return angle < -180.0 ? 360.0 + angle : angle > 180.0 ? -360.0 + angle : angle 
      return angle < -halfLenght ? length + angle : angle > halfLenght ? -length + angle : angle 
     case .latitude: 
      //  return angle < -90.0 ? -180.0 - angle : angle > 90.0 ? 180.0 - angle : angle 
      return angle < -halfLenght ? -length - angle : angle > halfLenght ? length - angle : angle 
     } 
    } 
} 

extension MKCoordinateRegion { 
    /// confirms that a region contains a location 
    func contains(_ coordinate: CLLocationCoordinate2D) -> Bool { 
     let deltaLat = abs((self.center.latitude - coordinate.latitude).wrapped(diemension: .latitude)) 
     let deltalong = abs((self.center.longitude - coordinate.longitude).wrapped(diemension: .longitude)) 
     return self.span.latitudeDelta/2.0 >= deltaLat && self.span.longitudeDelta/2.0 >= deltalong 
    } 
} 

// MARK - Unit tests 

class MKCoordinateRegionContaingTests: XCTestCase { 

    func testRegionContains() { 
     var region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 0), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)) 
     var coords = CLLocationCoordinate2DMake(0, 0) 
     XCTAssert(region.contains(coords)) 

     coords = CLLocationCoordinate2DMake(0.5, 0.5) 
     XCTAssert(region.contains(coords)) 

     coords = CLLocationCoordinate2DMake(-0.5, 0.5) 
     XCTAssert(region.contains(coords)) 
     coords = CLLocationCoordinate2DMake(0.5, 0.5000001) 
     XCTAssert(!region.contains(coords)) // NOT Contains 
     coords = CLLocationCoordinate2DMake(0.5, -0.5000001) 
     XCTAssert(!region.contains(coords)) // NOT Contains 
     coords = CLLocationCoordinate2DMake(1, 1) 
     XCTAssert(!region.contains(coords)) // NOT Contains 

     region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)) 
     coords = CLLocationCoordinate2DMake(0, 180.5) 
     XCTAssert(region.contains(coords)) 
     coords.longitude = 179.5 
     XCTAssert(region.contains(coords)) 
     coords.longitude = 180.5000001 
     XCTAssert(!region.contains(coords)) // NOT Contains 
     coords.longitude = 179.5000001 
     XCTAssert(region.contains(coords)) 
     coords.longitude = 179.4999999 
     XCTAssert(!region.contains(coords)) // NOT Contains 

     region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(90, -180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)) 
     coords = CLLocationCoordinate2DMake(90.5, -180.5) 
     XCTAssert(region.contains(coords)) 

     coords = CLLocationCoordinate2DMake(89.5, -180.5) 
     XCTAssert(region.contains(coords)) 

     coords = CLLocationCoordinate2DMake(90.50000001, -180.5) 
     XCTAssert(!region.contains(coords)) // NOT Contains 

     coords = CLLocationCoordinate2DMake(89.50000001, -180.5) 
     XCTAssert(region.contains(coords)) 

     coords = CLLocationCoordinate2DMake(89.49999999, -180.5) 
     XCTAssert(!region.contains(coords)) // NOT Contains 
    } 

    func testStandardAngle() { 
     var angle = 180.5.wrapped(diemension: .longitude) 
     var required = -179.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = 360.5.wrapped(diemension: .longitude) 
     required = 0.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = 359.5.wrapped(diemension: .longitude) 
     required = -0.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = 179.5.wrapped(diemension: .longitude) 
     required = 179.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = 90.5.wrapped(diemension: .latitude) 
     required = 89.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = 90.5000001.wrapped(diemension: .latitude) 
     required = 89.4999999 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = -90.5.wrapped(diemension: .latitude) 
     required = -89.5 
     XCTAssert(self.areAngleEqual(angle, required)) 

     angle = -90.5000001.wrapped(diemension: .latitude) 
     required = -89.4999999 
     XCTAssert(self.areAngleEqual(angle, required)) 
    } 

    /// compare doubles with presition to 8 digits after the decimal point 
    func areAngleEqual(_ a:Double, _ b:Double) -> Bool { 
     let presition = 0.00000001 
     let equal = Int(a/presition) == Int(b/presition) 
     print(String(format:"%14.9f %@ %14.9f", a, equal ? "==" : "!=", b)) 
     return equal 
    } 
}