Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .basedpyright/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -5128,14 +5128,6 @@
"endColumn": 44,
"lineCount": 1
}
},
{
"code": "reportAttributeAccessIssue",
"range": {
"startColumn": 38,
"endColumn": 69,
"lineCount": 1
}
}
],
"./monitoring/uss_qualifier/resources/flight_planning/flight_planners.py": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from .flight_intents_resource import FlightIntentsResource as FlightIntentsResource
from .flight_intents_resource import (
FlightIntentsTriangularCascadeSoutheastResource as FlightIntentsTriangularCascadeSoutheastResource,
)
from .flight_planners import (
FlightPlannerCombinationSelectorResource as FlightPlannerCombinationSelectorResource,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import json

import s2sphere
from implicitdict import ImplicitDict

from monitoring.monitorlib.clients.flight_planning.flight_info_template import (
FlightInfoTemplate,
)
from monitoring.monitorlib.geo import (
LatLngBoundingBox,
RelativeTranslation,
Transformation,
)
from monitoring.monitorlib.geotemporal import Volume4D
from monitoring.uss_qualifier.resources.files import load_dict
from monitoring.uss_qualifier.resources.flight_planning.flight_intent import (
FlightIntentCollection,
FlightIntentID,
FlightIntentsSpecification,
)
from monitoring.uss_qualifier.resources.geospatial import (
GeospatialResource,
TriangularCascadeSoutheastResource,
)
from monitoring.uss_qualifier.resources.resource import Resource


class FlightIntentsResource(Resource[FlightIntentsSpecification]):
class FlightIntentsResource(GeospatialResource, Resource[FlightIntentsSpecification]):
_spec: FlightIntentsSpecification
_intent_collection: FlightIntentCollection

def __init__(self, specification: FlightIntentsSpecification, resource_origin: str):
super().__init__(specification, resource_origin)
self._spec = specification
has_file = "file" in specification and specification.file
has_literal = (
"intent_collection" in specification and specification.intent_collection
Expand All @@ -34,17 +49,61 @@ def __init__(self, specification: FlightIntentsSpecification, resource_origin: s
load_dict(specification.file), FlightIntentCollection
)
elif has_literal:
self._intent_collection = specification.intent_collection
self._intent_collection = ImplicitDict.parse(
json.loads(
json.dumps(specification.intent_collection)
), # NB: We need a copy to avoid sharing '_intent_collection' between instances
FlightIntentCollection,
)
if "transformations" in specification and specification.transformations:
if (
"transformations" in self._intent_collection
and self._intent_collection.transformations
):
self._intent_collection.transformations.extend(
specification.transformations
specification.transformations[::]
)
else:
self._intent_collection.transformations = specification.transformations
self._intent_collection.transformations = specification.transformations[ # NB: We do a copy to be independent between instances
::
]

def get_flight_intents(self) -> dict[FlightIntentID, FlightInfoTemplate]:
return self._intent_collection.resolve()

def get_extents(self) -> LatLngBoundingBox:
rect = s2sphere.LatLngRect.empty()
for template in self.get_flight_intents().values():
transformations = (
template.transformations
if "transformations" in template and template.transformations
else []
)
for vt in template.basic_information.area:
v4d = Volume4D(volume=vt.resolve_3d())
for transformation in transformations:
v4d = v4d.transform(transformation)
rect = rect.union(v4d.rect_bounds)
return LatLngBoundingBox.from_latlng_rect(rect)

def move(self, meters_east: float, meters_north: float) -> "FlightIntentsResource":
new_spec = FlightIntentsSpecification(self._spec)

transformation = Transformation(
relative_translation=RelativeTranslation(
meters_east=meters_east,
meters_north=meters_north,
)
)

if "transformations" in new_spec and new_spec.transformations:
new_spec.transformations = new_spec.transformations + [transformation]
else:
new_spec.transformations = [transformation]
return FlightIntentsResource(new_spec, resource_origin=self.resource_origin)


class FlightIntentsTriangularCascadeSoutheastResource(
TriangularCascadeSoutheastResource[FlightIntentsResource]
):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import unittest

from monitoring.monitorlib.geo import area_of_latlngrect
from monitoring.uss_qualifier.resources.definitions import (
ResourceDeclaration,
ResourceID,
)
from monitoring.uss_qualifier.resources.geospatial import (
TriangularCascadeSoutheastSpecification,
)
from monitoring.uss_qualifier.resources.resource import (
create_resources,
)


class TestFlightIntentsTriangularCascadeSoutheastResource(unittest.TestCase):
def _build_declarations(self) -> dict[ResourceID, ResourceDeclaration]:
return {
"flight_intents": ResourceDeclaration(
resource_type="resources.flight_planning.FlightIntentsResource",
specification={
"file": {
"path": "file://./test_data/che/flight_intents/general_flight_auth_flights.yaml",
},
},
),
"flight_intents_modifier": ResourceDeclaration(
resource_type="resources.flight_planning.FlightIntentsTriangularCascadeSoutheastResource",
specification=TriangularCascadeSoutheastSpecification(
meters_east_margin=1000, meters_north_margin=1000
),
dependencies={
"base_resource": "flight_intents",
},
),
}

def test_overlap_only_for_same_index(self):
resources = create_resources(self._build_declarations(), "test", True)
modifier = resources["flight_intents_modifier"]

extents = [
modifier.provide_resource_for(index=i).get_extents() for i in range(11)
]
base_area = area_of_latlngrect(extents[0].to_latlngrect())

for i in range(11):
for j in range(11):
overlap = area_of_latlngrect(
extents[i].to_latlngrect().intersection(extents[j].to_latlngrect())
)
if i == j:
assert (
overlap > 0.99 * base_area
), ( # Use 99% to compensate for errors
f"index {i}: self-overlap area {overlap:.2f}m² "
f"expected ~{base_area:.2f}m²"
)
else:
assert (
overlap < 0.01 * base_area
), ( # Use 1% to compensate for errors
f"indices {i},{j}: unexpected overlap area {overlap:.2f}m²"
)
Loading