Esempio n. 1
0
    def setUp(self):
        def add_cast(lat, lon):
            self.lib.ssp.cur.meta.latitude = lat
            self.lib.ssp.cur.meta.longitude = lon
            self.lib.ssp.cur.meta.utc_time = datetime.now()
            self.lib.ssp.cur.init_data(self.levels)
            self.lib.ssp.cur.data.depth[:self.levels] = self.depth
            self.lib.ssp.cur.data.speed[:self.levels] = 1415
            self.lib.restart_proc()
            self.lib.store_data()

        self.max_pk = 5
        self.levels = self.max_pk * self.max_pk
        self.depth = np.array(range(self.levels))
        projects_folder = os.path.abspath(os.curdir)
        project_name = 'unittest'
        db_name = '%s.db' % project_name
        self.db_path = os.path.join(projects_folder, db_name)
        self.tearDown()
        self.lib = SoundSpeedLibrary()
        self.lib.projects_folder = projects_folder
        self.lib.current_project = project_name
        self.lib.ssp = ProfileList()
        self.lib.ssp.append()
        if len(self.lib.db_list_profiles()) < self.max_pk:
            for i in range(self.max_pk):
                add_cast(20 + i, -75)
Esempio n. 2
0
 def init_data(self) -> None:
     """Create a new empty profile list"""
     self._ssp = ProfileList()
Esempio n. 3
0
    def profile_by_pk(self, pk):
        if not self.conn:
            logger.error("missing db connection")
            return None

        # logger.info("retrieve profile with pk: %s" % pk)

        ssp = ProfileList()
        ssp.append()

        with self.conn:
            try:
                # ssp spatial timestamp
                # noinspection SqlResolve
                ssp_idx = self.conn.execute("SELECT * FROM ssp_pk WHERE id=?",
                                            (pk, )).fetchone()

                ssp.cur.meta.utc_time = ssp_idx['cast_datetime']
                ssp.cur.meta.longitude = ssp_idx['cast_position'].x
                ssp.cur.meta.latitude = ssp_idx['cast_position'].y

            except sqlite3.Error as e:
                logger.error("spatial timestamp for %s pk > %s: %s" %
                             (pk, type(e), e))
                return None

            try:
                # ssp metadata
                # noinspection SqlResolve
                ssp_meta = self.conn.execute("SELECT * FROM ssp WHERE pk=?",
                                             (pk, )).fetchone()

                # special handling in case of unknown future sensor type
                ssp.cur.meta.sensor_type = ssp_meta['sensor_type']
                if ssp.cur.meta.sensor_type not in Dicts.sensor_types.values():
                    ssp.cur.meta.sensor_type = Dicts.sensor_types['Future']

                # special handling in case of unknown future probe type
                ssp.cur.meta.probe_type = ssp_meta['probe_type']
                if ssp.cur.meta.probe_type not in Dicts.probe_types.values():
                    ssp.cur.meta.probe_type = Dicts.probe_types['Future']

                ssp.cur.meta.original_path = ssp_meta['original_path']
                ssp.cur.meta.institution = ssp_meta['institution']
                ssp.cur.meta.survey = ssp_meta['survey']
                ssp.cur.meta.vessel = ssp_meta['vessel']
                ssp.cur.meta.sn = ssp_meta['sn']
                ssp.cur.meta.proc_time = ssp_meta['proc_time']
                ssp.cur.meta.proc_info = ssp_meta['proc_info']
                ssp.cur.meta.comments = ssp_meta['comments']
                ssp.cur.meta.surveylines = ssp_meta['surveylines']

                ssp.cur.meta.pressure_uom = ssp_meta['pressure_uom']
                ssp.cur.meta.depth_uom = ssp_meta['depth_uom']
                ssp.cur.meta.speed_uom = ssp_meta['speed_uom']
                ssp.cur.meta.temperature_uom = ssp_meta['temperature_uom']
                ssp.cur.meta.conductivity_uom = ssp_meta['conductivity_uom']
                ssp.cur.meta.salinity_uom = ssp_meta['salinity_uom']

            except sqlite3.Error as e:
                logger.error("ssp meta for %s pk > %s: %s" % (pk, type(e), e))
                return None

            # raw data
            try:
                # noinspection SqlResolve
                ssp_samples = self.conn.execute(
                    "SELECT * FROM data WHERE ssp_pk=?", (pk, )).fetchall()
                num_samples = len(ssp_samples)
                ssp.cur.init_data(num_samples)
                # logger.debug("raw data samples: %s" % num_samples)
                for i in range(num_samples):
                    # print(ssp_samples[i])

                    ssp.cur.data.pressure[i] = ssp_samples[i]['pressure']
                    ssp.cur.data.depth[i] = ssp_samples[i]['depth']
                    ssp.cur.data.speed[i] = ssp_samples[i]['speed']
                    ssp.cur.data.temp[i] = ssp_samples[i]['temperature']
                    ssp.cur.data.conductivity[i] = ssp_samples[i][
                        'conductivity']
                    ssp.cur.data.sal[i] = ssp_samples[i]['salinity']
                    ssp.cur.data.source[i] = ssp_samples[i]['source']
                    ssp.cur.data.flag[i] = ssp_samples[i]['flag']

            except sqlite3.Error as e:
                logger.error("reading raw samples for %s pk, %s: %s" %
                             (pk, type(e), e))
                return None

            # proc data
            try:
                # noinspection SqlResolve
                ssp_samples = self.conn.execute(
                    "SELECT * FROM proc WHERE ssp_pk=?", (pk, )).fetchall()
                num_samples = len(ssp_samples)
                ssp.cur.init_proc(num_samples)
                # logger.debug("proc data samples: %s" % num_samples)
                for i in range(num_samples):
                    # print(ssp_samples[i])
                    ssp.cur.proc.pressure[i] = ssp_samples[i]['pressure']
                    ssp.cur.proc.depth[i] = ssp_samples[i]['depth']
                    ssp.cur.proc.speed[i] = ssp_samples[i]['speed']
                    ssp.cur.proc.temp[i] = ssp_samples[i]['temperature']
                    ssp.cur.proc.conductivity[i] = ssp_samples[i][
                        'conductivity']
                    ssp.cur.proc.sal[i] = ssp_samples[i]['salinity']
                    ssp.cur.proc.source[i] = ssp_samples[i]['source']
                    ssp.cur.proc.flag[i] = ssp_samples[i]['flag']

            except sqlite3.Error as e:
                logger.error("reading raw samples for %s pk, %s: %s" %
                             (pk, type(e), e))
                return None

            # sis data
            try:
                # noinspection SqlResolve
                ssp_samples = self.conn.execute(
                    "SELECT * FROM sis WHERE ssp_pk=?", (pk, )).fetchall()
                num_samples = len(ssp_samples)
                ssp.cur.init_sis(num_samples)
                # logger.debug("sis data samples: %s" % num_samples)
                for i in range(num_samples):
                    # print(ssp_samples[i])
                    ssp.cur.sis.pressure[i] = ssp_samples[i]['depth']
                    ssp.cur.sis.depth[i] = ssp_samples[i]['depth']
                    ssp.cur.sis.speed[i] = ssp_samples[i]['speed']
                    ssp.cur.sis.temp[i] = ssp_samples[i]['temperature']
                    ssp.cur.sis.conductivity[i] = ssp_samples[i][
                        'conductivity']
                    ssp.cur.sis.sal[i] = ssp_samples[i]['salinity']
                    ssp.cur.sis.source[i] = ssp_samples[i]['source']
                    ssp.cur.sis.flag[i] = ssp_samples[i]['flag']

            except sqlite3.Error as e:
                logger.error("reading sis samples for %s pk, %s: %s" %
                             (pk, type(e), e))
                return None

        # This is the only way for the library to load a profile from the project database
        ssp.loaded_from_db = True

        return ssp
    ssp.proc.speed = vs
    ssp.proc.temp = t
    ssp.proc.sal = s
    ssp.proc.flag[40:50] = Dicts.flags['user']
    ssp.proc.flag[50:70] = Dicts.flags['filtered']
    ssp.meta.latitude = 43.13555
    ssp.meta.longitude = -70.9395
    ssp.meta.utc_time = datetime.utcnow()
    return ssp


tss_depth = 5.0
tss_value = 1500.0
avg_depth = 1000.0
half_swath_angle = 70.0
ssp = make_fake_ssp(bias=0.0)
ssp_list = ProfileList()
ssp_list.append_profile(ssp)

start_time = datetime.now()
profile = TracedProfile(tss_depth=tss_depth,
                        tss_value=tss_value,
                        avg_depth=avg_depth,
                        half_swath=half_swath_angle,
                        ssp=ssp_list.cur)
end_time = datetime.now()
logger.debug("timing: %s" % (end_time - start_time))

logger.debug("traced profile:\n%s" % profile)
# logger.debug("api:\n%s" % dir(profile))
    ssp.init_proc(d.size)
    ssp.proc.depth = d
    ssp.proc.speed = vs
    ssp.proc.temp = t
    ssp.proc.sal = s
    ssp.meta.latitude = 43.13555
    ssp.meta.longitude = -70.9395
    ssp.meta.utc_time = datetime.utcnow()
    return ssp


avg_depth = 10000.0  # just a very deep value
half_swath_angle = 70.0  # a safely large angle

ssp1 = make_fake_ssp(ss_bias=0.0, fixed=False)
ssp1_list = ProfileList()
ssp1_list.append_profile(ssp1)
tp1 = TracedProfile(ssp=ssp1_list.cur,
                    avg_depth=avg_depth,
                    half_swath=half_swath_angle)
# with open("tp1.txt", "w") as fod:
#     fod.write(tp1.str_rays())

ssp2 = make_fake_ssp(ss_bias=50.0, d_bias=5, fixed=False)
ssp2_list = ProfileList()
ssp2_list.append_profile(ssp2)
tp2 = TracedProfile(ssp=ssp2_list.cur,
                    avg_depth=avg_depth,
                    half_swath=half_swath_angle)
# with open("tp2.txt", "w") as fod:
#     fod.write(tp2.str_rays())
Esempio n. 6
0
    def query(self, lat: float, lon: float, datestamp: Union[date, dt, None] = None, server_mode: bool = False):
        """Query WOA13 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

        if not self.has_data_loaded:
            if not self.load_grids():
                logger.error("No data")
                return None

        self.calc_indices(month=datestamp.month)

        # Find the nearest grid node
        lat_base_idx, lon_base_idx = self.grid_coords(lat=lat, lon=lon)
        lat_offsets = range(lat_base_idx - self.search_radius, lat_base_idx + self.search_radius + 1)
        lon_offsets = range(lon_base_idx - self.search_radius, lon_base_idx + self.search_radius + 1)

        # Search nodes surrounding the requested position to find the closest non-land
        t = np.zeros(self.num_levels)
        s = np.zeros(self.num_levels)
        t_min = np.zeros(self.num_levels)
        s_min = np.zeros(self.num_levels)
        t_max = np.zeros(self.num_levels)
        s_max = np.zeros(self.num_levels)
        dist_arr = np.zeros(self.num_levels)
        dist_t_sd = np.zeros(self.num_levels)
        dist_s_sd = np.zeros(self.num_levels)
        dist_arr[:] = 99999999
        dist_t_sd[:] = 99999999
        dist_s_sd[:] = 99999999
        min_dist = 999999999
        num_visited = 0
        # These keep track of the closest node found, this will also
        # be used to populate lat/lon of the cast to be delivered
        lat_idx = -1
        lon_idx = -1
        for this_lat_index in lat_offsets:
            for this_lon_index in lon_offsets:

                if this_lon_index >= self.lon.size:
                    # logger.debug("[%s] >= [%s]" % (this_lon_index, self.t[self.month_idx].variables['lon'].size))
                    this_lon_index -= self.lat.size

                # Check to see if we're at sea or on land
                if self.landsea[this_lat_index][this_lon_index] == 1:
                    # logger.debug("at land: %s, %s" % (this_lat_index, this_lon_index))
                    # from matplotlib import pyplot
                    # pyplot.imshow(self.landsea, origin='lower')
                    # pyplot.scatter([this_lon_index], [this_lat_index], c='r', s=40)
                    # pyplot.show()

                    continue

                # calculate the distance to the grid node
                dist = self.g.distance(lon, lat, self.lon[this_lon_index], self.lat[this_lat_index])
                # logger.debug("[%s %s] [%s %s]" % (self.lon.shape, self.lat.shape, this_lon_index, this_lat_index))
                # logger.debug("dist: %s" % dist)

                # Keep track of the closest valid grid node to report the pseudo-cast position
                if dist < min_dist:
                    min_dist = dist
                    lat_idx = this_lat_index
                    lon_idx = this_lon_index

                # Extract monthly temperature and salinity profile for this location
                t_profile = self.t[self.month_idx].variables['t_an'][0, :, this_lat_index, this_lon_index]
                s_profile = self.s[self.month_idx].variables['s_an'][0, :, this_lat_index, this_lon_index]
                # Extract seasonal temperature and salinity profile for this location
                t_profile2 = self.t[self.season_idx].variables['t_an'][0, :, this_lat_index, this_lon_index]
                s_profile2 = self.s[self.season_idx].variables['s_an'][0, :, this_lat_index, this_lon_index]

                # Now do the same for the standard deviation profiles
                t_sd_profile = self.t[self.month_idx].variables['t_sd'][0, :, this_lat_index, this_lon_index]
                s_sd_profile = self.s[self.month_idx].variables['s_sd'][0, :, this_lat_index, this_lon_index]
                t_sd_profile2 = self.t[self.season_idx].variables['t_sd'][0, :, this_lat_index, this_lon_index]
                s_sd_profile2 = self.s[self.season_idx].variables['s_sd'][0, :, this_lat_index, this_lon_index]

                # Overwrite the top of the seasonal profiles with the monthly profiles
                t_profile2[0:t_profile.size] = t_profile
                s_profile2[0:s_profile.size] = s_profile
                t_sd_profile2[0:t_sd_profile.size] = t_sd_profile
                s_sd_profile2[0:s_sd_profile.size] = s_sd_profile

                # For each element in the profile, only keep those whose distance is closer than values
                # found from previous iterations (maintain the closest value at each depth level)
                # logger.debug("profile sz: %d\n%s" % (t_profile2.size, t_profile2))
                for i in range(t_profile2.size):
                    if dist >= dist_arr[i]:
                        continue
                    if (t_profile2[i] < 50.0) and (s_profile2[i] < 500.0) and (s_profile2[i] >= 0):
                        t[i] = t_profile2[i]
                        s[i] = s_profile2[i]
                        dist_arr[i] = dist

                # Now do the same thing for the temperature standard deviations
                for i in range(t_sd_profile2.size):
                    if dist >= dist_t_sd[i]:
                        continue
                    if (t_sd_profile2[i] < 50.0) and (t_sd_profile2[i] > -2):
                        t_min[i] = t_profile2[i] - t_sd_profile2[i]
                        if t_min[i] < -2.0:  # can't have overly cold water
                            t_min[i] = -2.0
                        t_max[i] = t_profile2[i] + t_sd_profile2[i]
                        dist_t_sd[i] = dist

                # Now do the same thing for the salinity standard deviations
                for i in range(s_sd_profile2.size):
                    if dist >= dist_s_sd[i]:
                        continue
                    if (s_sd_profile2[i] < 500.0) and (s_sd_profile2[i] >= 0):
                        s_min[i] = s_profile2[i] - s_sd_profile2[i]
                        if s_min[i] < 0:  # Can't have a negative salinity
                            s_min[i] = 0
                        s_max[i] = s_profile2[i] + s_sd_profile2[i]
                        dist_s_sd[i] = dist

                num_visited += 1

        if (lat_idx == -1) and (lon_idx == -1):
            logger.info("possible request on land")
            return None

        valid = dist_arr != 99999999
        num_values = t[valid].size
        logger.debug("valid: %s" % num_values)

        if lon > 180.0:  # Go back to negative longitude
            lon -= 360.0

        # populate output profiles
        ssp = Profile()
        ssp.meta.sensor_type = Dicts.sensor_types['Synthetic']
        ssp.meta.probe_type = Dicts.probe_types['WOA13']
        ssp.meta.latitude = lat
        ssp.meta.longitude = lon
        ssp.meta.utc_time = dt(year=datestamp.year, month=datestamp.month, day=datestamp.day)
        ssp.init_data(num_values)
        ssp.data.depth = self.t[self.season_idx].variables['depth'][0:num_values]
        ssp.data.temp = t[valid]
        ssp.data.sal = s[valid]
        ssp.calc_data_speed()
        ssp.clone_data_to_proc()
        ssp.init_sis()

        # - min/max
        # Isolate realistic values
        for i in range(t_min.size):

            if dist_t_sd[i] == 99999999 or dist_s_sd[i] == 99999999:
                num_values = i
                break

        # -- min
        ssp_min = Profile()
        ssp_min.meta.sensor_type = Dicts.sensor_types['Synthetic']
        ssp_min.meta.probe_type = Dicts.probe_types['WOA13']
        ssp_min.meta.latitude = lat
        ssp_min.meta.longitude = lon
        ssp_min.meta.utc_time = dt(year=datestamp.year, month=datestamp.month, day=datestamp.day)
        if num_values > 0:
            ssp_min.init_data(num_values)
            ssp_min.data.depth = self.t[self.season_idx].variables['depth'][0:num_values]
            ssp_min.data.temp = t_min[valid][0:num_values]
            ssp_min.data.sal = s_min[valid][0:num_values]
            ssp_min.calc_data_speed()
            ssp_min.clone_data_to_proc()
            ssp_min.init_sis()
        else:
            ssp_min = None
        # -- max
        ssp_max = Profile()
        ssp_max.meta.sensor_type = Dicts.sensor_types['Synthetic']
        ssp_max.meta.probe_type = Dicts.probe_types['WOA13']
        ssp_max.meta.latitude = lat
        ssp_max.meta.longitude = lon
        ssp_max.meta.utc_time = dt(year=datestamp.year, month=datestamp.month, day=datestamp.day)
        ssp.meta.original_path = "WOA13_%s" % datestamp.strftime("%Y%m%d")
        if num_values > 0:
            ssp_max.init_data(num_values)
            ssp_max.data.depth = self.t[self.season_idx].variables['depth'][0:num_values].astype(np.float64)
            ssp_max.data.temp = t_max[valid][0:num_values]
            ssp_max.data.sal = s_max[valid][0:num_values]
            ssp_max.calc_data_speed()
            ssp_max.clone_data_to_proc()
            ssp_max.init_sis()
        else:
            ssp_max = None

        profiles = ProfileList()
        profiles.append_profile(ssp)
        if ssp_min:
            profiles.append_profile(ssp_min)
        if ssp_max:
            profiles.append_profile(ssp_max)
        profiles.current_index = 0

        # logger.debug("retrieved: %s" % profiles)

        return profiles
Esempio n. 7
0
    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
Esempio n. 8
0
    def query(self, nc_path: str, lat: float,
              lon: float) -> Optional[ProfileList]:
        if not os.path.exists(nc_path):
            raise RuntimeError('Unable to locate %s' % nc_path)
        logger.debug('nc path: %s' % nc_path)

        if (lat is None) or (lon is None):
            logger.error("invalid location query: (%s, %s)" % (lon, lat))
            return None
        logger.debug('query location: %s, %s' % (lat, lon))

        progress = CliProgress()

        try:
            self._file = Dataset(nc_path)
            progress.update(20)

        except (RuntimeError, IOError) as e:
            logger.warning("unable to access data: %s" % e)
            self.clear_data()
            progress.end()
            return None

        try:
            self.name = self._file.title

            time = self._file.variables['time']
            self._timestamp = num2date(time[0], units=time.units)
            logger.debug("Retrieved time: %s" % self._timestamp.isoformat())

            # Now get latitudes, longitudes and depths for x,y,z referencing
            self._lats = self._file.variables['lat'][:]
            self._lons = self._file.variables['lon'][:]
            # logger.debug('lat:(%s)\n%s' % (self._lats.shape, self._lats))
            # logger.debug('lon:(%s)\n%s' % (self._lons.shape, self._lons))

            self._zeta = self._file.variables['zeta'][0, :]
            self._siglay = self._file.variables['siglay'][:]
            self._h = self._file.variables['h'][:]
            # logger.debug('zeta:(%s)\n%s' % (self._zeta.shape, self._zeta))
            # logger.debug('siglay:(%s)\n%s' % (self._siglay.shape, self._siglay[:, 0]))
            # logger.debug('h:(%s)\n%s' % (self._h.shape, self._h))

            self._temp = self._file.variables['temp'][:]
            self._sal = self._file.variables['salinity'][:]
            # logger.debug('temp:(%s)\n%s' % (self._temp.shape, self._temp[:, 0]))
            # logger.debug('sal:(%s)\n%s' % (self._sal.shape, self._sal[:, 0]))

        except Exception as e:
            logger.error(
                "troubles in variable lookup for lat/long grid and/or depth: %s"
                % e)
            self.clear_data()
            progress.end()
            return None

        min_dist = 100000.0
        min_idx = None
        for idx, _ in enumerate(self._lats):
            nc_lat = self._lats[idx]
            nc_lon = self._lons[idx]
            if nc_lon > 180.0:
                nc_lon = nc_lon - 360.0
            nc_dist = self.g.distance(nc_lon, nc_lat, lon, lat)
            # logger.debug('loc: %.6f, %.6f -> %.6f' % (nc_lat, nc_lon, nc_dist))
            if nc_dist < min_dist:
                min_dist = nc_dist
                min_idx = idx
        if min_dist >= 10000.0:
            logger.error("location too far from model nodes: %.f" % min_dist)
            self.clear_data()
            progress.end()
            return None

        self._loc_idx = min_idx
        self._lon = self._lons[self._loc_idx]
        if self._lon > 180.0:
            self._lon = self._lon - 360.0
        self._lat = self._lats[self._loc_idx]
        logger.debug('closest node: %d [%s, %s] -> %s' %
                     (self._loc_idx, self._lat, self._lon, min_dist))

        zeta = self._zeta[self._loc_idx]
        h = self._h[self._loc_idx]
        siglay = -self._siglay[:, self._loc_idx]
        # logger.debug('zeta: %s, h: %s, siglay: %s' % (zeta, h, siglay))
        self._d = siglay * (h + zeta)
        # logger.debug('d:(%s)\n%s' % (self._h.shape, self._d))

        # 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[self.name]
        ssp.meta.latitude = self._lat
        ssp.meta.longitude = self._lon
        ssp.meta.utc_time = dt(year=self._timestamp.year,
                               month=self._timestamp.month,
                               day=self._timestamp.day,
                               hour=self._timestamp.hour,
                               minute=self._timestamp.minute,
                               second=self._timestamp.second)
        ssp.meta.original_path = "%s_%s" % (
            self.name, self._timestamp.strftime("%Y%m%d_%H%M%S"))
        ssp.init_data(self._d.shape[0])
        ssp.data.depth = self._d[:]
        ssp.data.temp = self._temp[0, :, self._loc_idx]
        ssp.data.sal = self._sal[0, :, self._loc_idx]
        ssp.calc_data_speed()
        ssp.clone_data_to_proc()
        ssp.init_sis()

        profiles = ProfileList()
        profiles.append_profile(ssp)

        progress.end()
        return profiles
Esempio n. 9
0
    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