-
Notifications
You must be signed in to change notification settings - Fork 128
Add tropical cyclones basin bounds #1031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Simple yet useful addition! I would suggest two things: Also, I am not sure to understand the point In what way does this work beyond what is already possible? |
Thanks Chahan, I will do so. Tracks from Emmanuel simulations, for example, often come without basin information. Hence, these bounds would allow you to classify tracks based on the origin basin. This would decrease the amount of memory needed because if you compute wind fields globally, all centroids near all tracks will be loaded into memory. So if you compute wind fields for a track in the NA, centroids in the NI will also be loaded. Whereas if you perform the computation basin-wide, the centroid overlap is much bigger between all tracks. This is my understanding of the discussion I had with @spjuhel, I might be misinterpreting though. |
Thanks for the explanation! I am a bit confused though, because for each track only the centroids near the track are considered (and they are loaded one by one into memory). Could you maybe post an example of a code that shows this increase in memory efficiency? That would certainly be useful to see. |
I see, then I might have misinterpreted Sam's words. I haven't tested yet if this indeed increases efficiency (it was my hypothesis given the above), but I will try soon. Also, if you know already areas in the Winfield computation that could be optimized memory-wise, let me know. As my memory is currently exploding using global exposure at 450 arcsec with only 200 tracks (>50GB on euler) and >150GB at 150 arcsec resolution. |
@chahank I converted it to a dataclass (unsure whether this is how you intended it) and added a method |
Thanks! Although the idea of the data class is precisely to not have a macro variable like BASIN. The idea is to have the data in that class directly. Also, the idea is that you can then clearly explain that this is an arbitrary boarder for basins based on one particular publication. I am not sure to understand the purpose of the method |
I am not sure to understand exactly... is your goal to have a basin class whose instances attributes are the basin boundaries ? The idea of split_by_basin() is that you can load world-wide tracks (e.g. Emmanuel's, which you can not select by basin) and then split this one global TCTrack instance into 6 new TCTracks instances, each containing tracks of only one basin. This is currently not possible if you have a global dataset that does not provide the basin information (e.g Emmanuel's, FAST) and it very handy even with those datasets that do provide that information. |
Exactly. The data class is there to contain the data. A global variable like BASIN is hard to manage and document, and is usually quite obscure to the user as it does not appear in the documentation. |
@chahank - do you really mean dataclass and not enum? Because if it's about managing some structured kind of constants, an enum seems to be the better match and the benefits of a dataclass (free |
Thanks for the addition! I will take a closer look, but just by a quick read I noticed that there might be problems with the coordinate range (and the antimeridian)? There is no convention in CLIMADA about whether longitude is in -180 to 180 or 0 to 360 or others. But if you use the wrong convention, the polygon.contains does not work anymore. See this code
Maybe this is not a problem when connected to the STORM algorithm, just to note if we wanted to use these definitions in more generality |
@emanuel-schmid aaa no of course, sorry for the confusion! I meant an |
@emanuel-schmid makes more sense now! Let me know if it is implemented as intended. For consistency, we may also consider adding the basin pressure to that class ? Thanks @ValentinGebhart. I agree, I switched to -180,+180 as it is consistent with the tracks coordinate range. I had to split the "SP" basin into two polygons to avoid issues with the antimeridian (original reason why I chose longitude between 0-360). Before I write the test for |
@NicolasColombi many thanks. I've got two questions about the subset_by_basin method
|
@emanuel-schmid For 2. Speed is not so much of an issue here, but if you know a more efficient way I am absolutely keen to modify the function. Can you advise on how to optimize it? |
@emanuel-schmid Any updates ? |
Would it make sense to also add the bounds for the impact basins as defined in https://www.nature.com/articles/s41467-022-33918-1 ? |
Maybe, what do you have in mind ? But at the moment I personally do not see a direct application. So, we can either add it now without a clear idea, or we add it later if needed. I would personally opt for the latter, unless you had something in mind. |
I am a bit confused now. I thought your goal was to be able to load tracks for specific basins. You will also use the basins defined in https://www.nature.com/articles/s41467-022-33918-1 . Hence, why would you not implement these? |
Because I do not need them, nor I see a use case aside from defining centroids, which I already have. But of course feel free to add them; I am sure someone will find it useful. |
Still confused, but ok. Then please just choose a name that makes obvious that this is only the definition used in STORM (is this really defined by STORM, or did they use the definition from somewhere else?). Currently, it looks like the Basin is a universally defined set of boundaries. |
Not really, STORM defined boundaries based on IBTracks, but did not use exactly the same. I wrote it clearly at the beginning of the class description that these boundaries were defined according to STORM. I think it is up to the user to check this information, especially since it is easy to find and basin boundaries are very similar across different publications. Adding "STORM" to the class name would be confusing in my opinion. |
Ok. It is not particularly important here. But in general, it is not a good idea to have code that is named to imply something which the code does not deliver. Good python code is as unambiguous as possible without having to read the docstring. It would here be better to either name the class |
|
||
Example: | ||
-------- | ||
>>> tc = TCTracks.from_ibtracks("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as an example this is suboptimal:
>>> tc = TCTracks.from_ibtracks("")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'TCTracks' has no attribute 'from_ibtracks'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updates 😄
lat, lon = track.lat.values[0], track.lon.values[0] | ||
origin_point = Point(lon, lat) | ||
point_in_basin = False | ||
|
||
# Find the basin that contains the point | ||
for basin in Basin: | ||
if basin.value.contains(origin_point): | ||
basins_dict[basin.name].append(track) | ||
point_in_basin = True | ||
break | ||
|
||
if not point_in_basin: | ||
tracks_outside_basin.append(track.id_no) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NicolasColombi as to 1. and 2.
I would first define a method that assigns basins to a track:
BASINS_GDF = gpd.GeoDataFrame(
{'basin': b, 'geometry': b.value} for b in Basin
)
def get_basins(track): # this is the method I had in mind for 1. and I'd guess it could be a performance boost
track_coordinates = GeoDataFrame(
geometry=gpd.points_from_xy(track.lon, track.lat)
)
return track_coordinates.sjoin(BASINS_GDF , how='left', predicate='within').basin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having that I'd write the track loop like this:
for track in self.data:
touched = get_basins(track).dropna().drop_duplicates()
if touched.size:
for basin in touched:
basins_dict[basin].append(track)
else:
tracks_outside_basin.append(track)
Changes proposed in this PR:
This PR adds tropical cyclone basin bounds as a dictionary named
BASINS_BOUNDS
in 0–360 coordinates, following STORM dataset bounds definitions.This addition can be used to determine whether a track originated in that basin simply by using shapely functionalities:
Particularly useful to decrease the memory overload during global wind fields computations by computing fields basin-wide.
Preview of the bounds:



PR Author Checklist
develop
)PR Reviewer Checklist