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")
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}