water-column-sonar-processing 0.0.1__py3-none-any.whl → 26.1.14__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.
Potentially problematic release.
This version of water-column-sonar-processing might be problematic. Click here for more details.
- water_column_sonar_processing/__init__.py +13 -0
- water_column_sonar_processing/aws/__init__.py +7 -0
- water_column_sonar_processing/aws/dynamodb_manager.py +355 -0
- water_column_sonar_processing/aws/s3_manager.py +418 -0
- water_column_sonar_processing/aws/s3fs_manager.py +64 -0
- {model → water_column_sonar_processing}/aws/sns_manager.py +10 -21
- {model → water_column_sonar_processing}/aws/sqs_manager.py +11 -19
- water_column_sonar_processing/cruise/__init__.py +4 -0
- water_column_sonar_processing/cruise/create_empty_zarr_store.py +129 -0
- water_column_sonar_processing/cruise/datatree_manager.py +21 -0
- water_column_sonar_processing/cruise/resample_regrid.py +323 -0
- water_column_sonar_processing/geometry/__init__.py +13 -0
- water_column_sonar_processing/geometry/elevation_manager.py +111 -0
- water_column_sonar_processing/geometry/geometry_manager.py +241 -0
- water_column_sonar_processing/geometry/line_simplification.py +176 -0
- water_column_sonar_processing/geometry/pmtile_generation.py +266 -0
- water_column_sonar_processing/geometry/spatiotemporal.py +106 -0
- water_column_sonar_processing/index/__init__.py +3 -0
- water_column_sonar_processing/index/index_manager.py +381 -0
- water_column_sonar_processing/model/__init__.py +3 -0
- water_column_sonar_processing/model/zarr_manager.py +741 -0
- water_column_sonar_processing/processing/__init__.py +4 -0
- water_column_sonar_processing/processing/raw_to_netcdf.py +320 -0
- water_column_sonar_processing/processing/raw_to_zarr.py +331 -0
- water_column_sonar_processing/utility/__init__.py +13 -0
- {model → water_column_sonar_processing}/utility/cleaner.py +7 -7
- water_column_sonar_processing/utility/constants.py +118 -0
- {model → water_column_sonar_processing}/utility/pipeline_status.py +47 -24
- water_column_sonar_processing/utility/timestamp.py +12 -0
- water_column_sonar_processing-26.1.14.dist-info/METADATA +240 -0
- water_column_sonar_processing-26.1.14.dist-info/RECORD +34 -0
- {water_column_sonar_processing-0.0.1.dist-info → water_column_sonar_processing-26.1.14.dist-info}/WHEEL +1 -1
- {water_column_sonar_processing-0.0.1.dist-info → water_column_sonar_processing-26.1.14.dist-info/licenses}/LICENSE +1 -1
- water_column_sonar_processing-26.1.14.dist-info/top_level.txt +1 -0
- __init__.py +0 -0
- model/__init__.py +0 -0
- model/aws/__init__.py +0 -0
- model/aws/dynamodb_manager.py +0 -149
- model/aws/s3_manager.py +0 -356
- model/aws/s3fs_manager.py +0 -74
- model/cruise/__init__.py +0 -0
- model/cruise/create_empty_zarr_store.py +0 -166
- model/cruise/resample_regrid.py +0 -248
- model/geospatial/__init__.py +0 -0
- model/geospatial/geometry_manager.py +0 -194
- model/geospatial/geometry_simplification.py +0 -81
- model/geospatial/pmtile_generation.py +0 -74
- model/index/__init__.py +0 -0
- model/index/index.py +0 -228
- model/model.py +0 -138
- model/utility/__init__.py +0 -0
- model/utility/constants.py +0 -56
- model/utility/timestamp.py +0 -12
- model/zarr/__init__.py +0 -0
- model/zarr/bar.py +0 -28
- model/zarr/foo.py +0 -11
- model/zarr/zarr_manager.py +0 -298
- water_column_sonar_processing-0.0.1.dist-info/METADATA +0 -89
- water_column_sonar_processing-0.0.1.dist-info/RECORD +0 -32
- water_column_sonar_processing-0.0.1.dist-info/top_level.txt +0 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from water_column_sonar_processing.aws import DynamoDBManager, S3Manager
|
|
7
|
+
from water_column_sonar_processing.model import ZarrManager
|
|
8
|
+
from water_column_sonar_processing.utility import Cleaner
|
|
9
|
+
from water_column_sonar_processing.utility import Constants
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# TODO: change name to "CreateLocalEmptyZarrStore"
|
|
13
|
+
class CreateEmptyZarrStore:
|
|
14
|
+
#######################################################
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
):
|
|
18
|
+
self.__overwrite = True
|
|
19
|
+
# self.input_bucket_name = os.environ.get("INPUT_BUCKET_NAME")
|
|
20
|
+
# self.output_bucket_name = os.environ.get("OUTPUT_BUCKET_NAME")
|
|
21
|
+
|
|
22
|
+
#######################################################
|
|
23
|
+
@staticmethod
|
|
24
|
+
def create_cruise_level_zarr_store(
|
|
25
|
+
output_bucket_name: str,
|
|
26
|
+
ship_name: str,
|
|
27
|
+
cruise_name: str,
|
|
28
|
+
sensor_name: str,
|
|
29
|
+
table_name: str,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Initialize zarr store for the entire cruise which aggregates all the raw data.
|
|
33
|
+
All cruises will be resampled at 20 cm depth.
|
|
34
|
+
# tempdir="/tmp", # TODO: create better tmp directory for testing
|
|
35
|
+
"""
|
|
36
|
+
tempdir = tempfile.TemporaryDirectory()
|
|
37
|
+
try:
|
|
38
|
+
dynamo_db_manager = DynamoDBManager()
|
|
39
|
+
s3_manager = S3Manager()
|
|
40
|
+
|
|
41
|
+
df = dynamo_db_manager.get_table_as_df(
|
|
42
|
+
table_name=table_name,
|
|
43
|
+
cruise_name=cruise_name,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# TODO: filter the dataframe just for enums >= LEVEL_1_PROCESSING
|
|
47
|
+
# df[df['PIPELINE_STATUS'] < PipelineStatus.LEVEL_1_PROCESSING] = np.nan
|
|
48
|
+
|
|
49
|
+
# TODO: VERIFY GEOJSON EXISTS as prerequisite!!! ...no more geojson needed
|
|
50
|
+
|
|
51
|
+
print(f"DataFrame shape: {df.shape}")
|
|
52
|
+
cruise_channels = list(
|
|
53
|
+
set([i for sublist in df["CHANNELS"].dropna() for i in sublist])
|
|
54
|
+
)
|
|
55
|
+
cruise_channels.sort()
|
|
56
|
+
|
|
57
|
+
consolidated_zarr_width = np.sum(
|
|
58
|
+
df["NUM_PING_TIME_DROPNA"].dropna().astype(int)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# [4] max measurement resolution for the whole cruise
|
|
62
|
+
# Each max-echo-range is paired with water-level and then find the max of that
|
|
63
|
+
cruise_max_echo_range = np.max(
|
|
64
|
+
(df["MAX_ECHO_RANGE"] + df["WATER_LEVEL"]).dropna().astype(float)
|
|
65
|
+
) # max_echo_range now includes water_level
|
|
66
|
+
|
|
67
|
+
print(f"cruise_max_echo_range: {cruise_max_echo_range}")
|
|
68
|
+
|
|
69
|
+
# [5] get number of channels
|
|
70
|
+
cruise_frequencies = [
|
|
71
|
+
float(i) for i in df["FREQUENCIES"].dropna().values.flatten()[0]
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
new_width = int(consolidated_zarr_width)
|
|
75
|
+
################################################################
|
|
76
|
+
# Delete any existing stores
|
|
77
|
+
zarr_prefix = os.path.join(
|
|
78
|
+
str(Constants.LEVEL_2.value), ship_name, cruise_name, sensor_name
|
|
79
|
+
)
|
|
80
|
+
child_objects = s3_manager.get_child_objects(
|
|
81
|
+
bucket_name=output_bucket_name,
|
|
82
|
+
sub_prefix=zarr_prefix,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if len(child_objects) > 0:
|
|
86
|
+
s3_manager.delete_nodd_objects(
|
|
87
|
+
bucket_name=output_bucket_name,
|
|
88
|
+
objects=child_objects,
|
|
89
|
+
)
|
|
90
|
+
################################################################
|
|
91
|
+
# Create new model store
|
|
92
|
+
zarr_manager = ZarrManager()
|
|
93
|
+
zarr_manager.create_zarr_store(
|
|
94
|
+
path=tempdir.name,
|
|
95
|
+
ship_name=ship_name,
|
|
96
|
+
cruise_name=cruise_name,
|
|
97
|
+
sensor_name=sensor_name,
|
|
98
|
+
frequencies=cruise_frequencies,
|
|
99
|
+
width=new_width,
|
|
100
|
+
max_echo_range=cruise_max_echo_range,
|
|
101
|
+
# cruise_min_epsilon=cruise_min_epsilon,
|
|
102
|
+
calibration_status=True,
|
|
103
|
+
)
|
|
104
|
+
#################################################################
|
|
105
|
+
# TODO: would be more elegant to create directly into s3 bucket
|
|
106
|
+
s3_manager.upload_zarr_store_to_s3(
|
|
107
|
+
output_bucket_name=output_bucket_name,
|
|
108
|
+
local_directory=tempdir.name,
|
|
109
|
+
object_prefix=zarr_prefix,
|
|
110
|
+
cruise_name=cruise_name,
|
|
111
|
+
)
|
|
112
|
+
#################################################################
|
|
113
|
+
# TODO: verify count of the files uploaded
|
|
114
|
+
#################################################################
|
|
115
|
+
# TODO: update enum in dynamodb
|
|
116
|
+
print("Done creating cruise level zarr store.")
|
|
117
|
+
#################################################################
|
|
118
|
+
except Exception as err:
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"Problem trying to create new cruise model store, {err}"
|
|
121
|
+
)
|
|
122
|
+
finally:
|
|
123
|
+
cleaner = Cleaner()
|
|
124
|
+
cleaner.delete_local_files()
|
|
125
|
+
# TODO: should delete zarr store in temp directory too?
|
|
126
|
+
print("Done creating cruise level model store")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
###########################################################
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# ### https://xarray-datatree.readthedocs.io/en/latest/data-structures.html
|
|
2
|
+
# import xarray as xr
|
|
3
|
+
# from datatree import DataTree
|
|
4
|
+
#
|
|
5
|
+
#
|
|
6
|
+
# class DatatreeManager:
|
|
7
|
+
# #######################################################
|
|
8
|
+
# def __init__(
|
|
9
|
+
# self,
|
|
10
|
+
# ):
|
|
11
|
+
# self.dtype = "float32"
|
|
12
|
+
#
|
|
13
|
+
# #################################################################
|
|
14
|
+
# def create_datatree(
|
|
15
|
+
# self,
|
|
16
|
+
# input_ds,
|
|
17
|
+
# ) -> None:
|
|
18
|
+
# ds1 = xr.Dataset({"foo": "orange"})
|
|
19
|
+
# dt = DataTree(name="root", dataset=ds1) # create root node
|
|
20
|
+
# # ds2 = xr.Dataset({"bar": 0}, coords={"y": ("y", [0, 1, 2])})
|
|
21
|
+
# return dt
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import xarray as xr
|
|
7
|
+
|
|
8
|
+
from water_column_sonar_processing.aws import DynamoDBManager
|
|
9
|
+
from water_column_sonar_processing.model import ZarrManager
|
|
10
|
+
|
|
11
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ResampleRegrid:
|
|
15
|
+
#######################################################
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
):
|
|
19
|
+
self.__overwrite = True
|
|
20
|
+
self.dtype = "float32"
|
|
21
|
+
|
|
22
|
+
#################################################################
|
|
23
|
+
def interpolate_data(
|
|
24
|
+
self,
|
|
25
|
+
input_xr: xr.Dataset,
|
|
26
|
+
ping_times: np.ndarray,
|
|
27
|
+
all_cruise_depth_values: np.ndarray, # includes water_level offset
|
|
28
|
+
water_level: float = 0.0,
|
|
29
|
+
) -> np.ndarray:
|
|
30
|
+
"""
|
|
31
|
+
Input dataset is passed in along with times and depth values to regrid to.
|
|
32
|
+
"""
|
|
33
|
+
print("Interpolating dataset.")
|
|
34
|
+
try:
|
|
35
|
+
# add offset for the water level to the whole input xarray
|
|
36
|
+
input_xr.depth.values = input_xr.depth.values + water_level
|
|
37
|
+
|
|
38
|
+
data = np.empty(
|
|
39
|
+
( # Depth / Time / Frequency
|
|
40
|
+
len(all_cruise_depth_values),
|
|
41
|
+
len(ping_times),
|
|
42
|
+
len(input_xr.frequency_nominal.values),
|
|
43
|
+
),
|
|
44
|
+
dtype=self.dtype,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
data[:] = np.nan
|
|
48
|
+
|
|
49
|
+
regrid_resample = xr.DataArray( # where data will be written to
|
|
50
|
+
data=data,
|
|
51
|
+
coords={
|
|
52
|
+
"depth": all_cruise_depth_values,
|
|
53
|
+
"time": ping_times,
|
|
54
|
+
"frequency": input_xr.frequency_nominal.values,
|
|
55
|
+
},
|
|
56
|
+
dims=("depth", "time", "frequency"),
|
|
57
|
+
name="Sv",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
channels = input_xr.channel.values
|
|
61
|
+
for channel in range(len(channels)):
|
|
62
|
+
gc.collect()
|
|
63
|
+
max_depths = np.nanmax(
|
|
64
|
+
a=input_xr.depth.sel(channel=input_xr.channel[channel]).values,
|
|
65
|
+
# + water_level,
|
|
66
|
+
axis=1,
|
|
67
|
+
)
|
|
68
|
+
superset_of_max_depths = set(max_depths)
|
|
69
|
+
set_of_max_depths = list(
|
|
70
|
+
{x for x in superset_of_max_depths if x == x}
|
|
71
|
+
) # To speed things up resample in groups denoted by max_depth -- so samples might no longer be adjacent
|
|
72
|
+
for select_max_depth in set_of_max_depths:
|
|
73
|
+
# TODO: for nan just skip and leave all nan's
|
|
74
|
+
select_indices = [
|
|
75
|
+
i
|
|
76
|
+
for i in range(0, len(max_depths))
|
|
77
|
+
if max_depths[i] == select_max_depth
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
data_select = input_xr.Sv.sel(channel=input_xr.channel[channel])[
|
|
81
|
+
select_indices, :
|
|
82
|
+
].T.values
|
|
83
|
+
|
|
84
|
+
times_select = input_xr.ping_time.values[select_indices]
|
|
85
|
+
# input_xr.depth[0][0] -> [0., 499.9] before
|
|
86
|
+
# input_xr.depth.values = input_xr.depth.values + water_level # issue here!! overwritting all the data
|
|
87
|
+
# input_xr.depth[0][0] -> [7.5, 507.40] after
|
|
88
|
+
depths_all = input_xr.depth.sel(
|
|
89
|
+
channel=input_xr.channel[channel],
|
|
90
|
+
ping_time=input_xr.ping_time[select_indices[0]],
|
|
91
|
+
).values
|
|
92
|
+
depths_select = depths_all[~np.isnan(depths_all)]
|
|
93
|
+
#
|
|
94
|
+
da_select = xr.DataArray(
|
|
95
|
+
data=data_select[: len(depths_select), :],
|
|
96
|
+
dims=("depth", "time"),
|
|
97
|
+
coords={
|
|
98
|
+
"depth": depths_select,
|
|
99
|
+
"time": times_select,
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
# 'resampled' is now the interpolated superset of new dimensions
|
|
103
|
+
resampled = da_select.interp( # need to define the data with water level (domain)
|
|
104
|
+
depth=all_cruise_depth_values, # and need to interpolate over the (range)
|
|
105
|
+
method="nearest",
|
|
106
|
+
assume_sorted=True,
|
|
107
|
+
) # good through here, @27 is -3.11 which is 5.4 m depth
|
|
108
|
+
|
|
109
|
+
### write to outptut ###
|
|
110
|
+
regrid_resample.loc[ # ~150 MB for 5001x7706x4
|
|
111
|
+
dict(
|
|
112
|
+
time=times_select,
|
|
113
|
+
frequency=input_xr.frequency_nominal.values[channel],
|
|
114
|
+
)
|
|
115
|
+
] = resampled
|
|
116
|
+
# print(f"updated {len(times_select)} ping times")
|
|
117
|
+
gc.collect()
|
|
118
|
+
return regrid_resample.values.copy()
|
|
119
|
+
except Exception as err:
|
|
120
|
+
raise RuntimeError(f"Problem finding the dynamodb table, {err}")
|
|
121
|
+
finally:
|
|
122
|
+
gc.collect()
|
|
123
|
+
print("Done interpolating dataset.")
|
|
124
|
+
|
|
125
|
+
#################################################################
|
|
126
|
+
def resample_regrid(
|
|
127
|
+
self,
|
|
128
|
+
ship_name,
|
|
129
|
+
cruise_name,
|
|
130
|
+
sensor_name,
|
|
131
|
+
table_name,
|
|
132
|
+
bucket_name,
|
|
133
|
+
override_select_files=None,
|
|
134
|
+
endpoint_url=None,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
The goal here is to interpolate the dataset against the depth values already populated
|
|
138
|
+
in the existing file level model stores. We open the cruise-level store with model for
|
|
139
|
+
read/write operations. We open the file-level store with Xarray to leverage tools for
|
|
140
|
+
resampling and subsetting the dataset.
|
|
141
|
+
"""
|
|
142
|
+
print("Resample Regrid, Interpolating dataset.")
|
|
143
|
+
try:
|
|
144
|
+
zarr_manager = ZarrManager()
|
|
145
|
+
|
|
146
|
+
output_zarr_store = zarr_manager.open_s3_zarr_store_with_zarr(
|
|
147
|
+
ship_name=ship_name,
|
|
148
|
+
cruise_name=cruise_name,
|
|
149
|
+
sensor_name=sensor_name,
|
|
150
|
+
output_bucket_name=bucket_name,
|
|
151
|
+
endpoint_url=endpoint_url,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
dynamo_db_manager = DynamoDBManager()
|
|
155
|
+
cruise_df = dynamo_db_manager.get_table_as_df(
|
|
156
|
+
cruise_name=cruise_name,
|
|
157
|
+
table_name=table_name,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
#########################################################
|
|
161
|
+
#########################################################
|
|
162
|
+
all_file_names = cruise_df["FILE_NAME"]
|
|
163
|
+
|
|
164
|
+
if override_select_files is not None:
|
|
165
|
+
all_file_names = override_select_files
|
|
166
|
+
|
|
167
|
+
# Iterate files
|
|
168
|
+
for file_name in all_file_names:
|
|
169
|
+
gc.collect()
|
|
170
|
+
file_name_stem = Path(file_name).stem
|
|
171
|
+
print(f"Processing file: {file_name_stem}.")
|
|
172
|
+
|
|
173
|
+
if f"{file_name_stem}.raw" not in list(cruise_df["FILE_NAME"]):
|
|
174
|
+
print("Raw file file_stem not found in dynamodb.")
|
|
175
|
+
raise Exception("Raw file file_stem not found in dynamodb.")
|
|
176
|
+
|
|
177
|
+
# status = PipelineStatus['LEVEL_1_PROCESSING']
|
|
178
|
+
# TODO: filter rows by enum success, filter the dataframe just for enums >= LEVEL_1_PROCESSING
|
|
179
|
+
# df[df['PIPELINE_STATUS'] < PipelineStatus.LEVEL_1_PROCESSING] = np.nan
|
|
180
|
+
|
|
181
|
+
# Get index from all cruise files. Note: should be based on which are included in cruise.
|
|
182
|
+
index = int(
|
|
183
|
+
cruise_df.index[cruise_df["FILE_NAME"] == f"{file_name_stem}.raw"][
|
|
184
|
+
0
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Get input store
|
|
189
|
+
input_xr_zarr_store = zarr_manager.open_s3_zarr_store_with_xarray(
|
|
190
|
+
ship_name=ship_name,
|
|
191
|
+
cruise_name=cruise_name,
|
|
192
|
+
sensor_name=sensor_name,
|
|
193
|
+
file_name_stem=file_name_stem,
|
|
194
|
+
bucket_name=bucket_name,
|
|
195
|
+
endpoint_url=endpoint_url,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
#########################################################################
|
|
199
|
+
# This is the vertical offset of the sensor related to the ocean surface
|
|
200
|
+
# See https://echopype.readthedocs.io/en/stable/data-proc-additional.html
|
|
201
|
+
if "water_level" in input_xr_zarr_store.keys():
|
|
202
|
+
water_level = float(input_xr_zarr_store.water_level.values)
|
|
203
|
+
else:
|
|
204
|
+
water_level = 0.0
|
|
205
|
+
#########################################################################
|
|
206
|
+
# [3] Get needed time indices — along the x-axis
|
|
207
|
+
# Offset from start index to insert new dataset. Note that missing values are excluded.
|
|
208
|
+
ping_time_cumsum = np.insert(
|
|
209
|
+
np.cumsum(
|
|
210
|
+
cruise_df["NUM_PING_TIME_DROPNA"].dropna().to_numpy(dtype=int)
|
|
211
|
+
),
|
|
212
|
+
obj=0,
|
|
213
|
+
values=0,
|
|
214
|
+
)
|
|
215
|
+
start_ping_time_index = ping_time_cumsum[index]
|
|
216
|
+
end_ping_time_index = ping_time_cumsum[index + 1]
|
|
217
|
+
|
|
218
|
+
max_echo_range = np.max( # Should water level go in here?
|
|
219
|
+
(cruise_df["MAX_ECHO_RANGE"] + cruise_df["WATER_LEVEL"])
|
|
220
|
+
.dropna()
|
|
221
|
+
.astype(np.float32)
|
|
222
|
+
)
|
|
223
|
+
# cruise_min_epsilon = np.min(
|
|
224
|
+
# cruise_df["MIN_ECHO_RANGE"].dropna().astype(float)
|
|
225
|
+
# ) # TODO: currently overwriting to 0.25 m
|
|
226
|
+
|
|
227
|
+
all_cruise_depth_values = zarr_manager.get_depth_values(
|
|
228
|
+
max_echo_range=max_echo_range,
|
|
229
|
+
# cruise_min_epsilon=cruise_min_epsilon,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if set(
|
|
233
|
+
input_xr_zarr_store.Sv.dims
|
|
234
|
+
) != { # Cruise dimensions are: (depth, time, frequency)
|
|
235
|
+
"channel",
|
|
236
|
+
"ping_time",
|
|
237
|
+
"range_sample",
|
|
238
|
+
}:
|
|
239
|
+
raise Exception("Xarray dimensions are not as expected.")
|
|
240
|
+
|
|
241
|
+
# indices, geospatial = geo_manager.read_s3_geo_json( # TODO: remove this!!!!
|
|
242
|
+
# ship_name=ship_name,
|
|
243
|
+
# cruise_name=cruise_name,
|
|
244
|
+
# sensor_name=sensor_name,
|
|
245
|
+
# file_name_stem=file_name_stem,
|
|
246
|
+
# input_xr_zarr_store=input_xr_zarr_store,
|
|
247
|
+
# endpoint_url=endpoint_url,
|
|
248
|
+
# output_bucket_name=bucket_name,
|
|
249
|
+
# )
|
|
250
|
+
|
|
251
|
+
input_xr = input_xr_zarr_store # .isel(ping_time=indices)
|
|
252
|
+
|
|
253
|
+
ping_times = input_xr.ping_time.values
|
|
254
|
+
output_zarr_store["time"][start_ping_time_index:end_ping_time_index] = (
|
|
255
|
+
input_xr.ping_time.data
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# --- UPDATING --- # # TODO: problem, this returns dimensionless array
|
|
259
|
+
regrid_resample = self.interpolate_data(
|
|
260
|
+
input_xr=input_xr,
|
|
261
|
+
ping_times=ping_times,
|
|
262
|
+
all_cruise_depth_values=all_cruise_depth_values, # should accommodate the water_level already
|
|
263
|
+
water_level=water_level,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
print(
|
|
267
|
+
f"start_ping_time_index: {start_ping_time_index}, end_ping_time_index: {end_ping_time_index}"
|
|
268
|
+
)
|
|
269
|
+
#########################################################################
|
|
270
|
+
# write Sv values to cruise-level-model-store
|
|
271
|
+
|
|
272
|
+
for fff in range(regrid_resample.shape[-1]):
|
|
273
|
+
output_zarr_store["Sv"][
|
|
274
|
+
: regrid_resample[:, :, fff].shape[0],
|
|
275
|
+
start_ping_time_index:end_ping_time_index,
|
|
276
|
+
fff,
|
|
277
|
+
] = regrid_resample[:, :, fff]
|
|
278
|
+
#########################################################################
|
|
279
|
+
# in the future. See https://github.com/CI-CMG/water-column-sonar-processing/issues/11
|
|
280
|
+
if "detected_seafloor_depth" in list(input_xr.variables):
|
|
281
|
+
print("Adding detected_seafloor_depth to output")
|
|
282
|
+
detected_seafloor_depth = input_xr.detected_seafloor_depth.values
|
|
283
|
+
detected_seafloor_depth[detected_seafloor_depth == 0.0] = np.nan
|
|
284
|
+
|
|
285
|
+
# As requested, use the lowest frequencies to determine bottom
|
|
286
|
+
detected_seafloor_depths = detected_seafloor_depth[0, :]
|
|
287
|
+
|
|
288
|
+
detected_seafloor_depths[detected_seafloor_depths == 0.0] = np.nan
|
|
289
|
+
print(f"min depth measured: {np.nanmin(detected_seafloor_depths)}")
|
|
290
|
+
print(f"max depth measured: {np.nanmax(detected_seafloor_depths)}")
|
|
291
|
+
output_zarr_store["bottom"][
|
|
292
|
+
start_ping_time_index:end_ping_time_index
|
|
293
|
+
] = detected_seafloor_depths
|
|
294
|
+
#
|
|
295
|
+
#########################################################################
|
|
296
|
+
# [5] write subset of latitude/longitude
|
|
297
|
+
# output_zarr_store["latitude"][
|
|
298
|
+
# start_ping_time_index:end_ping_time_index
|
|
299
|
+
# ] = geospatial.dropna()[
|
|
300
|
+
# "latitude"
|
|
301
|
+
# ].values # TODO: get from ds_sv directly, dont need geojson anymore
|
|
302
|
+
# output_zarr_store["longitude"][
|
|
303
|
+
# start_ping_time_index:end_ping_time_index
|
|
304
|
+
# ] = geospatial.dropna()["longitude"].values
|
|
305
|
+
#########################################################################
|
|
306
|
+
output_zarr_store["latitude"][
|
|
307
|
+
start_ping_time_index:end_ping_time_index
|
|
308
|
+
] = input_xr_zarr_store.latitude.dropna(dim="ping_time").values
|
|
309
|
+
output_zarr_store["longitude"][
|
|
310
|
+
start_ping_time_index:end_ping_time_index
|
|
311
|
+
] = input_xr_zarr_store.longitude.dropna(dim="ping_time").values
|
|
312
|
+
#########################################################################
|
|
313
|
+
except Exception as err:
|
|
314
|
+
raise RuntimeError(f"Problem with resample_regrid, {err}")
|
|
315
|
+
finally:
|
|
316
|
+
print("Exiting resample_regrid.")
|
|
317
|
+
# TODO: read across times and verify dataset was written?
|
|
318
|
+
gc.collect()
|
|
319
|
+
|
|
320
|
+
#######################################################
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
###########################################################
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .elevation_manager import ElevationManager
|
|
2
|
+
from .geometry_manager import GeometryManager
|
|
3
|
+
from .line_simplification import LineSimplification
|
|
4
|
+
from .pmtile_generation import PMTileGeneration
|
|
5
|
+
from .spatiotemporal import Spatiotemporal
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ElevationManager",
|
|
9
|
+
"GeometryManager",
|
|
10
|
+
"LineSimplification",
|
|
11
|
+
"PMTileGeneration",
|
|
12
|
+
"Spatiotemporal",
|
|
13
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
https://gis.ngdc.noaa.gov/arcgis/rest/services/DEM_mosaics/DEM_global_mosaic/ImageServer/identify?geometry=-31.70235%2C13.03332&geometryType=esriGeometryPoint&returnGeometry=false&returnCatalogItems=false&f=json
|
|
3
|
+
|
|
4
|
+
https://gis.ngdc.noaa.gov/arcgis/rest/services/DEM_mosaics/DEM_global_mosaic/ImageServer/
|
|
5
|
+
identify?
|
|
6
|
+
geometry=-31.70235%2C13.03332
|
|
7
|
+
&geometryType=esriGeometryPoint
|
|
8
|
+
&returnGeometry=false
|
|
9
|
+
&returnCatalogItems=false
|
|
10
|
+
&f=json
|
|
11
|
+
{"objectId":0,"name":"Pixel","value":"-5733","location":{"x":-31.702349999999999,"y":13.03332,"spatialReference":{"wkid":4326,"latestWkid":4326}},"properties":null,"catalogItems":null,"catalogItemVisibilities":[]}
|
|
12
|
+
-5733
|
|
13
|
+
|
|
14
|
+
(base) rudy:deleteME rudy$ curl https://api.opentopodata.org/v1/gebco2020?locations=13.03332,-31.70235
|
|
15
|
+
{
|
|
16
|
+
"results": [
|
|
17
|
+
{
|
|
18
|
+
"dataset": "gebco2020",
|
|
19
|
+
"elevation": -5729.0,
|
|
20
|
+
"location": {
|
|
21
|
+
"lat": 13.03332,
|
|
22
|
+
"lng": -31.70235
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"status": "OK"
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import time
|
|
32
|
+
from collections.abc import Generator
|
|
33
|
+
|
|
34
|
+
import requests
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def chunked(ll: list, n: int) -> Generator:
|
|
38
|
+
# Yields successively n-sized chunks from ll.
|
|
39
|
+
for i in range(0, len(ll), n):
|
|
40
|
+
yield ll[i : i + n]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ElevationManager:
|
|
44
|
+
#######################################################
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
):
|
|
48
|
+
self.DECIMAL_PRECISION = 5 # precision for GPS coordinates
|
|
49
|
+
self.TIMEOUT_SECONDS = 10
|
|
50
|
+
|
|
51
|
+
#######################################################
|
|
52
|
+
def get_arcgis_elevation(
|
|
53
|
+
self,
|
|
54
|
+
lngs: list,
|
|
55
|
+
lats: list,
|
|
56
|
+
chunk_size: int = 500, # I think this is the api limit
|
|
57
|
+
) -> int:
|
|
58
|
+
# Reference: https://developers.arcgis.com/rest/services-reference/enterprise/map-to-image/
|
|
59
|
+
# Info: https://www.arcgis.com/home/item.html?id=c876e3c96a8642ab8557646a3b4fa0ff
|
|
60
|
+
### 'https://gis.ngdc.noaa.gov/arcgis/rest/services/DEM_mosaics/DEM_global_mosaic/ImageServer/identify?geometry={"points":[[-31.70235,13.03332],[-32.70235,14.03332]]}&geometryType=esriGeometryMultipoint&returnGeometry=false&returnCatalogItems=false&f=json'
|
|
61
|
+
if len(lngs) != len(lats):
|
|
62
|
+
raise ValueError("lngs and lats must have same length")
|
|
63
|
+
|
|
64
|
+
geometryType = "esriGeometryMultipoint" # TODO: allow single point?
|
|
65
|
+
|
|
66
|
+
depths = []
|
|
67
|
+
|
|
68
|
+
list_of_points = [list(elem) for elem in list(zip(lngs, lats))]
|
|
69
|
+
for chunk in chunked(list_of_points, chunk_size):
|
|
70
|
+
time.sleep(0.1)
|
|
71
|
+
# order: (lng, lat)
|
|
72
|
+
geometry = f'{{"points":{str(chunk)}}}'
|
|
73
|
+
url = f"https://gis.ngdc.noaa.gov/arcgis/rest/services/DEM_mosaics/DEM_global_mosaic/ImageServer/identify?geometry={geometry}&geometryType={geometryType}&returnGeometry=false&returnCatalogItems=false&f=json"
|
|
74
|
+
result = requests.get(url, timeout=self.TIMEOUT_SECONDS)
|
|
75
|
+
res = json.loads(result.content.decode("utf8"))
|
|
76
|
+
if "results" in res:
|
|
77
|
+
for element in res["results"]:
|
|
78
|
+
depths.append(float(element["value"]))
|
|
79
|
+
elif "value" in res:
|
|
80
|
+
depths.append(float(res["value"]))
|
|
81
|
+
|
|
82
|
+
return depths
|
|
83
|
+
|
|
84
|
+
# def get_gebco_bathymetry_elevation(self) -> int:
|
|
85
|
+
# # Documentation: https://www.opentopodata.org/datasets/gebco2020/
|
|
86
|
+
# latitude = 13.03332
|
|
87
|
+
# longitude = -31.70235
|
|
88
|
+
# dataset = "gebco2020"
|
|
89
|
+
# url = f"https://api.opentopodata.org/v1/{dataset}?locations={latitude},{longitude}"
|
|
90
|
+
# pass
|
|
91
|
+
|
|
92
|
+
# def get_elevation(
|
|
93
|
+
# self,
|
|
94
|
+
# df,
|
|
95
|
+
# lat_column,
|
|
96
|
+
# lon_column,
|
|
97
|
+
# ) -> int:
|
|
98
|
+
# """Query service using lat, lon. add the elevation values as a new column."""
|
|
99
|
+
# url = r'https://epqs.nationalmap.gov/v1/json?'
|
|
100
|
+
# elevations = []
|
|
101
|
+
# for lat, lon in zip(df[lat_column], df[lon_column]):
|
|
102
|
+
# # define rest query params
|
|
103
|
+
# params = {
|
|
104
|
+
# 'output': 'json',
|
|
105
|
+
# 'x': lon,
|
|
106
|
+
# 'y': lat,
|
|
107
|
+
# 'units': 'Meters'
|
|
108
|
+
# }
|
|
109
|
+
# result = requests.get((url + urllib.parse.urlencode(params)))
|
|
110
|
+
# elevations.append(result.json()['value'])
|
|
111
|
+
# return elevations
|