344 lines
12 KiB
Python
344 lines
12 KiB
Python
import sys
|
|
sys.dont_write_bytecode = True
|
|
|
|
import math
|
|
from adcptool.vector import Vector
|
|
|
|
#
|
|
# handy functions
|
|
#
|
|
|
|
def compute_direction_angle(vec):
|
|
''' compute the direction angle based on direction vector vector
|
|
'''
|
|
|
|
# two points --> direction angle
|
|
dx = vec.x
|
|
dy = vec.y
|
|
|
|
# handling the quadrants
|
|
if dy == 0 and dx == 0:
|
|
# raise NotImplementedError('zero length vectors dont have any direction')
|
|
return 0
|
|
if dy == 0 and dx > 0:
|
|
# "0"
|
|
return 0.5 * math.pi
|
|
elif dy == 0 and dx < 0:
|
|
# "0"
|
|
return 1.5 * math.pi
|
|
elif dy < 0 and dx < 0:
|
|
# II + III
|
|
return math.atan(dx/dy) + math.pi
|
|
elif dy < 0 and dx > 0:
|
|
# IV
|
|
return math.atan(dx/dy) + math.pi
|
|
else:
|
|
# I
|
|
return math.atan(dx/dy)
|
|
|
|
def unitconversion(unit,p):
|
|
''' return the conversion factor from [unit]^p to [meters]^p
|
|
'''
|
|
# factors from X to meter
|
|
d = dict(cm=0.01, ft=0.3048)
|
|
return math.pow(d[unit],p)
|
|
|
|
def print_type(o):
|
|
''' print out the type of the given thing. useful for debugging.
|
|
'''
|
|
print(type(o).__name__)
|
|
|
|
|
|
#
|
|
# the actual objects
|
|
#
|
|
|
|
class ProcessedProfileObj():
|
|
''' holds a stack of finished (processed) ensembles
|
|
(and some extra data)
|
|
'''
|
|
|
|
#
|
|
# methods needed for defining the profile itself
|
|
def compute_direction_vector(self, a, b=False):
|
|
if b != False:
|
|
# two vectors --> direction vector
|
|
return (b - a).norm()
|
|
else:
|
|
# angle --> direction vector
|
|
return Vector(math.cos(a), math.sin(a))
|
|
|
|
|
|
def compute_direction_angle(self, vec):
|
|
# two points --> direction angle
|
|
dx = vec.x
|
|
dy = vec.y
|
|
|
|
# handling the quadrants
|
|
if dy == 0 and dx > 0:
|
|
# "0"
|
|
return 0.5 * math.pi
|
|
elif dy == 0 and dx < 0:
|
|
# "0"
|
|
return 1.5 * math.pi
|
|
elif dy < 0 and dx < 0:
|
|
# II + III
|
|
return math.atan(dx/dy) + math.pi
|
|
elif dy < 0 and dx > 0:
|
|
# IV
|
|
return math.atan(dx/dy) + math.pi
|
|
else:
|
|
# I
|
|
return math.atan(dx/dy)
|
|
|
|
def update_velocities(self, vm=None, vgm=None):
|
|
''' write back modified velocities into this object.
|
|
|
|
arguments:
|
|
vm ... velocity matrix. a 3d numpy array
|
|
vgm ... good value matrix belonging to vm. boolean numpy array, True if value is good, bad otherwise.
|
|
should match the masking in this object.
|
|
'''
|
|
|
|
# if (vm.all() == None) or (vgm.all() == None):
|
|
if (vm.all() == None) | (vgm.all() == None):
|
|
# only update vepth averaged velocities.
|
|
for i in range(len(self.ensembles)):
|
|
if not self.ensembles[i].void:
|
|
self.ensembles[i].velocity = self.ensembles[i].comp_avg_velocity()
|
|
|
|
else:
|
|
(rows, columns, dimension) = vm.shape
|
|
|
|
# velocities in cells
|
|
for i in range(columns):
|
|
if not self.ensembles[i].void: # for each ensemble
|
|
for r in range(len(self.ensembles[i].cells)):
|
|
self.ensembles[i].cells[r].velocity = Vector(vm[r,i].tolist())
|
|
|
|
# depth averaged velocities in ensembles
|
|
self.ensembles[i].velocity = self.ensembles[i].comp_avg_velocity()
|
|
|
|
|
|
|
|
def __init__(self, rawprofile, processing_settings, global_profile_position = False):
|
|
''' object to store the processed ADCP data.
|
|
|
|
attributes for this object:
|
|
self.start_dir direction of the profile, given in [rad], starting from north, clockwise
|
|
self.start_dir_vec unit vector version of the above, in X/Y coordinates
|
|
self.start_pos Vector() in X/Y/Z where the profile starts
|
|
self.start_offset distance from the first ensemble to the start_pos
|
|
(its a Vector() too; useful if there is no start_dir given and when computing absolute coordinates)
|
|
self.ensembles list where the ProcessedEnsembleObj()'s will be stored
|
|
'''
|
|
|
|
self._cfg = processing_settings # saved for later usage
|
|
|
|
#
|
|
# define the profile which will eventually be used
|
|
|
|
if global_profile_position != False:
|
|
self.start_pos = global_profile_position['start']
|
|
|
|
|
|
if 'offset' in global_profile_position:
|
|
self.start_offset = global_profile_position['offset']
|
|
else:
|
|
self.start_offset = Vector(0,0)
|
|
|
|
|
|
if 'dir' in global_profile_position:
|
|
# angle is defined, compute direction vector
|
|
self.start_dir = global_profile_position['dir']
|
|
self.start_dir_vec = self.compute_direction_vector(self.start_dir)
|
|
|
|
elif 'end' in global_profile_position:
|
|
# 2nd point was given, compute angle and direction vector
|
|
self.start_dir_vec = self.compute_direction_vector(global_profile_position['start'], global_profile_position['end'])
|
|
self.start_dir = self.compute_direction_angle(self.start_dir_vec)
|
|
|
|
else:
|
|
# no hint was given, use last ensemble as 2nd point and then do the same as above
|
|
endpos_n = rawprofile.ensembles[len(rawprofile.ensembles)-1].total_distance_traveled_north
|
|
endpos_e = rawprofile.ensembles[len(rawprofile.ensembles)-1].total_distance_traveled_east
|
|
|
|
self.start_dir_vec = (Vector(endpos_e, endpos_n) - self.start_offset).norm()
|
|
self.start_dir = self.compute_direction_angle(self.start_dir_vec)
|
|
|
|
|
|
#
|
|
# process ensembles
|
|
|
|
self.ensembles = []
|
|
|
|
for ensemble in rawprofile.ensembles:
|
|
self.ensembles.append(ProcessedEnsembleObj(ensemble, self.start_pos, self.start_dir, self.start_dir_vec, self.start_offset, processing_settings))
|
|
|
|
self.cell_height = rawprofile.depth_cell_length * 0.01 # in the ascii file its always in cm, here its always in m, so this can be hard coded.
|
|
|
|
|
|
class ProcessedEnsembleObj():
|
|
'''
|
|
object to hold the finished (processed) ensemble, ready for the output
|
|
|
|
attributes:
|
|
self.velocity a Vector() that holds the velocity in U/V coordinates
|
|
sel.position a Vector() that holds the position of the ensemble in X/Y/Z
|
|
(currently we have self.position_{a,b,c} for different methods to store the things)
|
|
'''
|
|
|
|
|
|
|
|
def comp_avg_velocity(self, method=0):
|
|
''' compute and return depth averaged velocity
|
|
kwargs:
|
|
method int define method for averaging. 0 is the only supported one
|
|
'''
|
|
if method == 0:
|
|
sum_v_comp = Vector(0,0,0)
|
|
for i in range(len(self.cells)):
|
|
sum_v_comp += self.cells[i].velocity
|
|
|
|
return sum_v_comp / len(self.cells)
|
|
|
|
else:
|
|
raise Exception('method no {} not implemented'.format(method))
|
|
|
|
|
|
def __init__(self, ensemble, profile_start, profile_dir, profile_dir_vec, profile_offset, settings):
|
|
|
|
#
|
|
# velocity processing
|
|
|
|
# its the conversion factor from the unit defined in the raw data to meters.
|
|
conversionfactor = unitconversion(ensemble.measurement_units, 1)
|
|
|
|
if ensemble.number_of_good_cells != 0:
|
|
# sometimes there is no good cell and hence no velocity,
|
|
# (needed if someone wants to skip these)
|
|
self.void = False
|
|
|
|
# process the velocity in each cell
|
|
if 'dimensions' not in settings or settings['dimensions'] == 3:
|
|
self.cells = []
|
|
for c in ensemble.cells:
|
|
self.cells.append(ProcessedCellObj(
|
|
c, profile_start, profile_dir, profile_dir_vec,
|
|
profile_offset, settings, conversionfactor))
|
|
|
|
# compute the averaged velocity
|
|
if 'dimensions' not in settings or settings['dimensions'] == 2:
|
|
self.velocity = Vector()
|
|
# get averaged velocity (should be a Vector object)
|
|
avg_method = settings['avg_method'] if 'avg_method' in settings else 0
|
|
self.velocity = self.comp_avg_velocity(avg_method)
|
|
|
|
# rotation
|
|
if 'uv_rot' in settings and settings['uv_rot']:
|
|
#trans_uvrot(profile_dir)
|
|
base_rot = math.pi / 2
|
|
self.velocity = self.velocity.rot(base_rot + profile_dir)
|
|
|
|
|
|
else:
|
|
self.void = True
|
|
|
|
#
|
|
# position processing
|
|
|
|
|
|
# processing the 4 depths and averaging
|
|
self.four_depths = []
|
|
self.four_depths += ensemble.depth_reading
|
|
|
|
self.depth = sum(ensemble.depth_reading) / 4
|
|
|
|
# z-value of the river ground for this ensemble
|
|
#height = Vector(0,0, profile_start.z - self.depth)
|
|
height = Vector(0,0, profile_start.z)
|
|
|
|
dmg = ensemble.total_distance_made_good
|
|
self.dmg = dmg
|
|
|
|
if settings['proj_method'] == 1:
|
|
|
|
# position on the profile (based on total distance made good)
|
|
self.position = profile_dir_vec * dmg + profile_start + height
|
|
|
|
elif settings['proj_method'] == 2:
|
|
# position according to movement data
|
|
self.position = Vector(
|
|
ensemble.total_distance_traveled_east,
|
|
ensemble.total_distance_traveled_north) \
|
|
- profile_offset + profile_start + height
|
|
|
|
|
|
elif settings['proj_method'] == 3:
|
|
# position from movement data, projected on the profile
|
|
self.position = Vector(
|
|
ensemble.total_distance_traveled_east,
|
|
ensemble.total_distance_traveled_north).proj(profile_dir_vec) \
|
|
- profile_offset + profile_start + height
|
|
#p3 = (p2-profile_start).proj(profile_dir_vec) + profile_start
|
|
|
|
else:
|
|
raise NotImplementedError('you specified an invalid (or no) proj_method!')
|
|
|
|
|
|
|
|
|
|
class ProcessedCellObj():
|
|
def __init__(self, cell, profile_start, profile_dir, profile_dir_vec, profile_offset, settings, conversionfactor):
|
|
'''
|
|
object to hold a finished (processed) cell , ready for the output
|
|
|
|
attributes:
|
|
self.velocity a Vector() that holds the velocity in u/v/w coordinates
|
|
self.z_position a Vector() that holds the position of the cell in x/y/z coords
|
|
'''
|
|
|
|
# it's a measured cell
|
|
self.artificial = False
|
|
|
|
|
|
#
|
|
# velocity processing again
|
|
self.velocity = Vector()
|
|
|
|
# switch coordinates
|
|
# self.velocity.x = cell.velocity_comp.y # east value
|
|
# self.velocity.y = cell.velocity_comp.x # north value
|
|
# self.velocity.z = cell.velocity_comp.z # up value
|
|
|
|
self.velocity = Vector(cell.velocity_comp.x, cell.velocity_comp.y, cell.velocity_comp.z)
|
|
|
|
|
|
|
|
# rotation
|
|
if 'uv_rot' in settings and settings['uv_rot']:
|
|
# base_rot = math.pi / 2
|
|
self.velocity = self.velocity.rot(profile_dir)
|
|
|
|
# unit conversion
|
|
self.velocity *= conversionfactor
|
|
|
|
#
|
|
# position processing
|
|
|
|
self.z_position = profile_start[2] - (cell.depth)
|
|
if 'flip_z' in settings and settings['flip_z']:
|
|
self.z_position *= -1
|
|
|
|
|
|
class ArtificialCellObj():
|
|
def __init__(self, position, velocitiy):
|
|
'''
|
|
like ProcessedCellObj but created from extrapolated data.
|
|
'''
|
|
|
|
self.artificial = True
|
|
self.z_position = position
|
|
self.velocity = velocitiy
|
|
|
|
|