diff --git a/measurements.md b/measurements.md index 28f830c..64b4535 100644 --- a/measurements.md +++ b/measurements.md @@ -427,6 +427,7 @@ square(bbox) | ------- | ---------------------------------------------------------- | ---------------------------------------------- | | `points` | Feature/FeatureCollection of Points | FeatureCollection of Points to find | | `polygons` | Feature/FeatureCollection of Polygon(s)/MultiPolygon(s) | FeatureCollection of Polygon(s)/MultiPolygon(s)| +| `chunk_size` | int | Number of chunks each process to handle. The default value is 1, for a large number of features please use `chunk_size` greater than 1 to get better results in terms of performance.| | Return | Type | Description | | ----------- | ------------------ | ----------------------------------------------------------------- | diff --git a/turfpy/helper.py b/turfpy/helper.py index 7203028..b338622 100644 --- a/turfpy/helper.py +++ b/turfpy/helper.py @@ -59,11 +59,15 @@ def get_coord(coord): raise Exception("coord is required") if not isinstance(coord, list): - if coord.type == "Feature" and coord.geometry and coord.geometry.type == "Point": - return coord.geometry.coordinates - - if coord.type == "Point": - return coord.coordinates + if ( + coord["type"] == "Feature" + and coord["geometry"] + and coord["geometry"]["type"] == "Point" + ): + return coord["geometry"]["coordinates"] + + if coord["type"] == "Point": + return coord["coordinates"] if ( isinstance(coord, list) diff --git a/turfpy/measurement.py b/turfpy/measurement.py index 1c31eaa..42680a9 100644 --- a/turfpy/measurement.py +++ b/turfpy/measurement.py @@ -4,8 +4,11 @@ This is mainly inspired by turf.js. link: http://turfjs.org/ """ +import concurrent.futures +from functools import partial from math import asin, atan2, cos, degrees, log, pi, pow, radians, sin, sqrt, tan -from typing import Optional, Union +from multiprocessing import Manager +from typing import List, Optional, Union from geojson import ( Feature, @@ -1294,7 +1297,9 @@ def square(bbox: list): def points_within_polygon( - points: Union[Feature, FeatureCollection], polygons: Union[Feature, FeatureCollection] + points: Union[Feature, FeatureCollection], + polygons: Union[Feature, FeatureCollection], + chunk_size: int = 1, ) -> FeatureCollection: """Find Point(s) that fall within (Multi)Polygon(s). @@ -1308,26 +1313,46 @@ def points_within_polygon( :param points: A single GeoJSON ``Point`` feature or FeatureCollection of Points. :param polygons: A Single GeoJSON Polygon/MultiPolygon or FeatureCollection of Polygons/MultiPolygons. + :param chunk_size: Number of chunks each process to handle. The default value is + 1, for a large number of features please use `chunk_size` greater than 1 + to get better results in terms of performance. :return: A :class:`geojson.FeatureCollection` of Points. """ - results = [] + if not points: + raise Exception("Points cannot be empty") - def __callback_feature_each(feature, feature_index): - contained = False + if points["type"] == "Point": + points = FeatureCollection([Feature(geometry=points)]) - def __callback_geom_each( - current_geometry, feature_index, feature_properties, feature_bbox, feature_id - ): - if boolean_point_in_polygon(feature, current_geometry): - nonlocal contained - contained = True + if points["type"] == "Feature": + points = FeatureCollection([points]) + + manager = Manager() + results: List[dict] = manager.list() + + part_func = partial( + check_each_point, + polygons=polygons, + results=results, + ) - if contained: - nonlocal results - results.append(feature) + with concurrent.futures.ProcessPoolExecutor() as executor: + for _ in executor.map(part_func, points["features"], chunksize=chunk_size): + pass + + return FeatureCollection(list(results)) + + +def check_each_point(point, polygons, results): + def __callback_geom_each( + current_geometry, feature_index, feature_properties, feature_bbox, feature_id + ): + contained = False + if boolean_point_in_polygon(point, current_geometry): + contained = True - geom_each(polygons, __callback_geom_each) - return True + if contained: + nonlocal results + results.append(point) - feature_each(points, __callback_feature_each) - return FeatureCollection(results) + geom_each(polygons, __callback_geom_each) diff --git a/turfpy/transformation.py b/turfpy/transformation.py index 90be9a4..f04ca2e 100644 --- a/turfpy/transformation.py +++ b/turfpy/transformation.py @@ -814,7 +814,10 @@ def tesselate(poly: Feature) -> FeatureCollection: ... [21, 15], [11, 11], [11, 0]]], "type": "Polygon"}) >>> tesselate(polygon) """ - if poly.geometry.type != "Polygon" and poly.geometry.type != "MultiPolygon": + if ( + poly["geometry"]["type"] != "Polygon" + and poly["geometry"]["type"] != "MultiPolygon" + ): raise ValueError("Geometry must be Polygon or MultiPolygon") fc = FeatureCollection([])