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