Beispiel #1
0
    def process(data, process_kwargs):
        if "features" not in data or len(data["features"]) == 0:
            return data  # do nothing for non-feature or empty requests

        features = data["features"].copy()
        projection = data["projection"]
        path = utils.safe_abspath(process_kwargs["url"])
        fields = process_kwargs["fields"]
        extension = process_kwargs["extension"]
        driver = GeometryFileSink.supported_extensions[extension]

        # generate the directory if necessary
        os.makedirs(path, exist_ok=True)

        # the target file path is a deterministic hash of the request
        filename = ".".join([process_kwargs["hash"], extension])

        # add the index to the columns if necessary
        index_name = features.index.name
        if index_name in fields.values(
        ) and index_name not in features.columns:
            features[index_name] = features.index

        # copy the dataframe
        features = features[["geometry"] + list(fields.values())]

        # rename the columns
        features.columns = ["geometry"] + list(fields.keys())

        # serialize nested fields (lists or dicts)
        for col in fields.keys():
            series = features[col]
            if series.dtype == object or (str(series.dtype) == "category"
                                          and series.cat.categories.dtype
                                          == object):
                features[col] = series.map(_to_json)

        # convert categoricals
        for col in fields.keys():
            series = features[col]
            if str(series.dtype) == "category":
                features[col] = series.astype(series.cat.categories.dtype)

        # GeoJSON needs reprojection to EPSG:4326
        if driver == "GeoJSON" and projection.upper() != "EPSG:4326":
            features = utils.geodataframe_transform(features, projection,
                                                    "EPSG:4326")

        # generate the file
        features.to_file(os.path.join(path, filename), driver=driver)

        result = geopandas.GeoDataFrame(index=features.index)
        result["saved"] = True
        return {"features": result, "projection": projection}
    def expected_to_geojson(expected):
        """The result from gpd.read_file for a geojson is different from the
        others for list and dict typed values:

        - lists are omitted
        - dicts are deserialized

        Call this function to transform self.expected for GeoJSON testing.
        """
        del expected["lst"]
        expected["dct"] = expected["dct"].map(json.loads)
        return utils.geodataframe_transform(expected, "EPSG:3857", "EPSG:4326")
Beispiel #3
0
    def process(url, request):
        path = utils.safe_abspath(url)

        # convert the requested projection to a geopandas CRS
        crs = utils.get_crs(request["projection"])

        # convert the requested shapely geometry object to a GeoSeries
        filt_geom = gpd.GeoSeries([request["geometry"]], crs=crs)

        # acquire the data, filtering on the filt_geom bbox
        f = gpd.GeoDataFrame.from_file(path,
                                       bbox=filt_geom,
                                       layer=request["layer"])
        if len(f) == 0:
            # return directly if there is no data
            if request.get("mode") == "extent":
                return {"projection": request["projection"], "extent": None}
            else:  # this takes modes 'centroid' and 'intersects'
                return {
                    "projection": request["projection"],
                    "features": gpd.GeoDataFrame([]),
                }

        f.set_index(request["id_field"], inplace=True)

        # apply the non-geometry field filters first
        mask = None
        for field, value in request["filters"].items():
            if field not in f.columns:
                continue
            _mask = f[field] == value
            if mask is None:
                mask = _mask
            else:
                mask &= _mask
        if mask is not None:
            f = f[mask]

        # convert the data to the requested crs
        utils.geodataframe_transform(f, utils.crs_to_srs(f.crs),
                                     request["projection"])

        # compute the bounds of each geometry and filter on min_size
        min_size = request.get("min_size")
        if min_size:
            bounds = f["geometry"].bounds
            widths = bounds["maxx"] - bounds["minx"]
            heights = bounds["maxy"] - bounds["miny"]
            f = f[(widths > min_size) | (heights > min_size)]

        # only return geometries that truly intersect the requested geometry
        if request["mode"] == "centroid":
            with warnings.catch_warnings():  # geopandas warns if in WGS84
                warnings.simplefilter("ignore")
                f = f[f["geometry"].centroid.within(filt_geom.iloc[0])]
        else:
            f = f[f["geometry"].intersects(filt_geom.iloc[0])]

        if request.get("mode") == "extent":
            return {
                "projection": request["projection"],
                "extent": tuple(f.total_bounds),
            }
        else:  # this takes modes 'centroid' and 'intersects'
            # truncate the number of geometries if necessary
            if request.get("limit") and len(f) > request["limit"]:
                f = f.iloc[:request["limit"]]
            elif request.get("limit") is None:
                global_limit = config.get("geomodeling.geometry-limit")
                if len(f) > global_limit:
                    raise RuntimeError(
                        "The amount of returned geometries exceeded "
                        "the maximum of {} geometries.".format(global_limit))

            return {"projection": request["projection"], "features": f}