Ejemplo n.º 1
0
def is_line_in_poly(feature_1: Sequence, feature_2: Sequence) -> bool:
    """
    Checks if feature_1 linestring feature is in feature_2 polygon

    :param feature_1: Coordinates of linestring feature 1
    :param feature_2: Coordinates of polygon feature 2

    :return: boolean True/False if feature 1 is within feature 2
    """
    line_in_poly = False

    line_bbox = bbox(feature_1)
    poly_bbox = bbox(feature_2)

    if not bbox_overlap(poly_bbox, line_bbox):
        return False

    for i in range(len(feature_1) - 1):

        if not boolean_point_in_polygon(feature_1[i], feature_2):
            return False

        if not line_in_poly:
            line_in_poly = boolean_point_in_polygon(feature_1[i], feature_2,
                                                    {"ignoreBoundary": True})

        if not line_in_poly:
            mid = midpoint(point(feature_1[i]), point(feature_1[i + 1]))
            line_in_poly = boolean_point_in_polygon(mid, feature_2,
                                                    {"ignoreBoundary": True})

    return line_in_poly
Ejemplo n.º 2
0
class TestRhumbDistance:
    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_rhumb_distance(self, fixture):

        pt1 = fixture["in"]["features"][0]
        pt2 = fixture["in"]["features"][1]

        distances = {
            "miles": round(rhumb_distance(pt1, pt2, {"units": "miles"}), 6),
            "nauticalmiles": round(
                rhumb_distance(pt1, pt2, {"units": "nautical_miles"}), 6
            ),
            "kilometers": round(rhumb_distance(pt1, pt2, {"units": "kilometers"}), 6),
            "greatCircleDistance": round(
                distance(pt1, pt2, {"units": "kilometers"}), 6
            ),
            "radians": round(rhumb_distance(pt1, pt2, {"units": "radians"}), 6),
            "degrees": round(rhumb_distance(pt1, pt2, {"units": "degrees"}), 6),
        }

        assert distances == fixture["out"]

    @pytest.mark.parametrize(
        "input_value,exception_value",
        [
            pytest.param(
                (point([0, 0]), point([10, 10]), {"units": "foo"}),
                error_code_messages["InvalidUnits"]("foo"),
                id="InvalidUnits",
            ),
            pytest.param(
                (None, point([10, 10])),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="InvalidGeometry",
            ),
            pytest.param(
                (point([10, 10]), [10]),
                error_code_messages["InvalidPointInput"],
                id="InvalidPointInput",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            rhumb_distance(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 3
0
    def test_input_mutation(self):

        point_1 = point([40, 50], {"featureIndex": "foo"})
        point_2 = point([20, -10], {"distanceToPoint": "bar"})
        points = feature_collection([point_1, point_2])

        result = nearest_point([0, 0], points)

        # Check if featureIndex properties was added to properties
        assert result["properties"]["featureIndex"] == 1

        # Check if previous input points have been modified
        assert point_1["properties"] == {"featureIndex": "foo"}
        assert point_2["properties"] == {"distanceToPoint": "bar"}
Ejemplo n.º 4
0
    def test_exception(self):

        with pytest.raises(Exception) as excinfo:
            rhumb_bearing(point([10, 10]), "point")

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == error_code_messages["InvalidGeometry"](["Point"])
Ejemplo n.º 5
0
def centroid(features, options=None):
    """
    Takes one or more features and calculates the centroid using the mean of all vertices.
    This lessens the effect of small islands and artifacts when calculating the centroid of a set of polygons.

    :param features: GeoJSON features to be centered
    :param options: optional parameters
        [options["properties"]={}] Translate GeoJSON Properties to Point
    :return: a Point feature corresponding to the centroid of the input features
    """

    if not options:
        options = {}

    coords = get_coords_from_features(features)

    if get_input_dimensions(coords) == 1:
        coords = [coords]

    x_sum = 0
    y_sum = 0
    length = 0

    x_sum, y_sum, length = reduce(reduce_coords, coords,
                                  [x_sum, y_sum, length])

    return point([x_sum / length, y_sum / length],
                 options.get("properties", None))
Ejemplo n.º 6
0
class TestPolygonTangents:
    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_polygon_tangens(self, fixture):

        poly = fixture["in"]["features"][0]
        pnt = fixture["in"]["features"][1]

        result = polygon_tangents(pnt, poly)

        result["features"].extend(fixture["in"]["features"])

        assert result == fixture["out"]

    def test_input_mutation_prevention(self):

        pnt = point([61, 5])
        poly = polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15],
                         [11, 11], [11, 0]]])
        result = polygon_tangents(pnt.get("geometry"), poly.get("geometry"))

        assert result != None

    @pytest.mark.parametrize(
        "input_value,exception_value",
        [
            pytest.param(
                (point([61, 5]), point([5, 61])),
                error_code_messages["InvalidGeometry"](
                    ("Polygon", "MultiPolygon")),
                id="InvalidPolygon",
            )
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            polygon_tangents(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 7
0
    def test_nearest_point(self, fixture):

        target_point = point(fixture["in"]["properties"]["targetPoint"])
        result = nearest_point(target_point, fixture["in"])

        result = prepare_response(result, target_point, fixture["in"])

        assert result == fixture["out"]
Ejemplo n.º 8
0
    def test_input_mutation_prevention(self):

        pnt = point([61, 5])
        poly = polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15],
                         [11, 11], [11, 0]]])
        result = polygon_tangents(pnt.get("geometry"), poly.get("geometry"))

        assert result != None
Ejemplo n.º 9
0
class TestBearing:

    start = point([-75, 45], {"marker-color": "#F00"})
    end = point([20, 60], {"marker-color": "#00F"})

    allowed_types = ["Point"]

    def test_calculate_bearing(self):

        initial_bearing = bearing(self.start, self.end)

        assert round(initial_bearing, 2) == 37.75

    def test_calculate_final_bearing(self):

        final_bearing = bearing(self.start, self.end, {"final": True})

        assert round(final_bearing, 2) == 120.01

    @pytest.mark.parametrize(
        "input_value, exception_value",
        [
            pytest.param(
                ([[0, 1]], [2, 3]),
                error_code_messages["InvalidGeometry"](allowed_types),
                id="InvalidStartPoint",
            ),
            pytest.param(
                (point([0, 0]), [2, "xyz"]),
                error_code_messages["InvalidDegrees"],
                id="InvalidEndPoint",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            bearing(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 10
0
def rhumb_destination(features: Dict, options: Dict = None) -> Point:
    """
    Returns the destination {Point} having travelled the given distance along a
    Rhumb line from the origin Point with the (varant) given bearing.

    # https://en.wikipedia.org/wiki/Rhumb_line

    :param features: any GeoJSON feature or feature collection
    :param properties: specification to calculate the rhumb line
        [options["distance"]=100] distance from the starting point
        [options["bearing"]=180] varant bearing angle ranging from -180 to 180 degrees from north
        [options["units"]=kilometers] units: specifies distance (can be degrees, radians, miles, or kilometers)

    :param options: optional parameters also be part of features["properties"]
        [options["units"]={}] can be degrees, radians, miles, or kilometers
        [options["properties"]={}] Translate GeoJSON Properties to Point
        [options["id"]={}] Translate GeoJSON Id to Point

    :return: a FeatureDestination point.
    """
    if not options:
        options = features.get("properties", {})

    coords = get_coords_from_features(features, ["Point"])

    bearing = options.get("bearing", 180)
    distance = options.get("dist", 100)
    units = options.get("units", "kilometers")

    distance_in_meters = convert_length(abs(distance),
                                        original_unit=units,
                                        final_unit="meters")

    if distance < 0:
        distance_in_meters *= -1

    destination = calculate_rhumb_destination(coords, distance_in_meters,
                                              bearing)

    # compensate the crossing of the 180th meridian:
    #  (https://macwright.org/2016/09/26/the-180th-meridian.html)
    # solution from:
    # https://github.com/mapbox/mapbox-gl-js/issues/3250#issuecomment-294887678

    if (destination[0] - coords[0]) > 180:
        destination[0] -= 360
    elif (coords[0] - destination[0]) > 180:
        destination[0] += 360

    return point(destination, options.get("properties", None))
Ejemplo n.º 11
0
def along(line, dist, options=None):
    """
    Takes a LineString and returns a Point at a specified distance along the line

    :param line: input LineString
    :param dist: distance along the line
    :param options: optional parameters
        [options["units"]="kilometers"] can be degrees, radians, miles, or kilometers
    :return: Point `dist` `units` along the line
    """

    if not options or not isinstance(options, dict):
        options = {}

    if not isinstance(dist, (float, int)) or dist < 0:
        raise InvalidInput(error_code_messages["InvalidDistance"])

    coords = get_coords_from_features(line, ["LineString"])

    travelled = 0
    for i in range(len(coords)):
        if dist >= travelled and i == len(coords) - 1:
            break
        elif travelled >= dist:
            overshot = dist - travelled
            if not overshot:
                return point([truncate(coord, 6) for coord in coords[i]])
            else:
                direction = bearing(coords[i], coords[i - 1]) - 180
                interpolated = destination(coords[i], overshot, direction,
                                           options)
                return interpolated
        else:
            travelled += distance(coords[i], coords[i + 1])

    return point([truncate(coord, 6) for coord in coords[-1]])
Ejemplo n.º 12
0
def destination(origin, distance, bearing, options=None):
    """
    Takes a Point and calculates the location of a destination point given a distance in
    degrees, radians, miles, or kilometers; and bearing in degrees.
    This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature.

    :param origin: starting point
    :param distance: distance from the origin point
    :param bearing: bearing ranging from -180 to 180
    :param options: optional parameters
        [options["units"]='kilometers'] miles, kilometers, degrees, or radians
        [options["properties"]={}] Translate properties to Point
    :return: destination GeoJSON Point feature
    """

    if not options:
        options = {}

    kwargs = {}
    if "units" in options:
        kwargs["units"] = options.get("units")

    coords = get_coords_from_features(origin, ["Point"])

    longitude1 = degrees_to_radians(coords[0])
    latitude1 = degrees_to_radians(coords[1])
    bearing_rads = degrees_to_radians(bearing)

    radians = length_to_radians(distance, **kwargs)

    latitude2 = asin(
        sin(latitude1) * cos(radians) +
        cos(latitude1) * sin(radians) * cos(bearing_rads))

    longitude2 = longitude1 + atan2(
        sin(bearing_rads) * sin(radians) * cos(latitude1),
        cos(radians) - sin(latitude1) * sin(latitude2),
    )

    lng = truncate(radians_to_degrees(longitude2), 6)
    lat = truncate(radians_to_degrees(latitude2), 6)

    return point([lng, lat], options.get("properties", None))
Ejemplo n.º 13
0
class TestAlong:

    allowed_types = ["LineString"]

    @pytest.mark.parametrize(
        "fixture,fixture_name",
        [
            pytest.param(fixture, fixture_name, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_along(self, fixture, fixture_name):

        distance = float(fixture_name.split("-")[1])

        print(fixture)
        print(fixture_name)

        assert along(line_string_fixture, distance) == fixture["out"]

    @pytest.mark.parametrize(
        "input_value, exception_value",
        [
            pytest.param(
                (point([0, 1]), 1, 15),
                error_code_messages["InvalidGeometry"](allowed_types),
                id="InvalidPoint",
            ),
            pytest.param(
                ([[0, 1], [2, 3]], -10),
                error_code_messages["InvalidDistance"],
                id="InvalidDistance",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            along(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 14
0
def calculate_intersect(
    seg_1_start: Sequence,
    seg_1_end: Sequence,
    seg_2_start: Sequence,
    seg_2_end: Sequence,
) -> Union[None, Point]:
    """
    Calculates the intersection point of two segments otherwise None

    :param seg_1_start: coordinates of segment 1 start
    :param seg_1_end: coordinates of segment 1 end
    :param seg_2_start: coordinates of segment 2 start
    :param seg_2_end: coordinates of segment 2 end
    :returns: {Point} if intersection, otherwise None
    """
    x1 = seg_1_start[0]
    y1 = seg_1_start[1]
    x2 = seg_1_end[0]
    y2 = seg_1_end[1]
    x3 = seg_2_start[0]
    y3 = seg_2_start[1]
    x4 = seg_2_end[0]
    y4 = seg_2_end[1]

    pnt = None

    denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1))
    numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))
    numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))

    if denom == 0:
        return pnt

    uA = numeA / denom
    uB = numeB / denom

    if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1:
        x = round(x1 + (uA * (x2 - x1)), 6)
        y = round(y1 + (uA * (y2 - y1)), 6)
        pnt = point([x, y])

    return pnt
Ejemplo n.º 15
0
def center(features, options=None):
    """
    Takes a Feature or FeatureCollection and returns the absolute center point of all features.

    :param features: features or collection of features
    :param options: optional parameters
        [options["properties"]={}] Translate GeoJSON Properties to Point
        [options["bbox"]={}] Translate GeoJSON BBox to Point
        [options["id"]={}] Translate GeoJSON Id to Point
    :return: a Point feature at the absolute center point of all input features
    """

    if not options or not isinstance(options, dict):
        options = {}

    bounding_box = bbox(features)
    x = (bounding_box[0] + bounding_box[2]) / 2
    y = (bounding_box[1] + bounding_box[3]) / 2

    return point([x, y], options.get("properties", {}), options)
Ejemplo n.º 16
0
def reduce_coordinates_to_points(
    coords: Sequence, properties: Dict
) -> Sequence[Feature]:
    """
    Transforms dimensionality of the incoming coordinates into Point Features

    :param coords: any sequence of coordinates
    :param properties: properties of the coordinates sequence
    :return: {Feature} points of transformed coordinates
    """

    dim = get_input_dimensions(coords)

    if dim == 1:
        coords = [coords]

    while dim > 2:
        coords = [c for coord in coords for c in coord]
        dim = get_input_dimensions(coords)

    points = [point(coord, properties) for coord in coords]

    return points
Ejemplo n.º 17
0
def polygon_tangents(
    start_point: PointFeature, polygon: PolygonFeature
) -> FeatureCollection:
    """ Finds the tangents of a {Polygon or(MultiPolygon} from a {Point}.

    more:
    http://geomalgorithms.com/a15-_tangents.html

    :param point: point [lng, lat] or Point feature to calculate the tangent points from
    :param polygon: polygon to get tangents from

    :return:
        Feature Collection containing the two tangent points
    """
    point_features = []

    point_coord = get_coords_from_features(start_point, ("Point",))
    polygon_coords = get_coords_from_features(polygon, ("Polygon", "MultiPolygon"))

    geometry_type = get_geometry_type(polygon)

    if isinstance(geometry_type, str):
        geometry_type = [geometry_type]
        polygon_coords = [polygon_coords]

    box = bbox(polygon)

    near_point_index = 0
    near_point = False

    # If the point lies inside the polygon bbox then it's a bit more complicated
    # points lying inside a polygon can reflex angles on concave polygons
    if (
        (point_coord[0] > box[0])
        and (point_coord[0] < box[2])
        and (point_coord[1] > box[1])
        and (point_coord[1] < box[3])
    ):

        near_point = nearest_point(start_point, explode(polygon))
        near_point_index = near_point["properties"]["featureIndex"]

    for geo_type, poly_coords in zip(geometry_type, polygon_coords):

        if geo_type == "Polygon":

            tangents = process_polygon(
                poly_coords, point_coord, near_point, near_point_index
            )

        # bruteforce approach
        # calculate both tangents for each polygon
        # define all tangents as a new polygon and calculate tangetns out of those coordinates
        elif geo_type == "MultiPolygon":

            multi_tangents = []

            for poly_coord in poly_coords:

                tangents = process_polygon(
                    poly_coord, point_coord, near_point, near_point_index
                )
                multi_tangents.extend(tangents)

            tangents = process_polygon(
                [multi_tangents], point_coord, near_point, near_point_index
            )

        r_tangents = tangents[0]
        l_tangents = tangents[1]

        point_features.extend([point(r_tangents), point(l_tangents)])

    return feature_collection(point_features)
class TestGeometryTypeFromFeatures:
    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_get_geometry_type_from_features_geojson(self, fixture):

        result = get_geometry_type(fixture["in"])

        if isinstance(result, tuple):
            assert result == tuple(fixture["out"])
        else:
            assert result == fixture["out"][0]

    @pytest.mark.parametrize(
        "input_value,output_value",
        [
            pytest.param(
                point([4.83, 45.75], as_geojson=False),
                "Point",
                id="point_feature_object",
            ),
            pytest.param(
                line_string([[4.86, 45.76], [4.85, 45.74]], as_geojson=False),
                "LineString",
                id="line_string_feature_object",
            ),
            pytest.param(
                polygon(
                    [[
                        [4.82, 45.79],
                        [4.88, 45.79],
                        [4.91, 45.76],
                        [4.89, 45.72],
                        [4.82, 45.71],
                        [4.77, 45.74],
                        [4.77, 45.77],
                        [4.82, 45.79],
                    ]],
                    as_geojson=False,
                ),
                "Polygon",
                id="polygon_feature_object",
            ),
            pytest.param(
                feature_collection(
                    [
                        polygon(
                            [[
                                [4.82, 45.79],
                                [4.88, 45.79],
                                [4.91, 45.76],
                                [4.89, 45.72],
                                [4.82, 45.71],
                                [4.77, 45.74],
                                [4.77, 45.77],
                                [4.82, 45.79],
                            ]],
                            as_geojson=False,
                        ),
                        line_string([[4.86, 45.76], [4.85, 45.74]],
                                    as_geojson=False),
                        point([4.83, 45.75]),
                    ],
                    as_geojson=False,
                ),
                ("Polygon", "LineString", "Point"),
                id="feature_collection_object",
            ),
            pytest.param(
                Point([4.83, 45.75]),
                "Point",
                id="Point_object",
            ),
            pytest.param(
                LineString([[4.86, 45.76], [4.85, 45.74]]),
                "LineString",
                id="LineString_object",
            ),
        ],
    )
    def test_get_geometry_from_features_objects(self, input_value,
                                                output_value):
        assert get_geometry_type(input_value) == output_value

    @pytest.mark.parametrize(
        "input_value,output_value",
        [
            pytest.param(
                ([4.83, 45.75], ["Point"]),
                "Point",
                id="point_feature_object",
            ),
            pytest.param(
                ([[4.86, 45.76], [4.85, 45.74]], ["LineString"]),
                "LineString",
                id="line_string_feature_object",
            ),
            pytest.param(
                (
                    [[
                        [4.82, 45.79],
                        [4.88, 45.79],
                        [4.91, 45.76],
                        [4.89, 45.72],
                        [4.82, 45.71],
                        [4.77, 45.74],
                        [4.77, 45.77],
                        [4.82, 45.79],
                    ]],
                    ["Polygon"],
                ),
                "Polygon",
                id="polygon_feature_object",
            ),
        ],
    )
    def test_get_geometry_from_lists(self, input_value, output_value):

        assert get_geometry_type(*input_value) == output_value

    @pytest.mark.parametrize(
        "input_value, exception_value",
        [
            pytest.param(
                (4.83),
                error_code_messages["InvalidGeometry"](allowed_types_default),
                id="InvalidFloat",
            ),
            pytest.param(
                (4),
                error_code_messages["InvalidGeometry"](allowed_types_default),
                id="InvalidInt",
            ),
            pytest.param(
                ("4.83"),
                error_code_messages["InvalidGeometry"](allowed_types_default),
                id="InvalidString",
            ),
            pytest.param(
                (None),
                error_code_messages["InvalidGeometry"](allowed_types_default),
                id="InvalidNone",
            ),
            pytest.param(
                ({}),
                error_code_messages["InvalidGeometry"](allowed_types_default),
                id="InvalidFeaturesInput",
            ),
        ],
    )
    def test_general_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            get_geometry_type(input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value

    @pytest.mark.parametrize(
        "input_value,exception_value",
        [
            pytest.param(
                (point([4.83, 45.75], as_geojson=False), ("LineString", )),
                error_code_messages["InvalidGeometry"](["LineString"]),
                id="point_vs_linestring_feature_object",
            ),
            pytest.param(
                (
                    line_string([[4.86, 45.76], [4.85, 45.74]],
                                as_geojson=False),
                    ["Point", "MultiPoint"],
                ),
                error_code_messages["InvalidGeometry"](["Point", "MultiPoint"
                                                        ]),
                id="point_vs_line_string_feature_object",
            ),
            pytest.param(
                (
                    polygon(
                        [[
                            [4.82, 45.79],
                            [4.88, 45.79],
                            [4.91, 45.76],
                            [4.89, 45.72],
                            [4.82, 45.71],
                            [4.77, 45.74],
                            [4.77, 45.77],
                            [4.82, 45.79],
                        ]],
                        as_geojson=False,
                    ),
                    ["Point"],
                ),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="point_vs_polygon_feature_object",
            ),
            pytest.param(
                (
                    feature_collection(
                        [
                            polygon(
                                [[
                                    [4.82, 45.79],
                                    [4.88, 45.79],
                                    [4.91, 45.76],
                                    [4.89, 45.72],
                                    [4.82, 45.71],
                                    [4.77, 45.74],
                                    [4.77, 45.77],
                                    [4.82, 45.79],
                                ]],
                                as_geojson=False,
                            ),
                            line_string([[4.86, 45.76], [4.85, 45.74]],
                                        as_geojson=False),
                            point([4.83, 45.75]),
                        ],
                        as_geojson=False,
                    ),
                    ["Polygon", "Point"],
                ),
                error_code_messages["InvalidGeometry"](["Polygon", "Point"]),
                id="one_wrong_feature_collection_object",
            ),
            pytest.param(
                (Point([4.83, 45.75]), ["MultiPoint"]),
                error_code_messages["InvalidGeometry"](["MultiPoint"]),
                id="No_Point_object",
            ),
            pytest.param(
                (LineString([[4.86, 45.76], [4.85, 45.74]
                             ]), ["MultiLineString"]),
                error_code_messages["InvalidGeometry"](["MultiLineString"]),
                id="No_LineString_object",
            ),
        ],
    )
    def test_specifid_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            get_geometry_type(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 19
0
class TestBooleanPointInPolygon:

    allowed_types_polygon = [
        "Polygon",
        "MultiPolygon",
    ]

    @pytest.mark.parametrize(
        "fixture,points",
        [
            pytest.param(
                fixtures["poly-with-hole"],
                [
                    (point([-86.69208526611328, 36.20373274711739]), False),
                    (point([-86.72229766845702, 36.20258997094334]), True),
                    (point([-86.75079345703125, 36.18527313913089]), False),
                ],
                id="poly-with-hole",
            ),
            pytest.param(
                fixtures["multipoly-with-hole"],
                [
                    (point([-86.69208526611328, 36.20373274711739]), False),
                    (point([-86.72229766845702, 36.20258997094334]), True),
                    (point([-86.75079345703125, 36.18527313913089]), True),
                    (point([-86.75302505493164, 36.23015046460186]), False),
                ],
                id="multipoly-with-hole",
            ),
        ],
    )
    def test_boolean_point_in_polygon(self, fixture, points):

        poly = fixture["in"]

        for pt, result in points:
            assert boolean_point_in_polygon(pt, poly) is result

    @pytest.mark.parametrize(
        "poly,point_in,point_out",
        [
            pytest.param(
                polygon([[[0, 0], [0, 100], [100, 100], [100, 0], [0, 0]]]),
                point([50, 50]),
                point([140, 150]),
                id="simple_polygon",
            ),
            pytest.param(
                polygon([[[0, 0], [50, 50], [0, 100], [100, 100], [100, 0], [0, 0]]]),
                point([75, 75]),
                point([25, 50]),
                id="concave_polygon",
            ),
        ],
    )
    def test_boolean_point_in_polygon_simple(self, poly, point_in, point_out):

        assert boolean_point_in_polygon(point_in, poly)
        assert not boolean_point_in_polygon(point_out, poly)

    @pytest.mark.parametrize(
        "poly,points",
        [
            pytest.param(
                polygon([[[10, 10], [30, 20], [50, 10], [30, 0], [10, 10]]]),
                [
                    [point([10, 10]), lambda ignore_boundary: ignore_boundary is False],
                    [point([30, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([50, 10]), lambda ignore_boundary: ignore_boundary is False],
                    [point([30, 10]), lambda ignore_boundary: True],
                    [point([0, 10]), lambda ignore_boundary: False],
                    [point([60, 10]), lambda ignore_boundary: False],
                    [point([30, -10]), lambda ignore_boundary: False],
                    [point([30, 30]), lambda ignore_boundary: False],
                ],
                id="poly-1",
            ),
            pytest.param(
                polygon([[[10, 0], [30, 20], [50, 0], [30, 10], [10, 0]]]),
                [
                    [point([30, 0]), lambda ignore_boundary: False],
                    [point([0, 0]), lambda ignore_boundary: False],
                    [point([60, 0]), lambda ignore_boundary: False],
                ],
                id="poly-2",
            ),
            pytest.param(
                polygon([[[10, 0], [30, 20], [50, 0], [30, -20], [10, 0]]]),
                [
                    [point([30, 0]), lambda ignore_boundary: True],
                    [point([0, 0]), lambda ignore_boundary: False],
                    [point([60, 0]), lambda ignore_boundary: False],
                ],
                id="poly-3",
            ),
            pytest.param(
                polygon(
                    [
                        [
                            [0, 0],
                            [0, 20],
                            [50, 20],
                            [50, 0],
                            [40, 0],
                            [30, 10],
                            [30, 0],
                            [20, 10],
                            [10, 10],
                            [10, 0],
                            [0, 0],
                        ]
                    ]
                ),
                [
                    [point([0, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([10, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([50, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([0, 10]), lambda ignore_boundary: ignore_boundary is False],
                    [point([5, 10]), lambda ignore_boundary: True],
                    [point([25, 10]), lambda ignore_boundary: True],
                    [point([35, 10]), lambda ignore_boundary: True],
                    [point([0, 0]), lambda ignore_boundary: ignore_boundary is False],
                    [point([20, 0]), lambda ignore_boundary: False],
                    [point([35, 0]), lambda ignore_boundary: False],
                    [point([50, 0]), lambda ignore_boundary: ignore_boundary is False],
                    [point([50, 10]), lambda ignore_boundary: ignore_boundary is False],
                    [point([5, 0]), lambda ignore_boundary: ignore_boundary is False],
                    [point([10, 0]), lambda ignore_boundary: ignore_boundary is False],
                ],
                id="poly-4",
            ),
            pytest.param(
                polygon(
                    [
                        [[0, 20], [20, 40], [40, 20], [20, 0], [0, 20]],
                        [[10, 20], [20, 30], [30, 20], [20, 10], [10, 20]],
                    ]
                ),
                [
                    [point([20, 30]), lambda ignore_boundary: ignore_boundary is False],
                    [point([25, 25]), lambda ignore_boundary: ignore_boundary is False],
                    [point([30, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([25, 15]), lambda ignore_boundary: ignore_boundary is False],
                    [point([20, 10]), lambda ignore_boundary: ignore_boundary is False],
                    [point([15, 15]), lambda ignore_boundary: ignore_boundary is False],
                    [point([10, 20]), lambda ignore_boundary: ignore_boundary is False],
                    [point([15, 25]), lambda ignore_boundary: ignore_boundary is False],
                    [point([20, 20]), lambda ignore_boundary: False],
                ],
                id="poly-5",
            ),
        ],
    )
    @pytest.mark.parametrize(
        "boundary",
        [
            pytest.param(True, id="include-boundary"),
            pytest.param(False, id="ignore-boundary"),
        ],
    )
    def test_boolean_point_in_polygon_boundary(self, boundary, poly, points):

        options = {"ignoreBoundary": boundary}

        for pt, result in points:
            assert boolean_point_in_polygon(pt, poly, options) is result(boundary)

    @pytest.mark.parametrize(
        "pt,poly,exception_value",
        [
            pytest.param(
                "xyz",
                polygon([[[10, 0], [30, 20], [50, 0], [30, 10], [10, 0]]]),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="InvalidGeometry",
            ),
            pytest.param(
                point([0, 1]),
                "",
                error_code_messages["InvalidGeometry"](allowed_types_polygon),
                id="InvalidGeometry",
            ),
            pytest.param(
                point([0, 1]),
                [[0, 1], [1, 2], [2, 3], [0, 1]],
                error_code_messages["InvalidGeometry"](allowed_types_polygon),
                id="InvalidGeometry-input_must_have_a_geometry",
            ),
        ],
    )
    def test_exception(self, pt, poly, exception_value):

        with pytest.raises(Exception) as excinfo:
            boolean_point_in_polygon(pt, poly)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 20
0
class TestPointLineDistance:
    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_point_to_line_distance(self, fixture):

        point = fixture["in"]["features"][0]
        line = fixture["in"]["features"][1]

        properties = fixture["in"].get("properties", {})
        options = {"units": properties.get("units", "kilometers")}

        result = {}

        for method in ["geodesic", "planar"]:

            options["method"] = method
            result[method] = round(
                point_to_line_distance(point, line, options), 8)

        test_result = {k: round(v, 8) for k, v in fixture["out"].items()}

        assert result == test_result

    @pytest.mark.parametrize(
        "input_value,exception_value",
        [
            pytest.param(
                (point([0, 0]), line_string([[1, 1], [-1, 1]]), {
                    "units": "foo"
                }),
                error_code_messages["InvalidUnits"]("foo"),
                id="InvalidUnits",
            ),
            pytest.param(
                (None, line_string([[1, 1], [-1, 1]])),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="InvalidPoint",
            ),
            pytest.param(
                (point([10, 10]), None),
                error_code_messages["InvalidGeometry"](["LineString"]),
                id="InvalidLineStringInput",
            ),
            pytest.param(
                (point([10, 10]), point([10, 10])),
                error_code_messages["InvalidGeometry"](["LineString"]),
                id="InvalidLineStringInput",
            ),
            pytest.param(
                (line_string([[1, 1], [-1, 1]]), line_string([[1, 1], [-1, 1]
                                                              ])),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="InvalidPoint",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):
        with pytest.raises(Exception) as excinfo:
            point_to_line_distance(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 21
0
def point_grid(
    bbox: List[float],
    n_cells: Union[int, float],
    options: Dict = {},
) -> FeatureCollection:
    """
    Creates a square of rectangles from a bounding box, Feature or FeatureCollection.

    :param bbox: Array extent in [minX, minY, maxX, maxY] order
    :param n_cells: number of each cell, in units
    :param options: Optional parameters
        [options["units"]]: units ("degrees", "radians", "miles", "kilometers")
                            of the given cell_width and cell_height
        [options["mask"]]: if passed a Polygon or MultiPolygon here,
                           the grid Points will be created only inside it
        [options["properties"]]: passed to each point of the grid

    :returns: FeatureCollection of a grid of polygons
    """
    if not isinstance(options, dict):
        options = {}

    results = []
    west = bbox[0]
    south = bbox[1]
    east = bbox[2]
    north = bbox[3]

    x_fraction = n_cells / (distance([west, south], [east, south], options))
    cell_width_deg = x_fraction * (east - west)
    y_fraction = n_cells / (distance([west, south], [west, north], options))
    cell_height_deg = y_fraction * (north - south)

    # rows & columns
    bbox_width = east - west
    bbox_height = north - south
    columns = int(bbox_width // cell_width_deg)
    rows = int(bbox_height // cell_height_deg)

    # if the grid does not fill the bbox perfectly, center it.
    delta_x = (bbox_width - columns * cell_width_deg) / 2
    delta_y = (bbox_height - rows * cell_height_deg) / 2

    # iterate over columns & rows
    current_x = west + delta_x
    while current_x <= east:
        current_y = south + delta_y
        while current_y <= north:
            cell_point = point([current_x, current_y],
                               options.get("properties", {}))

            if "mask" in options:
                if boolean_within(cell_point, options["mask"]):
                    results.append(cell_point)
            else:
                results.append(cell_point)

            current_y += cell_height_deg

        current_x += cell_width_deg

    return feature_collection(results)
Ejemplo n.º 22
0
 def point(self) -> Point:
     return point(self._rnd([self.lon, self.lat]))
Ejemplo n.º 23
0
class TestGeometryFromFeatures:
    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_get_geometry_from_features_geojson(self, fixture):

        try:
            allowed_types = fixture["in"]["properties"]["allowed_types"]
        except TypeError:

            allowed_types = fixture["in"][0]
            fixture["in"] = fixture["in"][1]

        assert (
            get_geometry_from_features(fixture["in"], allowed_types) == fixture["out"]
        )

    @pytest.mark.parametrize(
        "input_value,output_value",
        [
            pytest.param(
                (point([4.83, 45.75], as_geojson=False), ["Point"]),
                Point([4.83, 45.75]),
                id="point_feature_object",
            ),
            pytest.param(
                (
                    line_string([[4.86, 45.76], [4.85, 45.74]], as_geojson=False),
                    ["LineString"],
                ),
                LineString([[4.86, 45.76], [4.85, 45.74]]),
                id="line_string_feature_object",
            ),
            pytest.param(
                (
                    polygon(
                        [
                            [
                                [4.82, 45.79],
                                [4.88, 45.79],
                                [4.91, 45.76],
                                [4.89, 45.72],
                                [4.82, 45.71],
                                [4.77, 45.74],
                                [4.77, 45.77],
                                [4.82, 45.79],
                            ]
                        ],
                        as_geojson=False,
                    ),
                    ["Polygon"],
                ),
                Polygon(
                    [
                        [
                            [4.82, 45.79],
                            [4.88, 45.79],
                            [4.91, 45.76],
                            [4.89, 45.72],
                            [4.82, 45.71],
                            [4.77, 45.74],
                            [4.77, 45.77],
                            [4.82, 45.79],
                        ]
                    ]
                ),
                id="polygon_feature_object",
            ),
            pytest.param(
                (
                    feature_collection(
                        [
                            polygon(
                                [
                                    [
                                        [4.82, 45.79],
                                        [4.88, 45.79],
                                        [4.91, 45.76],
                                        [4.89, 45.72],
                                        [4.82, 45.71],
                                        [4.77, 45.74],
                                        [4.77, 45.77],
                                        [4.82, 45.79],
                                    ]
                                ],
                                as_geojson=False,
                            ),
                            line_string(
                                [[4.86, 45.76], [4.85, 45.74]], as_geojson=False
                            ),
                            point([4.83, 45.75]),
                        ],
                        as_geojson=False,
                    ),
                    ["LineString", "Polygon", "Point"],
                ),
                [
                    Polygon(
                        [
                            [
                                [4.82, 45.79],
                                [4.88, 45.79],
                                [4.91, 45.76],
                                [4.89, 45.72],
                                [4.82, 45.71],
                                [4.77, 45.74],
                                [4.77, 45.77],
                                [4.82, 45.79],
                            ]
                        ]
                    ),
                    LineString([[4.86, 45.76], [4.85, 45.74]]),
                    Point([4.83, 45.75]),
                ],
                id="feature_collection_object",
            ),
            pytest.param(
                (Point([4.83, 45.75]), ["Point"]),
                Point([4.83, 45.75]),
                id="Point_object",
            ),
            pytest.param(
                (LineString([[4.86, 45.76], [4.85, 45.74]]), ["LineString"],),
                LineString([[4.86, 45.76], [4.85, 45.74]]),
                id="LineString_object",
            ),
        ],
    )
    def test_get_geometry_from_features_objects(self, input_value, output_value):

        assert get_geometry_from_features(*input_value) == output_value

    @pytest.mark.parametrize(
        "input_value, exception_value",
        [
            pytest.param(
                (point([4.83, 45.75], as_geojson=False), ["LineString"]),
                error_code_messages["InvalidGeometry"](["LineString"]),
                id="InvalidGeometry-geojson",
            ),
            pytest.param(
                ([4.83, 45.75], ["LineString"]),
                error_code_messages["InvalidGeometry"](["LineString"]),
                id="InvalidGeometry-list",
            ),
            pytest.param(
                ([4.83], ["Point"]),
                error_code_messages["InvalidPointInput"],
                id="InvalidPoint",
            ),
            pytest.param(
                ({}, ["Point"]),
                error_code_messages["InvalidGeometry"](["Point"]),
                id="InvalidFeaturesInput",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            get_geometry_from_features(*input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value
Ejemplo n.º 24
0
class TestLength:

    allowed_inpuy_types = [
        "LineString",
        "MultiLineString",
        "Polygon",
        "MultiPolygon",
    ]

    @pytest.mark.parametrize(
        "fixture",
        [
            pytest.param(fixture, id=fixture_name)
            for fixture_name, fixture in fixtures.items()
        ],
    )
    def test_length(self, fixture):

        assert round(length(fixture["in"],
                            {"units": "feet"})) == fixture["out"]

    def test_length_with_feature_classes(self):

        feature = multi_line_string([
            [
                [-77.031669, 38.878605],
                [-77.029609, 38.881946],
                [-77.020339, 38.884084],
                [-77.025661, 38.885821],
                [-77.021884, 38.889563],
                [-77.019824, 38.892368],
            ],
            [
                [-77.041669, 38.885821],
                [-77.039609, 38.881946],
                [-77.030339, 38.884084],
                [-77.035661, 38.878605],
            ],
        ])

        assert round(length(feature, {"units": "feet"})) == 15433

    @pytest.mark.parametrize(
        "input_value, exception_value",
        [
            pytest.param(
                [0, 0],
                error_code_messages["InvalidGeometry"](allowed_inpuy_types),
                id="InvalidGeometry",
            ),
            pytest.param(
                point([0, 0]),
                error_code_messages["InvalidGeometry"](allowed_inpuy_types),
                id="InvalidGeometry",
            ),
        ],
    )
    def test_exception(self, input_value, exception_value):

        with pytest.raises(Exception) as excinfo:
            length(input_value)

        assert excinfo.type == InvalidInput
        assert str(excinfo.value) == exception_value