Exemple #1
0
 def test_one_triangle(self):
     a = numpy.array([0., 0., 0.])
     b = numpy.array([1., 0., 0.])
     c = numpy.array([0., 1., 0.])
     self.assertAlmostEqual(utils.triangle_area(a - b, a - c, b - c), 0.5)
     b = numpy.array([1., 0., 1.])
     self.assertAlmostEqual(utils.triangle_area(a - b, a - c, b - c),
                            (2 ** 0.5) / 2)
Exemple #2
0
    def get_cell_dimensions(self):
        """
        Calculate centroid, width, length and area of each mesh cell.

        :returns:
            Tuple of four elements, each being 2d numpy array.
            Each array has both dimensions less by one the dimensions
            of the mesh, since they represent cells, not vertices.
            Arrays contain the following cell information:

            #. centroids, 3d vectors in a Cartesian space,
            #. length (size along row of points) in km,
            #. width (size along column of points) in km,
            #. area in square km.
        """
        points, along_azimuth, updip, diag = self.triangulate()
        top = along_azimuth[:-1]
        left = updip[:, :-1]
        tl_area = geo_utils.triangle_area(top, left, diag)
        top_length = numpy.sqrt(numpy.sum(top * top, axis=-1))
        left_length = numpy.sqrt(numpy.sum(left * left, axis=-1))

        bottom = along_azimuth[1:]
        right = updip[:, 1:]
        br_area = geo_utils.triangle_area(bottom, right, diag)
        bottom_length = numpy.sqrt(numpy.sum(bottom * bottom, axis=-1))
        right_length = numpy.sqrt(numpy.sum(right * right, axis=-1))

        cell_area = tl_area + br_area

        tl_center = (points[:-1, :-1] + points[:-1, 1:] + points[1:, :-1]) / 3
        br_center = (points[:-1, 1:] + points[1:, :-1] + points[1:, 1:]) / 3

        cell_center = ((tl_center * tl_area.reshape(tl_area.shape + (1, ))
                        + br_center * br_area.reshape(br_area.shape + (1, )))
                       / cell_area.reshape(cell_area.shape + (1, )))

        cell_length = ((top_length * tl_area + bottom_length * br_area)
                       / cell_area)
        cell_width = ((left_length * tl_area + right_length * br_area)
                      / cell_area)

        return cell_center, cell_length, cell_width, cell_area
Exemple #3
0
    def test_arrays(self):
        # 1d array of vectors
        aa = numpy.array([(0.5, 0., 3.), (0., 2., -1.)])
        bb = numpy.array([(0.5, 4., 3.), (0, 2, -2.)])
        cc = numpy.array([(-1.5, 0., 3.), (1, 2, -2)])
        areas = utils.triangle_area(aa - bb, aa - cc, bb - cc)
        self.assertTrue(numpy.allclose(areas, [4.0, 0.5]))

        # 2d array
        aa = numpy.array([aa, aa * 2])
        bb = numpy.array([bb, bb * 2])
        cc = numpy.array([cc, cc * 2])
        expected_area = [[4.0, 0.5], [16.0, 2.0]]
        areas = utils.triangle_area(aa - bb, aa - cc, bb - cc)
        self.assertTrue(numpy.allclose(areas, expected_area), msg=str(areas))

        # 3d array
        aa = numpy.array([aa])
        bb = numpy.array([bb])
        cc = numpy.array([cc])
        expected_area = numpy.array([expected_area])
        areas = utils.triangle_area(aa - bb, aa - cc, bb - cc)
        self.assertTrue(numpy.allclose(areas, expected_area), msg=str(areas))
Exemple #4
0
    def get_mean_inclination_and_azimuth(self):
        """
        Calculate weighted average inclination and azimuth of the mesh surface.

        :returns:
            Tuple of two float numbers: inclination angle in a range [0, 90]
            and azimuth in range [0, 360) (in decimal degrees).

        The mesh is triangulated, the inclination and azimuth for each triangle
        is computed and average values weighted on each triangle's area
        are calculated. Azimuth is always defined in a way that inclination
        angle doesn't exceed 90 degree.
        """
        assert not 1 in self.lons.shape, (
            "inclination and azimuth are only defined for mesh of more than "
            "one row and more than one column of points"
        )

        if self.depths is not None:
            assert ((self.depths[1:] - self.depths[:-1]) >= 0).all(), (
                "get_mean_inclination_and_azimuth() requires next mesh row "
                "to be not shallower than the previous one"
            )

        points, along_azimuth, updip, diag = self.triangulate()

        # define planes that are perpendicular to each point's vector
        # as normals to those planes
        earth_surface_tangent_normal = geo_utils.normalized(points)

        # calculating triangles' area and normals for top-left triangles
        e1 = along_azimuth[:-1]
        e2 = updip[:, :-1]
        tl_area = geo_utils.triangle_area(e1, e2, diag)
        tl_normal = geo_utils.normalized(numpy.cross(e1, e2))
        # ... and bottom-right triangles
        e1 = along_azimuth[1:]
        e2 = updip[:, 1:]
        br_area = geo_utils.triangle_area(e1, e2, diag)
        br_normal = geo_utils.normalized(numpy.cross(e1, e2))

        if self.depths is None:
            # mesh is on earth surface, inclination is zero
            inclination = 0
        else:
            # inclination calculation
            # top-left triangles
            en = earth_surface_tangent_normal[:-1, :-1]
            # cosine of inclination of the triangle is scalar product
            # of vector normal to triangle plane and (normalized) vector
            # pointing to top left corner of a triangle from earth center
            incl_cos = numpy.sum(en * tl_normal, axis=-1).clip(-1.0, 1.0)
            # we calculate average angle using mean of circular quantities
            # formula: define 2d vector for each triangle where length
            # of the vector corresponds to triangle's weight (we use triangle
            # area) and angle is equal to inclination angle. then we calculate
            # the angle of vector sum of all those vectors and that angle
            # is the weighted average.
            xx = numpy.sum(tl_area * incl_cos)
            # express sine via cosine using Pythagorean trigonometric identity,
            # this is a bit faster than sin(arccos(incl_cos))
            yy = numpy.sum(tl_area * numpy.sqrt(1 - incl_cos * incl_cos))

            # bottom-right triangles
            en = earth_surface_tangent_normal[1:, 1:]
            # we need to clip scalar product values because in some cases
            # they might exceed range where arccos is defined ([-1, 1])
            # because of floating point imprecision
            incl_cos = numpy.sum(en * br_normal, axis=-1).clip(-1.0, 1.0)
            # weighted angle vectors are calculated independently for top-left
            # and bottom-right triangles of each cell in a mesh. here we
            # combine both and finally get the weighted mean angle
            xx += numpy.sum(br_area * incl_cos)
            yy += numpy.sum(br_area * numpy.sqrt(1 - incl_cos * incl_cos))
            inclination = numpy.degrees(numpy.arctan2(yy, xx))

        # azimuth calculation is done similar to one for inclination. we also
        # do separate calculations for top-left and bottom-right triangles
        # and also combine results using mean of circular quantities approach

        # unit vector along z axis
        z_unit = numpy.array([0.0, 0.0, 1.0])

        # unit vectors pointing west from each point of the mesh, they define
        # planes that contain meridian of respective point
        norms_west = geo_utils.normalized(numpy.cross(points + z_unit, points))
        # unit vectors parallel to planes defined by previous ones. they are
        # directed from each point to a point lying on z axis on the same
        # distance from earth center
        norms_north = geo_utils.normalized(numpy.cross(points, norms_west))
        # need to normalize triangles' azimuthal edges because we will project
        # them on other normals and thus calculate an angle in between
        along_azimuth = geo_utils.normalized(along_azimuth)

        # process top-left triangles
        # here we identify the sign of direction of the triangles' azimuthal
        # edges: is edge pointing west or east? for finding that we project
        # those edges to vectors directing to west by calculating scalar
        # product and get the sign of resulting value: if it is negative
        # than the resulting azimuth should be negative as top edge is pointing
        # west.
        sign = numpy.sign(numpy.sign(
            numpy.sum(along_azimuth[:-1] * norms_west[:-1, :-1], axis=-1))
            # we run numpy.sign(numpy.sign(...) + 0.1) to make resulting values
            # be only either -1 or 1 with zero values (when edge is pointing
            # strictly north or south) expressed as 1 (which means "don't
            # change the sign")
            + 0.1
        )
        # the length of projection of azimuthal edge on norms_north is cosine
        # of edge's azimuth
        az_cos = numpy.sum(along_azimuth[:-1] * norms_north[:-1, :-1], axis=-1)
        # use the same approach for finding the weighted mean
        # as for inclination (see above)
        xx = numpy.sum(tl_area * az_cos)
        # the only difference is that azimuth is defined in a range
        # [0, 360), so we need to have two reference planes and change
        # sign of projection on one normal to sign of projection to another one
        yy = numpy.sum(tl_area * numpy.sqrt(1 - az_cos * az_cos) * sign)

        # bottom-right triangles
        sign = numpy.sign(numpy.sign(
            numpy.sum(along_azimuth[1:] * norms_west[1:, 1:], axis=-1))
            + 0.1
        )
        az_cos = numpy.sum(along_azimuth[1:] * norms_north[1:, 1:], axis=-1)
        xx += numpy.sum(br_area * az_cos)
        yy += numpy.sum(br_area * numpy.sqrt(1 - az_cos * az_cos) * sign)

        azimuth = numpy.degrees(numpy.arctan2(yy, xx))
        if azimuth < 0:
            azimuth += 360

        if inclination > 90:
            # average inclination is over 90 degree, that means that we need
            # to reverse azimuthal direction in order for inclination to be
            # in range [0, 90]
            inclination = 180 - inclination
            azimuth = (azimuth + 180) % 360

        return inclination, azimuth