Python: find area of polygon from xyz coordinates

后端 未结 5 1134
你的背包
你的背包 2020-12-28 18:07

I\'m trying to use the shapely.geometry.Polygon module to find the area of polygons but it performs all calculations on the xy plane. This is fine

相关标签:
5条回答
  • 2020-12-28 18:19

    Here is the derivation of a formula for calculating the area of a 3D planar polygon

    Here is Python code that implements it:

    #determinant of matrix a
    def det(a):
        return a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0] + a[0][2]*a[1][0]*a[2][1] - a[0][2]*a[1][1]*a[2][0] - a[0][1]*a[1][0]*a[2][2] - a[0][0]*a[1][2]*a[2][1]
    
    #unit normal vector of plane defined by points a, b, and c
    def unit_normal(a, b, c):
        x = det([[1,a[1],a[2]],
                 [1,b[1],b[2]],
                 [1,c[1],c[2]]])
        y = det([[a[0],1,a[2]],
                 [b[0],1,b[2]],
                 [c[0],1,c[2]]])
        z = det([[a[0],a[1],1],
                 [b[0],b[1],1],
                 [c[0],c[1],1]])
        magnitude = (x**2 + y**2 + z**2)**.5
        return (x/magnitude, y/magnitude, z/magnitude)
    
    #dot product of vectors a and b
    def dot(a, b):
        return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
    
    #cross product of vectors a and b
    def cross(a, b):
        x = a[1] * b[2] - a[2] * b[1]
        y = a[2] * b[0] - a[0] * b[2]
        z = a[0] * b[1] - a[1] * b[0]
        return (x, y, z)
    
    #area of polygon poly
    def area(poly):
        if len(poly) < 3: # not a plane - no area
            return 0
    
        total = [0, 0, 0]
        for i in range(len(poly)):
            vi1 = poly[i]
            if i is len(poly)-1:
                vi2 = poly[0]
            else:
                vi2 = poly[i+1]
            prod = cross(vi1, vi2)
            total[0] += prod[0]
            total[1] += prod[1]
            total[2] += prod[2]
        result = dot(total, unit_normal(poly[0], poly[1], poly[2]))
        return abs(result/2)
    

    And to test it, here's a 10x5 square that leans over:

    >>> poly = [[0, 0, 0], [10, 0, 0], [10, 3, 4], [0, 3, 4]]
    >>> poly_translated = [[0+5, 0+5, 0+5], [10+5, 0+5, 0+5], [10+5, 3+5, 4+5], [0+5, 3+5, 4+5]]
    >>> area(poly)
    50.0
    >>> area(poly_translated)
    50.0
    >>> area([[0,0,0],[1,1,1]])
    0
    

    The problem originally was that I had oversimplified. It needs to calculate the unit vector normal to the plane. The area is half of the dot product of that and the total of all the cross products, not half of the sum of all the magnitudes of the cross products.

    This can be cleaned up a bit (matrix and vector classes would make it nicer, if you have them, or standard implementations of determinant/cross product/dot product), but it should be conceptually sound.

    0 讨论(0)
  • 2020-12-28 18:29

    Same as @Tom Smilack's answer, but in javascript

    //determinant of matrix a
    function det(a) {
      return a[0][0] * a[1][1] * a[2][2] + a[0][1] * a[1][2] * a[2][0] + a[0][2] * a[1][0] * a[2][1] - a[0][2] * a[1][1] * a[2][0] - a[0][1] * a[1][0] * a[2][2] - a[0][0] * a[1][2] * a[2][1];
    }
    //unit normal vector of plane defined by points a, b, and c
    function unit_normal(a, b, c) {
      let x = math.det([
        [1, a[1], a[2]],
        [1, b[1], b[2]],
        [1, c[1], c[2]]
      ]);
      let y = math.det([
        [a[0], 1, a[2]],
        [b[0], 1, b[2]],
        [c[0], 1, c[2]]
      ]);
      let z = math.det([
        [a[0], a[1], 1],
        [b[0], b[1], 1],
        [c[0], c[1], 1]
      ]);
      let magnitude = Math.pow(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2), 0.5);
      return [x / magnitude, y / magnitude, z / magnitude];
    }
    // dot product of vectors a and b
    function dot(a, b) {
      return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    }
    // cross product of vectors a and b
    function cross(a, b) {
      let x = (a[1] * b[2]) - (a[2] * b[1]);
      let y = (a[2] * b[0]) - (a[0] * b[2]);
      let z = (a[0] * b[1]) - (a[1] * b[0]);
      return [x, y, z];
    }
    
    // area of polygon poly
    function area(poly) {
      if (poly.length < 3) {
        console.log("not a plane - no area");
        return 0;
      } else {
        let total = [0, 0, 0]
        for (let i = 0; i < poly.length; i++) {
          var vi1 = poly[i];
          if (i === poly.length - 1) {
            var vi2 = poly[0];
          } else {
            var vi2 = poly[i + 1];
          }
          let prod = cross(vi1, vi2);
          total[0] = total[0] + prod[0];
          total[1] = total[1] + prod[1];
          total[2] = total[2] + prod[2];
        }
        let result = dot(total, unit_normal(poly[0], poly[1], poly[2]));
    
        return Math.abs(result/2);
      }
    
    }

    0 讨论(0)
  • This is the final code I've used. It doesn't use shapely, but implements Stoke's theorem to calculate the area directly. It builds on @Tom Smilack's answer which shows how to do it without numpy.

    import numpy as np
    
    #unit normal vector of plane defined by points a, b, and c
    def unit_normal(a, b, c):
        x = np.linalg.det([[1,a[1],a[2]],
             [1,b[1],b[2]],
             [1,c[1],c[2]]])
        y = np.linalg.det([[a[0],1,a[2]],
             [b[0],1,b[2]],
             [c[0],1,c[2]]])
        z = np.linalg.det([[a[0],a[1],1],
             [b[0],b[1],1],
             [c[0],c[1],1]])
        magnitude = (x**2 + y**2 + z**2)**.5
        return (x/magnitude, y/magnitude, z/magnitude)
    
    #area of polygon poly
    def poly_area(poly):
        if len(poly) < 3: # not a plane - no area
            return 0
        total = [0, 0, 0]
        N = len(poly)
        for i in range(N):
            vi1 = poly[i]
            vi2 = poly[(i+1) % N]
            prod = np.cross(vi1, vi2)
            total[0] += prod[0]
            total[1] += prod[1]
            total[2] += prod[2]
        result = np.dot(total, unit_normal(poly[0], poly[1], poly[2]))
        return abs(result/2)
    
    0 讨论(0)
  • 2020-12-28 18:41

    The area of a 2D polygon can be calculated using Numpy as a one-liner...

    poly_Area(vertices) = np.sum( [0.5, -0.5] * vertices * np.roll( np.roll(vertices, 1, axis=0), 1, axis=1) )
    
    0 讨论(0)
  • 2020-12-28 18:45

    Fyi, here is the same algorithm in Mathematica, with a baby unit test

    ClearAll[vertexPairs, testPoly, area3D, planeUnitNormal, pairwise];
    pairwise[list_, fn_] := MapThread[fn, {Drop[list, -1], Drop[list, 1]}];
    vertexPairs[Polygon[{points___}]] := Append[{points}, First[{points}]];
    testPoly = Polygon[{{20, -30, 0}, {40, -30, 0}, {40, -30, 20}, {20, -30, 20}}];
    planeUnitNormal[Polygon[{points___}]] :=
      With[{ps = Take[{points}, 3]},
       With[{p0 = First[ps]},
        With[{qs = (# - p0) & /@ Rest[ps]},
         Normalize[Cross @@ qs]]]];
    area3D[p : Polygon[{polys___}]] :=
      With[{n = planeUnitNormal[p], vs = vertexPairs[p]},
       With[{areas = (Dot[n, #]) & /@ pairwise[vs, Cross]},
        Plus @@ areas/2]];
    area3D[testPoly]
    
    0 讨论(0)
提交回复
热议问题