Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ instructions here below to set up a conda environment and test multiple use-case

### Create environment

Create an environment and install the package dependencies with the script `setup_env.sh` provided in the `tools` directory.
Check available options with
Create an environment and install the package dependencies with conda

tools/setup_env.sh -h
conda env create -n pytrajplot -f requirements/environment.yml

We distinguish pinned installations based on exported (reproducible) environments,
saved in `requirements/environment.yml`,
Expand Down Expand Up @@ -72,7 +71,7 @@ installed by conda and should not be modified by pip, use the `--no-deps` flag.

For development, install the package in editable mode:

pip install --editable --no-deps .
pip install --no-deps --editable .

*Warning:* Make sure you use the right pip, i.e. the one from the installed conda environment (`which pip` should point to something like `path/to/miniconda/envs/<package_env_name>/bin/pip`).

Expand Down
24 changes: 12 additions & 12 deletions src/pytrajplot/parse_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,34 +360,34 @@ def read_trajectory(trajectory_file_path, start_df, plot_info_dict):
# clean up the df in case its generated from a COSMO trajectory file
if case == "COSMO":
# at missing data points (i.e. if trajectory leaves computational domain), the z & hsurf values default to -999
traj_df.loc[(traj_df["z"] < 0), "z"] = np.NaN
traj_df.loc[(traj_df["hsurf"] < 0), "hsurf"] = np.NaN
traj_df.loc[(traj_df["z"] < 0), "z"] = np.nan
traj_df.loc[(traj_df["hsurf"] < 0), "hsurf"] = np.nan
traj_df.dropna(
subset=["lon"], inplace=True
) # remove rows containing only the origin/z_type
traj_df.reset_index(inplace=True)

# clean up the df in case its generated from a HRES trajectory file
if case == "HRES":
traj_df.loc[(traj_df["lon"] == -999.00), "lon"] = np.NaN
traj_df.loc[(traj_df["lat"] == -999.00), "lat"] = np.NaN
traj_df.loc[(traj_df["z"] == -999), "z"] = np.NaN
traj_df.loc[(traj_df["lon"] == -999.00), "lon"] = np.nan
traj_df.loc[(traj_df["lat"] == -999.00), "lat"] = np.nan
traj_df.loc[(traj_df["z"] == -999), "z"] = np.nan

# add various (empty) keys to the trajectory dataframe
traj_df["z_type"] = None
traj_df["origin"] = None
traj_df["side_traj"] = None
traj_df["start_altitude"] = np.NaN
traj_df["lon_precise"] = np.NaN
traj_df["lat_precise"] = np.NaN
traj_df["start_altitude"] = np.nan
traj_df["lon_precise"] = np.nan
traj_df["lat_precise"] = np.nan
traj_df["altitude_levels"] = None
traj_df["#trajectories"] = number_of_trajectories
traj_df["block_length"] = number_of_times
traj_df["trajectory_direction"] = trajectory_file_path[
-1:
] # last letter of key = F/B --> direction of trajectory
traj_df["subplot_index"] = np.NaN
traj_df["max_start_altitude"] = np.NaN
traj_df["subplot_index"] = np.nan
traj_df["max_start_altitude"] = np.nan

# add information to newly created (empty) keys in trajectory dataframe
# basically, at this point the information from the start_df, gets merged into the trajectory dataframe
Expand Down Expand Up @@ -443,8 +443,8 @@ def read_trajectory(trajectory_file_path, start_df, plot_info_dict):
reference_time=reference_time,
)

# change the lon/lat values where the trajectory leaves the domain from their computational domain-boundary values to np.NaN.
traj_df.loc[np.isnan(traj_df["z"]), ["lon", "lat"]] = np.NaN
# change the lon/lat values where the trajectory leaves the domain from their computational domain-boundary values to np.nan.
traj_df.loc[np.isnan(traj_df["z"]), ["lon", "lat"]] = np.nan

return traj_df

Expand Down
5 changes: 3 additions & 2 deletions src/pytrajplot/plotting/analyse_trajectories.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from numpy.lib.function_base import mean
from numpy import mean


# FUNCTIONS
Expand Down Expand Up @@ -306,7 +306,8 @@ def _create_plot(traj_dict, central_longitude, dynamic_domain):
dynamic_domain[2] - offset,
dynamic_domain[3] + offset,
],
ccrs.PlateCarree(central_longitude=0),
#in the following line i substituted 0 with the second central_longitude
ccrs.PlateCarree(central_longitude=central_longitude),
)

# https://stackoverflow.com/questions/65086715/longitude-bounds-of-cartopy-crs-geodetic
Expand Down
87 changes: 77 additions & 10 deletions src/pytrajplot/plotting/plot_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import pandas as pd

# Local
from ..__init__ import cities_data_path
from .. import cities_data_path
from .plot_utils import alps_cities_list
from .plot_utils import centraleurope_cities_list
from .plot_utils import ch_cities_list
Expand Down Expand Up @@ -216,10 +216,6 @@ def is_visible(lat, lon, domain_boundaries, cross_dateline) -> bool:
bool True if city is within domain boundaries, else false.

"""
if cross_dateline:
if lon < 0:
lon = 360 - abs(lon)

lon = float(lon.iloc[0]) if isinstance(lon, pd.Series) else float(lon)
lat = float(lat.iloc[0]) if isinstance(lat, pd.Series) else float(lat)
is_in_domain = (
Expand Down Expand Up @@ -721,6 +717,64 @@ def get_intersection_point_on_domain_boundaries(
intersection_point = intersection_point + 0.001 * (point_in - intersection_point)
return intersection_point

#the next two functions sre used later to plot the slice of traj beyond the dateline
def _unwrap_dateline_series(lon: pd.Series | np.ndarray) -> pd.Series:
"""Makes the trajectory continuous avoiding the jump -180↔+180 (e.g. -179 → -181)."""
arr = np.asarray(lon, dtype=float)
if arr.size == 0:
return pd.Series(arr, index=getattr(lon, "index", None))
diffs = np.diff(arr)
offset = 0.0
out = [arr[0]]
for d, x in zip(diffs, arr[1:]):
if d > 180: # e.g. 179 → -179 (big backwards jump)
offset -= 360
elif d < -180: # e.g. -179 → 179 (big forwards jump)
offset += 360
out.append(x + offset)
return pd.Series(out, index=getattr(lon, "index", None))

def create_trajectory_slice_over_dateline(
lon: pd.Series | np.ndarray,
lat: pd.Series | np.ndarray,
threshold: float = 180.0
) -> tuple[pd.Series, pd.Series]:
"""
gives back the subset (lon_slice, lat_slice) of the points that, after the unwrapping, have |lon| >= threshold (default 180).

Parameters
---------
lon : pd.Series | np.ndarray
Longitudes of the trajectory.
lat : pd.Series | np.ndarray
Corresponding latitudes (same lenght).
threshold : float, default 180.0
threshold for the unwrapped longitudes |lon_unwrapped|.

Returns
-------
(lon_slice, lat_slice) : tuple[pd.Series, pd.Series]
filtered series (maintain the original indexes if lon/lat were Series).
"""
# Convert to Series
lon_s = lon if isinstance(lon, pd.Series) else pd.Series(np.asarray(lon, dtype=float))
lat_s = lat if isinstance(lat, pd.Series) else pd.Series(np.asarray(lat, dtype=float))

if len(lon_s) != len(lat_s):
raise ValueError("lon e lat must have the same length.")

# Unwrap longitudes
lon_unwrapped = _unwrap_dateline_series(lon_s)

# identify points over the dateline
mask = np.abs(lon_unwrapped.values) >= float(threshold)

# apply the mask mantaining the index
lon_slice = lon_unwrapped[mask]
lat_slice = lat_s[mask]

return lon_slice, lat_slice


def add_trajectories_within_domain(
plot_dict: dict,
Expand Down Expand Up @@ -753,10 +807,8 @@ def add_trajectories_within_domain(
for traj in range(5):
latitude = plot_dict["altitude_" + str(i)]["traj_" + str(traj)]["lat"]
longitude = plot_dict["altitude_" + str(i)]["traj_" + str(traj)]["lon"]
if cross_dateline:
longitude = longitude.apply(
lambda lon: 360 - np.abs(lon) if lon < 0 else lon
)
#NEW: unwraps the longitude over the dateline
longitude_unwrapped = _unwrap_dateline_series(longitude)
ystart = latitude.iloc[0]
xstart = longitude.iloc[0]
linestyle = subplot_properties_dict[sub_index]
Expand Down Expand Up @@ -809,6 +861,21 @@ def add_trajectories_within_domain(
transform=ccrs.Geodetic(),
rasterized=True,
)
#I ADDED THE FOLLOWING IF: IT PLOTS THE SLICE OF TRAJ CLIPPED OVER THE DATELINE
if cross_dateline:
lon_slice, lat_slice= create_trajectory_slice_over_dateline(longitude_unwrapped, latitude, 180.0)
ax.plot(
lon_slice, # define x-axis
lat_slice, # define y-axis
linestyle, # define linestyle
alpha=alpha, # define line opacity
label=(
None
), # We don't want this slice to have its own label
transform=ccrs.Geodetic(),
rasterized=True,
)

if is_main_trajectory:
# add time interval points to main trajectory
add_time_interval_points_within_domain(
Expand Down Expand Up @@ -894,7 +961,7 @@ def generate_map_plot(
domain=domain,
custom_domain_boundaries=trajectory_expansion,
origin_coordinates=origin_coordinates,
) # sets extent of map
)
if not domain_boundaries:
return ax.text(
0.5,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_pytrajplot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pytrajplot $input_dir_tests/test_hres/2_altitudes $output_dir_tests/test_hres/2_
pytrajplot $input_dir_tests/test_hres/1_altitudes $output_dir_tests/test_hres/1_altitudes --datatype png --domain europe

echo "Test HRES backward trajectory"
pytrajplot $input_dir_tests/test_hres/backward $output_dir_tests/test_hres/backward --datatype png --domain europe
pytrajplot $input_dir_tests/test_hres/backward $output_dir_tests/test_hres/backward --datatype png --domain europe --domain dynamic

echo "Test all domains combined w/ HRES model"
pytrajplot $input_dir_tests/test_hres/4_altitudes $output_dir_tests/test_hres/4_altitudes --datatype png --domain ch --domain alps --domain centraleurope --domain europe --domain dynamic
Expand All @@ -37,7 +37,7 @@ echo "Test HRES dateline crossing"
pytrajplot $input_dir_tests/test_hres/dateline $output_dir_tests/test_hres/dateline --datatype png --domain dynamic

echo "Test HRES w/o side trajectories and the german case"
pytrajplot $input_dir_tests/test_hres/dateline $output_dir_tests/test_hres/dateline/german --datatype png --domain europe --language de
pytrajplot $input_dir_tests/test_hres/dateline $output_dir_tests/test_hres/dateline/german --datatype png --domain dynamic --language de

echo "Test HRES Europe with trajectory crossing of zero longitude from east and then leaving domain"
# (failed in v1.0.0 due to NaNs in the argument of the numpy max function - v1.0.1 uses nanmax instead)
Expand Down
19 changes: 10 additions & 9 deletions tests/test_recent_opr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
# Conda default environment
conda_env=pytrajplot
# Lagranto-output storage location
store_osm=/store/mch/msopr/osm
store_osm=/store_new/mch/msopr/osm
# pytrajplot output location
pytrajplot_out=.
pytrajplot_out=local
# Graphics format
datatype_opt="--datatype png --datatype pdf"
datatype_opt="--datatype png" # --datatype pdf"
# Domain options for
# COSMO-1E-CTR (c1), IFS-HRES-Europe (ie), IFS-HRES global (ig)
domain_opt_c1="--domain ch --domain alps"
# COSMO-1E-CTR (i1), IFS-HRES-Europe (ie), IFS-HRES global (ig)
domain_opt_i1="--domain ch --domain alps"
domain_opt_ie="--domain alps --domain centraleurope --domain europe"
domain_opt_ig="--domain dynamic --domain dynamic_zoom"
# --------
Expand All @@ -32,7 +32,8 @@ bt_00=$(date --utc --date="today 00" +%Y%m%d%H)
bt_03=$(date --utc --date="today 03" +%Y%m%d%H)

# Load conda env for pytrajplot if CONDA_PREFIX not defined
[[ -z $CONDA_PREFIX ]] && conda activate $conda_env
#[[ -z $CONDA_PREFIX ]] &&
conda activate $conda_env
echo CONDA_PREFIX=$CONDA_PREFIX
# Report version
$CONDA_PREFIX/bin/pytrajplot --version
Expand All @@ -46,17 +47,17 @@ for basetime in $bt_06 $bt_12 $bt_18 $bt_21 $bt_00 $bt_03 ; do
echo Basetime: $(date --utc --date="${basetime:0:8} $hh" "+%F %H UTC")

# Operational INPUT_DIRs:
input_dir_c1=$(echo $store_osm/COSMO-1E/FCST${yy}/${yymmddhh}_4??/lagranto_c/000)
input_dir_i1=$(echo $store_osm/ICON-CH1-EPS/FCST${yy}/${yymmddhh}_???/lagranto_c/000)
input_dir_ie=$store_osm/IFS-HRES/IFS-HRES-LAGRANTO${yy}/${yymmddhh}_LIH/lagranto_f
input_dir_ig=$store_osm/IFS-HRES/IFS-HRES-LAGRANTO${yy}/${yymmddhh}_LIH/lagranto_c

# Output directories
output_dir_c1=$pytrajplot_out/plot_c1_${basetime}
output_dir_i1=$pytrajplot_out/plot_i1_${basetime}
output_dir_ie=$pytrajplot_out/plot_ie_${basetime}
output_dir_ig=$pytrajplot_out/plot_ig_${basetime}

# Submit jobs
for model in c1 ie ig ; do
for model in i1 ie ig ; do
eval input_dir=\$input_dir_$model
eval output_dir=\$output_dir_$model
eval domain_opt=\$domain_opt_$model
Expand Down
Loading