Akses Data

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_4623/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
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[8], line 59
     48 ax.text(
     49     1.0, 1.03,                                                      # posisi (x=kanan, y=sedikit di atas)
     50     f"{da_sst_idx.date.dt.strftime('%d %b %Y').values}",            # teks tanggal
   (...)     55     fontweight='bold'
     56 )
     58 plt.title("Sea Surface Temperature")
---> 59 plt.show()

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/pyplot.py:614, in show(*args, **kwargs)
    570 """
    571 Display all open figures.
    572 
   (...)    611 explicitly there.
    612 """
    613 _warn_if_gui_out_of_main_thread()
--> 614 return _get_backend_mod().show(*args, **kwargs)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib_inline/backend_inline.py:90, in show(close, block)
     88 try:
     89     for figure_manager in Gcf.get_all_fig_managers():
---> 90         display(
     91             figure_manager.canvas.figure,
     92             metadata=_fetch_figure_metadata(figure_manager.canvas.figure)
     93         )
     94 finally:
     95     show._to_draw = []

File /opt/conda/envs/ofs/lib/python3.13/site-packages/IPython/core/display_functions.py:278, in display(include, exclude, metadata, transient, display_id, raw, clear, *objs, **kwargs)
    276     publish_display_data(data=obj, metadata=metadata, **kwargs)
    277 else:
--> 278     format_dict, md_dict = format(obj, include=include, exclude=exclude)
    279     if not format_dict:
    280         # nothing to display (e.g. _ipython_display_ took over)
    281         continue

File /opt/conda/envs/ofs/lib/python3.13/site-packages/IPython/core/formatters.py:238, in DisplayFormatter.format(self, obj, include, exclude)
    236 md = None
    237 try:
--> 238     data = formatter(obj)
    239 except:
    240     # FIXME: log the exception
    241     raise

File /opt/conda/envs/ofs/lib/python3.13/site-packages/decorator.py:235, in decorate.<locals>.fun(*args, **kw)
    233 if not kwsyntax:
    234     args, kw = fix(args, kw, sig)
--> 235 return caller(func, *(extras + args), **kw)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/IPython/core/formatters.py:282, in catch_format_error(method, self, *args, **kwargs)
    280 """show traceback on failed format call"""
    281 try:
--> 282     r = method(self, *args, **kwargs)
    283 except NotImplementedError:
    284     # don't warn on NotImplementedErrors
    285     return self._check_return(None, args[0])

File /opt/conda/envs/ofs/lib/python3.13/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
    400     pass
    401 else:
--> 402     return printer(obj)
    403 # Finally look for special method names
    404 method = get_real_method(obj, self.print_method)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    167     from matplotlib.backend_bases import FigureCanvasBase
    168     FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
    171 data = bytes_io.getvalue()
    172 if fmt == 'svg':

File /opt/conda/envs/ofs/lib/python3.13/site-packages/matplotlib/backend_bases.py:2155, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2152     # we do this instead of `self.figure.draw_without_rendering`
   2153     # so that we can inject the orientation
   2154     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2155         self.figure.draw(renderer)
   2156 if bbox_inches:
   2157     if bbox_inches == "tight":

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:425, in GSHHSFeature.intersecting_geometries(self, extent)
    421 if geoms is None:
    422     # Load GSHHS geometries from appropriate shape file.
    423     # TODO selective load based on bbox of each geom in file.
    424     path = shapereader.gshhs(scale, level)
--> 425     geoms = tuple(shapereader.Reader(path).geometries())
    426     GSHHSFeature._geometries_cache[(scale, level)] = geoms
    427 for geom in geoms:

File /opt/conda/envs/ofs/lib/python3.13/site-packages/cartopy/io/shapereader.py:167, in BasicReader.geometries(self)
    164 for shape in self._reader.iterShapes(bbox=self._bbox):
    165     # Skip the shape that can not be represented as geometry.
    166     if shape.shapeType != shapefile.NULL:
--> 167         yield sgeom.shape(shape)

File /opt/conda/envs/ofs/lib/python3.13/site-packages/shapely/geometry/geo.py:91, in shape(context)
     89 else:
     90     ob = context
---> 91 geom_type = ob.get("type").lower()
     93 if geom_type == "feature":
     94     # GeoJSON features must have a 'geometry' field.
     95     ob = ob["geometry"]

KeyboardInterrupt: 

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
../_images/5e0f710d6cd0608b8cf682a3d0cca0317684685ae9cf4025a9fa3fc1dbc62b59.png
# 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()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6017 entries, 0 to 6016
Data columns (total 23 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   ID                6016 non-null   float64
 1   WMO               6016 non-null   float64
 2   time              6017 non-null   object 
 3   latitude          6017 non-null   object 
 4   longitude         6017 non-null   object 
 5   sst               5854 non-null   object 
 6   err_sst           6017 non-null   object 
 7   ve                6016 non-null   float64
 8   vn                6016 non-null   float64
 9   err_lat           6016 non-null   float64
 10  err_lon           6016 non-null   float64
 11  deploy_date       5017 non-null   object 
 12  deploy_lat        5285 non-null   object 
 13  deploy_lon        5285 non-null   object 
 14  start_date        6017 non-null   object 
 15  start_lat         6017 non-null   object 
 16  start_lon         6017 non-null   object 
 17  end_date          1292 non-null   object 
 18  end_lat           1292 non-null   object 
 19  end_lon           1292 non-null   object 
 20  drogue_lost_date  1625 non-null   object 
 21  DrogueType        0 non-null      float64
 22  DrogueLength      6016 non-null   object 
dtypes: float64(7), object(16)
memory usage: 1.1+ MB
# 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]]
(37,
 [300234010820530,
  300234010827560,
  300234010828550,
  300234010829560,
  300234060253330,
  300234060352970,
  300234060842100,
  300234061473430,
  300234067275190,
  300534060057050,
  300534060156820,
  300534060158830,
  300534060159970,
  300534060382270,
  300534060526130,
  300534060526170,
  300534060717640,
  300534060719650,
  300534061490000,
  300534061656610])

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()
../_images/1fc2ee6cfa74f125777c097cc51db1ae500b707d3ced641b122e2af70a290ae3.png
# Buat animasi
from mods import animate_drifter_trajectory

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

argo_html
../_images/d545a5597123789ec41a03088be62b236bf03a12c673f0e273162e55def9bb3b.png