SwiftOverpassAPI
一个用于查询、解码和可视化 Overpass API 数据的 Swift 模块。
什么是 Overpass API?
Overpass API 是一个只读数据库,用于查询 OpenStreetMap 项目提供的开源测绘信息。更多信息请访问 Overpass API 维基 和 OpenStreetMap 维基。
安装
SwiftOverpassAPI 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中:
pod 'SwiftOverpassAPI'
使用
创建边界框
创建一个包含查询的框形区域
选项1:使用 MKCoordinateRegion 初始化
let center = CLLocationCoordinate2D(
latitude: 37.7749,
longitude: -122.4194)
let queryRegion = MKCoordinateRegion(
center: center,
latitudinalMeters: 50000,
longitudinalMeters: 50000)
let boundingBox = OPBoundingBox(region: region)
选项2:使用纬度和经度初始化
let boundingBox = OPBoundingBox(
minLatitude: 38.62661651293796,
minLongitude: -90.1998908782745,
maxLatitude: 38.627383487062005,
maxLongitude: -90.1989091217254)
构建查询
对于简单的查询生成,您可以使用 OPQueryBuilder
类
do {
let query = try OPQueryBuilder()
.setTimeOut(180) //1
.setElementTypes([.relation]) //2
.addTagFilter(key: "network", value: "BART", exactMatch: false) //3
.addTagFilter(key: "type", value: "route") //4
.addTagFilter(key: "name") //5
.setBoundingBox(boundingBox) //6
.setOutputType(.geometry) //7
.buildQueryString() //8
} catch {
print(error.localizedDescription)
}
- 设置服务器请求的超时时间
- 设置一个或多个要查询的元素类型(可以是
.node
、.way
和/或.relation
的任意组合) - 过滤包含 "network" 标记值为 "BART" 的元素(不区分大小写)
- 过滤 "type" 标记值精确为 "route" 的元素
- 过滤所有具有 "name" 标记的元素。可以具有任何关联的值。
- 在指定的边界框内查询
- 指定查询的输出类型(请参阅以下关于“选择查询输出类型”的部分)
- 构建一个字符串,将其传递给访问 Overpass API 端点的 overpass 客户端
Overpass 查询语言允许进行多样化和强大的查询,这使得构建一个万能的查询构建器变得非常困难。对于更复杂的查询,您可能需要直接构建查询字符串
let boundingBoxString = OPBoundingBox(region: region).toString()
let query = """
data=[out:json];
node["network"="BART"]
["railway"="stop"]
\(boundingBoxString)
->.bartStops;
(
way(around.bartStops:200)["amenity"="cinema"];
node(around.bartStops:200)["amenity"="cinema"];
);
out center;
"""
此查询找到所有距离任何 BART(湾区快速交通)站点不到 200 米的剧院。要了解更多关于 Overpass 查询语言的信息,我建议查看 Overpass 语言指南、Overpass 查询语言 Wiki 和 Overpass API 示例。您可以在浏览器中使用 Overpass Turbo 测试 overpass 查询。
选择查询输出类型
当使用 OPQueryBuiler
时,您可以从以下输出类型中选择
public enum OPQueryOutputType {
case standard, center, geometry, recurseDown, recurseUp, recurseUpAndDown
// The Overpass API language syntax for each output type
func toString() -> String {
switch self {
case .standard:
return "out;"
case .recurseDown:
return "(._;>;);out;"
case .recurseUp:
return "(._;<;);out;"
case .recurseUpAndDown:
return "((._;<;);>;);out;"
case .geometry:
return "out geom;"
case .center:
return "out center;"
}
}
}
- 标准:不带获取额外元素或几何信息的基本输出
- 递归向下:启用查询元素的完整几何重建。返回查询元素以及
- 所有初始结果集中的路径的节点;加上
- 所有初始结果集中的关系成员的节点和路径;加上
- 所有初始结果集路径的节点
- 递增上: 返回查询元素以及
- 所有包含初始结果集中出现节点的路径
- 所有包含初始结果集中出现节点或路径的关系
- 所有包含在初始结果集中出现的路径的关系
- 递增上下: 首先递增上,然后对向上递增的结果递增下
- 几何: 返回完整的足够用于可视化的元素几何信息
- 中心: 返回元素包含其中心坐标。当您不想可视化完整的元素几何时,这是最佳/最有效率的选项。
制作Overpass请求
let client = OPClient() //1
client.endpoint = .kumiSystems //2
//3
client.fetchElements(query: query) { result in
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(let elements):
print(elements) // Do something with returned the elements
}
}
- 实例化客户端
- 指定一个端点:提供的免费使用的端点通常会较慢,并可能限制您的使用。为了更好的性能,您可以指定您自己的自定义端点。
- 获取元素:解码后的响应将以字典的形式返回,键是Overpass元素的数据库ID。
生成MapKit可视化
为所有元素生成返回的元素字典的可视化
// Creates a dictionary of mapkit visualizations keyed by the corresponding element's id
let visualizations = OPVisualizationGenerator
.mapKitVisualizations(forElements: elements)
为单个元素生成可视化
if let visualization = OPVisualizationGenerator.mapKitVisualization(forElement: element) {
// Do something
} else {
print("Element doesn't have a geometry to visualize")
}
通过MKMapView显示可视化
步骤1: 使用包含的可视化生成器将叠加和注释添加到mapView中
func addVisualizations(_ visualizations: [Int: OPMapKitVisualization]) {
var annotations = [MKAnnotation]()
var polylines = [MKPolyline]()
var polygons = [MKPolygon]()
for visualization in visualizations.values {
switch visualization {
case .annotation(let annotation):
newAnnotations.append(annotation)
case .polyline(let polyline):
polylines.append(polyline)
case .polylines(let newPolylines):
polylines.append(contentsOf: newPolylines)
case .polygon(let polygon):
polygons.append(polygon)
case .polygons(let newPolygons):
polygons.append(contentsOf: newPolygons)
}
}
if #available(iOS 13, *) {
// MKMultipolyline and MKMultipolygon generate a single renderer for all of their elements. If available, it is more efficient than creating a renderer for each overlay.
let multiPolyline = MKMultiPolyline(polylines)
let multiPolygon = MKMultiPolygon(polygons)
mapView.addOverlay(multiPolygon)
mapView.addOverlay(multiPolyline)
} else {
mapView.addOverlays(polygons)
mapView.addOverlays(polylines)
}
mapView.addAnnotations(annotations)
}
根据其情况,一个可视化可以有以下相关值类型之一
MKAnnotation
:用于单个坐标。注释的标题是元素名称标签的值。MKPolyline
:通常用于道路MKPolygon
:通常用于简单的结构,如建筑物[MKPolyline]
:一个相关多线数组,如在路径或航道中的集合[MKPolygon]
:组成更复杂结构的多个多边形数组。
步骤2: 显示叠加和注释的视图
extension MapViewController: MKMapViewDelegate {
// Delegate method for rendering overlays
func mapView(
_ mapView: MKMapView,
rendererFor overlay: MKOverlay) -> MKOverlayRenderer
{
let strokeWidth: CGFloat = 2
let strokeColor = UIColor.theme
let fillColor = UIColor.theme.withAlphaComponent(0.5)
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(
polyline: polyline)
renderer.strokeColor = strokeColor
renderer.lineWidth = strokeWidth
return renderer
} else if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(
polygon: polygon)
renderer.fillColor = fillColor
renderer.strokeColor = strokeColor
renderer.lineWidth = strokeWidth
return renderer
} else if let multiPolyline = overlay as? MKMultiPolyline {
let renderer = MKMultiPolylineRenderer(
multiPolyline: multiPolyline)
renderer.strokeColor = strokeColor
renderer.lineWidth = strokeWidth
return renderer
} else if let multiPolygon = overlay as? MKMultiPolygon {
let renderer = MKMultiPolygonRenderer(
multiPolygon: multiPolygon)
renderer.fillColor = fillColor
renderer.strokeColor = strokeColor
renderer.lineWidth = strokeWidth
return renderer
} else {
return MKOverlayRenderer()
}
}
/*
// Make sure to add the following when configure your mapView:
let markerReuseIdentifier = "MarkerAnnotationView"
mapView.register(
MKMarkerAnnotationView.self,
forAnnotationViewWithReuseIdentifier: markerReuseIdentifier)
*/
// Delegate method for setting annotation views.
func mapView(
_ mapView: MKMapView,
viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
guard
let pointAnnotation = annotation as? MKPointAnnotation
else {
return nil
}
let view = MKMarkerAnnotationView(
annotation: pointAnnotation,
reuseIdentifier: markerReuseIdentifier)
view.markerTintColor = UIColor.theme
return view
}
}
示例应用
要运行示例项目,请克隆仓库,然后在示例目录中首先运行pod install
。
作者
ebsamson3, [email protected]
致谢
感谢所有为Overpass API和OpenStreetMap做出贡献的人。感谢Martin Raifer,他的osmtogeojson代码帮助我节省了大量时间,并帮助我理解如何处理Overpass API元素。
许可证
SwiftOverpassAPI遵循MIT许可证。请参阅LICENSE文件了解详细信息。