|
15 | 15 | import pandas as pd |
16 | 16 |
|
17 | 17 | # Local |
18 | | -from ..__init__ import cities_data_path |
| 18 | +from .. import cities_data_path |
19 | 19 | from .plot_utils import alps_cities_list |
20 | 20 | from .plot_utils import centraleurope_cities_list |
21 | 21 | from .plot_utils import ch_cities_list |
@@ -216,10 +216,6 @@ def is_visible(lat, lon, domain_boundaries, cross_dateline) -> bool: |
216 | 216 | bool True if city is within domain boundaries, else false. |
217 | 217 |
|
218 | 218 | """ |
219 | | - if cross_dateline: |
220 | | - if lon < 0: |
221 | | - lon = 360 - abs(lon) |
222 | | - |
223 | 219 | lon = float(lon.iloc[0]) if isinstance(lon, pd.Series) else float(lon) |
224 | 220 | lat = float(lat.iloc[0]) if isinstance(lat, pd.Series) else float(lat) |
225 | 221 | is_in_domain = ( |
@@ -721,6 +717,64 @@ def get_intersection_point_on_domain_boundaries( |
721 | 717 | intersection_point = intersection_point + 0.001 * (point_in - intersection_point) |
722 | 718 | return intersection_point |
723 | 719 |
|
| 720 | +#the next two functions are used later to plot the slice of traj beyond the dateline |
| 721 | +def _unwrap_dateline_series(lon: pd.Series | np.ndarray) -> pd.Series: |
| 722 | + """Makes the trajectory continuous avoiding the jump -180↔+180 (e.g. -179 → -181).""" |
| 723 | + arr = np.asarray(lon, dtype=float) |
| 724 | + if arr.size == 0: |
| 725 | + return pd.Series(arr, index=getattr(lon, "index", None)) |
| 726 | + diffs = np.diff(arr) |
| 727 | + offset = 0.0 |
| 728 | + out = [arr[0]] |
| 729 | + for d, x in zip(diffs, arr[1:]): |
| 730 | + if d > 180: # e.g. 179 → -179 (big backwards jump) |
| 731 | + offset -= 360 |
| 732 | + elif d < -180: # e.g. -179 → 179 (big forwards jump) |
| 733 | + offset += 360 |
| 734 | + out.append(x + offset) |
| 735 | + return pd.Series(out, index=getattr(lon, "index", None)) |
| 736 | + |
| 737 | +def create_trajectory_slice_over_dateline( |
| 738 | + lon: pd.Series | np.ndarray, |
| 739 | + lat: pd.Series | np.ndarray, |
| 740 | + threshold: float = 180.0 |
| 741 | +) -> tuple[pd.Series, pd.Series]: |
| 742 | + """ |
| 743 | + gives back the subset (lon_slice, lat_slice) of the points that, after the unwrapping, have |lon| >= threshold (default 180). |
| 744 | + |
| 745 | + Parameters |
| 746 | + --------- |
| 747 | + lon : pd.Series | np.ndarray |
| 748 | + Longitudes of the trajectory. |
| 749 | + lat : pd.Series | np.ndarray |
| 750 | + Corresponding latitudes (same lenght). |
| 751 | + threshold : float, default 180.0 |
| 752 | + threshold for the unwrapped longitudes |lon_unwrapped|. |
| 753 | +
|
| 754 | + Returns |
| 755 | + ------- |
| 756 | + (lon_slice, lat_slice) : tuple[pd.Series, pd.Series] |
| 757 | + filtered series (maintain the original indexes if lon/lat were Series). |
| 758 | + """ |
| 759 | + # Convert to Series |
| 760 | + lon_s = lon if isinstance(lon, pd.Series) else pd.Series(np.asarray(lon, dtype=float)) |
| 761 | + lat_s = lat if isinstance(lat, pd.Series) else pd.Series(np.asarray(lat, dtype=float)) |
| 762 | + |
| 763 | + if len(lon_s) != len(lat_s): |
| 764 | + raise ValueError("lon e lat must have the same length.") |
| 765 | + |
| 766 | + # Unwrap longitudes |
| 767 | + lon_unwrapped = _unwrap_dateline_series(lon_s) |
| 768 | + |
| 769 | + # identify points over the dateline |
| 770 | + mask = np.abs(lon_unwrapped.values) >= float(threshold) |
| 771 | + |
| 772 | + # apply the mask mantaining the index |
| 773 | + lon_slice = lon_unwrapped[mask] |
| 774 | + lat_slice = lat_s[mask] |
| 775 | + |
| 776 | + return lon_slice, lat_slice |
| 777 | + |
724 | 778 |
|
725 | 779 | def add_trajectories_within_domain( |
726 | 780 | plot_dict: dict, |
@@ -753,10 +807,8 @@ def add_trajectories_within_domain( |
753 | 807 | for traj in range(5): |
754 | 808 | latitude = plot_dict["altitude_" + str(i)]["traj_" + str(traj)]["lat"] |
755 | 809 | longitude = plot_dict["altitude_" + str(i)]["traj_" + str(traj)]["lon"] |
756 | | - if cross_dateline: |
757 | | - longitude = longitude.apply( |
758 | | - lambda lon: 360 - np.abs(lon) if lon < 0 else lon |
759 | | - ) |
| 810 | + # Unwrap the longitude over the dateline |
| 811 | + longitude_unwrapped = _unwrap_dateline_series(longitude) |
760 | 812 | ystart = latitude.iloc[0] |
761 | 813 | xstart = longitude.iloc[0] |
762 | 814 | linestyle = subplot_properties_dict[sub_index] |
@@ -809,6 +861,21 @@ def add_trajectories_within_domain( |
809 | 861 | transform=ccrs.Geodetic(), |
810 | 862 | rasterized=True, |
811 | 863 | ) |
| 864 | + # The following plots the slice of the trajectory beyond the dateline that gets clipped |
| 865 | + if cross_dateline: |
| 866 | + lon_slice, lat_slice= create_trajectory_slice_over_dateline(longitude_unwrapped, latitude, 180.0) |
| 867 | + ax.plot( |
| 868 | + lon_slice, # define x-axis |
| 869 | + lat_slice, # define y-axis |
| 870 | + linestyle, # define linestyle |
| 871 | + alpha=alpha, # define line opacity |
| 872 | + label=( |
| 873 | + None |
| 874 | + ), # We don't want this slice to have its own label |
| 875 | + transform=ccrs.Geodetic(), |
| 876 | + rasterized=True, |
| 877 | + ) |
| 878 | + |
812 | 879 | if is_main_trajectory: |
813 | 880 | # add time interval points to main trajectory |
814 | 881 | add_time_interval_points_within_domain( |
@@ -894,7 +961,7 @@ def generate_map_plot( |
894 | 961 | domain=domain, |
895 | 962 | custom_domain_boundaries=trajectory_expansion, |
896 | 963 | origin_coordinates=origin_coordinates, |
897 | | - ) # sets extent of map |
| 964 | + ) |
898 | 965 | if not domain_boundaries: |
899 | 966 | return ax.text( |
900 | 967 | 0.5, |
|
0 commit comments