def _write_alignment_data(self, data, parent): ''' Write individual alignment to XML ''' _align_node = LandXml.add_child(parent, 'Alignment') #write the alignment attributes self._write_tree_data(data['meta'], _align_node, maps.XML_ATTRIBS['Alignment']) _coord_geo_node = LandXml.add_child(_align_node, 'CoordGeom') #write the geo coordinate attributes self._write_tree_data(data['meta'], _coord_geo_node, maps.XML_ATTRIBS['CoordGeom']) #write the station equation data self.write_station_data(data['station'], _align_node) #write the alignment geometry data for _geo in data['geometry']: _node = None if _geo['Type'] == 'line': _node = LandXml.add_child(_coord_geo_node, 'Line') self._write_tree_data(_geo, _node, maps.XML_ATTRIBS['Line']) elif _geo['Type'] == 'arc': _node = LandXml.add_child(_coord_geo_node, 'Curve') self._write_tree_data(_geo, _node, maps.XML_ATTRIBS['Curve']) if _node is not None: self._write_coordinates(_geo, _node)
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
def write_meta_data(self, data, node): ''' Write the project and application data to the file ''' self._write_tree_data(data, LandXml.get_child(node, 'Project'), maps.XML_ATTRIBS['Project']) node = LandXml.get_child(node, 'Application') node.set('version', ''.join(App.Version()[0:3])) node.set('timeStamp', datetime.datetime.utcnow().isoformat())
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 write(self, data, source_path, target_path): ''' Write the alignment data to a land xml file in the target location ''' root = etree.parse(source_path).getroot() _parent = LandXml.add_child(root, 'Alignments') for _align in data: self._write_alignment_data(_align, _parent) LandXml.write_to_file(root, target_path)
def import_file(self, filepath): ''' Import a LandXML and build the Python dictionary fronm the appropriate elements ''' #get element tree and key nodes for parsing doc = etree.parse(filepath) root = doc.getroot() project = LandXml.get_child(root, 'Project') units = LandXml.get_child(root, 'Units') alignments = LandXml.get_child(root, 'Alignments') #aport if key nodes are missing if not units: self.errors.append('Missing project units') return None unit_name = self._validate_units(units) if not unit_name: self.errors.append('Invalid project units') return None #default project name if missing project_name = 'Unknown Project' if not project is None: project_name = project.attrib['name'] #build final dictionary and return result = {} result['Project'] = {} result['Project'][maps.XML_MAP['name']] = project_name result['Alignments'] = {} for alignment in alignments: align_name = self._get_alignment_name(alignment, list(result.keys())) result['Alignments'][align_name] = {} align_dict = result['Alignments'][align_name] align_dict['meta'] = self._parse_meta_data(align_name, alignment) align_dict['station'] = self._parse_station_data( align_name, alignment) align_dict['geometry'] = self._parse_coord_geo_data( align_name, alignment) 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)
def _parse_meta_data(self, align_name, alignment): ''' Parse the alignment elements and strip out meta data for each alignment, returning it as a dictionary keyed to the alignment name ''' result = self._parse_data(align_name, maps.XML_ATTRIBS['Alignment'], alignment.attrib) _start = LandXml.get_child_as_vector(alignment, 'Start') if _start: _start.multiply() result['Start'] = _start return result
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)
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 _parse_station_data(self, align_name, alignment): ''' Parse the alignment data to get station equations and return a list of equation dictionaries ''' equations = LandXml.get_children(alignment, 'StaEquation') print(equations) result = [] for equation in equations: print(equation.attrib) _dict = self._parse_data(align_name, maps.XML_ATTRIBS['StaEquation'], equation.attrib) _dict['Alignment'] = align_name print('\n<--- dict --->\n', _dict) result.append(_dict) return result