如何从GeoJSON创建MapPolygon对象(SwiftUI MapKit场景)
问题:从GeoJSON解析坐标生成SwiftUI MapPolygon覆盖层
我正尝试将GeoJSON解码为CLLocationCoordinate2D数组,以此为MapPolygon创建地图覆盖层。使用第三方GeoJSON解析库解码JSON,采用SwiftUI的新版MapKit构建地图,希望尽量避免使用UIKit。获取GeoJSON的API始终返回MultiPolygon或Polygon类型。
我的问题在于如何将所有坐标对整理成数组以在地图上创建多边形。使用该库时需通过switch case解码GeoJSON,这让遍历提取所有坐标对变得困难。当前代码如下:
func createPolygons() -> [CLLocationCoordinate2D] { var polygons = [CLLocationCoordinate2D]() for feature in perimeter!.perimeterFeatures.features { switch feature.geometry! { case .point, .multiPoint, .lineString, .multiLineString: break case .polygon: break case .multiPolygon(let multiPolygon): var coords = multiPolygon.coordinates.map {_ in CLLocationCoordinate2D(latitude: multiPolygon.coordinates.coordinates.coordinates.latitude, longitude: multiPolygon.coordinates.coordinates.coordinates.longitude) //--> ERROR: Value of type '[Polygon]' has no member 'coordinates' } return polygons case .geometryCollection: break default: return polygons } //switch case } //for loop return polygons }
我未找到从GeoJSON创建MapPolygon的相关资料,仅能找到基于预填充CLLocationCoordinate2D数组的实现。Swift结合GeoJSON的相关资源较少。
我也曾尝试自定义结构体来解码GeoJSON,但最终遇到元组不遵循Codable协议的问题。
请问解决该问题并在地图上显示这些多边形的最佳方案是什么?是否有更优的从GeoJSON创建MapPolygon的方式?
解决方案
一、修正第三方库的坐标遍历逻辑
你遇到的错误是因为对库的层级结构理解有误:
MultiPolygon的coordinates属性是[Polygon]数组- 每个
Polygon的coordinates是[LinearRing]数组(LinearRing是闭合的坐标环,第一个环是多边形外边界,后续是内部洞) - 每个
LinearRing是[Position]数组,Position包含latitude和longitude属性
重写函数直接返回[MapPolygon](更贴合SwiftUI Map的使用场景):
func createMapPolygons() -> [MapPolygon] { var mapPolygons = [MapPolygon]() guard let features = perimeter?.perimeterFeatures.features else { return mapPolygons } for feature in features { guard let geometry = feature.geometry else { continue } switch geometry { case .polygon(let polygon): // 提取第一个LinearRing作为多边形外边界(忽略内部洞,如需处理可遍历所有LinearRing) if let outerRing = polygon.coordinates.first { let coordinates = outerRing.map { position in CLLocationCoordinate2D(latitude: position.latitude, longitude: position.longitude) } mapPolygons.append(MapPolygon(coordinates: coordinates)) } case .multiPolygon(let multiPolygon): // 遍历每个Polygon,重复上述处理 for polygon in multiPolygon.coordinates { if let outerRing = polygon.coordinates.first { let coordinates = outerRing.map { position in CLLocationCoordinate2D(latitude: position.latitude, longitude: position.longitude) } mapPolygons.append(MapPolygon(coordinates: coordinates)) } } // 忽略不需要的几何类型 case .point, .multiPoint, .lineString, .multiLineString, .geometryCollection: continue default: continue } } return mapPolygons }
使用时直接将返回的数组传入SwiftUI Map的overlays参数即可。
二、自定义GeoJSON解码(无需第三方库)
如果不想依赖第三方库,可以通过自定义Codable结构体避开元组的问题:
// 定义GeoJSON核心结构 struct GeoJSONFeatureCollection: Codable { let type: String let features: [GeoJSONFeature] } struct GeoJSONFeature: Codable { let type: String let geometry: GeoJSONGeometry? } enum GeoJSONGeometry: Codable { case polygon(Polygon) case multiPolygon(MultiPolygon) enum CodingKeys: String, CodingKey { case type, coordinates } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: .type) switch type { case "Polygon": let coordinates = try container.decode([[Position]].self, forKey: .coordinates) self = .polygon(Polygon(coordinates: coordinates)) case "MultiPolygon": let coordinates = try container.decode([[[Position]]].self, forKey: .coordinates) self = .multiPolygon(MultiPolygon(coordinates: coordinates)) default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unsupported geometry type") } } func encode(to encoder: Encoder) throws { // 如需编码可补充实现,此处省略 } } struct Polygon: Codable { let coordinates: [[Position]] } struct MultiPolygon: Codable { let coordinates: [[[Position]]] } // 核心:自定义Position结构体,处理GeoJSON的[lon, lat]数组格式 struct Position: Codable { let longitude: Double let latitude: Double init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() longitude = try container.decode(Double.self) latitude = try container.decode(Double.self) } func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(longitude) try container.encode(latitude) } }
解码后同样可以按照层级遍历坐标,生成MapPolygon,逻辑和第三方库的处理方式一致。
三、注意事项
- GeoJSON的坐标顺序是经度在前,纬度在后,转成
CLLocationCoordinate2D时要对应正确(latitude对应第二个值,longitude对应第一个) - 如果需要处理多边形内部的洞,可以遍历
Polygon.coordinates中的所有LinearRing,为每个洞创建MapPolygon并设置interiorPolygons属性
内容的提问来源于stack exchange,提问作者whaleTooth
相关产品推荐
相关产品推荐

