def get_dataframe_with_weather_tilepaths(placements, weather_path, zoom):
"""
This method will generate a dataframe from a list of input placements
and add the link to the corresponding weather data tile in a new dataframe
column 'source'.
placements : pd.DataFrame with 'geom' column and osgeo.ogr.Geometry point
objects in EPSG:4326 or 'lat' and 'lon' columns with degrees in EPSG:4326.
weather_path : The path to the tilepath or a dummy path containing '<X-TILE>'
and '<Y-TILE>', optionally also <ZOOM> as spacers. These will be replaced by
the actual tile ID in x and y direction, plus zoom value if applicable.
weather_path can also be None only if 'source' is an existing attribute of the
placements dataframe, the column values will be assumed as existing filepaths
of weather data tiles then.
in placements dataframe or if no '<ZOOM>' spacer in weather_path.
"""
if not isinstance(placements, pd.DataFrame):
if not hasattr(placements, "__iter__"):
raise TypeError(f"If placements is not a pd.DataFrame, it must be an iterable.")
# we definitely have an iterator at hand, unpack and check of what types
if all([isinstance(x, tuple) and all([isinstance(latlon, (float, int)) for latlon in x]) for x in placements]):
# we have lon and lat values at hand, generate two lon/lat columns
lons, lats = zip(*placements)
placements = pd.DataFrame()
placements["lon"] = lons
placements["lat"] = lats
elif all([isinstance(x, osgeo.ogr.Geometry) for x in placements]):
assert all(
[
x.GetSpatialReference() is None or x.GetSpatialReference().IsSame(gk.srs.loadSRS(4326))
for x in placements
]
), f"All srs of objects in placements must be EPSG:4326"
assert all(["POINT" in x.GetGeometryName() for x in placements]), f"All geometries must be POINT features."
# we have geometries, create a geom column and extract lat/lon
_placements = copy(placements)
placements = pd.DataFrame()
placements["geom"] = _placements
placements["lon"] = placements["geom"].apply(lambda x: x.GetX())
placements["lat"] = placements["geom"].apply(lambda x: x.GetY())
else:
raise TypeError(
f"If placements is an iterator, it must contain either (lon, lat) tuples or osgeo.ogr.Geometry point geometries in EPS:4326."
)
else:
# we have a df, make sure the necessary data is available and add lat/lon where needed
assert "geom" in placements.columns or all([c in placements.columns for c in ["lon", "lat"]]), (
f"pd.DataFrame must contain 'geom' or 'lat' and 'lon' columns."
)
if "RESKit_sim_order" in placements.columns:
assert (placements["RESKit_sim_order"].to_numpy() == np.arange(len(placements))).all(), (
"'RESKit_sim_order' must equal range(len(placements))"
)
if not "lon" in placements.columns:
placements["lon"] = placements.geom.apply(lambda x: x.GetX())
if not "lat" in placements.columns:
placements["lat"] = placements.geom.apply(lambda x: x.GetY())
# get the actual weather tilepath
def _get_tilepath(weather_path, zoom, lat, lon):
if "<X-TILE>" in weather_path or "<Y-TILE>" in weather_path:
assert isinstance(zoom, int), (
f"zoom must be a positive integer tiling level if weather_path contains X/Y spacers"
)
_X, _Y = get_tile_XY(zoom=zoom, lon=lon, lat=lat, geom=None)
return weather_path.replace("<X-TILE>", str(_X)).replace("<Y-TILE>", str(_Y)).replace("<ZOOM>", str(zoom))
else:
return weather_path
if weather_path is None:
# the info must already be in the dataframe then
assert "source" in placements.columns, (
f"weather_path is None yet no 'source' attribute in placements dataframe."
)
if any([_str in _fp for _str in ["<X-TILE>", "<Y-TILE>", "<ZOOM>"] for _fp in placements.source]):
# overwrite source attributes with specific tilepaths
print(f"NOTE: 'source' attributes will be overwritten with specific filepath!")
placements["source"] = placements.apply(
lambda x: _get_tilepath(weather_path=x.source, zoom=zoom, lon=x.lon, lat=x.lat).replace(
"<ZOOM>", str(zoom)
),
axis=1,
)
else:
# make sure we have either no source column to avoid overwriting data, or it is the correct one already
source = placements.apply(
lambda x: _get_tilepath(weather_path=weather_path, zoom=zoom, lon=x.lon, lat=x.lat),
axis=1,
)
if "source" in placements.columns:
assert (placements["source"] == source).all(), (
"If weather_path is given, placements must not have a 'source' attribute already, or it needs to be the same as derived from weather_path"
)
else:
# add source column with the actual tile filepaths
placements["source"] = source
# add an id column to ensure correct order preservation
# placements["RESKit_sim_order"] = range(len(placements)) #TODO remove
return placements