Skip to content

[uss_qualifier/resource] Add GeospatialModifier for FlightIntentsResource#1468

Open
the-glu wants to merge 1 commit into
interuss:mainfrom
Orbitalize:1458_flight_data_modifier
Open

[uss_qualifier/resource] Add GeospatialModifier for FlightIntentsResource#1468
the-glu wants to merge 1 commit into
interuss:mainfrom
Orbitalize:1458_flight_data_modifier

Conversation

@the-glu

@the-glu the-glu commented May 19, 2026

Copy link
Copy Markdown
Member

This PR is stacked on top of #1467 and contrib to #1458

It does make FlightIntentsResource a GeospatialResource, with its FlightIntentsModifier.

Small unit tests included as well.

@the-glu the-glu force-pushed the 1458_flight_data_modifier branch from d9b1de5 to 0a5f15a Compare June 5, 2026 06:03
@the-glu

the-glu commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@BenjaminPelletier Rebased and ready for review

Comment on lines +93 to +109
# Apply the translation as degrees, not meters. RelativeTranslation in
# meters is converted per-polygon using each polygon's vertex_average as
# the tangent-plane origin, which yields slightly different absolute
# offsets for different polygons. That sub-meter drift is enough to break
# pre-existing intent overlaps (e.g. "tiny_overlap" conflicts). Converting
# meters → degrees here using the resource's overall extents produces a
# rigid lat/lng shift applied identically to every vertex.
extents = self.get_extents()
lat0 = (extents.lat_min + extents.lat_max) / 2
longitude_length = EARTH_CIRCUMFERENCE_M * math.cos(math.radians(lat0))

transformation = Transformation(
relative_translation=RelativeTranslation(
degrees_east=meters_east * 360 / longitude_length,
degrees_north=meters_north * 360 / EARTH_CIRCUMFERENCE_M,
)
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the core problem is that elements of the flight intents are not moved in the same way. Let's fix that rather than performing a degree-based movement since moving purely in degrees introduces a new type of inaccuracy (for instance, a 1km shape in Scotland shrinks by 25m east-west when moved 100km north). The much-better solution is to perform a rotation of the points on the sphere. Gemini recommends the code below, though I think we would want to split it into A) determine the rotation matrix transformation (up to step 3 below) and B) apply the rotation matrix transformation (step 3 below). Instead of flattening and unflattening, and instead of adding degree offsets, the transform method should determine the rotation matrix from the requested degrees or meters offset, and then apply that same rotation matrix to everything. After making this change, the meters_east/meters_north can be used directly without need to convert to degrees here.

import numpy as np
import s2sphere

def move_shape_rigidly(vertices, src_center, dst_center):
    """
    Moves a list of (lat, lng) vertices from a source center to a destination center
    without distortion using a 3D spherical rotation matrix.
    
    :param vertices: List of (lat, lng) tuples representing the shape's boundary
    :param src_center: (lat, lng) tuple of the original shape's anchor/center point
    :param dst_center: (lat, lng) tuple of the target destination point
    :return: List of moved (lat, lng) tuples
    """
    # 1. Convert center anchors to s2sphere unit vectors (S2Points)
    p_src = s2sphere.LatLng.from_degrees(*src_center).to_point()
    p_dst = s2sphere.LatLng.from_degrees(*dst_center).to_point()
    
    # Extract raw X, Y, Z components into numpy arrays
    a = np.array([p_src[0], p_src[1], p_src[2]])
    b = np.array([p_dst[0], p_dst[1], p_dst[2]])
    
    # 2. Compute the rotation matrix from vector 'a' to vector 'b'
    # using Rodrigues' rotation formula
    v = np.cross(a, b)
    c = np.dot(a, b)
    
    # Handle edge case where src and dst centers are identical or antipodal
    if np.allclose(v, 0):
        if c > 0:
            return vertices  # No translation needed
        else:
            # Shifted exactly to the opposite side of the Earth
            return [(-lat, (lng + 180) % 360 - 180) for lat, lng in vertices]

    # Create the skew-symmetric cross-product matrix of v
    kmat = np.array([
        [0, -v[2], v[1]],
        [v[2], 0, -v[0]],
        [-v[1], v[0], 0]
    ])
    
    # Calculate the definitive rotation matrix
    r_matrix = np.eye(3) + kmat + np.dot(kmat, kmat) * (1.0 / (1.0 + c))
    
    # 3. Apply the rotation matrix to every vertex in the shape
    moved_vertices = []
    for lat, lng in vertices:
        # Convert vertex to an S2Point vector
        v_pt = s2sphere.LatLng.from_degrees(lat, lng).to_point()
        v_arr = np.array([v_pt[0], v_pt[1], v_pt[2]])
        
        # Rotate the vector in 3D space
        rotated_arr = r_matrix.dot(v_arr)
        
        # Convert the new 3D vector back into an S2Point -> LatLng -> Degrees
        rotated_pt = s2sphere.Point(rotated_arr[0], rotated_arr[1], rotated_arr[2])
        rotated_ll = s2sphere.LatLng.from_point(rotated_pt)
        
        moved_vertices.append((rotated_ll.lat().degrees, rotated_ll.lng().degrees))
        
    return moved_vertices

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants