def test_c_s(self): c_ck = 75 t_ck = 40 p_ck = 10000 s_ck = 36.616 s_calc = Oc.c2s(c=c_ck, t=t_ck, p=p_ck) self.assertAlmostEqual(s_calc, s_ck, places=1) c_calc = Oc.s2c(s=s_calc, p=p_ck, t=t_ck) self.assertAlmostEqual(c_calc, c_ck, places=1)
def test_theta_t0(self): theta_ck = 36.89073 s_ck = 40 t0_ck = 40 p0_ck = 10000 p_ref_ck = 0 theta_calc = Oc.pot_temp(s=s_ck, t=t0_ck, p=p0_ck, pr=p_ref_ck) self.assertAlmostEqual(theta_calc, theta_ck, places=1) t0_calc = Oc.in_situ_temp(s=s_ck, t=theta_ck, p=p0_ck, pr=p_ref_ck) self.assertAlmostEqual(t0_calc, t0_ck, places=1)
def _write_body(self): # logger.debug('generating body') vi = self.ssp.cur.proc_valid for idx in range(np.sum(vi)): self.fod.io.write( "%8.2f%10.2f%10.2f%10.2f%10.2f\n" % (self.ssp.cur.proc.depth[vi][idx], self.ssp.cur.proc.speed[vi][idx], self.ssp.cur.proc.temp[vi][idx], self.ssp.cur.proc.sal[vi][idx], Oc.s2c(s=self.ssp.cur.proc.sal[vi][idx], p=Oc.d2p(d=self.ssp.cur.proc.depth[vi][idx], lat=self.ssp.cur.meta.latitude), t=self.ssp.cur.proc.temp[vi][idx])))
def test_d2p_gsw(self): # check values from generate_gsw_trusted_values trusted_gsw_d = 9713.7 # m trusted_gsw_p = 10000 # dBar trusted_gsw_lat = 30.0 calc_p = Oc.d2p_gsw(d=trusted_gsw_d, lat=trusted_gsw_lat, dyn_height=None) self.assertAlmostEqual(calc_p, trusted_gsw_p, places=1)
def test_cr2s(self): cr_ck = 1.1 t_ck = 40.0 s_ck = 38.999 s_calc = Oc.cr2s(cr=cr_ck, t=t_ck) self.assertAlmostEqual(s_calc, s_ck, places=1)
def test_d2p_backup(self): # check values from Fofonoff and Millard(1983) trusted_fof_d = 9712.653 # m trusted_fof_p = 10000 # dBar trusted_fof_lat = 30.0 calc_p = Oc.d2p_backup(d=trusted_fof_d, lat=trusted_fof_lat) self.assertAlmostEqual(calc_p, trusted_fof_p, places=1)
def test_p2d_gsw(self): # check values from generate_gsw_trusted_values trusted_gsw_d = 9713.7 # m trusted_gsw_p = 10000 # dBar trusted_gsw_lat = 30.0 calc_d = Oc.p2d_gsw(p=trusted_gsw_p, lat=trusted_gsw_lat, dyn_height=None) self.assertLess(abs(calc_d - trusted_gsw_d), 0.1)
def test_atg(self): # check values from Fofonoff and Millard(1983) atg_ck = 3.255976e-4 s_ck = 40.0 t_ck = 40 p_ck = 10000 atg_calc = Oc.atg(s=s_ck, t=t_ck, p=p_ck) self.assertAlmostEqual(atg_calc, atg_ck, places=1)
def test_d2p_gsw(self): # check values from generate_gsw_trusted_values trusted_gsw_d = 9713.7 # m trusted_gsw_p = 10000 # dBar trusted_gsw_lat = 30.0 calc_p = Oc.d2p_gsw(d=trusted_gsw_d, lat=trusted_gsw_lat, dyn_height=None) self.assertLess(abs(calc_p - trusted_gsw_p), 0.1)
def test_sal(self): # check values from Fofonoff and Millard(1983) trusted_fof_d = 9712.653 # m trusted_fof_lat = 30.0 # check values dBar from Wong and Zhu(1995), Table III trusted_fof_s = 35 # ppt trusted_fof_t = 20 # deg C trusted_fof_vs = 1687.198 # m/sec calc_s = Oc.sal(d=trusted_fof_d, speed=trusted_fof_vs, t=trusted_fof_t, lat=trusted_fof_lat) self.assertAlmostEqual(calc_s, trusted_fof_s, places=1)
def test_dyn_height_1000(self): # absolute salinity sa = np.array([34.7118, 34.8915, 35.0256, 34.8472, 34.7366, 34.7324]) # conservative temperature ct = np.array([28.8099, 28.4392, 22.7862, 10.2262, 6.8272, 4.3236]) # sea pressure p = np.array([10.0, 50.0, 125.0, 250.0, 600.0, 1000.0]) p_ref = 1000.0 # golden reference values gold_ref = np.array([17.0392, 14.6659, 10.9129, 7.5679, 3.3935, 0]) calc_out = Oc.geo_strf_dyn_height(sa=sa, ct=ct, p=p, p_ref=p_ref) for i, val in enumerate(gold_ref): self.assertAlmostEqual(calc_out[i], val, places=0)
def _write_body_abs(self, freq): # logger.debug('generating body for %d kHz' % freq) ti = self.ssp.cur.sis_thinned top_mean = 0 bottom_mean = 0 body = str() sample_sz = int(np.sum(ti)) has_skipped_salinity = False for i in range(sample_sz): if self.ssp.cur.sis.sal[ti][i] <= 0: if not has_skipped_salinity: logger.info("skipping invalid salinity values") has_skipped_salinity = True continue abs = Oc.attenuation(f=freq, t=self.ssp.cur.sis.temp[ti][i], d=self.ssp.cur.sis.depth[ti][i], s=self.ssp.cur.sis.sal[ti][i], ph=8.1) if i == 0: # first delta = (self.ssp.cur.sis.depth[ti][0] + self.ssp.cur.sis.depth[ti][1]) / 2.0 elif i == sample_sz - 1: # last delta = (self.ssp.cur.sis.depth[ti][sample_sz - 1] - (self.ssp.cur.sis.depth[ti][sample_sz - 1] + self.ssp.cur.sis.depth[ti][sample_sz - 2]) / 2.0) else: delta = ((self.ssp.cur.sis.depth[ti][i + 1] + self.ssp.cur.sis.depth[ti][i]) / 2.0 - (self.ssp.cur.sis.depth[ti][i] + self.ssp.cur.sis.depth[ti][i - 1]) / 2.0) top_mean += abs * delta bottom_mean += delta mean_abs = top_mean / bottom_mean body += "%.3f %.3f %.3f %s\n" \ % (self.ssp.cur.sis.depth[ti][i], abs, mean_abs, "999.000") self.fod.io.write(body) self.fod.io.close()
from hyo2.abc.lib.logging import set_logging ns_list = [ "hyo2.soundspeed", "hyo2.soundspeedmanager", "hyo2.soundspeedsettings" ] set_logging(ns_list=ns_list) logger = logging.getLogger(__name__) # check values from Fofonoff and Millard(1983) trusted_fof_d = 9712.653 # m trusted_fof_p = 10000 # dBar trusted_fof_lat = 30.0 # check values from generate_gsw_trusted_values trusted_gsw_d = 9713.7 # m trusted_gsw_p = 10000 # dBar trusted_gsw_lat = 30.0 calc_d = Oc.p2d_backup(p=trusted_fof_p, lat=trusted_fof_lat) logger.info("Backup: Depth: %.3f <> %.3f" % (calc_d, trusted_fof_d)) calc_d = Oc.p2d_gsw(p=trusted_gsw_p, lat=trusted_gsw_lat, dyn_height=None) logger.info("GSW: Depth: %.3f <> %.3f" % (calc_d, trusted_gsw_d)) calc_p = Oc.d2p_backup(d=calc_d, lat=trusted_fof_lat) logger.info("Backup: Pressure: %.3f <> %.3f" % (calc_p, trusted_fof_p)) calc_p = Oc.d2p_gsw(d=trusted_gsw_d, lat=trusted_gsw_lat, dyn_height=None) logger.info("GSW: Pressure: %.3f <> %.3f" % (calc_p, trusted_gsw_p))
class ConstantGradientProfileDialog(AbstractDialog): def __init__(self, main_win, lib, parent=None): AbstractDialog.__init__(self, main_win=main_win, lib=lib, parent=parent) self.oc = Oceanography() self.setWindowTitle("Constant-gradient Profile") self.setMinimumWidth(480) # outline ui self.mainLayout = QtWidgets.QVBoxLayout() self.mainLayout.setContentsMargins(3, 3, 3, 3) self.setLayout(self.mainLayout) value_validator = QtGui.QDoubleValidator(0, 12000, 2, self) self.mainLayout.addStretch() # start point hbox = QtWidgets.QHBoxLayout() self.mainLayout.addLayout(hbox) hbox.addStretch() # depth label self.start_depth_label = QtWidgets.QLabel("Start [m]:") self.start_depth_label.setFixedWidth(50) self.start_depth_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_depth_label) # depth value self.start_depth_value = QtWidgets.QLineEdit() self.start_depth_value.setFixedWidth(50) self.start_depth_value.setValidator(value_validator) self.start_depth_value.setText("0.0") # noinspection PyUnresolvedReferences self.start_depth_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_depth_value) # temp label self.start_temp_label = QtWidgets.QLabel("temp [°C]:") self.start_temp_label.setFixedWidth(70) self.start_temp_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_temp_label) # temp value self.start_temp_value = QtWidgets.QLineEdit() self.start_temp_value.setFixedWidth(50) self.start_temp_value.setValidator(value_validator) self.start_temp_value.setText("12.94") # noinspection PyUnresolvedReferences self.start_temp_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_temp_value) # sal label self.start_sal_label = QtWidgets.QLabel("sal [PSU]:") self.start_sal_label.setFixedWidth(70) self.start_sal_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_sal_label) # sal value self.start_sal_value = QtWidgets.QLineEdit() self.start_sal_value.setFixedWidth(50) self.start_sal_value.setValidator(value_validator) self.start_sal_value.setText("35.0") # noinspection PyUnresolvedReferences self.start_sal_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_sal_value) # speed label self.start_speed_label = QtWidgets.QLabel("speed [m/s]:") self.start_speed_label.setFixedWidth(70) self.start_speed_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_speed_label) # speed value self.start_speed_value = QtWidgets.QLineEdit() self.start_speed_value.setFixedWidth(70) self.start_speed_value.setValidator(value_validator) self.start_speed_value.setText("1500.0") self.start_speed_value.setDisabled(True) # noinspection PyUnresolvedReferences self.start_speed_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_speed_value) hbox.addStretch() # end point hbox = QtWidgets.QHBoxLayout() self.mainLayout.addLayout(hbox) hbox.addStretch() # depth label self.end_depth_label = QtWidgets.QLabel("End [m]:") self.end_depth_label.setFixedWidth(50) self.end_depth_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_depth_label) # depth value self.end_depth_value = QtWidgets.QLineEdit() self.end_depth_value.setFixedWidth(50) self.end_depth_value.setValidator(value_validator) self.end_depth_value.setText("1000.0") # noinspection PyUnresolvedReferences self.end_depth_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_depth_value) # temp label self.end_temp_label = QtWidgets.QLabel("temp [°C]:") self.end_temp_label.setFixedWidth(70) self.end_temp_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_temp_label) # temp value self.end_temp_value = QtWidgets.QLineEdit() self.end_temp_value.setFixedWidth(50) self.end_temp_value.setValidator(value_validator) self.end_temp_value.setText("12.94") # noinspection PyUnresolvedReferences self.end_temp_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_temp_value) # sal label self.end_sal_label = QtWidgets.QLabel("sal [PSU]:") self.end_sal_label.setFixedWidth(70) self.end_sal_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_sal_label) # sal value self.end_sal_value = QtWidgets.QLineEdit() self.end_sal_value.setFixedWidth(50) self.end_sal_value.setValidator(value_validator) self.end_sal_value.setText("35.0") # noinspection PyUnresolvedReferences self.end_sal_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_sal_value) # speed label self.end_speed_label = QtWidgets.QLabel("speed [m/s]:") self.end_speed_label.setFixedWidth(70) self.end_speed_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_speed_label) # speed value self.end_speed_value = QtWidgets.QLineEdit() self.end_speed_value.setFixedWidth(70) self.end_speed_value.setValidator(value_validator) self.end_speed_value.setText("1500.0") self.end_speed_value.setDisabled(True) # noinspection PyUnresolvedReferences self.end_speed_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_speed_value) hbox.addStretch() self.mainLayout.addSpacing(12) # apply hbox = QtWidgets.QHBoxLayout() hbox.addStretch() self.apply = QtWidgets.QPushButton("Apply") # self.apply.setFixedHeight(30) # noinspection PyUnresolvedReferences self.apply.clicked.connect(self.on_apply) hbox.addWidget(self.apply) hbox.addStretch() self.mainLayout.addLayout(hbox) self.mainLayout.addStretch() self.on_values_changed() def on_values_changed(self): logger.debug("values changed") try: start_speed = self.oc.speed(d=float(self.start_depth_value.text()), t=float(self.start_temp_value.text()), s=float(self.start_sal_value.text())) self.start_speed_value.setText("%.2f" % start_speed) except Exception as e: logger.warning("Invalid start sound speed calculation: %s" % e) try: end_speed = self.oc.speed(d=float(self.end_depth_value.text()), t=float(self.end_temp_value.text()), s=float(self.end_sal_value.text())) self.end_speed_value.setText("%.2f" % end_speed) except Exception as e: logger.warning("Invalid end sound speed calculation: %s" % e) def on_apply(self): logger.debug("apply") try: start_depth = float(self.start_depth_value.text()) end_depth = float(self.end_depth_value.text()) if start_depth < 0: raise RuntimeError("Negative start depth: %s" % start_depth) if start_depth > end_depth: raise RuntimeError("Start depth > end depth: %s > %s" % (start_depth, end_depth)) except Exception as e: msg = "Issue in depth fields!\n\n%s" % e # noinspection PyCallByClass,PyArgumentList QtWidgets.QMessageBox.warning(self, "Constant-gradient profile", msg, QtWidgets.QMessageBox.Ok) return try: start_temp = float(self.start_temp_value.text()) end_temp = float(self.end_temp_value.text()) if start_temp < -1.0: raise RuntimeError("Too negative start temp: %s" % start_temp) except Exception as e: msg = "Issue in temperature fields!\n\n%s" % e # noinspection PyCallByClass,PyArgumentList QtWidgets.QMessageBox.warning(self, "Constant-gradient profile", msg, QtWidgets.QMessageBox.Ok) return try: start_sal = float(self.start_sal_value.text()) end_sal = float(self.end_sal_value.text()) if start_sal < 0: raise RuntimeError("Negative start sal: %s" % start_sal) except Exception as e: msg = "Issue in salinity fields!\n\n%s" % e # noinspection PyCallByClass,PyArgumentList QtWidgets.QMessageBox.warning(self, "Constant-gradient profile", msg, QtWidgets.QMessageBox.Ok) return try: start_speed = float(self.start_speed_value.text()) end_speed = float(self.end_speed_value.text()) if start_speed < 0: raise RuntimeError("Negative start speed: %s" % start_speed) except Exception as e: msg = "Issue in speed fields!\n\n%s" % e # noinspection PyCallByClass,PyArgumentList QtWidgets.QMessageBox.warning(self, "Constant-gradient profile", msg, QtWidgets.QMessageBox.Ok) return try: self.lib.create_profile(start_depth=start_depth, start_temp=start_temp, start_sal=start_sal, start_speed=start_speed, end_depth=end_depth, end_temp=end_temp, end_sal=end_sal, end_speed=end_speed) except RuntimeError as e: traceback.print_exc() msg = "Issue in creating the profile:\n\n> %s" % e # noinspection PyCallByClass,PyArgumentList QtWidgets.QMessageBox.critical(self, "Creation error", msg, QtWidgets.QMessageBox.Ok) return self.accept()
def __init__(self, main_win, lib, parent=None): AbstractDialog.__init__(self, main_win=main_win, lib=lib, parent=parent) self.oc = Oceanography() self.setWindowTitle("Constant-gradient Profile") self.setMinimumWidth(480) # outline ui self.mainLayout = QtWidgets.QVBoxLayout() self.mainLayout.setContentsMargins(3, 3, 3, 3) self.setLayout(self.mainLayout) value_validator = QtGui.QDoubleValidator(0, 12000, 2, self) self.mainLayout.addStretch() # start point hbox = QtWidgets.QHBoxLayout() self.mainLayout.addLayout(hbox) hbox.addStretch() # depth label self.start_depth_label = QtWidgets.QLabel("Start [m]:") self.start_depth_label.setFixedWidth(50) self.start_depth_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_depth_label) # depth value self.start_depth_value = QtWidgets.QLineEdit() self.start_depth_value.setFixedWidth(50) self.start_depth_value.setValidator(value_validator) self.start_depth_value.setText("0.0") # noinspection PyUnresolvedReferences self.start_depth_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_depth_value) # temp label self.start_temp_label = QtWidgets.QLabel("temp [°C]:") self.start_temp_label.setFixedWidth(70) self.start_temp_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_temp_label) # temp value self.start_temp_value = QtWidgets.QLineEdit() self.start_temp_value.setFixedWidth(50) self.start_temp_value.setValidator(value_validator) self.start_temp_value.setText("12.94") # noinspection PyUnresolvedReferences self.start_temp_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_temp_value) # sal label self.start_sal_label = QtWidgets.QLabel("sal [PSU]:") self.start_sal_label.setFixedWidth(70) self.start_sal_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_sal_label) # sal value self.start_sal_value = QtWidgets.QLineEdit() self.start_sal_value.setFixedWidth(50) self.start_sal_value.setValidator(value_validator) self.start_sal_value.setText("35.0") # noinspection PyUnresolvedReferences self.start_sal_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_sal_value) # speed label self.start_speed_label = QtWidgets.QLabel("speed [m/s]:") self.start_speed_label.setFixedWidth(70) self.start_speed_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.start_speed_label) # speed value self.start_speed_value = QtWidgets.QLineEdit() self.start_speed_value.setFixedWidth(70) self.start_speed_value.setValidator(value_validator) self.start_speed_value.setText("1500.0") self.start_speed_value.setDisabled(True) # noinspection PyUnresolvedReferences self.start_speed_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.start_speed_value) hbox.addStretch() # end point hbox = QtWidgets.QHBoxLayout() self.mainLayout.addLayout(hbox) hbox.addStretch() # depth label self.end_depth_label = QtWidgets.QLabel("End [m]:") self.end_depth_label.setFixedWidth(50) self.end_depth_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_depth_label) # depth value self.end_depth_value = QtWidgets.QLineEdit() self.end_depth_value.setFixedWidth(50) self.end_depth_value.setValidator(value_validator) self.end_depth_value.setText("1000.0") # noinspection PyUnresolvedReferences self.end_depth_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_depth_value) # temp label self.end_temp_label = QtWidgets.QLabel("temp [°C]:") self.end_temp_label.setFixedWidth(70) self.end_temp_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_temp_label) # temp value self.end_temp_value = QtWidgets.QLineEdit() self.end_temp_value.setFixedWidth(50) self.end_temp_value.setValidator(value_validator) self.end_temp_value.setText("12.94") # noinspection PyUnresolvedReferences self.end_temp_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_temp_value) # sal label self.end_sal_label = QtWidgets.QLabel("sal [PSU]:") self.end_sal_label.setFixedWidth(70) self.end_sal_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_sal_label) # sal value self.end_sal_value = QtWidgets.QLineEdit() self.end_sal_value.setFixedWidth(50) self.end_sal_value.setValidator(value_validator) self.end_sal_value.setText("35.0") # noinspection PyUnresolvedReferences self.end_sal_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_sal_value) # speed label self.end_speed_label = QtWidgets.QLabel("speed [m/s]:") self.end_speed_label.setFixedWidth(70) self.end_speed_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) hbox.addWidget(self.end_speed_label) # speed value self.end_speed_value = QtWidgets.QLineEdit() self.end_speed_value.setFixedWidth(70) self.end_speed_value.setValidator(value_validator) self.end_speed_value.setText("1500.0") self.end_speed_value.setDisabled(True) # noinspection PyUnresolvedReferences self.end_speed_value.textEdited.connect(self.on_values_changed) hbox.addWidget(self.end_speed_value) hbox.addStretch() self.mainLayout.addSpacing(12) # apply hbox = QtWidgets.QHBoxLayout() hbox.addStretch() self.apply = QtWidgets.QPushButton("Apply") # self.apply.setFixedHeight(30) # noinspection PyUnresolvedReferences self.apply.clicked.connect(self.on_apply) hbox.addWidget(self.apply) hbox.addStretch() self.mainLayout.addLayout(hbox) self.mainLayout.addStretch() self.on_values_changed()
def query(self, lat: Optional[float], lon: Optional[float], datestamp: Union[date, dt, None] = None, server_mode: bool = False): """Query RTOFS for passed location and timestamp""" if datestamp is None: datestamp = dt.utcnow() if isinstance(datestamp, dt): datestamp = datestamp.date() if not isinstance(datestamp, date): raise RuntimeError("invalid date passed: %s" % type(datestamp)) logger.debug("query: %s @ (%.6f, %.6f)" % (datestamp, lon, lat)) # check the inputs if (lat is None) or (lon is None) or (datestamp is None): logger.error("invalid query: %s @ (%s, %s)" % (datestamp.strftime("%Y%m%d"), lon, lat)) return None try: lat_idx, lon_idx = self.grid_coords(lat, lon, datestamp=datestamp, server_mode=server_mode) if lat_idx is None: logger.info("location outside of GoMOFS coverage") return None except TypeError as e: logger.critical("while converting location to grid coords, %s" % e) return None logger.debug("idx > lat: %s, lon: %s" % (lat_idx, lon_idx)) lat_s_idx = lat_idx - self._search_half_window if lat_s_idx < 0: lat_s_idx = 0 lat_n_idx = lat_idx + self._search_half_window if lat_n_idx >= self._lat.shape[0]: lat_n_idx = self._lat.shape[0] - 1 lon_w_idx = lon_idx - self._search_half_window if lon_w_idx < 0: lon_w_idx = 0 lon_e_idx = lon_idx + self._search_half_window if lon_e_idx >= self._lon.shape[1]: lon_e_idx = self._lon.shape[1] - 1 # logger.info("indices -> %s %s %s %s" % (lat_s_idx, lat_n_idx, lon_w_idx, lon_e_idx)) lat_search_window = lat_n_idx - lat_s_idx + 1 lon_search_window = lon_e_idx - lon_w_idx + 1 logger.info("updated search window: (%s, %s)" % (lat_search_window, lon_search_window)) # Need +1 on the north and east indices since it is the "stop" value in these slices t = self._file.variables['temp'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] s = self._file.variables['salt'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] # Set 'unfilled' elements to NANs (BUT when the entire array has valid data, it returns numpy.ndarray) if isinstance(t, np.ma.core.MaskedArray): t_mask = t.mask t._sharedmask = False t[t_mask] = np.nan if isinstance(s, np.ma.core.MaskedArray): s_mask = s.mask s._sharedmask = False s[s_mask] = np.nan # Calculate distances from requested position to each of the grid node locations distances = np.zeros( (self._d.size, lon_search_window, lat_search_window)) longitudes = self._lon[lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] latitudes = self._lat[lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] for i in range(lat_search_window): for j in range(lon_search_window): dist = self.g.distance(longitudes[i, j], latitudes[i, j], lon, lat) distances[:, i, j] = dist # logger.info("node (%s %s), pos: %3.2f, %3.2f, dist: %3.1f" # % (i, j, latitudes[i, j], longitudes[i, j], distances[0, i, j])) # Get mask of "no data" elements and replace these with NaNs in distance array t_mask = np.isnan(t) distances[t_mask] = np.nan s_mask = np.isnan(s) distances[s_mask] = np.nan # logger.info("distance array:\n%s" % distances[0]) # Spin through all the depth levels temp_pot = np.zeros(self._d.size) temp_in_situ = np.zeros(self._d.size) d = np.zeros(self._d.size) sal = np.zeros(self._d.size) num_values = 0 for i in range(self._d.size): t_level = t[i] s_level = s[i] d_level = distances[i] try: ind = np.nanargmin(d_level) except ValueError: # logger.info("%s: all-NaN slices" % i) continue if np.isnan(ind): logger.info("%s: bottom of valid data" % i) break ind2 = np.unravel_index(ind, t_level.shape) t_closest = t_level[ind2] s_closest = s_level[ind2] # d_closest = d_level[ind2] temp_pot[i] = t_closest sal[i] = s_closest d[i] = self._d[i] # Calculate in-situ temperature p = Oc.d2p(d[i], lat) temp_in_situ[i] = Oc.in_situ_temp(s=sal[i], t=t_closest, p=p, pr=self._ref_p) # logger.info("%02d: %6.1f %6.1f > T/S/Dist: %3.1f %3.1f %3.1f [pot.temp. %3.1f]" # % (i, d[i], p, temp_in_situ[i], s_closest, d_closest, t_closest)) num_values += 1 if num_values == 0: logger.info("no data from lookup!") return None # ind = np.nanargmin(distances[0]) # ind2 = np.unravel_index(ind, distances[0].shape) # switching to the query location # lat_out = latitudes[ind2] # lon_out = longitudes[ind2] # while lon_out > 180.0: # lon_out -= 360.0 # Make a new SV object to return our query in ssp = Profile() ssp.meta.sensor_type = Dicts.sensor_types['Synthetic'] ssp.meta.probe_type = Dicts.probe_types['GoMOFS'] ssp.meta.latitude = lat if lon > 180.0: # Go back to negative longitude lon -= 360.0 ssp.meta.longitude = lon ssp.meta.utc_time = dt(year=datestamp.year, month=datestamp.month, day=datestamp.day) ssp.meta.original_path = "GoMOFS_%s" % datestamp.strftime("%Y%m%d") ssp.init_data(num_values) ssp.data.depth = d[0:num_values] ssp.data.temp = temp_in_situ[0:num_values] ssp.data.sal = sal[0:num_values] ssp.calc_data_speed() ssp.clone_data_to_proc() ssp.init_sis() profiles = ProfileList() profiles.append_profile(ssp) return profiles
def query(self, lat: Optional[float], lon: Optional[float], dtstamp: Optional[dt] = None, server_mode: bool = False): """Query RTOFS for passed location and timestamp""" if dtstamp is None: dtstamp = dt.utcnow() if not isinstance(dtstamp, dt): raise RuntimeError("invalid datetime passed: %s" % type(dtstamp)) logger.debug("query: %s @ (%.6f, %.6f)" % (dtstamp, lon, lat)) # check the inputs if (lat is None) or (lon is None): logger.error("invalid query: %s @ (%s, %s)" % (dtstamp.strftime("%Y/%m/%d %H:%M:%S"), lon, lat)) return None try: lat_idx, lon_idx = self.grid_coords(lat, lon, dtstamp=dtstamp, server_mode=server_mode) except TypeError as e: logger.critical("while converting location to grid coords, %s" % e) return None # logger.debug("idx > lat: %s, lon: %s" % (lat_idx, lon_idx)) lat_s_idx = lat_idx - self._search_half_window lat_n_idx = lat_idx + self._search_half_window lon_w_idx = lon_idx - self._search_half_window lon_e_idx = lon_idx + self._search_half_window # logger.info("indices -> %s %s %s %s" % (lat_s_idx, lat_n_idx, lon_w_idx, lon_e_idx)) if lon < self._lon_0: # Make all longitudes safe lon += 360.0 longitudes = np.zeros((self._search_window, self._search_window)) if (lon_e_idx < self._lon.size) and (lon_w_idx >= 0): # logger.info("safe case") # Need +1 on the north and east indices since it is the "stop" value in these slices t = self._file_temp.variables['temperature'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] s = self._file_sal.variables['salinity'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] # Set 'unfilled' elements to NANs (BUT when the entire array has valid data, it returns numpy.ndarray) if isinstance(t, np.ma.core.MaskedArray): t_mask = t.mask t._sharedmask = False t[t_mask] = np.nan if isinstance(s, np.ma.core.MaskedArray): s_mask = s.mask s._sharedmask = False s[s_mask] = np.nan lons = self._lon[lon_w_idx:lon_e_idx + 1] for i in range(self._search_window): longitudes[i, :] = lons else: logger.info("split case") # --- Do the left portion of the array first, this will run into the wrap longitude lon_e_idx = self._lon.size - 1 # lon_west_index can be negative if lon_index is on the westernmost end of the array if lon_w_idx < 0: lon_w_idx = lon_w_idx + self._lon.size # logger.info("using lon west/east indices -> %s %s" % (lon_w_idx, lon_e_idx)) # Need +1 on the north and east indices since it is the "stop" value in these slices t_left = self._file_temp.variables['temperature'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] s_left = self._file_sal.variables['salinity'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] # Set 'unfilled' elements to NANs (BUT when the entire array has valid data, it returns numpy.ndarray) if isinstance(t_left, np.ma.core.MaskedArray): t_mask = t_left.mask t_left[t_mask] = np.nan if isinstance(s_left, np.ma.core.MaskedArray): s_mask = s_left.mask s_left[s_mask] = np.nan lons_left = self._lon[lon_w_idx:lon_e_idx + 1] for i in range(self._search_window): longitudes[i, 0:lons_left.size] = lons_left # logger.info("longitudes are now: %s" % longitudes) # --- Do the right portion of the array first, this will run into the wrap # longitude so limit it accordingly lon_w_idx = 0 lon_e_idx = self._search_window - lons_left.size - 1 # Need +1 on the north and east indices since it is the "stop" value in these slices t_right = self._file_temp.variables['temperature'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] s_right = self._file_sal.variables['salinity'][self._day_idx, :, lat_s_idx:lat_n_idx + 1, lon_w_idx:lon_e_idx + 1] # Set 'unfilled' elements to NANs (BUT when the entire array has valid data, it returns numpy.ndarray) if isinstance(t_right, np.ma.core.MaskedArray): t_mask = t_right.mask t_right[t_mask] = np.nan if isinstance(s_right, np.ma.core.MaskedArray): s_mask = s_right.mask s_right[s_mask] = np.nan lons_right = self._lon[lon_w_idx:lon_e_idx + 1] for i in range(self._search_window): longitudes[i, lons_left.size:self._search_window] = lons_right # merge data t = np.zeros((self._file_temp.variables['lev'].size, self._search_window, self._search_window)) t[:, :, 0:lons_left.size] = t_left t[:, :, lons_left.size:self._search_window] = t_right s = np.zeros((self._file_temp.variables['lev'].size, self._search_window, self._search_window)) s[:, :, 0:lons_left.size] = s_left s[:, :, lons_left.size:self._search_window] = s_right # Calculate distances from requested position to each of the grid node locations distances = np.zeros((self._d.size, self._search_window, self._search_window)) latitudes = np.zeros((self._search_window, self._search_window)) lats = self._lat[lat_s_idx:lat_n_idx + 1] for i in range(self._search_window): latitudes[:, i] = lats for i in range(self._search_window): for j in range(self._search_window): dist = self.g.distance(longitudes[i, j], latitudes[i, j], lon, lat) distances[:, i, j] = dist # logger.info("node %s, pos: %3.1f, %3.1f, dist: %3.1f" # % (i, latitudes[i, j], longitudes[i, j], distances[0, i, j])) # logger.info("distance array:\n%s" % distances[0]) # Get mask of "no data" elements and replace these with NaNs in distance array t_mask = np.isnan(t) distances[t_mask] = np.nan s_mask = np.isnan(s) distances[s_mask] = np.nan # Spin through all the depth levels temp_pot = np.zeros(self._d.size) temp_in_situ = np.zeros(self._d.size) d = np.zeros(self._d.size) sal = np.zeros(self._d.size) num_values = 0 for i in range(self._d.size): t_level = t[i] s_level = s[i] d_level = distances[i] try: ind = np.nanargmin(d_level) except ValueError: # logger.info("%s: all-NaN slices" % i) continue if np.isnan(ind): logger.info("%s: bottom of valid data" % i) break ind2 = np.unravel_index(ind, t_level.shape) t_closest = t_level[ind2] s_closest = s_level[ind2] # d_closest = d_level[ind2] temp_pot[i] = t_closest sal[i] = s_closest d[i] = self._d[i] # Calculate in-situ temperature p = Oc.d2p(d[i], lat) temp_in_situ[i] = Oc.in_situ_temp(s=sal[i], t=t_closest, p=p, pr=self._ref_p) # logger.info("%02d: %6.1f %6.1f > T/S/Dist: %3.1f %3.1f %3.1f [pot.temp. %3.1f]" # % (i, d[i], p, temp_in_situ[i], s_closest, d_closest, t_closest)) num_values += 1 if num_values == 0: logger.info("no data from lookup!") return None # ind = np.nanargmin(distances[0]) # ind2 = np.unravel_index(ind, distances[0].shape) # switching to the query location # lat_out = latitudes[ind2] # lon_out = longitudes[ind2] # while lon_out > 180.0: # lon_out -= 360.0 # Make a new SV object to return our query in ssp = Profile() ssp.meta.sensor_type = Dicts.sensor_types['Synthetic'] ssp.meta.probe_type = Dicts.probe_types['RTOFS'] ssp.meta.latitude = lat if lon > 180.0: # Go back to negative longitude lon -= 360.0 ssp.meta.longitude = lon ssp.meta.utc_time = dt(year=dtstamp.year, month=dtstamp.month, day=dtstamp.day, hour=dtstamp.hour, minute=dtstamp.minute, second=dtstamp.second) ssp.meta.original_path = "RTOFS_%s" % dtstamp.strftime("%Y%m%d_%H%M%S") ssp.init_data(num_values) ssp.data.depth = d[0:num_values] ssp.data.temp = temp_in_situ[0:num_values] ssp.data.sal = sal[0:num_values] ssp.calc_data_speed() ssp.clone_data_to_proc() ssp.init_sis() profiles = ProfileList() profiles.append_profile(ssp) return profiles
import logging from hyo2.soundspeed.profile.oceanography import Oceanography as Oc from hyo2.abc.lib.logging import set_logging ns_list = ["hyo2.soundspeed", "hyo2.soundspeedmanager", "hyo2.soundspeedsettings"] set_logging(ns_list=ns_list) logger = logging.getLogger(__name__) # check values from Fofonoff and Millard(1983) atg_ck = 3.255976e-4 s_ck = 40.0 t_ck = 40 p_ck = 10000 atg_calc = Oc.atg(s=s_ck, t=t_ck, p=p_ck) logger.info("Adiabatic Temperature Gradient: %g <> %g" % (atg_calc, atg_ck)) theta_ck = 36.89073 s_ck = 40 t0_ck = 40 p0_ck = 10000 p_ref_ck = 0 theta_calc = Oc.pot_temp(s=s_ck, t=t0_ck, p=p0_ck, pr=p_ref_ck) logger.info("Theta: %g <> %g" % (theta_calc, theta_ck)) t0_calc = Oc.in_situ_temp(s=s_ck, t=theta_ck, p=p0_ck, pr=p_ref_ck) logger.info("Temp: %.3f <> %.3f" % (t0_calc, t0_ck)) cr_ck = 1.1 t_ck = 40.0 s_ck = 38.999
# gold ref using the matlab script: generate_gsw_trusted_values.m and GSW 3.05 # - @ 1000m # absolute salinity sa = np.array([34.7118, 34.8915, 35.0256, 34.8472, 34.7366, 34.7324]) # conservative temperature ct = np.array([28.8099, 28.4392, 22.7862, 10.2262, 6.8272, 4.3236]) # sea pressure p = np.array([10.0, 50.0, 125.0, 250.0, 600.0, 1000.0]) p_ref = 1000.0 # golden reference values gold_ref = np.array([17.0392, 14.6659, 10.9129, 7.5679, 3.3935, 0]) calc_out = Oc.geo_strf_dyn_height(sa=sa, ct=ct, p=p, p_ref=p_ref) print("@1000") print("gold: %s" % gold_ref) print("calc: %s" % calc_out) print("diff: %s" % (calc_out - gold_ref)) # - @ 500m # absolute salinity sa = np.array([34.7118, 34.8915, 35.0256, 34.8472, 34.7366, 34.7324]) # conservative temperature ct = np.array([28.8099, 28.4392, 22.7862, 10.2262, 6.8272, 4.3236]) # sea pressure p = np.array([10.0, 50.0, 125.0, 250.0, 600.0, 1000.0]) p_ref = 500.0 # golden reference values
from hyo2.soundspeed.profile.oceanography import Oceanography as Oc from hyo2.abc.lib.logging import set_logging ns_list = [ "hyo2.soundspeed", "hyo2.soundspeedmanager", "hyo2.soundspeedsettings" ] set_logging(ns_list=ns_list) logger = logging.getLogger(__name__) # check values from Fofonoff and Millard(1983) trusted_fof_d = 9712.653 # m trusted_fof_lat = 30.0 # check values dBar from Wong and Zhu(1995), Table III trusted_fof_s = 35 # ppt trusted_fof_t = 20 # deg C trusted_fof_vs = 1687.198 # m/sec calc_vs = Oc.speed(d=trusted_fof_d, t=trusted_fof_t, s=trusted_fof_s, lat=trusted_fof_lat) logger.info("Speed: %.3f <> %.3f" % (calc_vs, trusted_fof_vs)) calc_s = Oc.sal(d=trusted_fof_d, speed=calc_vs, t=trusted_fof_t, lat=trusted_fof_lat) logger.info("Salinity: %.3f <> %.3f" % (calc_s, trusted_fof_s))