Beispiel #1
0
def convert_to_points(
    data: gpd.GeoDataFrame,
    crs: dict,
) -> gpd.GeoDataFrame:
    data.geometry = data.geometry.centroid
    data = data.to_crs(crs)
    return data
Beispiel #2
0
def reduce_precision(ingeo: GDF, precision: int = 3) -> GDF:
    """
    Reduces the number of after comma decimals of a shapely Polygon or geodataframe geometries.

    GeoJSON specification recommends 6 decimal places for latitude and longitude which equates to roughly 10cm of
    precision (https://github.com/perrygeo/geojson-precision).

    :param ingeo: input geodataframe.
    :param precision: number of after comma values that should remain.
    :return: Result polygon or geodataframe, same type as input.
    """
    @typechecked
    def _reduce_precision(poly: Polygon, precision: int) -> Polygon:
        geojson = shapely.geometry.mapping(poly)
        try:
            geojson["coordinates"] = np.round(np.array(geojson["coordinates"]),
                                              precision).tolist()
        except TypeError as e:
            # There is a strange polygon for which this does not work TODO! figure it out!
            pass
        poly = shapely.geometry.shape(geojson)
        if (
                not poly.is_valid
        ):  # Too low precision can potentially lead to invalid polygons due to line overlap effects.
            poly = poly.buffer(0)
        return poly

    ingeo.geometry = ingeo.geometry.apply(
        lambda _p: _reduce_precision(poly=_p, precision=precision))
    return ingeo
Beispiel #3
0
def get_pair_footprints(pair_ids: List, plot=True, save_path=None):
    """
    Plot or save overlapping areas between pairs of NAC images

    :param save_path: Path to output a vector file of the geometries, or None for no output. File based on extension.
    :param plot: Create a figure
    :param pair_ids: Pair ids, a list given like ['M106761561LExxM1101080055RE', 'M1096364254RExxM1142334242LE']
    :return: GeoDataFrame containing the pair footprints
    """
    pairs = [pair_id.split('xx') for pair_id in pair_ids]
    df = DataFrame(pairs, index=pair_ids, columns=['prod_id_0', 'prod_id_1'])
    df = df.applymap(get_geometry_from_ODE).applymap(wkt.loads)
    df['intersection'] = df.apply(lambda a: a[0].intersection(a[1]),
                                  axis='columns')
    gdf = GeoDataFrame(df, geometry='intersection')
    gdf['pair_ids'] = gdf.index.values
    if save_path:
        if save_path.endswith('.json'):
            save_driver = 'GeoJSON'
        gdf.drop(['prod_id_0', 'prod_id_1'],
                 axis='columns').to_file(save_path, driver=save_driver)
    if plot:
        gdf.geometry = gdf.geometry.boundary
        intersection_plot = gdf.plot(column='pair_ids',
                                     legend=True,
                                     legend_kwds={
                                         'loc': 'center left',
                                         'bbox_to_anchor': (1, 0.5)
                                     })
        pyplot.xlabel('Longitude, degrees E')
        pyplot.ylabel('Latitude, degrees N')
    return gdf
Beispiel #4
0
def drop_z(map_: gpd.GeoDataFrame):
    """Drops z attribute"""
    if pygeos.has_z(map_.geometry).any():
        warnings.warn(
            "Geometry contains Z co-ordinates. Removed from Map3D (height attribute)"
        )
    map_.geometry = pygeos.apply(map_.geometry, lambda x: x, include_z=False)
    return map_
Beispiel #5
0
def to_pixelcoords(
    ingeo: GDF,
    reference_bounds: Union[rasterio.coords.BoundingBox, tuple],
    scale: bool = False,
    nrows: int = None,
    ncols: int = None,
) -> GDF:
    """
    Converts projected polygon coordinates to pixel coordinates of an image array.

    Subtracts point of origin, scales to pixelcoordinates.

    :param ingeo: input geodataframe or shapely Polygon.
    :param reference_bounds:  Bounding box object or tuple of reference (e.g. image chip) in format (left, bottom,
        right, top)
    :param scale: Scale the polygons to the image size/resolution. Requires image array nrows and ncols parameters.
    :param nrows: image array nrows, required for scale.
    :param ncols: image array ncols, required for scale.
    :return:Result polygon or geodataframe, same type as input.
    """

    # TODO - Maxime this function should be just to polygon
    def _to_pixelcoords(poly: Polygon, reference_bounds, scale, nrows, ncols):
        try:
            minx, miny, maxx, maxy = reference_bounds
            w_poly, h_poly = (maxx - minx, maxy - miny)
        except (TypeError, ValueError):
            raise Exception(
                f"reference_bounds argument is of type {type(reference_bounds)}, needs to be a tuple or rasterio bounding box "
                f"instance. Can be delineated from transform, nrows, ncols via rasterio.transform.reference_bounds"
            )
        # Subtract point of origin of image bbox.
        x_coords, y_coords = poly.exterior.coords.xy
        p_origin = shapely.geometry.Polygon(
            [[x - minx, y - miny] for x, y in zip(x_coords, y_coords)])

        if scale is False:
            return p_origin
        elif scale is True:
            if ncols is None or nrows is None:
                raise ValueError("ncols and nrows required for scale")
            x_scaler = ncols / w_poly
            y_scaler = nrows / h_poly
            return shapely.affinity.scale(p_origin,
                                          xfact=x_scaler,
                                          yfact=y_scaler,
                                          origin=(0, 0, 0))

    ingeo.geometry = ingeo.geometry.apply(lambda _p: _to_pixelcoords(
        poly=_p,
        reference_bounds=reference_bounds,
        scale=scale,
        nrows=nrows,
        ncols=ncols,
    ))
    return ingeo
Beispiel #6
0
def split_edge_at_points(edge, points, tolerance=1e-9):
    """Split edge at point/multipoint"""
    try:
        segments = split_line(edge.geometry, points, tolerance)
    except ValueError:
        # if splitting fails, e.g. becuase points is empty GeometryCollection
        segments = [edge.geometry]
    edges = GeoDataFrame([edge] * len(segments))
    edges.geometry = segments
    return edges
Beispiel #7
0
    def _vectorize_single_raster(self,
                                 raster,
                                 affine_transform,
                                 crs,
                                 timestamp=None):
        """Vectorizes a data slice of a single time component

        :param raster: Numpy array or shape (height, width, channels)
        :type raster: numpy.ndarray
        :param affine_transform: Object holding a transform vector (i.e. geographical location vector) of the raster
        :type affine_transform: affine.Affine
        :param crs: Coordinate reference system
        :type crs: sentinelhub.CRS
        :param timestamp: Time of the data slice
        :type timestamp: datetime.datetime
        :return: Vectorized data
        :rtype: geopandas.GeoDataFrame
        """
        mask = None
        if self.values:
            mask = np.zeros(raster.shape, dtype=bool)
            for value in self.values:
                mask[raster == value] = True

        geo_list = []
        value_list = []
        for idx in range(raster.shape[-1]):
            for geojson, value in rasterio.features.shapes(
                    raster[..., idx],
                    mask=None if mask is None else mask[..., idx],
                    transform=affine_transform,
                    **self.rasterio_params,
            ):
                geo_list.append(shapely.geometry.shape(geojson))
                value_list.append(value)

        series_dict = {
            self.values_column: pd.Series(value_list, dtype=self.raster_dtype)
        }
        if timestamp is not None:
            series_dict["TIMESTAMP"] = pd.to_datetime([timestamp] *
                                                      len(geo_list))

        vector_data = GeoDataFrame(series_dict,
                                   geometry=geo_list,
                                   crs=crs.pyproj_crs())

        if not vector_data.geometry.is_valid.all():
            vector_data.geometry = vector_data.geometry.buffer(0)

        return vector_data
Beispiel #8
0
def clip(
    df: GDF,
    clip_poly: Polygon,
    explode_mp_: bool = False,
    keep_biggest_poly_: bool = False,
) -> GDF:
    """
    Filter and clip geodataframe to clipping geometry.

    The clipping geometry needs to be in the same projection as the geodataframe.

    :param df: input geodataframe
    :param clip_poly: Clipping polygon geometry, needs to be in the same crs as the input geodataframe.
    :param explode_mp_: Applies explode_mp function. Append dataframe rows for each polygon in potential
        multipolygons that were created by the intersection. Resets the dataframe index!
    :param keep_biggest_poly_: Applies keep_biggest_poly function. Replaces MultiPolygons with the biggest
        polygon contained in the MultiPolygon.
    :return: Result geodataframe.
    """
    df = df[df.geometry.intersects(clip_poly)].copy()
    df.geometry = df.geometry.apply(lambda _p: _p.intersection(clip_poly))
    # !TODO: Maxime's comments - return df

    # df = gpd.overlay(df, clip_poly, how='intersection')  # Slower.

    # !TODO: Maxime's comments - this shoud return just the clipped, should not have explosions and biggest polly

    row_idxs_mp = df.index[df.geometry.geom_type == "MultiPolygon"].tolist()

    if not row_idxs_mp:
        return df
    elif not explode_mp_ and (not keep_biggest_poly_):
        warnings.warn(
            f"Warning, intersection resulted in {len(row_idxs_mp)} split multipolygons. Use "
            f"explode_mp_=True or keep_biggest_poly_=True.")
        return df
    elif explode_mp_ and keep_biggest_poly_:
        raise ValueError(
            'You can only use one of "explode_mp_" or "keep_biggest_poly_"!')
    elif explode_mp_:
        return explode_mp(df)
    elif keep_biggest_poly_:
        return keep_biggest_poly(df)
Beispiel #9
0
    def read_vector_file(
        self, filename: str = "aoi.geojson", as_dataframe: bool = False
    ) -> Union[Dict, GeoDataFrame]:
        """
        Reads vector files (geojson, shapefile, kml, wkt) to a feature collection,
        for use as the aoi geometry in the workflow input parameters
        (see get_input_parameters).

        Example aoi fiels are provided, e.g. example/data/aoi_Berlin.geojson

        Args:
            filename: File path of the vector file.
            as_dataframe: Return type, default FeatureCollection, GeoDataFrame if True.

        Returns:
            Feature Collection
        """
        suffix = Path(filename).suffix

        if suffix == ".kml":
            gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw"
            df = gpd.read_file(filename, driver="KML")
        elif suffix == ".wkt":
            with open(filename) as wkt_file:
                wkt = wkt_file.read()
                df = pd.DataFrame({"geometry": [wkt]})
                df["geometry"] = df["geometry"].apply(shapely.wkt.loads)
                df = GeoDataFrame(df, geometry="geometry", crs=4326)
        else:
            df = gpd.read_file(filename)

        if df.crs.to_string() != "EPSG:4326":
            df = df.to_crs(epsg=4326)
        df.geometry = df.geometry.buffer(0)
        # TODO: Explode multipolygons (if neccessary as union in aoi anyway most often).

        # TODO: Have both bboxes for each feature and overall?

        if as_dataframe:
            return df
        else:
            return df.__geo_interface__
Beispiel #10
0
def invert_y_axis(ingeo: GDF, reference_height: int) -> GDF:
    """
    Invert y-axis of polygon or geodataframe geometries in reference to a bounding box e.g. of an image chip.

    Usage e.g. for COCOJson format.

    :param ingeo: Input Polygon or geodataframe.
    :param reference_height: Height (in coordinates or rows) of reference object (polygon or image, e.g. image chip.
    :return: Result polygon or geodataframe, same type as input.
    """
    def _invert_y_axis(poly: Polygon = ingeo,
                       reference_height=reference_height):
        x_coords, y_coords = poly.exterior.coords.xy
        p_inverted_y_axis = shapely.geometry.Polygon(
            [[x, reference_height - y] for x, y in zip(x_coords, y_coords)])
        return p_inverted_y_axis

    ingeo.geometry = ingeo.geometry.apply(
        lambda _p: _invert_y_axis(poly=_p, reference_height=reference_height))
    return ingeo
Beispiel #11
0
def get_footprints(product_ids: List, plot=True, save_path=None):
    """
    Plot footprints of images, given PDS (Planetary Data System) product IDs
    :param product_ids: Product id, for example M106761561LE
    :return: GeoDataFrame containing the plot footprints
    """
    df = DataFrame({'index': product_ids, 'footprint': product_ids})
    df['footprint'] = df['footprint'].apply(get_geometry_from_ODE).apply(
        wkt.loads)
    gdf = GeoDataFrame(df, geometry='footprint')
    gdf.geometry = gdf.geometry.boundary
    footprint_plot = gdf.plot(column='index',
                              legend=True,
                              legend_kwds={
                                  'loc': 'center left',
                                  'bbox_to_anchor': (1, 0.5)
                              })
    pyplot.xlabel('Longitude, degrees E')
    pyplot.ylabel('Latitude, degrees N')
    return gdf
Beispiel #12
0
def clip(
    df: GDF,
    clip_poly: Polygon,
    explode_mp_: bool = False,
    keep_biggest_poly_: bool = False,
) -> GDF:
    """Filter and clip geodataframe to clipping geometry.

    The clipping geometry needs to be in the same projection as the geodataframe.

    Args:
        df: input geodataframe
        clip_poly: Clipping polygon geometry, needs to be in the same crs as the input geodataframe.
        explode_mp_: Applies explode_mp function. Append dataframe rows for each polygon in potential
            multipolygons that were created by the intersection. Resets the dataframe index!
        keep_biggest_poly_: Applies keep_biggest_poly function. Drops Multipolygons by only keeping the Polygon with
            the biggest area.

    Returns:
        Result geodataframe.
    """
    df = df[df.geometry.intersects(clip_poly)].copy()
    df.geometry = df.geometry.apply(lambda _p: _p.intersection(clip_poly))
    # df = gpd.overlay(df, clip_poly, how='intersection')  # Slower.

    row_idxs_mp = df.index[df.geometry.geom_type == 'MultiPolygon'].tolist()

    if not row_idxs_mp:
        return df
    elif not explode_mp_ and (not keep_biggest_poly_):
        warnings.warn(
            f"Warning, intersection resulted in {len(row_idxs_mp)} split multipolygons. Use "
            f"explode_mp_=True or keep_biggest_poly_=True.")
        return df
    elif explode_mp_ and keep_biggest_poly_:
        raise ValueError(
            'You can only use only "explode_mp" or "keep_biggest"!')
    elif explode_mp_:
        return explode_mp(df)
    elif keep_biggest_poly_:
        return keep_biggest_poly(df)
Beispiel #13
0
    def append_raw_overhanging_PV_installations_to_intersected_installations(
        self,
        raw_overhanging_PV_installations: gpd.GeoDataFrame = None,
        raw_PV_installations_on_rooftop: gpd.GeoDataFrame = None,
    ) -> gpd.GeoDataFrame:
        """
        PV polygons which do not intersect with a rooftop polygon, although they do border to a rooftop, are matched to
        their nearest rooftop geometry and appended to the GeoDataFrame listing all rooftop PV polygons

        Parameters
        ----------
        raw_overhanging_PV_installations: GeoPandas.GeoDataFrame
            GeoDataFrame which specifies all the PV polygons which border to a rooftop, but are not intersected with
            a rooftop geometry
        raw_PV_installations_on_rooftop: GeoPandas.GeoDataFrame
            GeoDataFrame which specifies all the PV polygons which are intersected with a rooftop geometry

        Returns
        -------
        GeoPandas.GeoDataFrame
            GeoDataFrame where overhanging PV installations have been enriched with the attributes of the closest
            rooftop and appended to raw_PV_installations_on_rooftop
        """

        # IMPORTANT: if ckdnearest is used always reset_index before
        raw_overhanging_PV_installations = raw_overhanging_PV_installations.reset_index(
            drop=True)

        raw_overhanging_PV_installations.rename(
            columns={"identifier": "identifier_diff"}, inplace=True)

        # Extract centroid from intersected PV polygons while preserving their polygon geometry
        raw_PV_installations_on_rooftop[
            "geometry_intersected_polygon"] = raw_PV_installations_on_rooftop[
                "geometry"]
        raw_PV_installations_on_rooftop[
            "geometry"] = raw_PV_installations_on_rooftop["geometry"].centroid
        raw_PV_installations_on_rooftop[
            "centroid_intersect"] = raw_PV_installations_on_rooftop["geometry"]

        raw_overhanging_pv_installations_enriched_with_closest_rooftop_data = self.enrich_raw_overhanging_pv_installations_with_closest_rooftop_attributes(
            raw_overhanging_PV_installations, raw_PV_installations_on_rooftop)

        raw_PV_installations_on_rooftop.geometry = (
            raw_PV_installations_on_rooftop.geometry_intersected_polygon)

        raw_PV_installations_on_rooftop = raw_PV_installations_on_rooftop[[
            "raw_area",
            "identifier",
            "Area",
            "Azimuth",
            "Building_I",
            "City",
            "PostalCode",
            "RoofTopID",
            "RooftopTyp",
            "Street",
            "StreetNumb",
            "Tilt",
            "area_inter",
            "geometry",
        ]]

        # Append the dataframe of all raw overhanging PV installations, enriched with the
        # rooftop attributes of their nearest rooftop, to the dataframe of all intersected PV installations
        # Note 1: Attributes starting with capital letters specify rooftop attributes.
        # Note 2: The geometry of the overhanging PV installations is not yet dissolved with the geometry of the
        # intersected PV installations
        raw_PV_installations_on_rooftop = gpd.GeoDataFrame(
            raw_PV_installations_on_rooftop.append(
                raw_overhanging_pv_installations_enriched_with_closest_rooftop_data
            )).reset_index(drop=True)

        return raw_PV_installations_on_rooftop
Beispiel #14
0
def transform_islands(islands: GeoDataFrame) -> GeoDataFrame:
    islands.geometry = islands.buffer(0)
    islands = islands[['Id', 'geometry']]
    islands['FID'] = islands.Id - 1
    islands = islands.set_index('FID', drop=False, verify_integrity=True)
    return islands
Beispiel #15
0
def asGdf(geoms, crs=None):
    gdf = GeoDataFrame(crs=crs)
    gdf.geometry = geoms
    return gdf