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
Exemplo n.º 2
0
def get_points(arc_dict, interval, interval_type='Segment'):
    '''
    Discretize an arc into the specified segments.
    Resulting list of coordinates omits provided starting point and
    concludes with end point

    arc_dict    - A dictionary containing key elemnts:
        Direction   - non-zero.  <0 = ccw, >0 = cw
        Radius      - in document units (non-zero, positive)
        Delta       - in radians (non-zero, positive)
        BearingIn   - true north starting bearing in radians (0 to 2*pi)
        BearingOut  - true north ending bearing in radians (0 to 2*pi)

    interval    - value for the interval type (non-zero, positive)

    interval_type: (defaults to segment for invalid values)
        'Segment'   - subdivide into n equal segments
        'Interval'  - subdivide into fixed length segments
        'Tolerance' - limit error between segment and curve

    Points are returned references to start_coord
    '''

    angle = arc_dict['Delta']
    direction = arc_dict['Direction']
    bearing_in = arc_dict['BearingIn']
    radius = arc_dict['Radius']
    start_coord = arc_dict['Start']

    _forward = App.Vector(math.sin(bearing_in), math.cos(bearing_in), 0.0)
    _right = App.Vector(_forward.y, -_forward.x, 0.0)

    result = [start_coord]

    #define the incremental angle for segment calculations, defaulting to 'Segment'
    _delta = angle / interval

    _ratio = (interval * Units.scale_factor()) / radius

    if interval_type == 'Interval':
        _delta = _ratio

    elif interval_type == 'Tolerance':
        _delta = 2.0 * math.acos(1 - _ratio)

    #pre-calculate the segment deltas, increasing from zero to the central angle
    segment_deltas = [
        float(_i + 1) * _delta for _i in range(0,
                                               int(angle / _delta) + 1)
    ]
    segment_deltas[-1] = angle

    for delta in segment_deltas:

        _dfw = App.Vector(_forward).multiply(math.sin(delta))
        _drt = App.Vector(_right).multiply(direction * (1 - math.cos(delta)))

        result.append(start_coord.add(_dfw.add(_drt).multiply(radius)))

    return result
Exemplo n.º 3
0
def convert_units(arc, to_document=False):
    '''
    Cnvert the units of the arc parameters to or from document units

    to_document = True - convert to document units
                  False - convert to system units (mm / radians)
    '''

    angle_keys = ['Delta', 'BearingIn', 'BearingOut']

    result = {}

    angle_fn = math.radians
    scale_factor = Units.scale_factor()

    if to_document:
        angle_fn = math.degrees
        scale_factor = 1.0 / scale_factor

    for _k, _v in arc.items():

        result[_k] = _v

        if _v is None:
            continue

        if _k in angle_keys:
            result[_k] = angle_fn(_v)
            continue

        if _k != 'Direction':
            result[_k] = _v * scale_factor

    return result
    def _get_internal_station(self, station):
        '''
        Using the station equations, determine the internal station
        (position) along the alingment, scaled to the document units
        '''
        start_sta = self.Object.Station_Equations[0].y
        eqs = self.Object.Station_Equations

        #default to distance from starting station
        position = 0.0

        for _eq in eqs[1:]:

            #if station falls within equation, quit
            if start_sta < station < _eq.x:
                break

            #increment the position by the equaion length and
            #set the starting station to the next equation
            position += _eq.x - start_sta
            start_sta = _eq.y

        #add final distance to position
        position += station - start_sta

        return position * Units.scale_factor()
Exemplo n.º 5
0
    def _parse_data(self, align_name, tags, attrib):
        '''
        Build a dictionary keyed to the internal attribute names from the XML
        '''
        result = {}

        #test to ensure all required tags are in the imrpoted XML data
        missing_tags = set(tags[0]).difference(set(list(attrib.keys())))

        #report error and skip the alignment if required tags are missing
        if missing_tags:
            self.errors.append(
                'The required XML tags were not found in alignment %s:\n%s' %
                (align_name, missing_tags))
            return None

        #merge the required / optional tag dictionaries and iterate the items
        for _tag in tags[0] + tags[1]:

            attr_val = LandXml.convert_token(_tag, attrib.get(_tag))

            if attr_val is None:

                if _tag in tags[0]:
                    self.errors.append(
                        'Missing or invalid %s attribute in alignment %s' %
                        (_tag, align_name))

            #test for angles and convert to radians
            elif _tag in maps.XML_TAGS['angle']:
                attr_val = Utils.to_float(attrib.get(_tag))

                if attr_val:
                    attr_val = math.radians(attr_val)

            #test for lengths to convert to mm
            elif _tag in maps.XML_TAGS['length']:
                attr_val = Utils.to_float(attrib.get(_tag))

                if attr_val:
                    attr_val = attr_val * Units.scale_factor()

            #convert rotation from string to number
            elif _tag == 'rot':

                attr_val = 0.0

                if attrib.get(_tag) == 'cw':
                    attr_val = 1.0

                elif attrib.get(_tag) == 'ccw':
                    attr_val = -1.0

            if not attr_val:
                attr_val = LandXml.get_tag_default(_tag)

            result[maps.XML_MAP[_tag]] = attr_val

        return result
Exemplo n.º 6
0
    def _validate_units(self, units):
        '''
        Validate the units of the alignment, ensuring it matches the document
        '''

        if units is None:
            print('Missing units')
            return ''

        xml_units = units[0].attrib['linearUnit']

        if xml_units != Units.get_doc_units()[1]:
            self.errors.append('Document units of ' +
                               Units.get_doc_units()[1] +
                               ' expected, units of ' + xml_units + 'found')
            return ''

        return xml_units
    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'])
Exemplo n.º 8
0
    def _parse_coord_geo_data(self, align_name, alignment):
        '''
        Parse the alignment coorinate geometry data to get curve information and
        return as a dictionary
        '''

        coord_geo = LandXml.get_child(alignment, 'CoordGeom')

        if not coord_geo:
            print('Missing coordinate geometry for ', align_name)
            return None

        result = []

        for geo_node in coord_geo:

            node_tag = geo_node.tag.split('}')[1]

            if not node_tag in ['Curve', 'Spiral', 'Line']:
                continue

            points = []

            for _tag in ['Start', 'End', 'Center', 'PI']:

                _pt = LandXml.get_child_as_vector(geo_node, _tag)

                points.append(None)

                if _pt:
                    points[-1] = (_pt.multiply(Units.scale_factor()))
                    continue

                if not (node_tag == 'Line' and _tag in ['Center', 'PI']):
                    self.errors.append(
                        'Missing %s %s coordinate in alignment %s' %
                        (node_tag, _tag, align_name))

            coords = {
                'Type': node_tag,
                'Start': points[0],
                'End': points[1],
                'Center': points[2],
                'PI': points[3]
            }

            result.append({
                **coords,
                **self._parse_data(align_name, maps.XML_ATTRIBS[node_tag], geo_node.attrib)
            })

        print('\n<---- Import result ---->\n', result)
        return result
    def _update_alignment(self, value):

        subset = self.data['Alignments'][value]

        if subset['meta'].get('StartStation'):
            self.panel.startStationValueLabel.setText('{0:.2f}'.format(subset['meta']['StartStation']))

        if subset['meta'].get('Length'):
            self.panel.lengthTitleLabel.setText('Length ({0:s}):'.format(Units.get_doc_units()[0]))
            self.panel.lengthValueLabel.setText('{0:.2f}'.format(subset['meta']['Length']))

        sta_model = []

        if subset['station']:
            for st_eq in subset['station']:
                sta_model.append([st_eq['Back'], st_eq['Ahead']])

        widget_model = WidgetModel.create(sta_model, ['Back'], ['Ahead'])

        self.panel.staEqTableView.setModel(widget_model)

        curve_model = []

        for curve in subset['geometry']:

            _vals = [curve[_k] if curve.get(_k) else 0.0 for _k in [
                        'Direction', 'StartStation', 'BearingIn', 'BearingOut', 'Radius']
                    ]

            row = '{0:s}, {1:f}, {2:.2f}, {3:.2f}, {4:.2f}, {5:.2f}'.format(
                curve['Type'], _vals[0], _vals[1], _vals[2], _vals[3], _vals[4]
                )
                #curve['Type'], curve['Direction'], curve['StartStation'],
                #curve['BearingIn'], curve['BearingOut'], curve['Radius']
            #)
            curve_model.append(row.split(','))

        widget_model_2 = WidgetModel.create(curve_model, ['Type', 'Dir', 'Start',
                                                          'In', 'Out', 'Radius'])

        self.panel.curveTableView.setModel(widget_model_2)
        self.panel.curveTableView.resizeColumnsToContents()
    def _write_tree_data(self, data, node, attribs):
        '''
        Write data to the tree using the passed parameters
        '''

        for _tag in attribs[0] + attribs[1]:

            #find the xml tag for the current dictionary key
            _key = maps.XML_MAP.get(_tag)

            _value = None

            if _key:
                value = data.get(_key)

            #assign default if no value in the dictionary
            if value is None:
                value = LandXml.get_tag_default(_tag)

            if _tag in maps.XML_TAGS['angle']:
                value = math.degrees(value)

            elif _tag in maps.XML_TAGS['length']:
                value /= Units.scale_factor()

            elif _tag == 'rot':

                if value < 0.0:
                    value = 'ccw'

                elif value > 0.0:
                    value = 'cw'

                else:
                    value = ''

            result = str(value)

            if isinstance(value, float):
                result = '{:.8f}'.format(value)

            node.set(_tag, result)
Exemplo n.º 11
0
    def _parse_geo_data(self, align_name, geometry, curve_type):
        '''
        Parse curve data and return as a dictionary
        '''

        result = []

        for curve in geometry:

            #add the curve / line start / center / end coordinates, skipping if any are missing
            _points = []

            for _tag in ['Start', 'End', 'Center', 'PI']:

                _pt = LandXml.get_child_as_vector(curve, _tag)

                if _pt:
                    _pt.multiply(Units.scale_factor())
                else:

                    #report missing coordinates
                    if not (curve_type == 'line' and _tag in ['Center', 'PI']):
                        self.errors.append(
                            'Missing %s %s coordinate in alignment %s' %
                            (curve_type, _tag, align_name))

                _points.append(_pt)

            coords = {
                'Type': curve_type,
                'Start': _points[0],
                'End': _points[1],
                'Center': _points[2],
                'PI': _points[3]
            }

            result.append({
                **coords,
                **self._parse_data(align_name, maps.XML_ATTRIBS[curve_type], curve.attrib)
            })

        return result
    def _write_coordinates(self, data, parent):
        '''
        Write coordinate children to parent geometry
        '''

        _sf = 1.0 / Units.scale_factor()

        for _key in maps.XML_TAGS['coordinate']:

            if not _key in data:
                continue

            #scale the coordinates to the document units
            _vec = App.Vector(data[_key])
            _vec.multiply(_sf)

            _child = LandXml.add_child(parent, _key)

            _vec_string = LandXml.get_vector_string(_vec)

            LandXml.set_text(_child, _vec_string)
Exemplo n.º 13
0
    def write_xml(self):
        '''
        Serialize the object data and it's children to xml files
        '''

        _list = []

        #iterate the list of children, acquiring their data sets
        #and creating a total data set for alignments.
        for _obj in self.Object.OutList:
            _list.append(_obj.Proxy.get_geometry())

        exporter = AlignmentExporter()

        template_path = App.getUserAppDataDir() + 'Mod/freecad-transportation-wb/Resources/data/'
        template_file = 'landXML-' + Units.get_doc_units()[1] + '.xml'

        xml_path = App.ActiveDocument.TransientDir + '/alignment.xml'

        print('writing xml...')
        exporter.write(_list, template_path + template_file, xml_path)

        self.Object.Xml_Path = xml_path
Exemplo n.º 14
0
    def assign_geometry_data(self, datum, data):
        """
        Iterate the dataset, extracting geometric data
        Validate data
        Create separate items for each curve / tangent
        Assign Northing / Easting datums
        """

        _points = [datum]
        _geometry = []

        for item in data:

            _ne = [item['Northing'], item['Easting']]
            _db = [item['Distance'], item['Bearing']]
            _rd = [item['Radius'], item['Degree']]

            curve = [0.0, 0.0, 0.0, 0.0]

            try:
                curve[3] = float(item['Spiral'])

            except:
                pass

            point_vector = App.Vector(0.0, 0.0, 0.0)

            #parse degree of curve / radius values
            if _rd[0]:
                try:
                    curve[2] = float(_rd[0])
                except:
                    self.errors.append('Invalid radius: %s' % _rd[0])

            elif _rd[1]:
                try:
                    curve[2] = Utils.doc_to_radius(float(_rd[1]))
                except:
                    self.errors.append('Invalid degree of curve: %s' % _rd[1])
            else:
                self.errors.append('Missing Radius / Degree of Curvature')

            #parse bearing / distance values
            if any(_db) and not all(_db):
                self.errors.append('(Distance, Bearing) Incomplete: (%s, %s)' %
                                   tuple(_db))

            elif all(_db):
                #get the last point in the geometry for the datum, unless empty
                datum = self.Object.Datum

                try:
                    _db[0] = float(_db[0])
                    _db[1] = float(_db[1])

                    #zero distance means coincident PI's.  Skip that.
                    if _db[0] > 0.0:

                        #set values to geo_vector, adding the previous position to the new one
                        point_vector = Utils.distance_bearing_to_coordinates(
                            _db[0], _db[1])
                        curve[0:2] = _db

                        if not Units.is_metric_doc():
                            point_vector.multiply(304.8)

                        point_vector = _points[-1].add(point_vector)

                except:
                    self.errors.append(
                        '(Distance, Bearing) Invalid: (%s, %s)' % tuple(_db))

            #parse northing / easting values
            if any(_ne) and not all(_ne):
                self.errors.append(
                    '(Easting, Northing) Incomplete: ( %s, %s)' % tuple(_ne))

            elif all(_ne):

                try:
                    point_vector.y = float(_ne[0])
                    point_vector.x = float(_ne[1])

                except:
                    self.errors.append(
                        '(Easting, Northing) Invalid: (%s, %s)' % tuple(_ne))

                curve[0:2] = Utils.coordinates_to_distance_bearing(
                    _points[-1], point_vector)

            #skip coincident PI's
            #save the point as a vector
            #save the geometry as a string
            if curve[0] > 0.0:
                _points.append(point_vector)
                print('saving ', str(curve))
                _geometry.append(str(curve).replace('[', '').replace(']', ''))

        self.Object.PIs = _points
        self.Object.Geometry = _geometry
Exemplo n.º 15
0
def parameter_test(excludes=None):
    '''
    '''
    scale_factor = 1.0 / Units.scale_factor()

    radius = 670.00
    delta = 50.3161
    half_delta = math.radians(delta) / 2.0

    arc = {
        'Type':
        'arc',
        'Direction':
        -1,
        'Delta':
        delta,
        'Radius':
        radius,
        'Length':
        radius * math.radians(delta),
        'Tangent':
        radius * math.tan(half_delta),
        'Chord':
        2 * radius * math.sin(half_delta),
        'External':
        radius * ((1 / math.cos(half_delta) - 1)),
        'MiddleOrd':
        radius * (1 - math.cos(half_delta)),
        'BearingIn':
        139.3986,
        'BearingOut':
        89.0825,
        'Start':
        App.Vector(122056.0603640062, -142398.20717496306,
                   0.0).multiply(scale_factor),
        'Center':
        App.Vector(277108.1622932797, -9495.910944558627,
                   0.0).multiply(scale_factor),
        'End':
        App.Vector(280378.2141876281, -213685.7280672748,
                   0.0).multiply(scale_factor),
        'PI':
        App.Vector(184476.32163324804, -215221.57431973785,
                   0.0).multiply(scale_factor)
    }

    #convert the arc to system units before processing, and back to document units on return

    comp = {
        'Type': 'arc',
        'Radius': 670.0,
        'Tangent': 314.67910063712156,
        'Chord': 569.6563702820052,
        'Delta': 50.31609999999997,
        'Direction': -1.0,
        'BearingIn': 139.3986,
        'BearingOut': 89.0825,
        'Length': 588.3816798810212,
        'External': 70.21816809491217,
        'Middle': 63.5571709144523,
        'Start': App.Vector(400.4463922703616, -467.1857190779628, 0.0),
        'Center': App.Vector(909.147514086, -31.1545634664, 0.0),
        'End': App.Vector(919.8760307993049, -701.0686616380407, 0.0),
        'PI': App.Vector(605.2372756996326, -706.1075272957279, 0.0)
    }

    if excludes:
        return run_test(arc, comp, excludes)

    keys = ['Start', 'End', 'Center', 'PI']

    run_test(arc, comp, None)

    for i in range(0, 4):
        run_test(arc, comp, [keys[i]])
        for j in range(i + 1, 4):
            run_test(arc, comp, [keys[i], keys[j]])
            for k in range(j + 1, 4):
                run_test(arc, comp, [keys[i], keys[j], keys[k]])

    run_test(arc, comp, keys)

    return arc
    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