water-column-sonar-annotation 26.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/__init__.py +0 -0
- tests/astronomical/__init__.py +0 -0
- tests/astronomical/test_astronomical_manager.py +148 -0
- tests/conftest.py +60 -0
- tests/cruise/__init__.py +0 -0
- tests/cruise/test_cruise_manager.py +80 -0
- tests/geospatial/__init__.py +0 -0
- tests/geospatial/test_geospatial_manager.py +86 -0
- tests/record/test_echoview_record_manager.py +160 -0
- water_column_sonar_annotation/__init__.py +5 -0
- water_column_sonar_annotation/astronomical/__init__.py +5 -0
- water_column_sonar_annotation/astronomical/astronomical_manager.py +82 -0
- water_column_sonar_annotation/cruise/__init__.py +5 -0
- water_column_sonar_annotation/cruise/cruise_manager.py +104 -0
- water_column_sonar_annotation/geospatial/__init__.py +5 -0
- water_column_sonar_annotation/geospatial/geospatial_manager.py +143 -0
- water_column_sonar_annotation/record/__init__.py +9 -0
- water_column_sonar_annotation/record/echoview_record_manager.py +426 -0
- water_column_sonar_annotation/record/graph_record_manager.py +82 -0
- water_column_sonar_annotation/record/parquet_record_manager.py +83 -0
- water_column_sonar_annotation/shape/__init__.py +5 -0
- water_column_sonar_annotation/shape/shape_manager.py +29 -0
- water_column_sonar_annotation-26.1.8.dist-info/METADATA +109 -0
- water_column_sonar_annotation-26.1.8.dist-info/RECORD +27 -0
- water_column_sonar_annotation-26.1.8.dist-info/WHEEL +5 -0
- water_column_sonar_annotation-26.1.8.dist-info/licenses/LICENSE +21 -0
- water_column_sonar_annotation-26.1.8.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import xarray as xr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CruiseManager:
|
|
6
|
+
#######################################################
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
bucket_name: str = "noaa-wcsd-zarr-pds",
|
|
10
|
+
ship_name: str = "Henry_B._Bigelow",
|
|
11
|
+
cruise_name: str = "HB1906",
|
|
12
|
+
sensor_name: str = "EK60",
|
|
13
|
+
):
|
|
14
|
+
self.level: str = "level_2a"
|
|
15
|
+
self.bucket_name: str = bucket_name
|
|
16
|
+
self.ship_name: str = ship_name
|
|
17
|
+
self.cruise_name: str = cruise_name
|
|
18
|
+
self.sensor_name: str = sensor_name
|
|
19
|
+
try:
|
|
20
|
+
zarr_store = f"{self.cruise_name}.zarr"
|
|
21
|
+
s3_zarr_store_path = f"{self.bucket_name}/{self.level}/{self.ship_name}/{self.cruise_name}/{self.sensor_name}/{zarr_store}"
|
|
22
|
+
|
|
23
|
+
kwargs = {"consolidated": False}
|
|
24
|
+
cruise = xr.open_dataset(
|
|
25
|
+
f"s3://{s3_zarr_store_path}",
|
|
26
|
+
engine="zarr",
|
|
27
|
+
storage_options={"anon": True},
|
|
28
|
+
# chunks={},
|
|
29
|
+
**kwargs,
|
|
30
|
+
)
|
|
31
|
+
self.cruise = cruise
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f"Could not open cruise: {e}")
|
|
34
|
+
|
|
35
|
+
def get_cruise(
|
|
36
|
+
self,
|
|
37
|
+
):
|
|
38
|
+
try:
|
|
39
|
+
return self.cruise
|
|
40
|
+
except Exception as e:
|
|
41
|
+
print(f"Could not open cruise: {e}")
|
|
42
|
+
|
|
43
|
+
def get_coordinates(
|
|
44
|
+
self,
|
|
45
|
+
start_time, # ="2019-10-16T16:20:00", # In UTC
|
|
46
|
+
end_time,
|
|
47
|
+
):
|
|
48
|
+
"""gets the gps coordinates using the time & cruise"""
|
|
49
|
+
try:
|
|
50
|
+
cruise_select = self.cruise.sel(time=slice(start_time, end_time))
|
|
51
|
+
return cruise_select.latitude.values[0], cruise_select.longitude.values[0]
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Could not find depth: {e}")
|
|
54
|
+
|
|
55
|
+
def get_depth(
|
|
56
|
+
self,
|
|
57
|
+
start_time="2019-10-16T16:20:00",
|
|
58
|
+
end_time="2019-10-16T16:50:00",
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
Returns the bottom depth in meters for a given interval of ISO
|
|
62
|
+
timestamps. Value returned is the minimum depth over that interval.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
cruise = self.cruise # get_cruise()
|
|
66
|
+
# bottom_depths = cruise.time.where(
|
|
67
|
+
# (
|
|
68
|
+
# (cruise.time > np.datetime64(start_time))
|
|
69
|
+
# & (cruise.time < np.datetime64(end_time))
|
|
70
|
+
# ),
|
|
71
|
+
# drop=True,
|
|
72
|
+
# ).bottom.values
|
|
73
|
+
time_slice = slice(start_time, end_time)
|
|
74
|
+
bottom_depths = cruise.sel(time=time_slice).bottom.values
|
|
75
|
+
if np.all(np.isnan(bottom_depths)):
|
|
76
|
+
return np.nan
|
|
77
|
+
return np.round(np.nanmin(bottom_depths), 2)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Could not find depth: {e}")
|
|
80
|
+
|
|
81
|
+
def get_altitude(
|
|
82
|
+
self,
|
|
83
|
+
start_time: str = "2019-10-16T16:20:00",
|
|
84
|
+
end_time: str = "2019-10-16T16:50:00",
|
|
85
|
+
bbox_max: float = 0.0,
|
|
86
|
+
):
|
|
87
|
+
"""
|
|
88
|
+
Will need a lot of improvement but this will do for first pass. In the
|
|
89
|
+
future need to check for each point the differential and find the
|
|
90
|
+
minimum.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
depth_min = self.get_depth(start_time, end_time)
|
|
94
|
+
if np.isnan(depth_min):
|
|
95
|
+
return 0.0
|
|
96
|
+
return np.round(depth_min - bbox_max, 2)
|
|
97
|
+
except Exception as get_altitude_exception:
|
|
98
|
+
print(f"Problem getting altitude: {get_altitude_exception}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# if __name__ == "__main__":
|
|
102
|
+
# astronomical_manager = AstronomicalManager()
|
|
103
|
+
# azimuth = astronomical_manager.get_solar_azimuth()
|
|
104
|
+
# print(azimuth)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import geopandas as gpd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pooch
|
|
7
|
+
from dateutil import tz
|
|
8
|
+
from shapely import Point
|
|
9
|
+
from timezonefinder import TimezoneFinder
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Gets the distance between a point and a coastline
|
|
13
|
+
https://www.kaggle.com/code/notcostheta/shortest-distance-to-a-coastline
|
|
14
|
+
https://www.naturalearthdata.com/downloads/50m-physical-vectors/50m-coastline/
|
|
15
|
+
|
|
16
|
+
Well known text map: https://wktmap.com/
|
|
17
|
+
Calculate distance map: https://www.calcmaps.com/map-distance/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
HB1906_DATA = pooch.create(
|
|
21
|
+
path=pooch.os_cache("water-column-sonar-annotation"),
|
|
22
|
+
base_url="https://github.com/CI-CMG/water-column-sonar-annotation/releases/download/v26.1.0/",
|
|
23
|
+
retry_if_failed=1,
|
|
24
|
+
registry={
|
|
25
|
+
# "HB201906_BOTTOMS.zip": "sha256:20609581493ea3326c1084b6868e02aafbb6c0eae871d946f30b8b5f0e7ba059",
|
|
26
|
+
# "HB201906_EVR.zip": "sha256:ceed912a25301be8f1b8f91e134d0ca4cff717f52b6623a58677832fd60c2990",
|
|
27
|
+
#
|
|
28
|
+
# "ne_50m_coastline.shp": "sha256:797d675af9613f80b51ab6049fa32e589974d7a97c6497ca56772965f179ed26",
|
|
29
|
+
# "ne_50m_coastline.shx": "sha256:0ff1792f2d16b58246d074215edd9d12fa280880ecaad61a91b9382fee854065",
|
|
30
|
+
#
|
|
31
|
+
"ne_10m_coastline.shp": "sha256:459a4a97c09db19aadf5244026612de9d43748be27f83a360242b99f7fabb3c1",
|
|
32
|
+
"ne_10m_coastline.shx": "sha256:f873afee7f56779ce52253f740ec251c2f12244aea911dc40f0a85d75de8d5f2",
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fetch_raw_files():
|
|
38
|
+
HB1906_DATA.fetch(fname="ne_10m_coastline.shp", progressbar=True)
|
|
39
|
+
file_name = HB1906_DATA.fetch(fname="ne_10m_coastline.shx", progressbar=True)
|
|
40
|
+
return Path(file_name).parent
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def data_path():
|
|
44
|
+
return {
|
|
45
|
+
"DATA_PATH": fetch_raw_files(),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GeospatialManager:
|
|
50
|
+
#######################################################
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
):
|
|
54
|
+
self.DECIMAL_PRECISION = 6
|
|
55
|
+
self.crs = "EPSG:4326" # "EPSG:3857" # "EPSG:4326"
|
|
56
|
+
|
|
57
|
+
def check_distance_from_coastline(
|
|
58
|
+
self, # -30.410156 51.508742)
|
|
59
|
+
latitude: float = 51.508742, # 42.682435,
|
|
60
|
+
longitude: float = -30.410156, # -68.741455,
|
|
61
|
+
shapefile_path: str = data_path()["DATA_PATH"],
|
|
62
|
+
) -> np.float32 | None:
|
|
63
|
+
"""
|
|
64
|
+
# Note this takes about 14 seconds each, very slow!!!
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
# requires the shape file too
|
|
68
|
+
geometry_one = gpd.read_file(f"{shapefile_path}/ne_10m_coastline.shp")
|
|
69
|
+
geometry_one = geometry_one.set_crs(self.crs)
|
|
70
|
+
geometry_two = Point([longitude, latitude])
|
|
71
|
+
gdf_p = gpd.GeoDataFrame(geometry=[geometry_two], crs=self.crs)
|
|
72
|
+
gdf_l = geometry_one
|
|
73
|
+
gdf_p = gdf_p.to_crs(gdf_p.estimate_utm_crs())
|
|
74
|
+
# print(gdf_p.to_string())
|
|
75
|
+
gdf_l = gdf_l.to_crs(gdf_p.crs)
|
|
76
|
+
# TODO: index 1399 has inf values, investigate
|
|
77
|
+
# RuntimeWarning: invalid value encountered in distance
|
|
78
|
+
# return lib.distance(a, b, **kwargs)
|
|
79
|
+
all_distances = [
|
|
80
|
+
gdf_p.geometry.distance(gdf_l.get_geometry(0)[i])[0]
|
|
81
|
+
for i in range(len(gdf_l.get_geometry(0)))
|
|
82
|
+
if gdf_l.get_geometry(0)[i].is_valid
|
|
83
|
+
]
|
|
84
|
+
return np.round(np.min(all_distances), 0)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Could not process the distance: {e}")
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_local_time(
|
|
90
|
+
# self,
|
|
91
|
+
iso_time: str = "2026-01-26T20:35:00Z",
|
|
92
|
+
latitude: float = 51.508742,
|
|
93
|
+
longitude: float = -30.410156,
|
|
94
|
+
) -> str:
|
|
95
|
+
# https://www.geeksforgeeks.org/python/get-time-zone-of-a-given-location-using-python/
|
|
96
|
+
obj = TimezoneFinder()
|
|
97
|
+
calculated_timezone = obj.timezone_at(lng=longitude, lat=latitude)
|
|
98
|
+
from_zone = tz.gettz("UTC")
|
|
99
|
+
to_zone = tz.gettz(calculated_timezone)
|
|
100
|
+
utc = datetime.fromisoformat(iso_time)
|
|
101
|
+
utc = utc.replace(tzinfo=from_zone)
|
|
102
|
+
local_time = utc.astimezone(to_zone)
|
|
103
|
+
return local_time.isoformat() # [:19]
|
|
104
|
+
|
|
105
|
+
def get_local_hour_of_day(
|
|
106
|
+
self,
|
|
107
|
+
iso_time: str = "2026-01-26T20:35:00Z",
|
|
108
|
+
latitude: float = 51.508742,
|
|
109
|
+
longitude: float = -30.410156,
|
|
110
|
+
) -> int:
|
|
111
|
+
obj = TimezoneFinder()
|
|
112
|
+
calculated_timezone = obj.timezone_at(lng=longitude, lat=latitude)
|
|
113
|
+
from_zone = tz.gettz("UTC")
|
|
114
|
+
to_zone = tz.gettz(calculated_timezone)
|
|
115
|
+
utc = datetime.fromisoformat(iso_time)
|
|
116
|
+
utc = utc.replace(tzinfo=from_zone)
|
|
117
|
+
local_time = utc.astimezone(to_zone)
|
|
118
|
+
return local_time.hour
|
|
119
|
+
|
|
120
|
+
def get_month_of_year(
|
|
121
|
+
self,
|
|
122
|
+
iso_time: str = "2026-01-26T20:35:00Z",
|
|
123
|
+
latitude: float = 51.508742,
|
|
124
|
+
longitude: float = -30.410156,
|
|
125
|
+
):
|
|
126
|
+
local_time = self.get_local_time(
|
|
127
|
+
iso_time=iso_time,
|
|
128
|
+
latitude=latitude,
|
|
129
|
+
longitude=longitude,
|
|
130
|
+
)
|
|
131
|
+
return int(local_time[5:7])
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# if __name__ == "__main__":
|
|
136
|
+
# geospatial_manager = GeospatialManager()
|
|
137
|
+
# # x = geospatial_manager.check_distance_from_coastline()
|
|
138
|
+
# x = geospatial_manager.get_local_time(
|
|
139
|
+
# iso_time="2026-01-26T20:35:00Z",
|
|
140
|
+
# latitude=51.508742,
|
|
141
|
+
# longitude=-30.410156,
|
|
142
|
+
# )
|
|
143
|
+
# print(x)
|