diff --git a/opendrift_leeway_webgui/leeway/forms.py b/opendrift_leeway_webgui/leeway/forms.py index 9e04f9e..54e619d 100644 --- a/opendrift_leeway_webgui/leeway/forms.py +++ b/opendrift_leeway_webgui/leeway/forms.py @@ -35,6 +35,7 @@ class Meta: "start_time", "duration", "radius", + "send_trajectories", ] help_texts = { "duration": "Length of simulation in hours.", diff --git a/opendrift_leeway_webgui/leeway/migrations/0009_leewaysimulation_send_animation_sea_and_more.py b/opendrift_leeway_webgui/leeway/migrations/0009_leewaysimulation_send_animation_sea_and_more.py new file mode 100644 index 0000000..9da1f52 --- /dev/null +++ b/opendrift_leeway_webgui/leeway/migrations/0009_leewaysimulation_send_animation_sea_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.7 on 2023-12-03 14:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [("leeway", "0008_leewaysimulation_traceback")] + + operations = [ + migrations.AddField( + model_name="leewaysimulation", + name="send_animation_sea", + field=models.BooleanField( + default=False, + help_text="Attach Gif to e-mail with animation of particles with sea currents.", + ), + ), + migrations.AddField( + model_name="leewaysimulation", + name="send_animation_wind", + field=models.BooleanField( + default=False, + help_text="Attach Gif to e-mail with animation of particles and wind fields.", + ), + ), + migrations.AddField( + model_name="leewaysimulation", + name="send_heatmap", + field=models.BooleanField( + default=False, + help_text="Attach PNG to e-mail with heat map of final particle locations.", + ), + ), + migrations.AddField( + model_name="leewaysimulation", + name="send_trajectories", + field=models.BooleanField( + default=True, + help_text="Attach PNG to e-mail with particle trajectories.", + ), + ), + ] diff --git a/opendrift_leeway_webgui/leeway/models.py b/opendrift_leeway_webgui/leeway/models.py index a6e03b0..9dd8005 100644 --- a/opendrift_leeway_webgui/leeway/models.py +++ b/opendrift_leeway_webgui/leeway/models.py @@ -35,6 +35,18 @@ class LeewaySimulation(models.Model): simulation_started = models.DateTimeField(null=True) simulation_finished = models.DateTimeField(null=True) radius = models.IntegerField(default=1000) + send_trajectories = models.BooleanField( + default=True, + help_text="Attach PNG to e-mail with particle trajectories.") + send_heatmap = models.BooleanField( + default=False, + help_text="Attach PNG to e-mail with heat map of final particle locations.") + send_animation_wind = models.BooleanField( + default=False, + help_text="Attach Gif to e-mail with animation of particles and wind fields.") + send_animation_sea = models.BooleanField( + default=False, + help_text="Attach Gif to e-mail with animation of particles with sea currents.") img = models.FileField( null=True, storage=simulation_storage, verbose_name=_("Image file") ) diff --git a/opendrift_leeway_webgui/simulation.py b/opendrift_leeway_webgui/simulation.py index 91060fc..b4d9527 100644 --- a/opendrift_leeway_webgui/simulation.py +++ b/opendrift_leeway_webgui/simulation.py @@ -3,8 +3,11 @@ Use it with the docker container by mounting a directory and copying the file to it: -docker run -it --volume ./simulation:/code/leeway opendrift/opendrift python3 leeway/simulation.py\ - --longitude 11.9545 --latitude 35.2966 --start-time "2022-12-05 03:00" --duration 12 +docker run -it --volume /opt/leeway/opendrift_leeway_webgui/simulation-files:/code/leeway\ + --volume /opt/leeway/opendrift_leeway_webgui/simulation.py:/code/leeway/simulation.py\ + opendrift/opendrift python3 leeway/simulation.py --longitude 12.0 --latitude 34.0 --radius 1000\ + --number 100 --start-time "2023-12-03 15:14" --object-type 27 --duration 12\ + --id b22cc8d6-1235-4cfa-9c8f-2ebca101e4fb """ import argparse @@ -15,6 +18,8 @@ # pylint: disable=import-error from opendrift.models.leeway import Leeway from opendrift.readers import reader_global_landmask +from sklearn.cluster import KMeans +import pyproj INPUTDIR = "/code/leeway/input" @@ -107,11 +112,53 @@ def main(): simulation.run( duration=timedelta(hours=args.duration), time_step=600, outfile=f"{outfile}.nc" ) + + simulation.plot( fast=True, legend=True, filename=f"{outfile}.png", linecolor="age_seconds" ) + print(f"Success: {outfile}.png written.") + lon, lat = simulation.get_lonlats() + coordinates = list(zip(lon[:, -1], lat[:, -1])) + plot_k_means(coordinates) + +def plot_k_means(coordinates, max_distance=3000): + """ + Calculate k-means of final distribution of particles. + max_dinstance indicates the maximum distance of each particle from the + center of each cluster. + """ + cartesian_coordinates = list(map(geodetic_to_cartesian, coordinates)) + distance = 9999 + k = 0 + while distance > max_distance: + k = k + 1 + kmeans = KMeans(n_clusters=k).fit(cartesian_coordinates) + coordinates_dist = kmeans.transform(cartesian_coordinates)**2 + print(coordinates_dist) + +def cartesian_to_geodetic(x, y, z): + """ + Convert cartesian coordinates to latitiude & longitude + """ + ecef = pyproj.Proj(proj='geocent', ellps='WGS84', datum='WGS84') + lla = pyproj.Proj(proj='latlong', ellps='WGS84', datum='WGS84') + lon, lat, alt = pyproj.transform(ecef, lla, x, y, z, radians=False) + return lat, lon, alt + +def geodetic_to_cartesian(lonlat): + """ + Convert latitiude & longitude to cartesian coordinates + """ + lon = lonlat[0] + lat = lonlat[1] + alt = 0 + ecef = pyproj.Proj(proj='geocent', ellps='WGS84', datum='WGS84') + lla = pyproj.Proj(proj='latlong', ellps='WGS84', datum='WGS84') + x, y, z = pyproj.transform(lla, ecef, lon, lat, alt, radians=False) + return x, y, z if __name__ == "__main__": main()