diff --git a/aixm_geo/aixm_features.py b/aixm_geo/aixm_features.py index 94710b2..00073a2 100644 --- a/aixm_geo/aixm_features.py +++ b/aixm_geo/aixm_features.py @@ -1,3 +1,4 @@ +import util from base import SinglePointAixm, MultiPointAixm from interfaces import IAixmFeature from settings import NAMESPACES @@ -15,7 +16,12 @@ def get_geographic_information(self): geo_dict(dict): A dictionary containing relevant information regarding the feature. """ - elevation, elevation_uom = self.get_elevation() + elevation, elevation_uom = self.get_field_elevation() + + if elevation_uom != 'M': + elevation = util.convert_elevation(elevation, elevation_uom) + elevation_uom = 'M' + geo_dict = { 'type': 'AirportHeliport', 'coordinates': [f"{self.get_first_value('.//aixm:ARP//gml:pos')} " @@ -41,6 +47,10 @@ def get_geographic_information(self): """ elevation, elevation_uom = self.get_elevation() + if elevation_uom != 'M': + elevation = util.convert_elevation(elevation, elevation_uom) + elevation_uom = 'M' + geo_dict = { 'type': 'NavaidComponent', 'coordinates': [f"{self.get_first_value('.//aixm:location//gml:pos')}" @@ -116,7 +126,9 @@ def get_geographic_information(self): coordinate_list = self.get_coordinate_list(subroot) - lower_layer, lower_layer_uom, upper_layer, upper_layer_uom = self.get_elevation() + lower_layer, lower_layer_uom, upper_layer, upper_layer_uom = self.get_airspace_elevation() + lower_layer, lower_layer_uom = util.convert_elevation(lower_layer, lower_layer_uom) + upper_layer, upper_layer_uom = util.convert_elevation(upper_layer, upper_layer_uom) geo_dict = { 'type': 'Airspace', @@ -130,3 +142,38 @@ def get_geographic_information(self): } return geo_dict + + +class VerticalStructure(MultiPointAixm, IAixmFeature): + def __init__(self, root): + super().__init__(root) + + def get_geographic_information(self): + """ + Args: + self + Returns: + geo_dict(dict): A dictionary containing relevant information regarding the feature. + """ + + subroot = self._root.findall('.//aixm:part', + namespaces=NAMESPACES)[0] + + elevation, elevation_uom = self.get_vertical_extent() + elevation, elevation_uom = util.convert_elevation(elevation, elevation_uom) + + coordinate_list = self.get_coordinate_list(subroot) + + if len(coordinate_list) == 1: + coordinate_list[0] = f'{coordinate_list[0]} {elevation}' + + geo_dict = { + 'type': 'VerticalStructure', + 'obstacle_type': self.get_first_value('.//aixm:type'), + 'coordinates': coordinate_list, + 'elevation': elevation, + 'elevation_uom': elevation_uom, + 'name': f'{self.get_first_value(".//aixm:name")} ({self.get_first_value(".//aixm:type")})', + } + + return geo_dict diff --git a/aixm_geo/aixm_geo.py b/aixm_geo/aixm_geo.py index 871b011..b82cce8 100644 --- a/aixm_geo/aixm_geo.py +++ b/aixm_geo/aixm_geo.py @@ -26,8 +26,11 @@ def draw_features(self, kml_obj): if geometry_type == 'cylinder': self.draw_cylinder(aixm_feature_dict, kml_obj) elif geometry_type == 'point': - kml_obj.point(aixm_feature_dict["coordinates"], fol=aixm_feature_dict['name'], - point_name=aixm_feature_dict['name']) + if aixm_feature_dict['type'] == 'VerticalStructure': + self.draw_vertical_structure_point(aixm_feature_dict, kml_obj) + else: + kml_obj.point(aixm_feature_dict["coordinates"], fol=aixm_feature_dict['name'], + point_name=aixm_feature_dict['name']) elif geometry_type == 'polyhedron': self.draw_airspace(aixm_feature_dict, kml_obj) print(aixm_feature_dict) @@ -35,31 +38,30 @@ def draw_features(self, kml_obj): else: pass - def draw_airspace(self, aixm_feature_dict, kml_obj): - aixm_feature_dict = util.convert_elevation(aixm_feature_dict) + def draw_vertical_structure_point(self, aixm_feature_dict, kml_obj): + kml_obj.point(aixm_feature_dict["coordinates"], uom=aixm_feature_dict['elevation_uom'], + fol=aixm_feature_dict['name'], point_name=aixm_feature_dict['name'], + altitude_mode='relativeToGround', extrude=1) + def draw_airspace(self, aixm_feature_dict, kml_obj): kml_obj.polyhedron(aixm_feature_dict["coordinates"], - # Convert to FL ie FL 95 x by 100 to get 9500 for correct conversion + aixm_feature_dict["coordinates"], upper_layer=float(aixm_feature_dict['upper_layer']), lower_layer=float(aixm_feature_dict['lower_layer']), - lower_layer_uom=aixm_feature_dict['lower_layer_uom'], - upper_layer_uom=aixm_feature_dict['upper_layer_uom'], uom=aixm_feature_dict['lower_layer_uom'], fol=aixm_feature_dict['name'], altitude_mode=util.altitude_mode(aixm_feature_dict)) def draw_cylinder(self, aixm_feature_dict, kml_obj): - aixm_feature_dict = util.convert_elevation(aixm_feature_dict) coordinates = aixm_feature_dict['coordinates'][0].split(',')[0].strip() radius = aixm_feature_dict['coordinates'][0].split(',')[1].split('=')[-1] radius_uom = util.switch_radius_uom(aixm_feature_dict['coordinates'][0].split(',')[2].split('=')[-1]) lower_layer = aixm_feature_dict['lower_layer'] upper_layer = aixm_feature_dict['upper_layer'] - uom = aixm_feature_dict['lower_layer_uom'] kml_obj.cylinder(coordinates, float(radius), radius_uom=radius_uom, lower_layer=float(lower_layer), - upper_layer=float(upper_layer), uom=uom, + upper_layer=float(upper_layer), fol=aixm_feature_dict['name'], lower_layer_uom=aixm_feature_dict['lower_layer_uom'], upper_layer_uom=aixm_feature_dict['upper_layer_uom'], altitude_mode=util.altitude_mode(aixm_feature_dict)) diff --git a/aixm_geo/base.py b/aixm_geo/base.py index 4fe4846..7e0f227 100644 --- a/aixm_geo/base.py +++ b/aixm_geo/base.py @@ -70,13 +70,23 @@ def get_first_value_attribute(self, xpath: str, **kwargs: Union[etree.Element, s return attribute - def get_elevation(self): + def get_field_elevation(self): elevation = self.get_first_value('.//aixm:fieldElevation') elevation_uom = self.get_first_value_attribute('.//aixm:fieldElevation', attribute_string='uom') if elevation == 'Unknown': elevation = 0 - elevation_uom = 'FT' + elevation_uom = 'M' + + return elevation, elevation_uom + + def get_vertical_extent(self): + elevation = self.get_first_value('.//aixm:elevation') + elevation_uom = self.get_first_value_attribute('.//aixm:elevation', attribute_string='uom') + + if elevation == 'Unknown': + elevation = 0 + elevation_uom = 'M' return elevation, elevation_uom @@ -116,6 +126,23 @@ class MultiPointAixm(SinglePointAixm): def __init__(self, root): super().__init__(root) + def get_airspace_elevation(self): + lower_layer = self.get_first_value('.//aixm:theAirspaceVolume//aixm:lowerLimit') + lower_layer_uom = self.get_first_value_attribute('.//aixm:theAirspaceVolume//aixm:lowerLimit', + attribute_string='uom') + upper_layer = self.get_first_value('.//aixm:theAirspaceVolume//aixm:upperLimit') + upper_layer_uom = self.get_first_value_attribute('.//aixm:theAirspaceVolume//aixm:upperLimit', + attribute_string='uom') + + if lower_layer == 'Unknown': + lower_layer = 0.0 + lower_layer_uom = 'M' + if upper_layer == 'Unknown': + upper_layer = 0.0 + upper_layer_uom = 'M' + + return lower_layer, lower_layer_uom, upper_layer, upper_layer_uom + def get_coordinate_list(self, subroot): """ Parses the LXML etree._Element object and returns a list of coordinate strings. @@ -132,6 +159,10 @@ def get_coordinate_list(self, subroot): unpacked_gml = self.unpack_gml(location) except TypeError: print('Coordinates can only be extracted from an LXML etree._Element object.') + + for x in unpacked_gml: + x.strip("r'\'") + return unpacked_gml def unpack_gml(self, location: etree.Element) -> list[str]: @@ -156,7 +187,7 @@ def extract_pos_and_poslist(self, location): """ for child in location.iterdescendants(): tag = child.tag.split('}')[-1] - if tag == 'GeodesicString': + if tag == 'GeodesicString' or tag == 'ElevatedPoint': for x in self.unpack_geodesic_string(child): yield x elif tag == 'CircleByCenterPoint': diff --git a/aixm_geo/factory.py b/aixm_geo/factory.py index cc5493d..c776f59 100644 --- a/aixm_geo/factory.py +++ b/aixm_geo/factory.py @@ -15,7 +15,8 @@ def __init__(self, root): 'DesignatedPoint': af.DesignatedPoint, 'NavaidComponent': af.NavaidComponent, 'RouteSegment': af.RouteSegment, - 'Airspace': af.Airspace + 'Airspace': af.Airspace, + 'VerticalStructure': af.VerticalStructure, } self._errors = [] diff --git a/aixm_geo/util.py b/aixm_geo/util.py index a9e64bb..b432139 100644 --- a/aixm_geo/util.py +++ b/aixm_geo/util.py @@ -47,24 +47,20 @@ def parse_timeslice(subroot: _Element) -> list: return timeslices -def convert_elevation(aixm_feature_dict): - if aixm_feature_dict['lower_layer_uom'] == 'FL': - aixm_feature_dict['lower_layer_uom'] = 'FT' - aixm_feature_dict['lower_layer'] = float(aixm_feature_dict['lower_layer']) * 100 +def convert_elevation(z_value: str, current_uom: str) -> float: + if z_value == 'GND': + current_uom = 'M' + z_value = 0.0 - if aixm_feature_dict['lower_layer'] == 'GND': - aixm_feature_dict['lower_layer_uom'] = 'M' - aixm_feature_dict['lower_layer'] = 0.0 + if z_value == 'UNL': + current_uom = 'M' + z_value = 18288.00 # 60,000 ft in metres - if aixm_feature_dict['upper_layer_uom'] == 'FL': - aixm_feature_dict['upper_layer_uom'] = 'FT' - aixm_feature_dict['upper_layer'] = float(aixm_feature_dict['upper_layer']) * 100 + if current_uom == 'FL': + current_uom = 'M' + z_value = (float(z_value) * 100) * .3048 # 1 ft in metres - if aixm_feature_dict['upper_layer'] == 'UNL': - aixm_feature_dict['upper_layer_uom'] = 'M' - aixm_feature_dict['upper_layer'] = 18288.00 # 60,000 ft in metres - - return aixm_feature_dict + return z_value, current_uom def altitude_mode(aixm_dict): @@ -85,6 +81,13 @@ def determine_geometry_type(aixm_feature_dict): if aixm_feature_dict['type'] == 'RouteSegment': geometry_type = 'LineString' + elif aixm_feature_dict['type'] == 'VerticalStructure': + if len(aixm_feature_dict['coordinates']) > 1: + aixm_feature_dict['lower_layer'] = 0.0 + aixm_feature_dict['upper_layer'] = aixm_feature_dict['elevation'] + geometry_type = 'Polygon' + else: + geometry_type = 'point' elif len(aixm_feature_dict["coordinates"]) == 1: if 'radius=' in aixm_feature_dict['coordinates'][0]: if aixm_feature_dict["upper_layer"]: diff --git a/test_data/donlon.xml b/test_data/donlon.xml index 137a09d..8a548c4 100644 --- a/test_data/donlon.xml +++ b/test_data/donlon.xml @@ -31742,7 +31742,6 @@ LIGHTED. - REMARK diff --git a/tests/test_base.py b/tests/test_base.py index 6f8a7d0..90d2591 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -29,9 +29,9 @@ def test_get_first_value_attribute(self): self.assertEqual('unknown', attribute['indeterminatePosition']) def test_get_elevation(self): - elevation = self.ah.get_elevation() - self.assertEqual(elevation, (0.0, 'FT')) - self.assertNotEqual(elevation, (0.0, 'M')) + elevation = self.ah.get_field_elevation() + self.assertEqual(elevation, (0.0, 'M')) + self.assertNotEqual(elevation, (0.0, 'FT')) self.assertTrue(isinstance(elevation, tuple))