def get_bearings(arc, mat, delta, rot): ''' Calculate the bearings from the matrix and delta value ''' bearing_in = arc.get('BearingIn') bearing_out = arc.get('BearingOut') bearings = [] for _i in range(0, 6): bearings.append(_GEO.FUNC[6][_i](mat.A[6][_i], delta, rot)) _b = [_v % C.TWO_PI for _v in bearings[0:6] if Utils.to_float(_v)] if _b: #check to ensure all tangent start bearing values are identical if not Support.within_tolerance(_b): return None #default to calculated if different from supplied bearing if not Support.within_tolerance(_b[0], bearing_in): bearing_in = _b[0] if not bearing_in: return None _b_out = bearing_in + (delta * rot) if not Support.within_tolerance(_b_out, bearing_out): bearing_out = _b_out _row = mat.A[6] _rad = [_row[0], _row[1]] _tan = [bearing_in, bearing_out] _int = [_row[4], _row[5]] if not Utils.to_float(_rad[0]): _rad[0] = bearing_in - rot * (C.HALF_PI) if not Utils.to_float(_rad[1]): _rad[1] = _rad[0] + rot * delta if not Utils.to_float(_int[0]): _int[0] = _rad[0] + rot * (delta / 2.0) if not Utils.to_float(_int[1]): _int[1] = _rad[0] + rot * ((math.pi + delta) / 2.0) mat_bearings = {'Radius': _rad, 'Tangent': _tan, 'Internal': _int} return { 'BearingIn': bearing_in, 'BearingOut': bearing_out, 'Bearings': mat_bearings }
def discretize_geometry(self): ''' Discretizes the alignment geometry to a series of vector points ''' interval = self.Object.Seg_Value interval_type = self.Object.Method geometry = self.geometry['geometry'] points = [[self.geometry['meta']['Start']]] last_curve = None #discretize each arc in the geometry list, #store each point set as a sublist in the main points list for curve in geometry: if not curve: continue if curve['Type'] == 'arc': points.append(Arc.get_points(curve, interval, interval_type)) #if curve['Type'] == 'line': # points.append([curve['Start'], curve['End']]) last_curve = curve #store the last point of the first geometry for the next iteration _prev = points[0][-1] result = points[0] if not (_prev and result): return None #iterate the point sets, adding them to the result set #and eliminating any duplicate points for item in points[1:]: if _prev.sub(item[0]).Length < 0.0001: result.extend(item[1:]) else: result.extend(item) _prev = item[-1] last_tangent = abs(self.geometry['meta']['Length'] - last_curve['InternalStation'][1]) if not Support.within_tolerance(last_tangent): _vec = Support.vector_from_angle( last_curve['BearingOut']).multiply(last_tangent) last_point = result[-1] result.append(last_point.add(_vec)) #print('\n<><><><><>\n', result) return result
def validate_coordinates(self): ''' Iterate the geometry, testing for incomplete / incorrect station / coordinate values Fix them where possible, error if values cannot be resolved ''' #calculate distance bewteen curve start and end using #internal station and coordinate vectors _datum = self.geometry['meta'] _geo_data = self.geometry['geometry'] _prev_geo = { 'End': _datum['Start'], 'InternalStation': (0.0, 0.0), 'StartStation': _datum['StartStation'], 'Length': 0.0 } for _geo in _geo_data: if not _geo: continue #get the vector between the two gemetries and the station distance _vector = _geo['Start'].sub(_prev_geo['End']) _sta_len = abs(_geo['InternalStation'][0] - _prev_geo['InternalStation'][1]) #calculate the difference between the vector length and station distance #in document units _delta = (_vector.Length - _sta_len) / Units.scale_factor() #if the stationing / coordinates are out of tolerance, #determine if the error is with the coordinate vector or station if not Support.within_tolerance(_delta): bearing_angle = Support.get_bearing(_vector) #if the coordinate vector bearing matches, fix the station if Support.within_tolerance(bearing_angle, _geo['BearingIn']): _geo['InternalStation'] = ( _prev_geo['InternalStation'][1] + _vector.Length, _geo['InternalStation'][0]) _geo['StartStation'] = _prev_geo['StartStation'] + \ _prev_geo['Length'] / Units.scale_factor() + \ _vector.Length / Units.scale_factor() #otherwise, fix the coordinate else: _bearing_vector = Support.vector_from_angle( _geo['BearingIn']) _bearing_vector.multiply(_sta_len) _geo['Start'] = _prev_geo['End'].add(_bearing_vector) _prev_geo = _geo
def get_parameters(line): ''' Return a fully-defined line ''' _coord_truth = [not line.get('Start') is None, not line.get('End') is None] _param_truth = [ not line.get('BearingIn') is None, not line.get('Length') is None ] #both coordinates defined _case_one = all(_coord_truth) #only one coordinate defined, plus both length and bearing _case_two = any(_coord_truth) and not all(_coord_truth) and all( _param_truth) if _case_one: line_vec = line['End'].sub(line['Start']) _bearing = Support.get_bearing(line_vec) _length = line_vec.Length #test for missing parameters, preserving the existing ones if line.get('BearingIn'): if Support.within_tolerance(line['BearingIn'], _bearing): _bearing = line['BearingIn'] line['BearingIn'] = line['BearingOut'] = _bearing if line.get('Length'): if Support.within_tolerance(line['Length'], _length): _length = line['Length'] line['Length'] = _length elif _case_two: _vec = Support.vector_from_angle(line['BearingIn']).multiply( line['Length']) if line.get('Start'): line['End'] = line['Start'].add(_vec) else: line['Start'] = line['End'].add(_vec) else: print('Unable to calculate line parametters') result = None if _case_one or _case_two: result = {**{'Type': 'line'}, **line} return result
def run_test(arc, comp, excludes): import copy dct = copy.deepcopy(arc) if excludes: for _exclude in excludes: dct[_exclude] = None result = convert_units(get_parameters(convert_units(dct)), True) print('----------- Comparison errors: ------------- \n') print('Exclusions: ', excludes) for _k, _v in comp.items(): _w = result[_k] _x = _v if isinstance(_v, App.Vector): _x = _v.Length _w = _w.Length if not Support.within_tolerance(_x, _w): print('Mismatch on %s: %f (%f)' % (_k, _w, _x)) return result
def validate_alignment(self): ''' Ensure the alignment geometry is continuous. Any discontinuities (gaps between end / start coordinates) must be filled by a completely defined line ''' _prev_coord = self.geometry['meta']['Start'] _geo_list = [] for _geo in self.geometry['geometry']: if not _geo: continue _coord = _geo['Start'] if not Support.within_tolerance(_coord.Length, _prev_coord.Length): #build the line using the provided parameters and add it _geo_list.append( Line.get_parameters({ 'Start': App.Vector(_prev_coord), 'End': App.Vector(_coord), 'BearingIn': _geo['BearingIn'], 'BearingOut': _geo['BearingOut'], })) _geo_list.append(_geo) _prev_coord = _geo['End'] self.geometry['geometry'] = _geo_list
def get_lengths(arc, mat): ''' Get needed parameters for arc calculation from the user-defined arc and the calculated vector matrix ''' #[0,1] = Radius; [2, 3] = Tangent, [4] = Middle, [5] = Chord lengths = mat.diagonal().A[0] params = [ arc.get('Radius'), arc.get('Tangent'), arc.get('Middle'), arc.get('Chord'), arc.get('Delta') ] for _i in range(0, 2): #get two consecutive elements, saving only if they're valid _s = [_v for _v in lengths[_i * 2:(_i + 1) * 2] if _v] #skip the rest if not defined, we'll use the user values if not _s: continue #if both were calculated and they aren't the same, quit if all(_s) and not Support.within_tolerance(_s): return None if _s[0] and Support.within_tolerance(_s[0], params[_i]): continue params[_i] = _s[0] #test middle and chord, if no user-defined value or out-of-tolerance, use calculated for _i in range(4, 6): if lengths[_i] and Support.within_tolerance(lengths[_i], params[_i - 2]): continue params[_i - 2] = lengths[_i] return {'Radius': params[0], 'Tangent': params[1], 'Chord': params[3]}
def get_scalar_matrix(vecs): ''' Calculate the square matrix of scalars for the provided vectors ''' #ensure list is a list of lists (not vectors) #and create the matrix mat_list = [list(_v) if _v else [0, 0, 0] for _v in vecs] rot_list = [0.0] * 7 #get rotation direction for vector bearings for _i in range(0, 6): rot_list[_i] = Support.get_rotation(C.UP, vecs[_i]) mat_list.append(list(C.UP)) mat = numpy.matrix(mat_list) result = mat * mat.T #abort for non-square matrices if result.shape[0] != result.shape[1] != 6: return None #calculate the magnitudes first (minus the UP vector) for _i in range(0, 6): result.A[_i][_i] = math.sqrt(result.A[_i][_i]) #calculate the delta for the lower left side for _i in range(0, 7): _d1 = result.A[_i][_i] for _j in range(0, _i): _denom = _d1 * result.A[_j][_j] _n = result.A[_i][_j] _angle = None if not (any([math.isnan(_v) for _v in [_denom, _n]]) or _denom == 0.0): _angle = math.acos(_n / _denom) #compute the arc central angle for all but the last row if _angle: if _i < 6: _angle = _GEO.FUNC[_i][_j](_angle) else: _angle *= rot_list[_j] if _angle < 0.0: _angle += C.TWO_PI result.A[_i][_j] = _angle #lower left half contains angles, diagonal contains scalars return result
def get_rotation(arc, vecs): ''' Determine the dirction of rotation ''' v1 = [_v for _v in vecs[0:3] if _v and _v != App.Vector()] v2 = [_v for _v in vecs[3:] if _v and _v != App.Vector()] if not (v1 and v2): return {'Direction': arc.get('Direction')} return {'Direction': Support.get_rotation(v1[0], v2[1])}
def get_missing_parameters(arc, new_arc): ''' Calculate any missing parameters from the original arc using the values from the new arc. These include: - Chord - Middle Ordinate - Tangent - Length - External distance ''' #by this point, missing radius / delta is a problem if new_arc.get('Radius') is None or new_arc.get('Delta') is None: return None #pre-calculate values and fill in remaining parameters radius = new_arc['Radius'] delta = new_arc['Delta'] half_delta = delta / 2.0 new_arc['Length'] = radius * delta new_arc['External'] = radius * ((1.0 / math.cos(half_delta)) - 1.0) new_arc['MiddleOrdinate'] = radius * (1.0 - math.cos(half_delta)) if not new_arc.get('Tangent'): new_arc['Tangent'] = radius * math.tan(half_delta) if not new_arc.get('Chord'): new_arc['Chord'] = 2.0 * radius * math.sin(half_delta) #quality-check - ensure everything is defined and default to existing where within tolerance _keys = ['Chord', 'MiddleOrdinate', 'Tangent', 'Length', 'External'] existing_vals = [arc.get(_k) for _k in _keys] new_vals = [new_arc.get(_k) for _k in _keys] vals = {} for _i, _v in enumerate(_keys): vals[_v] = existing_vals[_i] #if values are close enough, then keep existing if Support.within_tolerance(vals[_v], new_vals[_i]): continue #out of tolerance or existing is None - use the calculated value vals[_v] = new_vals[_i] return vals
def validate_stationing(self): ''' Iterate the geometry, calculating the internal start station based on the actual station and storing it in an 'InternalStation' parameter tuple for the start and end of the curve ''' prev_station = self.geometry['meta'].get('StartStation') prev_coord = self.geometry['meta'].get('Start') if not prev_coord or not prev_station: print('Unable to validate alignment stationing') return for _geo in self.geometry['geometry']: if not _geo: continue _geo['InternalStation'] = None geo_station = _geo.get('StartStation') geo_coord = _geo['Start'] #if no station is provided, try to infer it from the start coordinate #and the previous station if geo_station is None: geo_station = prev_station if not geo_coord: geo_coord = prev_coord delta = geo_coord.sub(prev_coord).Length if not Support.within_tolerance(delta): geo_station += delta / Units.scale_factor() _geo['StartStation'] = geo_station prev_coord = _geo['End'] prev_station = _geo[ 'StartStation'] + _geo['Length'] / Units.scale_factor() int_sta = self._get_internal_station(geo_station) _geo['InternalStation'] = (int_sta, int_sta + _geo['Length'])
def get_coordinates(arc, points): ''' Fill in any missing coordinates using arc parameters ''' vectors = {} for _k, _v in arc['Bearings'].items(): vectors[_k] = [Support.vector_from_angle(_x) for _x in _v] _start = points[0] _end = points[1] _center = points[2] _pi = points[3] _vr = vectors['Radius'][0].multiply(arc['Radius']) _vt = vectors['Tangent'][0].multiply(arc['Tangent']) _vc = vectors['Internal'][1].multiply(arc['Chord']) if not _start: if _pi: _start = _pi.sub(_vt) elif _center: _start = _center.add(_vr) elif _end: _start = _end.sub(_vc) if not _start: return None if not _pi: _pi = _start.add(_vt) if not _center: _center = _start.sub(_vr) if not _end: _end = _start.add(_vc) return {'Start': _start, 'Center': _center, 'End': _end, 'PI': _pi}
def validate_bearings(self): ''' Validate the bearings between geometry, ensuring they are equal ''' geo_data = self.geometry['geometry'] if len(geo_data) < 2: return True if geo_data[0] is None: return False prev_bearing = geo_data[0]['BearingOut'] for _geo in geo_data[1:]: if not _geo: continue _b = _geo.get('BearingIn') if _b is None: self.errors.append( 'Invalid bearings ({0:.4f}, {1:.4f}) at curve {2}'.format( prev_bearing, _b, _geo)) return False if not Support.within_tolerance(_b, prev_bearing): self.errors.append( 'Bearing mismatch ({0:.4f}, {1:.4f}) at curve {2}'.format( prev_bearing, _b, _geo)) return False prev_bearing = _geo.get('BearingOut') return True
def test_vector_from_angle(self): self.assertIsNone( Support.vector_from_angle('1fdsa.0'), 'Supprt.vector_from_angle() fails non-float test' )
def get_parameters(arc): #Vector order: #Radius in / out, Tangent in / out, Middle, and Chord points = [ arc.get('Start'), arc.get('End'), arc.get('Center'), arc.get('PI') ] point_count = len([_v for _v in points if _v]) #define the curve start at the origin if none is provided if point_count == 0: points[0] = App.Vector() vecs = [ Support.safe_sub(arc.get('Start'), arc.get('Center'), True), Support.safe_sub(arc.get('End'), arc.get('Center'), True), Support.safe_sub(arc.get('PI'), arc.get('Start'), True), Support.safe_sub(arc.get('End'), arc.get('PI'), True), Support.safe_sub(arc.get('PI'), arc.get('Center'), True), Support.safe_sub(arc.get('End'), arc.get('Start'), True) ] result = {'Type': 'arc'} mat = get_scalar_matrix(vecs) _p = get_lengths(arc, mat) if not _p: print( 'Invalid curve definition: cannot determine radius / tangent lengths' ) return None result.update(_p) _p = get_delta(arc, mat) if not _p: print('Invalid curve definition: cannot determine central angle') return None result.update(_p) _p = get_rotation(arc, vecs) if not _p: print('Invalid curve definition: cannot determine curve direction') return None result.update(_p) _p = get_bearings(arc, mat, result['Delta'], result['Direction']) if not _p: print('Invalid curve definition: cannot determine curve bearings') return None result.update(_p) _p = get_missing_parameters(result, result) if not _p: print('Invalid curve definition: cannot calculate all parameters') return None result.update(_p) _p = get_coordinates(result, points) if not _p: print('Invalid curve definition: cannot calculate coordinates') return None result.update(_p) #get rid of the Bearings dict since we're done using it result.pop('Bearings') #merge the result with the original dict to preserve other values return {**arc, **result}
def validate_datum(self): ''' Ensure the datum is valid, assuming 0+00 / (0,0,0) for station and coordinate where none is suplpied and it cannot be inferred fromt the starting geometry ''' _datum = self.geometry['meta'] _geo = self.geometry['geometry'][0] if not _geo or not _datum: print('Unable to validate alignment datum') return _datum_truth = [ not _datum.get('StartStation') is None, not _datum.get('Start') is None ] _geo_truth = [ not _geo.get('StartStation') is None, not _geo.get('Start') is None ] #---------------------------- #CASE 0 #---------------------------- #both defined? nothing to do if all(_datum_truth): return _geo_station = 0 _geo_start = App.Vector() if _geo_truth[0]: _geo_station = _geo['StartStation'] if _geo_truth[1]: _geo_start = _geo['Start'] #--------------------- #CASE 1 #--------------------- #no datum defined? use initial geometry or zero defaults if not any(_datum_truth): _datum['StartStation'] = _geo_station _datum['Start'] = _geo_start return #-------------------- #CASE 2 #-------------------- #station defined? #if the geometry has a station and coordinate, project the start coordinate if _datum['StartStation']: _datum['Start'] = _geo_start #assume geometry start if no geometry station if not _geo_truth[0]: return #scale the distance to the system units delta = _geo_station - _datum['StartStation'] #cutoff if error is below tolerance if not Support.within_tolerance(delta): delta *= Units.scale_factor() else: delta = 0.0 #assume geometry start if station delta is zero if delta: #calcualte the start based on station delta _datum['Start'] = _datum['Start'].sub( Support.vector_from_angle( _geo['BearingIn']).multiply(delta)) return #--------------------- #CASE 3 #--------------------- #datum start coordinate is defined #if the geometry has station and coordinate, project the start station _datum['StartStation'] = _geo_station #assume geometry station if no geometry start if _geo_truth[1]: #scale the length to the document units delta = _geo_start.sub( _datum['Start']).Length / Units.scale_factor() _datum['StartStation'] -= delta