79079926

Date: 2024-10-11 23:33:44
Score: 0.5
Natty:
Report link

Highlight countries, SwiftUI version

Here is a geojson: https://github.com/datasets/geo-boundaries-world-110m/blob/main/countries.geojson

import SwiftUI
import CoreLocation

struct GeoJson: Codable {
    let type: String
    let features: [GeoJsonFeature]
}

struct GeoJsonFeature: Codable {
    let type: String
    let geometry: GeoJsonGeometry
}

struct GeoJsonGeometry: Codable {
    let type: String
    let coordinates: GeoJsonCoordinates
}

struct GeoJsonCoordinates: Codable {
    var point: [Double]?
    var line: [[Double]]?
    var polygon: [[[Double]]]?
    var multiPolygon: [[[[Double]]]]?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let point = try? container.decode([Double].self) {
            self.point = point
            return
        }
        if let line = try? container.decode([[Double]].self) {
            self.line = line
            return
        }
        if let polygon = try? container.decode([[[Double]]].self) {
            self.polygon = polygon
            return
        }
        if let multiPolygon = try? container.decode([[[[Double]]]].self) {
            self.multiPolygon = multiPolygon
            return
        }
        throw DecodingError.valueNotFound(Self.self, .init(codingPath: [], debugDescription: ""))
    }
}

struct ContentView: View {
    @State private var shapes: [PolygonShape] = []
    
    var body: some View {
        ZStack {
            ForEach(shapes) { shape in
                Path { path in
                    guard let firstPoint = shape.points.first else { return }
                    path.move(to: firstPoint)
                    for point in shape.points {
                        path.addLine(to: point)
                    }
                    path.closeSubpath()
                }
                .fill(shape.color)
            }
        }
        .onAppear {
            loadGeoJson()
        }
        .frame(width: 300, height: 200)
        .border(Color.black, width: 1)
    }
    
    func loadGeoJson() {
        guard let url = Bundle.main.url(forResource: "countries", withExtension: "geojson"),
              let data = try? Data(contentsOf: url),
              let geoJson = try? JSONDecoder().decode(GeoJson.self, from: data)
        else {
            return
        }
        
        for feature in geoJson.features {
            let geometry = feature.geometry
            let randomColor = Color(hue: Double.random(in: 0...1), saturation: 1, brightness: 1)

            if geometry.type == "Polygon", let coordinates = feature.geometry.coordinates.polygon {
                for polygon in coordinates {
                    addShape(polygon: polygon, color: randomColor)
                }
            }
            if geometry.type == "MultiPolygon", let coordinates = feature.geometry.coordinates.multiPolygon {
                for multiPolygon in coordinates {
                    for polygon in multiPolygon {
                        addShape(polygon: polygon, color: randomColor)
                    }
                }
            }
        }
    }

    func addShape(polygon: [[Double]], color: Color) {
        let polygonCoordinates: [CLLocationCoordinate2D] = polygon.map { coordinate in
            CLLocationCoordinate2D(latitude: coordinate[1], longitude: coordinate[0])
        }
        let points: [CGPoint] = polygonCoordinates.map { coordinate in
            coordinateToPoint(coordinate)
        }
        let shape = PolygonShape(points: points, color: color)
        shapes.append(shape)
    }

    func coordinateToPoint(_ coordinate: CLLocationCoordinate2D) -> CGPoint {
        let width = 300.0
        let height = 200.0
        let x = (coordinate.longitude + 180.0) * (width / 360.0)
        let latitudeRadians = coordinate.latitude * .pi / 180.0
        let n = log(tan((.pi / 4.0) + (latitudeRadians / 2.0)))
        let y = (height / 2.0) - (width * n / (2.0 * .pi))
        return CGPoint(x: x, y: y)
    }
}

struct PolygonShape: Identifiable {
    let id = UUID()
    let points: [CGPoint]
    let color: Color
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

You can also add: let properties: [String: AnyCodableGeoJson] to get more data from .geojson

struct GeoJsonFeature: Codable {
    let type: String
    let geometry: GeoJsonGeometry
    let properties: [String: AnyCodableGeoJson]
}
Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Obrienser