You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 03:35:30