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
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
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"}
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"])
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))
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
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"]
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
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
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))
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]])
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))
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
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
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)
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
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
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
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
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)
def point(self) -> Point: return point(self._rnd([self.lon, self.lat]))
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
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