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:
- Mudah dibaca oleh mesin dan banyak didukung oleh pustaka pemrograman
- Dapat menyimpan metadata bersama data utama
- 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:
Namun pada pelatihan ini, akan digunakan pustaka Xarray
dengan beberapa alasan, antara lain:
-
Xarray
memiliki struktur dataDataset
danDataArray
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
, dancartopy
. -
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()

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