ios - superposicion - detectar si un punto está dentro de una superposición de MKPolygon
superposición de pantalla samsung s6 (6)
Aquí está la versión actualizada de swift 3 gracias a @Steve Stomp
extension MKPolygon {
func contains(coordinate: CLLocationCoordinate2D) -> Bool {
let polygonRenderer = MKPolygonRenderer(polygon: self)
let currentMapPoint: MKMapPoint = MKMapPointForCoordinate(coordinate)
let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
return polygonRenderer.path.contains(polygonViewPoint)
}
}
Quiero poder saber si el tap está dentro de un MKPolygon.
Tengo un MKPolygon:
CLLocationCoordinate2D points[4];
points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(41.002371, -102.052066);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(36.99892, -109.045267);
MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4];
[self.mapView addOverlay:poly];
//create UIGestureRecognizer to detect a tap
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(foundTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 1;
[self.mapView addGestureRecognizer:tapRecognizer];
Es solo un esquema básico del estado de Colorado.
Tengo el tap para la configuración de conversión lat / long:
-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
CGPoint point = [recognizer locationInView:self.mapView];
CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];
}
pero no estoy seguro de cómo utilizar la tecnología si mi punto de acceso está dentro del MKPolygon. no parece haber un método para realizar esta comprobación, así que supongo que necesito convertir el MKPolygon a un CGRect y usar CGRectContainsPoint.
MKPolygon tiene una propiedad .points pero parece que no puedo recuperarlos.
¿alguna sugerencia?
EDITAR:
Las dos soluciones siguientes funcionan en iOS 6 o inferior, pero se interrumpen en iOS 7. En iOS 7, la propiedad polygon.path
devuelve NULL
. La Sra. Anna tuvo la amabilidad de proporcionar una solución en otra pregunta de SO aquí . Implica crear su propia ruta desde los puntos de polígono para pasar a CGPathContainsPoint()
.
Imagen de mi polígono:
Creé esta categoría MKPolygon en caso de que alguien quiera usarla. Parece funcionar bien. Debe tener en cuenta los polígonos interiores (es decir, los agujeros en el polígono):
@interface MKPolygon (PointInPolygon)
-(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView;
@end
@implementation MKPolygon (PointInPolygon)
-(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
MKMapPoint mapPoint = MKMapPointForCoordinate(point);
MKPolygonView * polygonView = (MKPolygonView*)[mapView viewForOverlay:self];
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
return CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO) &&
![self pointInInteriorPolygons:point mapView:mapView];
}
-(BOOL) pointInInteriorPolygons:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
return [self pointInInteriorPolygonIndex:0 point:point mapView:mapView];
}
-(BOOL) pointInInteriorPolygonIndex:(int) index point:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
if(index >= [self.interiorPolygons count])
return NO;
return [[self.interiorPolygons objectAtIndex:index] pointInPolygon:point mapView:mapView] || [self pointInInteriorPolygonIndex:(index+1) point:point mapView:mapView];
}
@end
Determinar si un punto está en un polígono arbitrario no es trivial y no es sorprendente que Apple no lo suministre como parte de MKPolygon. Puede acceder a los puntos, lo que le permite iterar sobre los bordes.
Para determinar si un punto p está dentro de un polígono s, considere cada borde como un segmento de línea dirigida en s. Si un rayo de p en una dirección fija (generalmente paralela a cualquiera de los ejes X o Y) intersecta el segmento, tome el signo de la componente Z del producto cruzado del rayo con ese segmento de línea dirigido. Si el componente Z es> 0, agregue 1 a un contador. Si es <0, reste 1. El truco para implementar esto es evitar problemas cuando el borde es casi paralelo al rayo, o cuando el rayo pasa a través de un vértice (tiene que contar solo una vez, no una vez por cada borde) .
Cuando haya hecho esto para todos los bordes en s, habrá contado el número de veces que su rayo corta el contorno del polígono, donde si el borde iba de izquierda a derecha, agregó, y si iba de derecha a izquierda , restaste. Si la suma resultante es cero, estás fuera del polígono. De lo contrario estás dentro de él.
Hay numerosas optimizaciones posibles. Una de ellas es hacer una prueba rápida del cuadro delimitador antes de la prueba más completa. Otra es tener una estructura de datos con límites en todos los bordes para descartar trivialmente los bordes que no se intersecan con el rayo.
Edición: el componente Z de AXB (el producto cruzado de A con B) viene dado por:
a.x * b.y - a.y * b.x
ya que lo único que te importa es el signo, puedes verificar
a.x * b.y > a.y * b.x
Esto me funcionó en Swift:
extension MKPolygon {
func isCoordinateInsidePolyon(coordinate: CLLocationCoordinate2D) -> Bool {
var inside = false
let polygonRenderer = MKPolygonRenderer(polygon: self)
let currentMapPoint: MKMapPoint = MKMapPointForCoordinate(coordinate)
let polygonViewPoint: CGPoint = polygonRenderer.pointForMapPoint(currentMapPoint)
if CGPathContainsPoint(polygonRenderer.path, nil, polygonViewPoint, true) {
inside = true
}
return inside
}
}
Estoy obteniendo puntos de datos MKPolygon de un archivo xml en una cadena. Analizo la cadena de datos a Array of points y utilizo el enfoque dado en http://alienryderflex.com/polygon/
Esto funciona para mi....
-(BOOL)isPoint:(CLLocationCoordinate2D)findLocation inPloygon:(NSArray*)polygon{
NSMutableArray *tempPolygon=[NSMutableArray arrayWithArray:polygon];
int i, j=(int)tempPolygon.count-1 ;
bool oddNodes=NO;
double x=findLocation.latitude;
double y=findLocation.longitude;
for (i=0; i<tempPolygon.count; i++) {
NSString*coordString=[tempPolygon objectAtIndex:i];
NSArray*pointsOfCoordString=[coordString componentsSeparatedByString:@","];
CLLocationCoordinate2D point=CLLocationCoordinate2DMake([[pointsOfCoordString objectAtIndex:1] doubleValue], [[pointsOfCoordString objectAtIndex:0] doubleValue]);
NSString*nextCoordString=[tempPolygon objectAtIndex:j];
NSArray*nextPointsOfCoordString=[nextCoordString componentsSeparatedByString:@","];
CLLocationCoordinate2D nextPoint=CLLocationCoordinate2DMake([[nextPointsOfCoordString objectAtIndex:1] doubleValue], [[nextPointsOfCoordString objectAtIndex:0] doubleValue]);
if ((point.longitude<y && nextPoint.longitude>=y)
|| (nextPoint.longitude<y && point.longitude>=y)) {
if (point.latitude+(y-point.longitude)/(nextPoint.longitude-point.longitude)*(nextPoint.latitude-point.latitude)<x) {
oddNodes=!oddNodes; }}
j=i; }
return oddNodes;
}
mis objetos de polígono (NSArray) están en cadena para, por ejemplo, @"-89.860021,44.944266,0"
Su método foundTap
:
-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
CGPoint point = [recognizer locationInView:self.mapView];
CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];
[self pointInsideOverlay:tapPoint];
if (isInside)
{
....
}
}
Aquí hay un método para llamar desde el anterior para verificar si el punto está dentro de la superposición:
-(void)pointInsideOverlay:(CLLocationCoordinate2D )tapPoint
{
isInside = FALSE;
MKPolygonView *polygonView = (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];
MKMapPoint mapPoint = MKMapPointForCoordinate(tapPoint);
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);
if ( !mapCoordinateIsInPolygon )
//we are finding points that are inside the overlay
{
isInside = TRUE;
}
}