1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
from warnings import warn
import numpy
from shapely.geometry import MultiPoint
from geopandas.array import (
LINE_GEOM_TYPES,
POLYGON_GEOM_TYPES,
from_shapely,
points_from_xy,
)
from geopandas.geoseries import GeoSeries
def uniform(geom, size, rng=None):
"""
Sample uniformly at random from a geometry.
For polygons, this samples uniformly within the area of the polygon. For lines,
this samples uniformly along the length of the linestring. For multi-part
geometries, the weights of each part are selected according to their relevant
attribute (area for Polygons, length for LineStrings), and then points are
sampled from each part uniformly.
Any other geometry type (e.g. Point, GeometryCollection) are ignored, and an
empty MultiPoint geometry is returned.
Parameters
----------
geom : any shapely.geometry.BaseGeometry type
the shape that describes the area in which to sample.
size : integer
an integer denoting how many points to sample
Returns
-------
shapely.MultiPoint geometry containing the sampled points
Examples
--------
>>> from shapely.geometry import box
>>> square = box(0,0,1,1)
>>> uniform(square, size=102) # doctest: +SKIP
"""
generator = numpy.random.default_rng(seed=rng)
if geom is None or geom.is_empty:
return MultiPoint()
if geom.geom_type in POLYGON_GEOM_TYPES:
return _uniform_polygon(geom, size=size, generator=generator)
if geom.geom_type in LINE_GEOM_TYPES:
return _uniform_line(geom, size=size, generator=generator)
warn(
f"Sampling is not supported for {geom.geom_type} geometry type.",
UserWarning,
stacklevel=8,
)
return MultiPoint()
def _uniform_line(geom, size, generator):
"""Sample points from an input shapely linestring."""
fracs = generator.uniform(size=size)
return from_shapely(geom.interpolate(fracs, normalized=True)).union_all()
def _uniform_polygon(geom, size, generator):
"""Sample uniformly from within a polygon using batched sampling."""
xmin, ymin, xmax, ymax = geom.bounds
candidates = []
while len(candidates) < size:
batch = points_from_xy(
x=generator.uniform(xmin, xmax, size=size),
y=generator.uniform(ymin, ymax, size=size),
)
valid_samples = batch[batch.sindex.query(geom, predicate="contains")]
candidates.extend(valid_samples)
generator.shuffle(candidates) # avoid the artifacts of STRTree ordering
return GeoSeries(candidates[:size]).union_all()
|