示例#1
0
def geo_bounds(geometries):
    """Calculate bounds in WGS84 coordinates for each geometry.

    As a faster approximation, only the the bounding coordinates are projected
    to WGS84 before calculating the outer bounds.

    Coordinates are rounded to 5 decimal places.

    Parameters
    ----------
    flowlines : ndarray of pygeos geometries

    Returns
    -------
        ndarray of (xmin, ymin, xmax, ymax) for each geometry
    """

    transformer = Transformer.from_crs(CRS, GEO_CRS, always_xy=True)

    xmin, ymin, xmax, ymax = pg.bounds(geometries).T

    # transform all 4 corners then take min/max
    x1, y1 = transformer.transform(xmin, ymin)
    x2, y2 = transformer.transform(xmin, ymax)
    x3, y3 = transformer.transform(xmax, ymin)
    x4, y4 = transformer.transform(xmax, ymax)

    return (np.array([
        np.min([x1, x2], axis=0),
        np.min([y1, y3], axis=0),
        np.max([x3, x4], axis=0),
        np.max([y2, y4], axis=0),
    ]).round(5).astype("float32").T)
示例#2
0
    def _morphological_tessellation(self,
                                    gdf,
                                    unique_id,
                                    limit,
                                    shrink,
                                    segment,
                                    verbose,
                                    check=True):
        objects = gdf

        if shrink != 0:
            print("Inward offset...") if verbose else None
            mask = objects.type.isin(["Polygon", "MultiPolygon"])
            objects.loc[mask, objects.geometry.name] = objects[mask].buffer(
                -shrink, cap_style=2, join_style=2)

        objects = objects.reset_index(drop=True).explode()
        objects = objects.set_index(unique_id)

        print("Generating input point array...") if verbose else None
        points, ids = self._dense_point_array(objects.geometry.values.data,
                                              distance=segment,
                                              index=objects.index)

        hull = pygeos.convex_hull(limit)
        bounds = pygeos.bounds(hull)
        width = bounds[2] - bounds[0]
        leng = bounds[3] - bounds[1]
        hull = pygeos.buffer(hull, 2 * width if width > leng else 2 * leng)

        hull_p, hull_ix = self._dense_point_array(
            [hull], distance=pygeos.length(hull) / 100, index=[0])
        points = np.append(points, hull_p, axis=0)
        ids = ids + ([-1] * len(hull_ix))

        print("Generating Voronoi diagram...") if verbose else None
        voronoi_diagram = Voronoi(np.array(points))

        print("Generating GeoDataFrame...") if verbose else None
        regions_gdf = self._regions(voronoi_diagram,
                                    unique_id,
                                    ids,
                                    crs=gdf.crs)

        print("Dissolving Voronoi polygons...") if verbose else None
        morphological_tessellation = regions_gdf[[unique_id, "geometry"
                                                  ]].dissolve(by=unique_id,
                                                              as_index=False)

        morphological_tessellation = gpd.clip(
            morphological_tessellation, gpd.GeoSeries(limit, crs=gdf.crs))

        if check:
            self._check_result(morphological_tessellation,
                               gdf,
                               unique_id=unique_id)

        return morphological_tessellation
示例#3
0
def bounds(data):
    if compat.USE_PYGEOS:
        return pygeos.bounds(data)
    # ensure that for empty arrays, the result has the correct shape
    if len(data) == 0:
        return np.empty((0, 4), dtype="float64")
    # need to explicitly check for empty (in addition to missing) geometries,
    # as those return an empty tuple, not resulting in a 2D array
    bounds = np.array([
        geom.bounds if not (geom is None or geom.is_empty) else
        (np.nan, np.nan, np.nan, np.nan) for geom in data
    ])
    return bounds
示例#4
0
def as_boxes(x):
    """Convert an array of geometries to an array of bounding boxes polygons.

    Args:
        x (numpy.ndarray): An array of points.

    Returns:
        numpy.ndarray: An array of polygons.

    """
    coordinates = bounds(x)
    return box(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2],
               coordinates[:, 3])
示例#5
0
def length_width_diff(collection):
    """
    The Eig & Seitzinger (1981) shape measure, defined as:

    L - W

    Where L is the maximal east-west extent and W is the maximal north-south
    extent.

    Defined as measure LW_5 in Altman (1998)
    """
    ga = _cast(collection)
    box = pygeos.bounds(ga)
    (xmin, xmax), (ymin, ymax) = box[:, [0, 2]].T, box[:, [1, 3]].T
    width, height = numpy.abs(xmax - xmin), numpy.abs(ymax - ymin)
    return width - height
示例#6
0
def diameter_ratio(collection, rotated=True):
    """
    The Flaherty & Crumplin (1992) length-width measure, stated as measure LW_7
    in Altman (1998).

    It is given as the ratio between the minimum and maximum shape diameter.
    """
    ga = _cast(collection)
    if rotated:
        box = pygeos.minimum_rotated_rectangle(ga)
        coords = pygeos.get_coordinates(box)
        a, b, c, d = (coords[0::5], coords[1::5], coords[2::5], coords[3::5])
        widths = numpy.sqrt(numpy.sum((a - b)**2, axis=1))
        heights = numpy.sqrt(numpy.sum((a - d)**2, axis=1))
    else:
        box = pygeos.bounds(ga)
        (xmin, xmax), (ymin, ymax) = box[:, [0, 2]].T, box[:, [1, 3]].T
        widths, heights = numpy.abs(xmax - xmin), numpy.abs(ymax - ymin)
    return numpy.minimum(widths, heights) / numpy.maximum(widths, heights)
示例#7
0
def window(geometries, distance):
    """Return windows around geometries bounds +/- distance

    Parameters
    ----------
    geometries : Series or ndarray
        geometries to window
    distance : number or ndarray
        radius of window
        if ndarry, must match length of geometries

    Returns
    -------
    Series or ndarray
        polygon windows
    """
    minx, miny, maxx, maxy = pg.bounds(geometries).T
    windows = pg.box(minx - distance, miny - distance, maxx + distance, maxy + distance)

    if isinstance(geometries, pd.Series):
        return pd.Series(windows, index=geometries.index)

    return windows
示例#8
0
def test_bounds_dimensions(geom, shape):
    assert pygeos.bounds(geom).shape == shape
示例#9
0
def test_bounds(geom, expected):
    assert_array_equal(pygeos.bounds(geom), expected)
示例#10
0
        "geometry":
        pg.union_all(state_df.loc[state_df.id.isin(
            REGION_STATES[region])].geometry.values.data),
        "id":
        region,
    } for region in REGION_STATES],
    crs=CRS,
)
write_dataframe(bnd_df, out_dir / "region_boundary.gpkg")
bnd_df.to_feather(out_dir / "region_boundary.feather")

bnd = bnd_df.geometry.values.data[0]
sarp_bnd = bnd_df.loc[bnd_df.id == "se"].geometry.values.data[0]

bnd_geo = bnd_df.to_crs(GEO_CRS)
bnd_geo["bbox"] = pg.bounds(bnd_geo.geometry.values.data).round(2).tolist()

# used to render maps
with open(ui_dir / "region_bounds.json", "w") as out:
    out.write(bnd_geo[["id", "bbox"]].to_json(orient="records"))

# create mask
world = pg.box(-180, -85, 180, 85)
bnd_mask = bnd_geo.copy()
bnd_mask["geometry"] = pg.normalize(
    pg.difference(world, bnd_mask.geometry.values.data))
write_dataframe(bnd_mask, out_dir / "region_mask.gpkg")

### Extract HUC4 units that intersect boundaries

print("Extracting HUC2...")
示例#11
0
 def _(shape: pygeos.Geometry):
     """
     If we know we're working with a pygeos polygon, 
     then use pygeos.bounds
     """
     return pygeos.bounds(shape)
示例#12
0
def test_bounds_empty():
    actual = pygeos.bounds(empty)
    assert np.isnan(actual).all()
示例#13
0
def test_bounds_missing():
    actual = pygeos.bounds(None)
    assert np.isnan(actual).all()
示例#14
0
def test_bounds_array():
    actual = pygeos.bounds([[point, multi_point], [polygon, None]])
    assert actual.shape == (2, 2, 4)
示例#15
0
def test_bounds(geom, expected):
    actual = pygeos.bounds(geom)
    assert actual.tolist() == expected
示例#16
0
    def __init__(
        self,
        gdf,
        unique_id,
        limit=None,
        shrink=0.4,
        segment=0.5,
        verbose=True,
        enclosures=None,
        enclosure_id="eID",
        threshold=0.05,
        use_dask=True,
        n_chunks=None,
        **kwargs,
    ):
        self.gdf = gdf
        self.id = gdf[unique_id]
        self.limit = limit
        self.shrink = shrink
        self.segment = segment
        self.enclosure_id = enclosure_id

        if limit is not None and enclosures is not None:
            raise ValueError(
                "Both `limit` and `enclosures` cannot be passed together. "
                "Pass `limit` for morphological tessellation or `enclosures` "
                "for enclosed tessellation.")

        gdf = gdf.copy()

        if enclosures is not None:

            enclosures = enclosures.copy()

            bounds = enclosures.total_bounds
            centre_x = (bounds[0] + bounds[2]) / 2
            centre_y = (bounds[1] + bounds[3]) / 2

            gdf.geometry = gdf.geometry.translate(xoff=-centre_x,
                                                  yoff=-centre_y)
            enclosures.geometry = enclosures.geometry.translate(xoff=-centre_x,
                                                                yoff=-centre_y)

            self.tessellation = self._enclosed_tessellation(
                gdf,
                enclosures,
                unique_id,
                enclosure_id,
                threshold,
                use_dask,
                n_chunks,
            )
        else:
            if isinstance(limit, (gpd.GeoSeries, gpd.GeoDataFrame)):
                limit = limit.unary_union
            if isinstance(limit, BaseGeometry):
                limit = pygeos.from_shapely(limit)

            bounds = pygeos.bounds(limit)
            centre_x = (bounds[0] + bounds[2]) / 2
            centre_y = (bounds[1] + bounds[3]) / 2
            gdf.geometry = gdf.geometry.translate(xoff=-centre_x,
                                                  yoff=-centre_y)

            # add convex hull buffered large distance to eliminate infinity issues
            limit = (gpd.GeoSeries(limit, crs=gdf.crs).translate(
                xoff=-centre_x, yoff=-centre_y).values.data[0])

            self.tessellation = self._morphological_tessellation(
                gdf, unique_id, limit, shrink, segment, verbose)

        self.tessellation["geometry"] = self.tessellation[
            "geometry"].translate(xoff=centre_x, yoff=centre_y)
示例#17
0
    def _morphological_tessellation(self,
                                    gdf,
                                    unique_id,
                                    limit,
                                    shrink,
                                    segment,
                                    verbose,
                                    check=True):
        objects = gdf.copy()

        if isinstance(limit, (gpd.GeoSeries, gpd.GeoDataFrame)):
            limit = limit.unary_union
        if isinstance(limit, BaseGeometry):
            limit = pygeos.from_shapely(limit)

        bounds = pygeos.bounds(limit)
        centre_x = (bounds[0] + bounds[2]) / 2
        centre_y = (bounds[1] + bounds[3]) / 2
        objects["geometry"] = objects["geometry"].translate(xoff=-centre_x,
                                                            yoff=-centre_y)

        if shrink != 0:
            print("Inward offset...") if verbose else None
            mask = objects.type.isin(["Polygon", "MultiPolygon"])
            objects.loc[mask, "geometry"] = objects[mask].buffer(-shrink,
                                                                 cap_style=2,
                                                                 join_style=2)

        objects = objects.reset_index(drop=True).explode()
        objects = objects.set_index(unique_id)

        print("Generating input point array...") if verbose else None
        points, ids = self._dense_point_array(objects.geometry.values.data,
                                              distance=segment,
                                              index=objects.index)

        # add convex hull buffered large distance to eliminate infinity issues
        series = gpd.GeoSeries(limit, crs=gdf.crs).translate(xoff=-centre_x,
                                                             yoff=-centre_y)
        width = bounds[2] - bounds[0]
        leng = bounds[3] - bounds[1]
        hull = series.geometry[[0]].buffer(2 * width if width > leng else 2 *
                                           leng)
        # pygeos bug fix
        if (hull.type == "MultiPolygon").any():
            hull = hull.explode()
        hull_p, hull_ix = self._dense_point_array(
            hull.values.data,
            distance=pygeos.length(limit) / 100,
            index=hull.index)
        points = np.append(points, hull_p, axis=0)
        ids = ids + ([-1] * len(hull_ix))

        print("Generating Voronoi diagram...") if verbose else None
        voronoi_diagram = Voronoi(np.array(points))

        print("Generating GeoDataFrame...") if verbose else None
        regions_gdf = self._regions(voronoi_diagram,
                                    unique_id,
                                    ids,
                                    crs=gdf.crs)

        print("Dissolving Voronoi polygons...") if verbose else None
        morphological_tessellation = regions_gdf[[unique_id, "geometry"
                                                  ]].dissolve(by=unique_id,
                                                              as_index=False)

        morphological_tessellation = gpd.clip(morphological_tessellation,
                                              series)

        morphological_tessellation["geometry"] = morphological_tessellation[
            "geometry"].translate(xoff=centre_x, yoff=centre_y)

        if check:
            self._check_result(morphological_tessellation,
                               gdf,
                               unique_id=unique_id)

        return morphological_tessellation
# write out for tiles
write_dataframe(df.rename(columns={
    "ECO4": "id",
    "ECO4Name": "name"
}), out_dir / "sarp_eco4.gpkg")

### Extract bounds and names for unit search in user interface
print("Projecting geometries to geographic coordinates for search index")
print("Processing state and county")
state_geo_df = (gp.read_feather(out_dir / "region_states.feather",
                                columns=["geometry",
                                         "STATEFIPS"]).rename(columns={
                                             "STATEFIPS": "id"
                                         }).to_crs(GEO_CRS))
state_geo_df["bbox"] = pg.bounds(
    state_geo_df.geometry.values.data).round(1).tolist()

states_geo = (gp.read_feather(out_dir / "states.feather",
                              columns=["geometry",
                                       "STATEFIPS"]).rename(columns={
                                           "STATEFIPS": "id"
                                       }).to_crs(GEO_CRS))

county_geo_df = (county_df.loc[county_df.STATEFIPS.isin(states)].rename(
    columns={
        "COUNTYFIPS": "id",
        "County": "name",
        "STATEFIPS": "state"
    }).to_crs(GEO_CRS))
county_geo_df["bbox"] = pg.bounds(
    county_geo_df.geometry.values.data).round(2).tolist()