Pendahuluan#

InaCAWO#

Indonesian Coupled Atmosphere-Wave-Ocean (InaCAWO) merupakan model terpadu (coupled) beresolusi tinggi yang menyediakan prakiraan metorologi-oseanografi yang disesuaikan untuk wilayah "Benua Maritim". Seperti namanya, model terpadu ini menggunakan dari tiga (3) model utama yaitu: model atmosfer, model gelombang laut, dan model oseanografi, yang didesain untuk saling "berkomunikasi" satu dengan yang lain.

Model-model seperti Weather Research Forecasting (WRF), Regional Ocean Modelling System (ROMS), dan Simulating Waves Nearshore (SWAN) digunakan bersama-sama dengan Model Coupling Toolkit (MCT) untuk menghasilkan prediksi kondisi cuaca, laut, dan gelombang secara serentak.

Berikut merupakan tabel deskripsi umum dan singkat mengenai InaCAWO:

No

Parameter

Deskripsi

1

Cycle

4 cycles per day [00Z, 06Z, 12Z, 18Z]

2

Resolution - Temporal

1hr

3

Resolution - Spatial

~3km

4

Domain extent

[90°E - 145°E; 15°S - 15°N]


Data NetCDF#

Network Common Data Form (NetCDF) merupakan format data hierarkis, dalam hal ini berarti bahwa data disusun bertingkat. Format data ini banyak digunakan pada penyimpanan data geospasial (dalam hal ini data atmosfer dan oseanografi) karena beberapa alasan, yaitu:

  1. Mudah dibaca oleh mesin dan banyak didukung oleh pustaka pemrograman
  2. Dapat menyimpan metadata bersama data utama
  3. Mendukung dimensi waktu dan ruang, cocok untuk data atmosfer & laut

Berikut merupakan contoh struktur pada data NetCDF:

/ (root)
├── dimensions
   ├── time = 365
   ├── depth = 50
   ├── lat = 200
   └── lon = 300

├── variables
   ├── time(time)
   ├── depth(depth)
   ├── lat(lat)
   ├── lon(lon)
   ├── temperature(time, depth, lat, lon)
   ├── salinity(time, depth, lat, lon)
   ├── u_velocity(time, depth, lat, lon)
   └── v_velocity(time, depth, lat, lon)

├── attributes (metadata)
   ├── title = "Model Output: Ocean Circulation"
   ├── institution = "Pusat Oseanografi Fisis"
   ├── model = "ROMS"
   ├── references = "doi:10.xxxx/roms_output"
   └── history = "Created on 2025-04-01"

Akses Data#

Keluaran Model#

Umum#

Bahasa pemrograman Python memungkinkan kita untuk melakukan pengolahan data yang disimpan dalam format NetCDF. Tentu saja untuk melakukan pengolahan data, terlebih dahulu kita harus memuat atau membuka file terlebih dahulu. Terdapat beberapa pilihan pustaka yag dapat digunakan untuk membuka file NetCDF dalam bahasa pemrograman Python, diantaranya:

  1. NetCDF4
  2. Xarray
  3. Rioxarray

Namun pada pelatihan ini, akan digunakan pustaka Xarray dengan beberapa alasan, antara lain:

  • Xarray memiliki struktur data Dataset dan DataArray yang selaras dengan format NetCDF, sehingga mudah dimengerti dan digunakan.
  • Mendukung indexing dan slicing berbasis label, sehingga kita bisa mengakses data menggunakan nama dimensi atau koordinat (bukan hanya indeks numerik).
  • Cocok untuk analisis data multidimensi seperti suhu, curah hujan, kelembaban, dan parameter iklim lainnya.
  • Dapat dipadukan dengan dask untuk menangani data yang sangat besar secara efisien, bahkan jika ukuran data lebih besar dari memori.
  • Menyimpan metadata secara otomatis, seperti satuan, deskripsi, dan atribut lainnya dari file NetCDF.
  • Memiliki interoperabilitas tinggi dengan pustaka lain seperti matplotlib, pandas, rioxarray, dan cartopy.
  • Mendukung proses baca dan tulis file NetCDF secara langsung menggunakan fungsi .open_dataset() dan .to_netcdf().

Impor pustaka yang dibutuhkan#

import xarray as xr
import os

Muat Data Model#

Mendefinisikan path (lokasi) file netcdf#

dir_mod = '/data/local/marine-training/data/MATPEL_05/cawo_out'
paths_mod = []
for file in os.listdir(dir_mod):
    if file.endswith('nc') and 'cawo' in file:
        paths_mod.append(os.path.join(dir_mod, file))
paths_mod.sort()
dset_metoc_all = xr.open_mfdataset(paths_mod[:3])
dset_metoc_all
<xarray.Dataset> Size: 66GB
Dimensions:  (date: 90, depth: 23, lat: 1201, lon: 2201)
Coordinates:
  * date     (date) datetime64[ns] 720B 2024-02-01 2024-02-02 ... 2024-04-30
  * depth    (depth) float32 92B 0.0 -5.0 -10.0 ... -1.2e+03 -1.5e+03 -2e+03
  * lat      (lat) float32 5kB -15.0 -14.98 -14.95 -14.93 ... 14.95 14.98 15.0
  * lon      (lon) float32 9kB 90.0 90.03 90.05 90.07 ... 144.9 145.0 145.0
Data variables:
    sw_dens  (date, depth, lat, lon) float32 22GB dask.array<chunksize=(5, 4, 241, 441), meta=np.ndarray>
    sw_salt  (date, depth, lat, lon) float32 22GB dask.array<chunksize=(5, 4, 241, 441), meta=np.ndarray>
    sw_temp  (date, depth, lat, lon) float32 22GB dask.array<chunksize=(5, 4, 241, 441), meta=np.ndarray>

Membuat plot salah satu parameter metocean#

import datetime
# Memilih parameter Sea Surface Temperature (SST) pada tanggal pertama menggunakan index slicing
da_sst_idx = dset_metoc_all['sw_temp'].isel(depth=0, date=0)

# Memilih parameter Sea Surface Temperature (SST) pada tanggal pertama menggunakan slicing biasa
dt = datetime.datetime(2024,2,1,0)
da_sst_dt = dset_metoc_all['sw_temp'].sel(depth=0., date=dt)
da_sst_idx
<xarray.DataArray 'sw_temp' (lat: 1201, lon: 2201)> Size: 11MB
dask.array<getitem, shape=(1201, 2201), dtype=float32, chunksize=(241, 441), chunktype=numpy.ndarray>
Coordinates:
    date     datetime64[ns] 8B 2024-02-01
    depth    float32 4B 0.0
  * lat      (lat) float32 5kB -15.0 -14.98 -14.95 -14.93 ... 14.95 14.98 15.0
  * lon      (lon) float32 9kB 90.0 90.03 90.05 90.07 ... 144.9 145.0 145.0
Attributes:
    long_name:      TEMP
    standard_name:  sea_water_potential_temperature
    units:          Celsius
    var_desc:       Ocean Potential temperature, scalar, series              ...
da_sst_dt
<xarray.DataArray 'sw_temp' (lat: 1201, lon: 2201)> Size: 11MB
dask.array<getitem, shape=(1201, 2201), dtype=float32, chunksize=(241, 441), chunktype=numpy.ndarray>
Coordinates:
    date     datetime64[ns] 8B 2024-02-01
    depth    float32 4B 0.0
  * lat      (lat) float32 5kB -15.0 -14.98 -14.95 -14.93 ... 14.95 14.98 15.0
  * lon      (lon) float32 9kB 90.0 90.03 90.05 90.07 ... 144.9 145.0 145.0
Attributes:
    long_name:      TEMP
    standard_name:  sea_water_potential_temperature
    units:          Celsius
    var_desc:       Ocean Potential temperature, scalar, series              ...
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.feature import GSHHSFeature
import matplotlib.colors as mcolors
import numpy as np
import matplotlib.cm as cm
# Proyeksi dan fitur
proj_transf = ccrs.PlateCarree()
mfeature = cfeature.GSHHSFeature(scale='h', levels=[1,2,3,4], facecolor='linen')

# Level SST
vmin, vmax, step = 21, 34, 0.2
levels = np.arange(vmin, vmax + step, step)

# Buat colormap dan norm untuk diskritisasi warna
cmap = cm.get_cmap('jet', len(levels) - 1)  # colormap discrete
norm = mcolors.BoundaryNorm(boundaries=levels, ncolors=cmap.N)

# Buat figure dan axis dengan proyeksi Cartopy
fig, ax = plt.subplots(figsize=(10, 6), subplot_kw={'projection': proj_transf})

# Plotkan DataArray ke axis yang sudah berproyeksi
map_sst = da_sst_idx.plot(
    ax=ax,
    transform=proj_transf,
    cmap=cmap,
    norm=norm,
    add_colorbar=False  # kita akan tambahkan colorbar manual
)

# Tambahkan colorbar 
cbar = plt.colorbar(
    map_sst,
    ax=ax,
    orientation='horizontal',
    pad=0.07,
    shrink=0.85,
    aspect=30,
    ticks=np.arange(vmin, vmax + 1, 1),  # major ticks tiap 1 derajat
    label=r'$^{\circ}C$',
    extend='both'
)

# Tambahkan fitur GSHHS
ax.add_feature(mfeature)

# Tambahkan gridlines
gl = ax.gridlines(draw_labels=True)
gl.top_labels = False
gl.left_labels = False
gl.right_labels = True
gl.bottom_labels = True

ax.text(
    1.0, 1.03,                                                      # posisi (x=kanan, y=sedikit di atas)
    f"{da_sst_idx.date.dt.strftime('%d %b %Y').values}",            # teks tanggal
    transform=ax.transAxes,                                         # koordinat relatif ke axes
    ha='right',                                                     # horizontal alignment: kanan
    va='bottom',                                                    # vertical alignment: bawah
    fontsize=10,
    fontweight='bold'
)

plt.title("Sea Surface Temperature")
plt.show()
/tmp/ipykernel_9818/1980617915.py:10: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed in 3.11. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()`` or ``pyplot.get_cmap()`` instead.
  cmap = cm.get_cmap('jet', len(levels) - 1)  # colormap discrete
../_images/7967a7f9b4f0c7ca20c1b12e100e9984a6a8eb6567c1d5bff186f91608f844e5.png

Pengayaan 1#

Membuat peta serupa dengan parameter yang berbeda atau sama namun dengan waktu yang berbeda, yang telah disesuaikan dengan wilayah bekerja

# Write your code here

Data Argo float#

Load Data Argofloat#

fl_argo_nc = '/data/local/marine-training/data/MATPEL_05/argo_data/nc_argo/GL_PR_PF_2902800.nc'
ds_argo = xr.open_dataset(fl_argo_nc)
ds_argo
<xarray.Dataset> Size: 2MB
Dimensions:                   (TIME: 220, LATITUDE: 220, LONGITUDE: 220,
                               POSITION: 220, DEPTH: 103)
Coordinates:
  * TIME                      (TIME) datetime64[ns] 2kB 2021-01-10T05:49:54 ....
  * LATITUDE                  (LATITUDE) float32 880B 4.381 4.046 ... 4.243
  * LONGITUDE                 (LONGITUDE) float32 880B 147.0 146.8 ... 130.8
Dimensions without coordinates: POSITION, DEPTH
Data variables: (12/23)
    TIME_QC                   (TIME) float32 880B ...
    POSITION_QC               (POSITION) float32 880B ...
    DC_REFERENCE              (TIME) object 2kB ...
    DIRECTION                 (TIME) object 2kB ...
    VERTICAL_SAMPLING_SCHEME  (TIME) object 2kB ...
    PRES                      (TIME, DEPTH) float32 91kB ...
    ...                        ...
    PSAL                      (TIME, DEPTH) float64 181kB ...
    PSAL_QC                   (TIME, DEPTH) float32 91kB ...
    PSAL_ADJUSTED             (TIME, DEPTH) float64 181kB ...
    PSAL_ADJUSTED_QC          (TIME, DEPTH) float32 91kB ...
    PSAL_ADJUSTED_DM          (TIME, DEPTH) object 181kB ...
    PSAL_ADJUSTED_ERROR       (TIME, DEPTH) float64 181kB ...
Attributes: (12/49)
    data_type:                      OceanSITES vertical profile
    format_version:                 1.4
    platform_code:                  2902800
    institution:                    First Institute of Oceanography - Ministr...
    institution_edmo_code:          4640
    site_code:                       
    ...                             ...
    last_date_observation:          2025-04-05T01:30:25Z
    last_latitude_observation:      4.24300
    last_longitude_observation:     130.82900
    date_update:                    2025-04-15T09:50:18Z
    history:                        2025-04-15T09:50:18Z : Creation
    data_mode:                      M

Membuat plot trajektori float#

from matplotlib.lines import Line2D
import pandas as pd
from mods import compute_extent

lons, lats = ds_argo.LONGITUDE.values, ds_argo.LATITUDE.values
dts = pd.to_datetime(ds_argo['TIME'].values)
pts = ds_argo.POSITION.values


# Final extent
exts = compute_extent(lons, lats)

# Plotting
proj = ccrs.PlateCarree()
fig, ax = plt.subplots(figsize=(10, 6), subplot_kw=dict(projection=proj))
ax.set_extent(exts, crs=proj)
ax.add_feature(cfeature.GSHHSFeature(scale="high", levels=[1, 2, 3, 4], facecolor="linen"), linewidth=.8)
ax.add_feature(cfeature.BORDERS, linestyle=":")
ax.add_feature(cfeature.OCEAN)

# Plot trajektori
ax.plot(lons, lats, color='blue', linewidth=1.5, marker='o', markersize=8, transform=proj, label='Trajectory')
ax.plot(lons[0], lats[0], marker='x', color='yellow', markersize=12, transform=proj, label='Start')
ax.plot(lons[-1], lats[-1], marker='x', color='red', markersize=12, transform=proj, label='End')

# Label waktu opsional
for i,pt in enumerate(pts):
    ax.text(lons[i]+.03, lats[i]+.03, pt, fontsize=10, transform=proj)

gl = ax.gridlines(draw_labels=True)
gl.top_labels = False
gl.left_labels = True
gl.right_labels = True
gl.bottom_labels = True    

# Legenda dan layout
legend_elements = [
    Line2D([0], [0], lw=0, marker='x', color='yellow', label='Start', markersize=8),
    Line2D([0], [0], lw=0, marker='x', color='red', label='End', markersize=8)
]

# Menambahkan elemen legenda berdasarkan data
for i in range(min(5, len(pts))):
    legend_elements.append(
        Line2D([0], [0], lw=1, marker='o', color='blue', label=f'{pts[i]}: {dts[i].strftime("%d-%m-%Y")}')
    )

# Set legend dan title
ax.legend(handles=legend_elements, loc='upper right')
ax.set_title('Float Trajectory', fontsize=10)

# Menyesuaikan layout dan menampilkan plot
plt.tight_layout()
plt.show()
../_images/c349e67595456e4383d9d7818c7d0cdc62892b75ada70be0c3388c1eddeb2553.png
from mods import animate_argo_trajectory
import matplotlib as mpl
mpl.rcParams['animation.embed_limit'] = 52428800  # Naikkan limit ke 50 MB
argo_ani, argo_html = animate_argo_trajectory(lons, lats, dts, pts, exts)

# Tampilkan animasi di Jupyter
argo_html
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[12], line 4
      2 import matplotlib as mpl
      3 mpl.rcParams['animation.embed_limit'] = 52428800  # Naikkan limit ke 50 MB
----> 4 argo_ani, argo_html = animate_argo_trajectory(lons, lats, dts, pts, exts)
      6 # Tampilkan animasi di Jupyter
      7 argo_html

File ~/marinemet-training/5_/mods/disp.py:135, in animate_argo_trajectory(lons, lats, dts, pts, exts, interval)
    133 # Buat animasi
    134 ani = FuncAnimation(fig, update, frames=len(lons), init_func=init, blit=True, interval=interval)
--> 135 return ani, HTML(ani.to_jshtml())

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/animation.py:1376, in Animation.to_jshtml(self, fps, embed_frames, default_mode)
   1372         path = Path(tmpdir, "temp.html")
   1373         writer = HTMLWriter(fps=fps,
   1374                             embed_frames=embed_frames,
   1375                             default_mode=default_mode)
-> 1376         self.save(str(path), writer=writer)
   1377         self._html_representation = path.read_text()
   1379 return self._html_representation

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/animation.py:1122, in Animation.save(self, filename, writer, fps, dpi, codec, bitrate, extra_args, metadata, extra_anim, savefig_kwargs, progress_callback)
   1119 for data in zip(*[a.new_saved_frame_seq() for a in all_anim]):
   1120     for anim, d in zip(all_anim, data):
   1121         # TODO: See if turning off blit is really necessary
-> 1122         anim._draw_next_frame(d, blit=False)
   1123         if progress_callback is not None:
   1124             progress_callback(frame_number, total_frames)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/animation.py:1158, in Animation._draw_next_frame(self, framedata, blit)
   1156 self._pre_draw(framedata, blit)
   1157 self._draw_frame(framedata)
-> 1158 self._post_draw(framedata, blit)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/animation.py:1183, in Animation._post_draw(self, framedata, blit)
   1181     self._blit_draw(self._drawn_artists)
   1182 else:
-> 1183     self._fig.canvas.draw_idle()

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/backend_bases.py:1891, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1889 if not self._is_idle_drawing:
   1890     with self._idle_draw_cntx():
-> 1891         self.draw(*args, **kwargs)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
    379 # Acquire a lock on the shared font cache.
    380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    381       else nullcontext()):
--> 382     self.figure.draw(self.renderer)
    383     # A GUI class may be need to update a window using this draw, so
    384     # don't forget to call the superclass.
    385     super().draw()

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/figure.py:3257, in Figure.draw(self, renderer)
   3254             # ValueError can occur when resizing a window.
   3256     self.patch.draw(renderer)
-> 3257     mimage._draw_list_compositing_images(
   3258         renderer, self, artists, self.suppressComposite)
   3260     renderer.close_group('figure')
   3261 finally:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    132 if not_composite or not has_images:
    133     for a in artists:
--> 134         a.draw(renderer)
    135 else:
    136     # Composite any adjacent images together
    137     image_group = []

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/cartopy/mpl/geoaxes.py:524, in GeoAxes.draw(self, renderer, **kwargs)
    519         self.imshow(img, extent=extent, origin=origin,
    520                     transform=factory.crs, *factory_args[1:],
    521                     **factory_kwargs)
    522 self._done_img_factory = True
--> 524 return super().draw(renderer=renderer, **kwargs)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/axes/_base.py:3210, in _AxesBase.draw(self, renderer)
   3207 if artists_rasterized:
   3208     _draw_rasterized(self.get_figure(root=True), artists_rasterized, renderer)
-> 3210 mimage._draw_list_compositing_images(
   3211     renderer, self, artists, self.get_figure(root=True).suppressComposite)
   3213 renderer.close_group('axes')
   3214 self.stale = False

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    132 if not_composite or not has_images:
    133     for a in artists:
--> 134         a.draw(renderer)
    135 else:
    136     # Composite any adjacent images together
    137     image_group = []

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/cartopy/mpl/feature_artist.py:195, in FeatureArtist.draw(self, renderer)
    193 # Project (if necessary) and convert geometries to matplotlib paths.
    194 key = ax.projection
--> 195 for geom in geoms:
    196     # As Shapely geometries cannot be relied upon to be
    197     # hashable, we have to use a WeakValueDictionary to manage
    198     # their weak references. The key can then be a simple,
    199     # "disposable", hashable geom-key object that just uses the
    200     # id() of a geometry to determine equality and hash value.
    201     # The only persistent, strong reference to the geom-key is
    202     # in the WeakValueDictionary, so when the geometry is
    203     # garbage collected so is the geom-key.
    204     # The geom-key is also used to access the WeakKeyDictionary
    205     # cache of transformed geometries. So when the geom-key is
    206     # garbage collected so are the transformed geometries.
    207     geom_key = _GeomKey(geom)
    208     FeatureArtist._geom_key_to_geometry_cache.setdefault(
    209         geom_key, geom)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/cartopy/feature/__init__.py:428, in GSHHSFeature.intersecting_geometries(self, extent)
    426     GSHHSFeature._geometries_cache[(scale, level)] = geoms
    427 for geom in geoms:
--> 428     if extent is None or extent_geom.intersects(geom):
    429         yield geom

File /opt/conda/envs/ofs/lib/python3.13/site-packages/shapely/geometry/base.py:819, in BaseGeometry.intersects(self, other)
    817 def intersects(self, other):
    818     """Return True if geometries intersect, else False."""
--> 819     return _maybe_unpack(shapely.intersects(self, other))

File /opt/conda/envs/ofs/lib/python3.13/site-packages/shapely/decorators.py:87, in multithreading_enabled.<locals>.wrapped(*args, **kwargs)
     85     for arr in array_args:
     86         arr.flags.writeable = False
---> 87     return func(*args, **kwargs)
     88 finally:
     89     for arr, old_flag in zip(array_args, old_flags):

File /opt/conda/envs/ofs/lib/python3.13/site-packages/shapely/predicates.py:878, in intersects(a, b, **kwargs)
    844 @multithreading_enabled
    845 def intersects(a, b, **kwargs):
    846     """Return True if A and B share any portion of space.
    847 
    848     Intersects implies that overlaps, touches, covers, or within are True.
   (...)    876 
    877     """
--> 878     return lib.intersects(a, b, **kwargs)

KeyboardInterrupt: 
# save the animation
# argo_ani.save('trajectory_animation.gif', writer='pillow', fps=10)

Data Drifter#

Load Data#

# Load into dataframe
path_drifter_csv = '/data/local/marine-training/data/MATPEL_05/drifter_data/drifter_6hour_qc_f5dd_5de7_fd9d.csv'
raw_df = pd.read_csv(path_drifter_csv)

# Display basic info and the first few rows
raw_df.info()
# Ambil baris pertama sebagai header yang benar
new_header = np.asarray(raw_df.columns)
df = raw_df[1:]
df.columns = new_header

# Drop rows dengan ID, latitude, atau longitude kosong
df = df.dropna(subset=["ID", "latitude", "longitude"])

# Konversi tipe data yang diperlukan
df["ID"] = df["ID"].astype(int)
df["latitude"] = df["latitude"].astype(float)
df["longitude"] = df["longitude"].astype(float)
df["time"] = pd.to_datetime(df["time"], format="%Y-%m-%dT%H:%M:%SZ")
df["start_date"] = pd.to_datetime(df["start_date"], format="%Y-%m-%dT%H:%M:%SZ")
df["deploy_date"] = pd.to_datetime(df["deploy_date"], format="%Y-%m-%dT%H:%M:%SZ")
df["end_date"] = pd.to_datetime(df["end_date"], format="%Y-%m-%dT%H:%M:%SZ")
df["drogue_lost_date"] = pd.to_datetime(df["drogue_lost_date"], format="%Y-%m-%dT%H:%M:%SZ")


# Ambil ID unik
unique_ids = df["ID"].unique()

# Tampilkan jumlah dan sampel ID
len(unique_ids), [int(i) for i in unique_ids[:20]]

Plot Trajectory Drifter#

# Menentukan extend peta
exts = [88, 147, -17, 17]

# Menentukan colors yang akan digunakan
n = len(unique_ids)  # jumlah data
cmap = plt.get_cmap('jet')  # bisa juga 'plasma', 'hsv', 'turbo', dll.

# generate list warna
colors = [cmap(i / n) for i in range(n)]

# Menentukan rasio gambar
p=10
l=6
rat = p/l
pj = 12

# Plotting
proj = ccrs.PlateCarree()
fig, ax = plt.subplots(figsize=(pj, pj/rat), subplot_kw=dict(projection=proj))
ax.set_extent(exts, crs=proj)
ax.add_feature(cfeature.GSHHSFeature(scale="high", levels=[1, 2, 3, 4], facecolor="linen"), linewidth=.8)
ax.add_feature(cfeature.BORDERS, linestyle=":")
ax.add_feature(cfeature.OCEAN)
legend_elements = []

# Plot trajektori
for i, idd in enumerate(unique_ids):
    lons = df.loc[df["ID"]==idd]['longitude'].values
    lats = df.loc[df["ID"]==idd]['latitude'].values
    ax.plot(lons, lats, color=colors[i], linewidth=1.5, marker='.', markersize=8, transform=proj)

    # Menambahkan elemen legenda berdasarkan data
    legend_elements.append(
        Line2D([0], [0], lw=0, marker='o', color=colors[i], label=f'{idd}')
    )

ax.legend(handles=legend_elements, loc='upper left', ncol=2, fontsize=7)
gl = ax.gridlines(draw_labels=True)
gl.top_labels = False
gl.left_labels = True
gl.right_labels = True
gl.bottom_labels = True
ax.set_title('Drifter Trajectories', fontsize=12)

# Menampilkan plot
plt.tight_layout()
plt.show()
# Buat animasi
from mods import animate_drifter_trajectory

id_drifter = 300234060253330
argo_ani, argo_html = animate_drifter_trajectory(df, id_drifter, exts)

argo_html