1 # Copyright 2023 highstreet technologies USA CORP.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
18 A Class representing a 3GPP new radio cell du (NrCellDu)
21 import xml.etree.ElementTree as ET
22 from typing import Any, cast
24 import network_generation.model.python.hexagon as Hexagon
25 from network_generation.model.python.geo_location import (
29 from network_generation.model.python.o_ran_node import IORanNode, ORanNode
30 from network_generation.model.python.o_ran_termination_point import (
33 from network_generation.model.python.point import Point
36 # Define the "INrCellDu" interface
37 class INrCellDu(IORanNode):
39 cellScaleFactorForHandoverArea: int
44 default_value: INrCellDu = cast(
50 "cellScaleFactorForHandoverArea": 0,
58 # Define an abstract O-RAN Node class
59 class NrCellDu(ORanNode):
62 data: dict[str, Any] = cast(dict[str, Any], default_value),
63 **kwargs: dict[str, Any]
65 cell_data: INrCellDu = self._to_cell_data(data)
66 super().__init__(cast(dict[str, Any], cell_data), **kwargs)
67 self._cell_angle: int = int(str(cell_data["cellAngle"]))
68 self._cell_scale_factor: int = int(
69 str(cell_data["cellScaleFactorForHandoverArea"])
71 self._maxReach: int = int(str(cell_data["maxReach"]))
72 self._azimuth: int = int(str(cell_data["azimuth"]))
74 def _to_cell_data(self, data: dict[str, Any]) -> INrCellDu:
75 result: INrCellDu = default_value
76 for key, key_type in INrCellDu.__annotations__.items():
78 result[key] = data[key] # type: ignore
82 def cell_angle(self) -> int:
83 return self._cell_angle
86 def cell_angle(self, value: int) -> None:
87 self._cell_angle = value
90 def cell_scale_factor(self) -> int:
91 return self._cell_scale_factor
93 @cell_scale_factor.setter
94 def cell_scale_factor(self, value: int) -> None:
95 self._cell_scale_factor = value
98 def maxReach(self) -> int:
102 def maxReach(self, value: int) -> None:
103 self._maxReach = value
106 def azimuth(self) -> int:
110 def azimuth(self, value: int) -> None:
111 self._azimuth = value
113 def termination_points(self) -> list[ORanTerminationPoint]:
114 result: list[ORanTerminationPoint] = super().termination_points()
115 result.append(ORanTerminationPoint(
116 {"id": self.name, "name": self.name}
120 def to_topology_nodes(self) -> list[dict[str, Any]]:
121 # a cell is not a node it is a Termination Point
122 result: list[dict[str, Any]] = [] # super().to_topology_nodes()
125 def to_topology_links(self) -> list[dict[str, Any]]:
126 # as a cell is not a node, it does not have links
127 result: list[dict[str, Any]] = [] # super().to_topology_links()
130 def toKml(self) -> ET.Element:
131 placemark: ET.Element = ET.Element("Placemark")
132 name: ET.Element = ET.SubElement(placemark, "name")
133 name.text = self.name
134 style: ET.Element = ET.SubElement(placemark, "styleUrl")
135 style.text = "#" + self.__class__.__name__
136 multi_geometry: ET.Element = ET.SubElement(placemark, "MultiGeometry")
137 polygon: ET.Element = ET.SubElement(multi_geometry, "Polygon")
138 outer_boundary: ET.Element = ET.SubElement(polygon, "outerBoundaryIs")
139 linear_ring: ET.Element = ET.SubElement(outer_boundary, "LinearRing")
140 coordinates: ET.Element = ET.SubElement(linear_ring, "coordinates")
142 points: list[Point] = Hexagon.polygon_corners(
143 self.layout, self.position
146 self.parent.parent.parent.parent.parent.parent
147 .geo_location.point_to_geo_location
149 geo_locations: list[GeoLocation] = list(map(method, points))
152 index: int = 1 + int(self._azimuth / self._cell_angle)
153 network_center: GeoLocation = (
154 self.parent.parent.parent.parent.parent.parent.geo_location
157 p1: int = (2 * index + 1) % 6
158 p2: int = (2 * index + 2) % 6
159 intersect1: Point = Point(
160 (points[p1].x + points[p2].x) / 2,
161 (points[p1].y + points[p2].y) / 2,
163 intersect_gl1: GeoLocation = network_center.point_to_geo_location(
167 p3: int = (2 * index + 3) % 6
168 p4: int = (2 * index + 4) % 6
169 intersect2: Point = Point(
170 (points[p3].x + points[p4].x) / 2,
171 (points[p3].y + points[p4].y) / 2,
173 intersect_gl2: GeoLocation = network_center.point_to_geo_location(
177 tower: GeoLocation = GeoLocation(cast(IGeoLocation, self.geo_location))
178 # TODO: Why a cast is required
180 cell_polygon: list[GeoLocation] = []
181 cell_polygon.append(tower)
182 cell_polygon.append(intersect_gl1)
183 cell_polygon.append(geo_locations[(2 * index + 2) % 6])
184 cell_polygon.append(geo_locations[(2 * index + 3) % 6])
185 cell_polygon.append(intersect_gl2)
187 cell_polygon.append(tower)
189 for gl in cell_polygon:
191 str("%.6f" % float(gl.longitude)),
192 str("%.6f" % float(gl.latitude)),
193 str("%.6f" % float(gl.aboveMeanSeaLevel)),
195 text.append(",".join(strs))
196 coordinates.text = " ".join(text)
198 if self.cell_scale_factor > 0:
199 scaled_polygon: ET.Element = ET.SubElement(
200 multi_geometry, "Polygon")
201 scaled_outer_boundary: ET.Element = ET.SubElement(
202 scaled_polygon, "outerBoundaryIs")
203 scaled_linear_ring: ET.Element = ET.SubElement(
204 scaled_outer_boundary, "LinearRing")
205 scaled_coordinates: ET.Element = ET.SubElement(
206 scaled_linear_ring, "coordinates")
208 arc: float = self.azimuth * math.pi / 180
209 meterToDegree: float = (
210 2 * math.pi * GeoLocation().equatorialRadius / 360
212 centerX: float = self.layout.size.x * 0.5 * math.sin(arc)
213 centerY: float = self.layout.size.y * 0.5 * math.cos(arc)
214 cell_center: GeoLocation = GeoLocation(
216 "latitude": tower.latitude + centerY / meterToDegree,
217 "longitude": tower.longitude + centerX / meterToDegree,
218 "aboveMeanSeaLevel": tower.aboveMeanSeaLevel,
223 for gl in cell_polygon:
224 scale: float = 1 + self.cell_scale_factor / 100
226 1 * scale * (gl.longitude - cell_center.longitude)
227 ) + cell_center.longitude
229 1 * scale * (gl.latitude - cell_center.latitude)
230 ) + cell_center.latitude
231 scaled_strs: list[str] = [
232 str("%.6f" % float(lng_new)),
233 str("%.6f" % float(lat_new)),
234 str("%.6f" % float(gl.aboveMeanSeaLevel)),
236 text.append(",".join(scaled_strs))
238 scaled_coordinates.text = " ".join(text)
241 def toSvg(self) -> ET.Element:
242 return ET.Element("to-be-implemented")
244 def to_directory(self, parent_dir: str) -> None: