MKMapRect并显示跨越第180个子午线的地图叠加层

时间:2012-01-26 17:29:56

标签: ios mapkit

我正在处理从 Google地理编码API 返回的视口和边界。当对给定坐标进行反向地理编码时,服务返回具有各种粒度(国家,行政区域,地点,子地点,路线等)的若干结果。我想在给定地图上当前可见区域的结果中选择最合适的。

我已经确定了比较位置视口,当前地图视口和它们的交点(使用MKMapPoint函数)的区域比率(MKMapRectIntersection²)的算法。只要位置视口不跨越180子午线,这就可以很好地工作。在那种情况下,他们的交集是0。

我已经开始调查原因,并且作为调试辅助工具,我会在地图上显示MKPolygon叠加层,以便为我提供有关正在发生的事情的直观线索。为了避免我的代码在地理坐标和MKMapRect之间进行转换而引入的错误,我使用Google结果中的原始坐标构建了多边形叠加,如下所示:

CLLocationCoordinate2D sw, ne, nw, se;
sw = location.viewportSouthWest.coordinate;
ne = location.viewportNorthEast.coordinate;
nw = CLLocationCoordinate2DMake(ne.latitude, sw.longitude);
se = CLLocationCoordinate2DMake(sw.latitude, ne.longitude);
CLLocationCoordinate2D coords[] = {nw, ne, se, sw};
MKPolygon *p = [MKPolygon polygonWithCoordinates:coords count:4];

例如,有问题的地理位置,此处是为美国返回的视口,类型为国家/地区的最后结果,当geocoding coordinates somewhere in Virginia时:

Southwest: 18.9110643, 172.4546967  
Northeast: 71.3898880, -66.9453948

注意位于视口左下角的西南坐标是如何横跨180子午线的。当在地图上显示覆盖为多边形的此位置时,它会错误地显示在美国边框的右侧(大的棕色矩形,只有左下角可见):

Viewport of USA overlayed on map Viewport of Russia overlayed on map

同样地,显示location viewport for Russia显示矩形位于俄罗斯边境左侧不正确。

当我将位置视口转换为MKMapPointMKMapRect并且在地图视口(上图中的白色矩形)与之间没有找到交叉点时,这在视觉上确认存在类似的问题。位置视口。

我计算地图rect的方式类似于这个SO问题中的答案:
How to fit a certain bounds consisting of NE and SW coordinates into the visible map view?
......工作正常除非坐标跨越第180个子午线。使用MKMapRect返回MKMapRectSpans180thMeridian来测试false,以便构造方法不正确。

Apple文档在这方面没有帮助。我发现只有提示位于MKOverlay.h

// boundingMapRect should be the smallest rectangle that completely contains
// the overlay.
// For overlays that span the 180th meridian, boundingMapRect should have 
// either a negative MinX or a MaxX that is greater than MKMapSizeWorld.width.
@property (nonatomic, readonly) MKMapRect boundingMapRect;

显示跨越第180个子午线的多边形叠加层的正确方法是什么? 如何正确构建跨越180度子午线的MKMapRect

2 个答案:

答案 0 :(得分:5)

根据MKOverlay.h中的评论,如果nw和sw角被指定为负MKMapPoint值,则叠加层应“正确绘制”。

如果我们试试这个:

//calculation of the nw, ne, se, and sw coordinates goes here

MKMapPoint points[4];
if (nw.longitude > ne.longitude)  //does it cross 180th?
{
    //Get the mappoint for equivalent distance on
    //the "positive" side of the dateline...
    points[0] = MKMapPointForCoordinate(
                  CLLocationCoordinate2DMake(nw.latitude, -nw.longitude));

    //Reset the mappoint to the correct side of the dateline, 
    //now it will be negative (as per Apple comments)...
    points[0].x = - points[0].x;
}
else
{
    points[0] = MKMapPointForCoordinate(nw);
}
points[1] = MKMapPointForCoordinate(ne);
points[2] = MKMapPointForCoordinate(se);
points[3] = MKMapPointForCoordinate(sw);
points[3].x = points[0].x;    //set to same as NW's whether + or -

MKPolygon *p = [MKPolygon polygonWithPoints:points count:4];

[mapView addOverlay:p];

结果p.boundingMapRect确实为YES返回MKMapRectSpans180thMeridian(但是代码已经从坐标中找出,因为它没有以maprect开头)。

不幸的是,使用负值创建maprect只能解决问题的一半。现在可以正确绘制日期线以东的多边形的一半。但是,日期线以西的另一半根本没有被绘制。

显然,内置MKPolygonView不会调用MKMapRectSpans180thMeridian并将多边形分为两部分。

您可以创建自定义叠加视图并自己绘制(您可以创建一个叠加层,但视图会绘制两个多边形)。

或者,你可以创建两个MKPolygon叠加层,让地图视图通过在上面的代码之后添加以下内容来绘制它们:

if (MKMapRectSpans180thMeridian(p.boundingMapRect))
{
    MKMapRect remainderRect = MKMapRectRemainder(p.boundingMapRect);

    MKMapPoint remPoints[4];
    remPoints[0] = remainderRect.origin;
    remPoints[1] = MKMapPointMake(remainderRect.origin.x + remainderRect.size.width, remainderRect.origin.y);
    remPoints[2] = MKMapPointMake(remainderRect.origin.x + remainderRect.size.width, remainderRect.origin.y + remainderRect.size.height);
    remPoints[3] = MKMapPointMake(remainderRect.origin.x, remainderRect.origin.y + remainderRect.size.height);

    MKPolygon *remPoly = [MKPolygon polygonWithPoints:remPoints count:4];

    [mapView addOverlay:remPoly];
}

顺便说一句,绘制MKPolyline叠加层的问题与+/- 180相似(见this question)。

答案 1 :(得分:1)

简而言之,如果多边形越过antimeridian,请检查mapPoints。 如果mapPoint.x大于primeMeridian.x,请从mapPoint.x减去世界的宽度。

这会将地图拆分为本初子午线。 minX为负数,并且mapSize小于世界的宽度。 Palimondo的答案对弄清这一点非常有帮助。

我正在与geodesicPolylines合作,并花了几天时间。终于找到了答案!

/// Note: If both the prime meridian and the antimeridian are crossed, an empty polygon will be returned
func makePolygon(lines: [LineAnnotation]) -> MKPolygon {
    let polylines = lines.map({ $0.polyline })
    let sum = polylines.reduce(0, { $0 + $1.pointCount })
    let pointer = UnsafeMutablePointer<MKMapPoint>.allocate(capacity: sum)
    var advance = 0
    let spans180thMeridian = polylines.contains(where: { $0.boundingMapRect.spans180thMeridian })
    let primeMeridianMapPoint = MKMapPoint(CLLocationCoordinate2D(latitude: 0, longitude: 0))
    let spansPrimeMeridian = polylines.contains(where: {
        return $0.boundingMapRect.minX <= primeMeridianMapPoint.x && $0.boundingMapRect.maxX >= primeMeridianMapPoint.x
    })
    guard !(spans180thMeridian && spansPrimeMeridian) else { return MKPolygon() }
    if spans180thMeridian {
        for polyline in polylines {
            // initialize the pointer with a copy of the polyline points, adjusted if needed
            let points = UnsafeMutablePointer<MKMapPoint>.allocate(capacity: polyline.pointCount)
            points.initialize(from: polyline.points(), count: polyline.pointCount)
            for i in 0..<polyline.pointCount {
                let pointPointer = points.advanced(by: i)
                if pointPointer.pointee.x > primeMeridianMapPoint.x {
                    pointPointer.pointee.x -= MKMapSize.world.width
                }
            }
            pointer.advanced(by: advance).initialize(from: points, count: polyline.pointCount)
            advance += polyline.pointCount
            points.deinitialize(count: polyline.pointCount)
            points.deallocate()
        }

    } else {
        // initialize the pointer with the polyline points
        for polyline in polylines {
            pointer.advanced(by: advance).initialize(from: polyline.points(), count: polyline.pointCount)
            advance += polyline.pointCount
        }
    }
    let polygon = MKPolygon(points: pointer, count: sum)
    print(polygon.boundingMapRect)
    pointer.deinitialize(count: sum)
    pointer.deallocate()
    return polygon
}