[uss_qualifier/resource] Add GeospatialModifier for FlightIntentsResource#1468
[uss_qualifier/resource] Add GeospatialModifier for FlightIntentsResource#1468the-glu wants to merge 1 commit into
Conversation
7150849 to
0beb6a7
Compare
c5fb6dd to
4841b49
Compare
8c10ca5 to
d9b1de5
Compare
d9b1de5 to
0a5f15a
Compare
|
@BenjaminPelletier Rebased and ready for review |
| # 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, | ||
| ) | ||
| ) |
There was a problem hiding this comment.
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
This PR is stacked on top of #1467 and contrib to #1458
It does make
FlightIntentsResourceaGeospatialResource, with itsFlightIntentsModifier.Small unit tests included as well.