swiftmapboxmkpolygon

How Can I find the center coordinate in a MGLMultiPolygonFeature


I'm using iOS Mapbox SDK and I need to find the center coordinate in a polygon because I want to add a marker in the center coordinate. How can I do this in Swift?

func drawPolygonFeature(shapes: [MGLShape & MGLFeature]) {
    let shapeSource = MGLShapeSource(identifier: "MultiPolygonShapeSource", shapes: shapes, options: nil)

    let lineStyleLayer = MGLLineStyleLayer(identifier: "LineStyleLayer", source: shapeSource)
    lineStyleLayer.lineColor = NSExpression(forConstantValue: UIColor.purple)
    lineStyleLayer.lineOpacity = NSExpression(forConstantValue: 0.5)
    lineStyleLayer.lineWidth = NSExpression(forConstantValue: 4)

    DispatchQueue.main.async(execute: {[weak self] in
        guard let self = self else { return }
        self.mapView.style?.addSource(shapeSource)
        self.mapView.style?.addLayer(lineStyleLayer)

        let multiPolygonFeature = shapes.first as? MGLMultiPolygonFeature
        if let centerCoordinate = multiPolygonFeature?.polygons.first?.coordinate {
            self.mapView.centerCoordinate = centerCoordinate
            // but centerCoordinate var does not contain the center coordinate
        }
    })
}

Solution

  • A solution depends on your requirements. If it is required that the center is within the polygon, the solution provided by Paul van Roosendaal is perfect.
    However, in many cases it is better if the center can also be outside of the polygon. Think, e.g. of a polygon that looks like a nearly closed ring. In this case, it may be more natural that the center is roughly in the center of the ring, and the center is computed as the centroid of the polygon.
    In the cited wiki post, this reference discusses how to compute it, and shows a number of implementations in different languages.
    I have translated the Java version into Swift, and added an example:

    func signedPolygonArea(polygon: [CGPoint]) -> CGFloat {
        let nr = polygon.count
        var area: CGFloat = 0
        for i in 0 ..< nr {
            let j = (i + 1) % nr
            area = area + polygon[i].x * polygon[j].y
            area = area - polygon[i].y * polygon[j].x
        }
        area = area/2.0
        return area
    }
    
    func polygonCenterOfMass(polygon: [CGPoint]) -> CGPoint {
        let nr = polygon.count
        var centerX: CGFloat = 0
        var centerY: CGFloat = 0
        var area = signedPolygonArea(polygon: polygon)
        for i in 0 ..< nr {
            let j = (i + 1) % nr
            let factor1 = polygon[i].x * polygon[j].y - polygon[j].x * polygon[i].y
            centerX = centerX + (polygon[i].x + polygon[j].x) * factor1
            centerY = centerY + (polygon[i].y + polygon[j].y) * factor1
        }
        area = area * 6.0
        let factor2 = 1.0/area
        centerX = centerX * factor2
        centerY = centerY * factor2
        let center = CGPoint.init(x: centerX, y: centerY)
        return center
    }
    
    let point0 = CGPoint.init(x: 1, y: 1)
    let point1 = CGPoint.init(x: 2, y: 2)
    let point2 = CGPoint.init(x: 4, y: 3)
    let point3 = CGPoint.init(x: 4, y: 5)
    let point4 = CGPoint.init(x: 3, y: 4)
    let point5 = CGPoint.init(x: 2, y: 4)
    let point6 = CGPoint.init(x: 1, y: 5)
    let point7 = CGPoint.init(x: 3, y: 2)
    let polygon = [point0, point1, point2, point3, point4, point5, point6, point7]
    let center = polygonCenterOfMass(polygon: polygon)