def bbox_polygon(bbox, options=None): """ Takes a bounding box and returns an equivalent Polygon feature. :param bbox: bounding box extent in [minX, minY, maxX, maxY] order :param options: optional parameters [options["properties"]={}] Translate GeoJSON Properties to Point [options["id"]={}] Translate GeoJSON Id to Point :return: a Polygon representation of the bounding box """ if not options: options = {} if not isinstance(bbox, list) or len(bbox) != 4: raise InvalidInput(error_code_messages["InvalidBoundingBox"]) west = float(bbox[0]) south = float(bbox[1]) east = float(bbox[2]) north = float(bbox[3]) low_left = [west, south] top_left = [west, north] top_right = [east, north] low_right = [east, south] return polygon( [[low_left, low_right, top_right, top_left, low_left,]], options.get("properties", None), {"bbox": bbox, "id": options.get("id", None)}, )
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
def hexagon(center, rx, ry, properties, cosines, sines): vertices = [] for i in range(6): x = center[0] + rx * cosines[i] y = center[1] + ry * sines[i] vertices.append([x, y]) vertices.append(vertices[0]) return polygon([vertices], properties)
def calculate_area(geometry): """ Calculate geometry area :param geometry: GeoJSON geometry :return: the geometry area """ coords = get_coords_from_geometry(geometry, ["Polygon", "MultiPolygon"], raise_exception=False) if get_input_dimensions(coords) >= 4: areas = list(map(lambda sub_item: calculate_area(sub_item), coords)) return sum(areas) elif get_input_dimensions(coords) == 3: polygon(coords) return polygon_area(coords) else: return 0
def is_poly_in_poly(feature_1: Sequence, feature_2: Sequence) -> bool: """ Checks if polygon feature_1 is inside polygon feature_2 and either way See http://stackoverflow.com/a/4833823/1979085 :param feature: Coordinates of polygon feature 1 :param feature: Coordinates of polygon feature 1 :return: bool if there is an intersection """ feature_1_line = polygon_to_line(polygon(feature_1)) feature_2_line = polygon_to_line(polygon(feature_2)) for coord1 in feature_1_line["geometry"]["coordinates"]: if boolean_point_in_polygon(coord1, feature_2): return True for coord2 in feature_2_line["geometry"]["coordinates"]: if boolean_point_in_polygon(coord2, feature_1): return True if is_line_on_line(feature_1_line, feature_2_line): return True return False
def hex_triangles(center, rx, ry, properties, cosines, sines): triangles = [] for i in range(6): vertices = [] vertices.append(center) vertices.append( [center[0] + rx * cosines[i], center[1] + ry * sines[i]]) vertices.append([ center[0] + rx * cosines[(i + 1) % 6], center[1] + ry * sines[(i + 1) % 6] ]) vertices.append(center) triangles.append(polygon([vertices], properties)) return triangles
def is_line_in_poly(feature_1: Sequence, feature_2: Sequence) -> bool: """ Checks if a linestring feature is inside or intersects a polygon feature :param feature_1: Coordinates of polygon feature :param feature_2: Coordinates of linestring feature :return: bool if there is an intersection """ feature_1_line = polygon_to_line(polygon(feature_1)) if is_line_on_line(feature_2, feature_1_line): return True for coord in feature_2: if boolean_point_in_polygon(coord, feature_1): return True return False
def is_poly_in_poly(feature_1: Sequence, feature_2: Sequence) -> bool: """ Checks if feature_1 polygon feature is in feature_2 polygon :param feature_1: Coordinates of polygon feature 1 :param feature_2: Coordinates of polygon feature 2 :return: boolean True/False if feature 1 is within feature 2 """ poly_bbox_1 = bbox(feature_1) poly_bbox_2 = bbox(feature_2) if not bbox_overlap(poly_bbox_2, poly_bbox_1): return False feature_1 = polygon_to_line(polygon(feature_1)) line_coords = get_coords_from_features(feature_1) for coords in line_coords: if not boolean_point_in_polygon(coords, feature_2): return False return True
class TestLineIntersectss: @pytest.mark.parametrize( "fixture", [ pytest.param(fixture, id=fixture_name) for fixture_name, fixture in fixtures.items() ], ) def test_line_intersect(self, fixture): line_1 = fixture["in"]["features"][0] line_2 = fixture["in"]["features"][1] result = line_intersect(line_1, line_2) assert result.keys() == fixture["out"].keys() assert len(result["features"]) == len(fixture["out"]["features"]) assert (all([ i in result["features"] for i in fixture["out"]["features"] ]) == True) assert (all([ i in fixture["out"]["features"] for i in result["features"] ]) == True) def test_input_mutation_prevention(self): line_1 = line_string([[7, 50], [8, 50], [9, 50]]) line_2 = line_string([[8, 49], [8, 50], [8, 51]]) line_1_cpy = deepcopy(line_1) line_2_cpy = deepcopy(line_2) _ = line_intersect(line_1, line_2) assert line_1 == line_1_cpy assert line_2 == line_2_cpy @pytest.mark.parametrize( "input_value,expected_value", [ pytest.param( ( line_string([[7, 50], [8, 50], [9, 50]])["geometry"], line_string([[8, 49], [8, 50], [8, 51]])["geometry"], ), [8, 50], id="ListHandling", ), pytest.param( ( feature_collection( [line_string([[7, 50], [8, 50], [9, 50], [7, 50]])]), feature_collection( [line_string([[8, 49], [8, 50], [8, 51], [8, 49]])]), ), [8, 50], id="FeatureCollectionHandling", ), pytest.param( ( polygon([[[7, 50], [8, 50], [9, 50], [7, 50]]]), polygon([[[8, 49], [8, 50], [8, 51], [8, 49]]]), ), [8, 50], id="PolygonHandling", ), ], ) def test_geometric_objects(self, input_value, expected_value): result = line_intersect(*input_value) assert result["features"][0]["geometry"][ "coordinates"] == expected_value
class TestPolygonToLine: @pytest.mark.parametrize( "fixture", [ pytest.param(fixture, id=fixture_name) for fixture_name, fixture in fixtures.items() ], ) def test_polygon_to_line(self, fixture): result = polygon_to_line(fixture["in"]) assert result == fixture["out"] @pytest.mark.parametrize( "input_value,expected_value", [ pytest.param( ( polygon([[[0, 1], [1, 1], [1, 0], [0, 0], [0, 1]]]), { "properties": { "stroke": "#F00", "stroke-width": 6 } }, ), { "type": "Feature", "properties": { "stroke": "#F00", "stroke-width": 6 }, "geometry": { "type": "LineString", "coordinates": [[0, 1], [1, 1], [1, 0], [0, 0], [0, 1] ], }, }, id="PropertiesHandling", ) ], ) def test_properties_handling(self, input_value, expected_value): result = polygon_to_line(*input_value) assert result == expected_value @pytest.mark.parametrize( "input_value,exception_value", [ pytest.param( ( { "type": "Feature", "properties": {}, "geometry": { "coordinates": [[[0, 1], [1, 1], [1, 0], [0, 0], [0, 1]]] }, }, {}, ), error_code_messages["InvalidGeometry"]( ("Polygon", "MultiPolygon")), id="InvalidPolygon", ), pytest.param( ( { "coordinates": [[[0, 1], [1, 1], [1, 0], [0, 0], [0, 1]]] }, { "properties": { "stroke": "#F00", "stroke-width": 6 } }, ), error_code_messages["InvalidGeometry"]( ("Polygon", "MultiPolygon")), id="InvalidPolygon", ), ], ) def test_exception(self, input_value, exception_value): with pytest.raises(Exception) as excinfo: polygon_to_line(*input_value) assert excinfo.type == InvalidInput assert str(excinfo.value) == exception_value
def triangle_grid( bbox: List[float], cell_side: 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 cell_side: dimension of each cell :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 = cell_side / (distance([west, south], [east, south], options)) cell_width_deg = x_fraction * (east - west) y_fraction = cell_side / (distance([west, south], [west, north], options)) cell_height_deg = y_fraction * (north - south) # if the grid does not fill the bbox perfectly, center it. xi = 0 current_x = west while current_x <= east: yi = 0 current_y = south while current_y <= north: cell_triangle1 = None cell_triangle2 = None if (xi % 2 == 0) and (yi % 2 == 0): cell_triangle1 = polygon( [[ [current_x, current_y], [current_x, current_y + cell_height_deg], [current_x + cell_width_deg, current_y], [current_x, current_y], ]], options.get("properties", {}), ) cell_triangle2 = polygon( [[ [current_x, current_y + cell_height_deg], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x + cell_width_deg, current_y], [current_x, current_y + cell_height_deg], ]], options.get("properties", {}), ) elif (xi % 2 == 0) and (yi % 2 == 1): cell_triangle1 = polygon( [[ [current_x, current_y], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x + cell_width_deg, current_y], [current_x, current_y], ]], options.get("properties", {}), ) cell_triangle2 = polygon( [[ [current_x, current_y], [current_x, current_y + cell_height_deg], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x, current_y], ]], options.get("properties", {}), ) elif (yi % 2 == 0) and (xi % 2 == 1): cell_triangle1 = polygon( [[ [current_x, current_y], [current_x, current_y + cell_height_deg], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x, current_y], ]], options.get("properties", {}), ) cell_triangle2 = polygon( [[ [current_x, current_y], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x + cell_width_deg, current_y], [current_x, current_y], ]], options.get("properties", {}), ) elif (yi % 2 == 1) and (xi % 2 == 1): cell_triangle1 = polygon( [[ [current_x, current_y], [current_x, current_y + cell_height_deg], [current_x + cell_width_deg, current_y], [current_x, current_y], ]], options.get("properties", {}), ) cell_triangle2 = polygon( [[ [current_x, current_y + cell_height_deg], [ current_x + cell_width_deg, current_y + cell_height_deg ], [current_x + cell_width_deg, current_y], [current_x, current_y + cell_height_deg], ]], options.get("properties", {}), ) if "mask" in options: if boolean_intersects(options["mask"], cell_triangle1): results.append(cell_triangle1) if boolean_intersects(options["mask"], cell_triangle2): results.append(cell_triangle2) else: results.append(cell_triangle1) results.append(cell_triangle2) current_y += cell_height_deg yi += 1 current_x += cell_width_deg xi += 1 return feature_collection(results)
def rectangle_grid( bbox: List[float], cell_width: Union[int, float], cell_height: Union[int, float], options: Dict = {}, ) -> FeatureCollection: """ Creates a grid of rectangles from a bounding box, Feature or FeatureCollection. :param bbox: Array extent in [minX, minY, maxX, maxY] order :param cell_width: of each cell, in units :param cell_height: 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 = cell_width / (distance([west, south], [east, south], options)) cell_width_deg = x_fraction * (east - west) y_fraction = cell_height / (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 for _ in range(columns): current_y = south + delta_y for _ in range(rows): cell_poly = polygon( [ [ [current_x, current_y], [current_x, current_y + cell_height_deg], [current_x + cell_width_deg, current_y + cell_height_deg], [current_x + cell_width_deg, current_y], [current_x, current_y], ] ], options.get("properties", {}), ) if "mask" in options: if boolean_intersects(options["mask"], cell_poly): results.append(cell_poly) else: results.append(cell_poly) current_y += cell_height_deg current_x += cell_width_deg return feature_collection(results)
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
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
def point_on_feature(features: GeoJSON) -> Point: """ Takes a Feature or FeatureCollection and returns a {Point} guaranteed to be on the surface of the feature. Given a {Polygon}, the point will be in the area of the polygon Given a {LineString}, the point will be along the string Given a {Point}, the point will the same as the input :param features: any GeoJSON feature or feature collection :return: Point GeoJSON Feature on the surface of `input` """ feature_collection = normalize_to_feature_collection(features) center_point = center(feature_collection) center_coords = center_point.get("geometry").get("coordinates") # check to see if centroid is on surface center_on_surface = False geometry_type = get_geometry_type(feature_collection) geometry_coords = get_coords_from_features(feature_collection) if isinstance(geometry_type, str): geometry_type = [geometry_type] for geo_type, geo_coords in zip(geometry_type, geometry_coords): if geo_type in ["Point", "MultiPoint"]: if geo_type == "Point": geo_coords = [geo_coords] for point_coords in geo_coords: if (center_coords[0] == point_coords[0]) and (center_coords[1] == point_coords[1]): center_on_surface = True break elif geo_type in ["LineString", "MultiLineString"]: if geo_type == "LineString": geo_coords = [geo_coords] for line_coords in geo_coords: if boolean_point_on_line(center_coords, line_coords): center_on_surface = True break elif geo_type in ["Polygon", "MultiPolygon"]: if geo_type == "Polygon": geo_coords = polygon(geo_coords) else: geo_coords = multi_polygon(geo_coords) if boolean_point_in_polygon(center_point, geo_coords): center_on_surface = True break if center_on_surface: point_on_surface = center_point else: point_on_surface = nearest_point(center_point, feature_collection) return point_on_surface
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