我正在处理从 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子午线的。当在地图上显示覆盖为多边形的此位置时,它会错误地显示在美国边框的右侧(大的棕色矩形,只有左下角可见):
同样地,显示location viewport for Russia显示矩形位于俄罗斯边境左侧不正确。
当我将位置视口转换为MKMapPoint
和MKMapRect
并且在地图视口(上图中的白色矩形)与之间没有找到交叉点时,这在视觉上确认存在类似的问题。位置视口。
我计算地图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
?
答案 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
}