gri-plot 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {gri_plot-0.2.2 → gri_plot-0.2.4}/.docs_other_projects.md +10 -6
  2. {gri_plot-0.2.2 → gri_plot-0.2.4}/.init_venv.sh +8 -3
  3. {gri_plot-0.2.2 → gri_plot-0.2.4}/PKG-INFO +7 -18
  4. {gri_plot-0.2.2 → gri_plot-0.2.4}/README.md +5 -15
  5. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/__init__.py +5 -26
  6. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/figure3d.py +3 -83
  7. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/figure_map.py +38 -14
  8. gri_plot-0.2.4/gri_plot/observables/__init__.py +15 -0
  9. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/surfaces/__init__.py +3 -12
  10. gri_plot-0.2.4/gri_plot/surfaces/mesh.py +133 -0
  11. {gri_plot-0.2.2 → gri_plot-0.2.4}/pyproject.toml +10 -3
  12. gri_plot-0.2.4/report.xml +1 -0
  13. gri_plot-0.2.4/test/test_figure3d.py +79 -0
  14. gri_plot-0.2.4/test/test_frames.py +136 -0
  15. gri_plot-0.2.4/test/test_surfaces/test_mesh.py +51 -0
  16. {gri_plot-0.2.2 → gri_plot-0.2.4}/uv.lock +0 -175
  17. gri_plot-0.2.2/.coveragerc +0 -8
  18. gri_plot-0.2.2/gri_plot/observables/__init__.py +0 -21
  19. gri_plot-0.2.2/gri_plot/observables/fdoa.py +0 -256
  20. gri_plot-0.2.2/gri_plot/observables/tdoa.py +0 -203
  21. gri_plot-0.2.2/gri_plot/observables/terrain.py +0 -295
  22. gri_plot-0.2.2/gri_plot/surfaces/gradients/__init__.py +0 -39
  23. gri_plot-0.2.2/gri_plot/surfaces/gradients/axis.py +0 -76
  24. gri_plot-0.2.2/gri_plot/surfaces/gradients/line.py +0 -88
  25. gri_plot-0.2.2/gri_plot/surfaces/gradients/plane.py +0 -55
  26. gri_plot-0.2.2/gri_plot/surfaces/gradients/point.py +0 -42
  27. gri_plot-0.2.2/gri_plot/surfaces/intersection.py +0 -219
  28. gri_plot-0.2.2/gri_plot/surfaces/mesh.py +0 -605
  29. gri_plot-0.2.2/report.xml +0 -1
  30. gri_plot-0.2.2/test/test_figure3d.py +0 -287
  31. gri_plot-0.2.2/test/test_frames.py +0 -44
  32. gri_plot-0.2.2/test/test_observables/test_fdoa.py +0 -59
  33. gri_plot-0.2.2/test/test_observables/test_tdoa.py +0 -72
  34. gri_plot-0.2.2/test/test_observables/test_terrain.py +0 -45
  35. gri_plot-0.2.2/test/test_surfaces/test_intersection.py +0 -203
  36. gri_plot-0.2.2/test/test_surfaces/test_mesh.py +0 -239
  37. {gri_plot-0.2.2 → gri_plot-0.2.4}/.gitignore +0 -0
  38. {gri_plot-0.2.2 → gri_plot-0.2.4}/.gitlab-ci-deps.yml +0 -0
  39. {gri_plot-0.2.2 → gri_plot-0.2.4}/.gitlab-ci.yml +0 -0
  40. {gri_plot-0.2.2 → gri_plot-0.2.4}/.pre-commit-config.yaml +0 -0
  41. {gri_plot-0.2.2 → gri_plot-0.2.4}/.pre-commit-unskipped.py +0 -0
  42. {gri_plot-0.2.2 → gri_plot-0.2.4}/.python-version +0 -0
  43. {gri_plot-0.2.2 → gri_plot-0.2.4}/.ruff.toml +0 -0
  44. {gri_plot-0.2.2 → gri_plot-0.2.4}/.vscode/settings.json +0 -0
  45. {gri_plot-0.2.2 → gri_plot-0.2.4}/CONTRIBUTING.md +0 -0
  46. {gri_plot-0.2.2 → gri_plot-0.2.4}/LICENSE +0 -0
  47. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/frames.py +0 -0
  48. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/observables/aoa.py +0 -0
  49. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/observables/los.py +0 -0
  50. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/observables/range_sphere.py +0 -0
  51. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/plot_ellipse.py +0 -0
  52. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/py.typed +0 -0
  53. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/scatter.py +0 -0
  54. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/scatter_map.py +0 -0
  55. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/__init__.py +0 -0
  56. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/cone.py +0 -0
  57. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/cylinder.py +0 -0
  58. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/ellipse.py +0 -0
  59. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/ellipsoid.py +0 -0
  60. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/meshgen.py +0 -0
  61. {gri_plot-0.2.2 → gri_plot-0.2.4}/gri_plot/shapes/sphere.py +0 -0
  62. {gri_plot-0.2.2 → gri_plot-0.2.4}/notes.txt +0 -0
  63. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/__init__.py +0 -0
  64. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_figure_map.py +0 -0
  65. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_observables/__init__.py +0 -0
  66. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_observables/test_aoa.py +0 -0
  67. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_observables/test_los.py +0 -0
  68. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_observables/test_range_sphere.py +0 -0
  69. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_plot_ellipse.py +0 -0
  70. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_plot_scattermap.py +0 -0
  71. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_scatter.py +0 -0
  72. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/__init__.py +0 -0
  73. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/test_cone.py +0 -0
  74. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/test_cylinder.py +0 -0
  75. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/test_ellipse.py +0 -0
  76. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/test_ellipsoid.py +0 -0
  77. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_shapes/test_sphere.py +0 -0
  78. {gri_plot-0.2.2 → gri_plot-0.2.4}/test/test_surfaces/__init__.py +0 -0
@@ -16,10 +16,6 @@ Equation fitting wrapper around scipy.optimize.curve\_fit. Simplifies curve fitt
16
16
 
17
17
  Class-based memoization with full type hints and IDE integration. Provides caching decorators for performance optimization.
18
18
 
19
- ### [GRI Plot](https://gitlab.com/geosol-foss/python/gri-plot)
20
-
21
- Plotting utilities for Plotly and Matplotlib. Provides consistent styling and common plot types for geospatial visualization.
22
-
23
19
  ### [GRI Signal](https://gitlab.com/geosol-foss/python/gri-signal)
24
20
 
25
21
  Functional signal processing library with pure functions operating on numpy arrays. Includes signal generation (tones, PRN, pulsed), channel simulation (delay, AWGN, Doppler, multipath), modulation (AM/FM/PM, BPSK/QPSK/QAM), sampling (ADC), filtering (lowpass, highpass, bandpass, notch, analytic), spectral analysis (PSD, SNR, THD, time-bandwidth product), cross-correlation, and beamforming.
@@ -46,6 +42,14 @@ Solar flux and NeQuickG coefficient data pipeline for ionospheric modeling.
46
42
 
47
43
  A lot of the work we do is down to nanosecond or tens of nanosecond accuracy. We needed an object that could both handle that level of precision as well as do a lot of the string <=> time conversions for the many semi-standards formats we run into in this field.
48
44
 
45
+ ### [GRI Obs](https://gitlab.com/geosol-foss/python/gri-obs)
46
+
47
+ Typed observation objects (TDOA, FDOA, AOA, Range, PDOA) for geolocation measurements. Each observation carries measurement data, noise characterization, sensor geometry, and measurement math (predicted values, Jacobians, noise covariance) for EKF integration with Kalman filters.
48
+
49
+ ### [GRI Plot](https://gitlab.com/geosol-foss/python/gri-plot)
50
+
51
+ Plotting utilities for Plotly and Matplotlib. Provides consistent styling and common plot types for geospatial visualization.
52
+
49
53
  ### [GRI Pos](https://gitlab.com/geosol-foss/python/gri-pos)
50
54
 
51
55
  A position object that greatly simplifies handling coordinates, distances, and memory, mostly on the WGS-84 ellipsoid.
@@ -66,7 +70,7 @@ ERA5 data pipeline for downloading and processing tropospheric refractivity data
66
70
 
67
71
  ### [GRI Convolve](https://gitlab.com/geosol-foss/python/gri-convolve)
68
72
 
69
- Ellipsoid convolution with outlier detection and clustering. Combines multiple statistical position estimates into refined solutions.
73
+ Ellipsoid convolution with outlier detection and clustering, plus Kalman tracking via Interacting Multiple Model (IMM) filter. Combines multiple statistical position estimates into refined solutions. The Kalman tracker supports both ellipsoid-based and raw observable (EKF) updates, bidirectional outlier detection, and online track segmentation.
70
74
 
71
75
  ### [GRI GeoSim](https://gitlab.com/geosol-foss/python/gri-geosim)
72
76
 
@@ -78,4 +82,4 @@ Some neat (small) examples and projects we've put together over time using the a
78
82
 
79
83
  ### [GRI SigSim](https://gitlab.com/geosol-foss/python/gri-sigsim)
80
84
 
81
- Object-based RF signal simulation library. Wraps gri-signal functions with chainable object-oriented classes for waveforms (chirp, OFDM, pulsed), channel modeling (delay, Doppler, multipath), receiver simulation, and observable extraction (TOA, FOA, AOA).
85
+ Object-based RF signal simulation library. Wraps gri-signal functions with chainable object-oriented classes for waveforms (pulsed, PRN, tone), channel modeling (delay, Doppler, multipath), receiver simulation, and TOA estimation.
@@ -65,10 +65,15 @@ if [ $? -ne 0 ]; then
65
65
  fi
66
66
  uv sync
67
67
 
68
- # activate and print version
68
+ # activate and print version (Scripts/ on Windows, bin/ elsewhere)
69
69
  echo "${bold}Activating environment${normal}"
70
- . $VENV/bin/activate
71
- $VENV/bin/python --version
70
+ if [[ -d "$VENV/Scripts" ]]; then
71
+ . $VENV/Scripts/activate
72
+ $VENV/Scripts/python --version
73
+ else
74
+ . $VENV/bin/activate
75
+ $VENV/bin/python --version
76
+ fi
72
77
 
73
78
  # Install pre-commit scripts
74
79
  echo "${bold}Installing git hooks${normal}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gri-plot
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: 3D geolocation visualization and 2D plotting utilities built on Plotly
5
5
  Project-URL: Homepage, https://geosolresearch.com
6
6
  Project-URL: Repository, https://gitlab.com/geosol-foss/python/gri-plot
@@ -17,10 +17,9 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Topic :: Scientific/Engineering
18
18
  Classifier: Topic :: Scientific/Engineering :: Visualization
19
19
  Requires-Python: >=3.12
20
- Requires-Dist: gri-utils>=0.2.0
20
+ Requires-Dist: gri-utils>=0.3.3
21
21
  Requires-Dist: numpy>=2.3.3
22
22
  Requires-Dist: plotly>=6.3.0
23
- Requires-Dist: scikit-image>=0.25.0
24
23
  Description-Content-Type: text/markdown
25
24
 
26
25
  [![GeoSol Research Logo](https://geosolresearch.com/logos/foss_logo.png "GeoSol Research")](https://geosolresearch.com)
@@ -31,7 +30,7 @@ Description-Content-Type: text/markdown
31
30
 
32
31
  ## Overview
33
32
 
34
- This library renders implicit surfaces (TDOA hyperboloids, FDOA iso-Doppler surfaces, AOA cones, range spheres, terrain) and geometric shapes (ellipsoids, spheres, cones, cylinders) as interactive 3D meshes. It also provides shorthand functions for 2D scatter plots and geographic maps.
33
+ This library renders parametric geometric shapes (ellipsoids, spheres, cones, cylinders) and observable surfaces that inherit from them (AOA cones, line-of-sight rays, range spheres) as interactive 3D meshes. It also provides shorthand functions for 2D scatter plots and geographic maps.
35
34
 
36
35
  ## Installation
37
36
 
@@ -86,14 +85,12 @@ Scene builder for composing 3D visualizations from multiple surfaces and points:
86
85
  - **`add_surface()`**: Add any `ImplicitShape` (shapes or observables)
87
86
  - **`add_points()`**: Add labeled scatter points
88
87
  - **`add_line()`**: Add line segments connecting points
89
- - **`add_intersection()`**: Highlight where multiple surfaces intersect
90
88
  - **`plot_surfaces()`**: Convenience function for quick multi-surface plots
91
89
  - Method chaining, automatic color cycling from D3 palette, `plotly_dark` template
92
90
 
93
91
  ```python
94
92
  from gri_plot import Figure3D
95
93
  from gri_plot.shapes import Sphere
96
- from gri_plot.observables import TdoaSurface
97
94
  import numpy as np
98
95
 
99
96
  fig = Figure3D()
@@ -113,7 +110,7 @@ Coordinate frame handling for 3D display:
113
110
 
114
111
  ## Shapes
115
112
 
116
- Geometric primitives implementing the `ImplicitShape` ABC. Each supports parametric mesh generation (fast, exact geometry) and implicit residual evaluation (for intersection calculations).
113
+ Geometric primitives implementing the `ImplicitShape` ABC. Each supports parametric mesh generation (fast, exact geometry) and implicit residual evaluation.
117
114
 
118
115
  - **Ellipsoid**: From center + covariance matrix, or center + semi-axes + rotation matrix
119
116
  - **Sphere**: From center + radius
@@ -123,31 +120,23 @@ Geometric primitives implementing the `ImplicitShape` ABC. Each supports paramet
123
120
 
124
121
  ## Observables
125
122
 
126
- Geolocation-specific surfaces for visualizing measurement iso-surfaces. Some inherit from shapes (reusing parametric rendering), others use marching cubes for mesh extraction.
123
+ Geolocation-specific surfaces that inherit from parametric shapes:
127
124
 
128
- - **TdoaSurface**: TDOA hyperboloid where range_1 - range_2 = c x tdoa, from two collector positions + time difference
129
- - **FdoaSurface**: FDOA iso-Doppler surface from two collector positions/velocities + carrier frequency + frequency difference
130
125
  - **AoaSurface**: AOA cone (extends Cone) from collector position + measured direction + angular error
131
126
  - **LosSurface**: Line of sight ray (extends Cylinder) from start point + direction + length; also provides `to_line_trace()` for simple line rendering
132
127
  - **RangeSphere**: Range/TOA sphere (extends Sphere) from collector position + range measurement
133
- - **TerrainSurface**: Terrain from XYZ vertex grid with interpolated residual evaluation; factory methods `from_xyz_grids()` and `from_elevation_grid()`
134
128
 
135
129
  ## Surfaces
136
130
 
137
- Low-level infrastructure for implicit surface rendering:
131
+ Low-level infrastructure for shape rendering:
138
132
 
139
133
  - **ImplicitShape ABC**: Interface requiring `residual_fn()`, `get_bounds_xyz()`, `to_mesh()`, `to_trace()`, `is_volume`, `label`; also provides `contains()` for point-in-shape testing
140
- - **Mesh utilities**: `field_to_mesh()` (marching cubes with Newton-Raphson surface projection and adaptive refinement), `grid_to_mesh()` (parametric grid triangulation), `vertices_to_mesh3d()` (vertices/faces to Plotly Mesh3d trace)
141
- - **IntersectionField**: Euclidean distance in residual space for highlighting multi-surface intersections; distinguishes volumes (interior contribution) from surfaces (boundary contribution)
142
- - **Gradient functions**: Intensity calculators for mesh coloring -- `distance_from_point`, `distance_from_line`, `distance_from_line_segment`, `distance_from_plane`, `distance_from_axis`, `axis_value`
134
+ - **Mesh utilities**: `grid_to_mesh()` (parametric grid triangulation), `vertices_to_mesh3d()` (vertices/faces to Plotly Mesh3d trace)
143
135
 
144
136
  ## Dependencies
145
137
 
146
138
  - **plotly**: Interactive 2D and 3D visualization
147
- - **matplotlib**: Static plot support
148
139
  - **numpy**: Array operations
149
- - **scipy**: Marching cubes mesh extraction
150
- - **scikit-image**: Additional mesh processing
151
140
  - **gri-utils**: Coordinate conversions and constants
152
141
 
153
142
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Overview
8
8
 
9
- This library renders implicit surfaces (TDOA hyperboloids, FDOA iso-Doppler surfaces, AOA cones, range spheres, terrain) and geometric shapes (ellipsoids, spheres, cones, cylinders) as interactive 3D meshes. It also provides shorthand functions for 2D scatter plots and geographic maps.
9
+ This library renders parametric geometric shapes (ellipsoids, spheres, cones, cylinders) and observable surfaces that inherit from them (AOA cones, line-of-sight rays, range spheres) as interactive 3D meshes. It also provides shorthand functions for 2D scatter plots and geographic maps.
10
10
 
11
11
  ## Installation
12
12
 
@@ -61,14 +61,12 @@ Scene builder for composing 3D visualizations from multiple surfaces and points:
61
61
  - **`add_surface()`**: Add any `ImplicitShape` (shapes or observables)
62
62
  - **`add_points()`**: Add labeled scatter points
63
63
  - **`add_line()`**: Add line segments connecting points
64
- - **`add_intersection()`**: Highlight where multiple surfaces intersect
65
64
  - **`plot_surfaces()`**: Convenience function for quick multi-surface plots
66
65
  - Method chaining, automatic color cycling from D3 palette, `plotly_dark` template
67
66
 
68
67
  ```python
69
68
  from gri_plot import Figure3D
70
69
  from gri_plot.shapes import Sphere
71
- from gri_plot.observables import TdoaSurface
72
70
  import numpy as np
73
71
 
74
72
  fig = Figure3D()
@@ -88,7 +86,7 @@ Coordinate frame handling for 3D display:
88
86
 
89
87
  ## Shapes
90
88
 
91
- Geometric primitives implementing the `ImplicitShape` ABC. Each supports parametric mesh generation (fast, exact geometry) and implicit residual evaluation (for intersection calculations).
89
+ Geometric primitives implementing the `ImplicitShape` ABC. Each supports parametric mesh generation (fast, exact geometry) and implicit residual evaluation.
92
90
 
93
91
  - **Ellipsoid**: From center + covariance matrix, or center + semi-axes + rotation matrix
94
92
  - **Sphere**: From center + radius
@@ -98,31 +96,23 @@ Geometric primitives implementing the `ImplicitShape` ABC. Each supports paramet
98
96
 
99
97
  ## Observables
100
98
 
101
- Geolocation-specific surfaces for visualizing measurement iso-surfaces. Some inherit from shapes (reusing parametric rendering), others use marching cubes for mesh extraction.
99
+ Geolocation-specific surfaces that inherit from parametric shapes:
102
100
 
103
- - **TdoaSurface**: TDOA hyperboloid where range_1 - range_2 = c x tdoa, from two collector positions + time difference
104
- - **FdoaSurface**: FDOA iso-Doppler surface from two collector positions/velocities + carrier frequency + frequency difference
105
101
  - **AoaSurface**: AOA cone (extends Cone) from collector position + measured direction + angular error
106
102
  - **LosSurface**: Line of sight ray (extends Cylinder) from start point + direction + length; also provides `to_line_trace()` for simple line rendering
107
103
  - **RangeSphere**: Range/TOA sphere (extends Sphere) from collector position + range measurement
108
- - **TerrainSurface**: Terrain from XYZ vertex grid with interpolated residual evaluation; factory methods `from_xyz_grids()` and `from_elevation_grid()`
109
104
 
110
105
  ## Surfaces
111
106
 
112
- Low-level infrastructure for implicit surface rendering:
107
+ Low-level infrastructure for shape rendering:
113
108
 
114
109
  - **ImplicitShape ABC**: Interface requiring `residual_fn()`, `get_bounds_xyz()`, `to_mesh()`, `to_trace()`, `is_volume`, `label`; also provides `contains()` for point-in-shape testing
115
- - **Mesh utilities**: `field_to_mesh()` (marching cubes with Newton-Raphson surface projection and adaptive refinement), `grid_to_mesh()` (parametric grid triangulation), `vertices_to_mesh3d()` (vertices/faces to Plotly Mesh3d trace)
116
- - **IntersectionField**: Euclidean distance in residual space for highlighting multi-surface intersections; distinguishes volumes (interior contribution) from surfaces (boundary contribution)
117
- - **Gradient functions**: Intensity calculators for mesh coloring -- `distance_from_point`, `distance_from_line`, `distance_from_line_segment`, `distance_from_plane`, `distance_from_axis`, `axis_value`
110
+ - **Mesh utilities**: `grid_to_mesh()` (parametric grid triangulation), `vertices_to_mesh3d()` (vertices/faces to Plotly Mesh3d trace)
118
111
 
119
112
  ## Dependencies
120
113
 
121
114
  - **plotly**: Interactive 2D and 3D visualization
122
- - **matplotlib**: Static plot support
123
115
  - **numpy**: Array operations
124
- - **scipy**: Marching cubes mesh extraction
125
- - **scikit-image**: Additional mesh processing
126
116
  - **gri-utils**: Coordinate conversions and constants
127
117
 
128
118
 
@@ -1,16 +1,16 @@
1
1
  """Plotting utilities for GeoSol Research.
2
2
 
3
3
  This package provides utilities for 2D and 3D visualization using Plotly,
4
- including scatter plots, geographic maps, and 3D surfaces for geolocation
5
- observables.
4
+ including scatter plots, geographic maps, and parametric 3D shapes for
5
+ geolocation observables.
6
6
 
7
7
  Main components:
8
8
  - scatter: 2D scatter plots with Plotly
9
9
  - scatter_map: Geographic scatter maps
10
10
  - Figure3D: 3D figure for combining surfaces
11
11
  - shapes: Geometric shapes (Ellipsoid, Sphere, Cone, Cylinder)
12
- - observables: Geolocation surfaces (TDOA, FDOA, AOA, etc.)
13
- - surfaces: Low-level surface utilities
12
+ - observables: Parametric geolocation surfaces (AOA, LOS, Range)
13
+ - surfaces: ImplicitShape ABC and mesh trace utilities
14
14
  - frames: Coordinate frame handling
15
15
  """
16
16
 
@@ -23,11 +23,8 @@ from .frames import Bounds, Frame, FrameTransformer
23
23
  # Observables
24
24
  from .observables import (
25
25
  AoaSurface,
26
- FdoaSurface,
27
26
  LosSurface,
28
27
  RangeSphere,
29
- TdoaSurface,
30
- TerrainSurface,
31
28
  )
32
29
  from .plot_ellipse import plot_ellipse
33
30
  from .scatter import scatter
@@ -45,15 +42,7 @@ from .surfaces import (
45
42
  expand_bounds,
46
43
  merge_bounds,
47
44
  )
48
- from .surfaces.gradients import (
49
- axis_value,
50
- distance_from_axis,
51
- distance_from_line,
52
- distance_from_line_segment,
53
- distance_from_plane,
54
- distance_from_point,
55
- )
56
- from .surfaces.mesh import field_to_mesh, grid_to_mesh, vertices_to_mesh3d
45
+ from .surfaces.mesh import grid_to_mesh, vertices_to_mesh3d
57
46
 
58
47
  __all__ = [
59
48
  "DEFAULT_COLORS",
@@ -63,7 +52,6 @@ __all__ = [
63
52
  "Cylinder",
64
53
  "Ellipse",
65
54
  "Ellipsoid",
66
- "FdoaSurface",
67
55
  "Figure3D",
68
56
  "FigureMap",
69
57
  "Frame",
@@ -73,19 +61,10 @@ __all__ = [
73
61
  "MapStyle",
74
62
  "RangeSphere",
75
63
  "Sphere",
76
- "TdoaSurface",
77
- "TerrainSurface",
78
- "axis_value",
79
64
  "cone_mesh",
80
65
  "cylinder_mesh",
81
- "distance_from_axis",
82
- "distance_from_line",
83
- "distance_from_line_segment",
84
- "distance_from_plane",
85
- "distance_from_point",
86
66
  "ellipsoid_mesh",
87
67
  "expand_bounds",
88
- "field_to_mesh",
89
68
  "grid_to_mesh",
90
69
  "merge_bounds",
91
70
  "plot_ellipse",
@@ -12,9 +12,7 @@ import numpy as np
12
12
  import plotly.graph_objects as go
13
13
 
14
14
  from .frames import Frame, FrameTransformer
15
- from .surfaces import ImplicitShape, expand_bounds, merge_bounds
16
- from .surfaces.intersection import IntersectionField
17
- from .surfaces.mesh import field_to_mesh, vertices_to_mesh3d
15
+ from .surfaces import ImplicitShape, merge_bounds
18
16
 
19
17
  if TYPE_CHECKING:
20
18
  from collections.abc import Sequence
@@ -140,7 +138,7 @@ class Figure3D:
140
138
 
141
139
  Args:
142
140
  surface: ImplicitShape object to visualize.
143
- resolution: Mesh resolution. If None, uses field_to_mesh default.
141
+ resolution: Mesh resolution. If None, uses the shape's default.
144
142
  color: Base color for single-color gradient. If None, uses full
145
143
  Viridis colorscale. For multiple surfaces, pick colors from
146
144
  DEFAULT_COLORS to distinguish them (e.g., color=DEFAULT_COLORS[0]).
@@ -304,84 +302,6 @@ class Figure3D:
304
302
  trace_type = type(trace)
305
303
  return trace_type(**trace_dict)
306
304
 
307
- def add_intersection( # noqa: PLR0913 - intersection has many visual options
308
- self,
309
- *surfaces: ImplicitShape,
310
- threshold: float = 1.5,
311
- steepness: float | None = None,
312
- resolution: int | None = None,
313
- color: str | None = None,
314
- opacity: float = 0.8,
315
- label: str | None = None,
316
- **kwargs,
317
- ) -> Figure3D:
318
- """Add an intersection visualization for multiple implicit surfaces.
319
-
320
- Highlights regions where multiple surfaces intersect by computing a
321
- combined "closeness" field. Single-surface regions are hidden (below
322
- threshold), while multi-surface intersections are progressively
323
- highlighted.
324
-
325
- Args:
326
- *surfaces: ImplicitSurface objects to intersect. Must be at least 2.
327
- threshold: Value subtracted from combined closeness. Default 1.5
328
- hides single-surface regions while showing 2+ intersections.
329
- steepness: Exponential falloff rate. If None, auto-computed based
330
- on number of surfaces to ensure good visual gradient.
331
- resolution: Mesh resolution. If None, uses field_to_mesh default.
332
- color: Base color for gradient. If None, uses next palette color.
333
- opacity: Surface opacity (0-1).
334
- label: Optional label for legends.
335
- **kwargs: Additional arguments passed to the Mesh3d trace.
336
-
337
- Returns:
338
- Self for method chaining.
339
-
340
- Raises:
341
- ValueError: If fewer than 2 surfaces provided.
342
- """
343
- intersection = IntersectionField(
344
- surfaces,
345
- threshold=threshold,
346
- steepness=steepness,
347
- )
348
-
349
- # Get bounds and add to tracking
350
- bounds_xyz = intersection.get_bounds_xyz()
351
- self._surface_bounds.append(bounds_xyz)
352
-
353
- # Determine color
354
- if color is None:
355
- color = self._next_color()
356
- colorscale = _color_to_gradient(color)
357
-
358
- # Generate mesh using marching cubes via field_to_mesh
359
- bounds = expand_bounds(bounds_xyz, 1.05)
360
- if resolution is None:
361
- vertices, faces = field_to_mesh(intersection.residual_fn, bounds)
362
- else:
363
- vertices, faces = field_to_mesh(
364
- intersection.residual_fn,
365
- bounds,
366
- resolution=resolution,
367
- )
368
-
369
- # Create Mesh3d trace
370
- trace = vertices_to_mesh3d(
371
- vertices,
372
- faces,
373
- colorscale=colorscale,
374
- opacity=opacity,
375
- name=label,
376
- **kwargs,
377
- )
378
-
379
- if self.display_frame != Frame.XYZ:
380
- trace = self._transform_trace(trace)
381
-
382
- self._traces.append(trace)
383
- return self
384
-
385
305
  def get_bounds_xyz(
386
306
  self,
387
307
  ) -> tuple[NDArray[np.floating], NDArray[np.floating]] | None:
@@ -457,7 +377,7 @@ def plot_surfaces(
457
377
  display_frame: Coordinate frame for visualization.
458
378
  origin_xyz: Origin for ENU frame.
459
379
  title: Figure title.
460
- resolution: Mesh resolution. If None, uses field_to_mesh default.
380
+ resolution: Mesh resolution. If None, uses the shape's default.
461
381
  **kwargs: Additional arguments passed to add_surface.
462
382
 
463
383
  Returns:
@@ -241,11 +241,9 @@ class FigureMap:
241
241
  showlegend=show_legend and label is not None,
242
242
  )
243
243
  else:
244
- x_vals = display_coords[:, 0]
245
- y_vals = display_coords[:, 1]
246
244
  trace = go.Scatter(
247
- x=x_vals,
248
- y=y_vals,
245
+ x=display_coords[:, 1],
246
+ y=display_coords[:, 0],
249
247
  mode="lines",
250
248
  line={"color": color, "width": width},
251
249
  name=label,
@@ -278,8 +276,8 @@ class FigureMap:
278
276
  )
279
277
  else:
280
278
  trace = go.Scatter(
281
- x=display_coords[:, 0],
282
- y=display_coords[:, 1],
279
+ x=display_coords[:, 1],
280
+ y=display_coords[:, 0],
283
281
  mode="lines",
284
282
  fill="toself",
285
283
  fillcolor=fill_color,
@@ -478,21 +476,40 @@ class FigureMap:
478
476
  self._add_line_trace(display, color, width, label, show_legend=True)
479
477
  return self
480
478
 
481
- def add_heatmap(
479
+ def add_heatmap( # noqa: PLR0913
482
480
  self,
483
481
  sheet_xyz: NDArray[np.floating],
482
+ *,
483
+ values: NDArray[np.floating] | None = None,
484
+ lat_deg: NDArray[np.floating] | None = None,
485
+ lon_deg: NDArray[np.floating] | None = None,
484
486
  colorscale: str = "earth",
485
487
  opacity: float = 0.6,
486
488
  label: str | None = None,
487
489
  ) -> FigureMap:
488
- """Add a terrain-like heatmap colored by altitude.
490
+ """Add a heatmap over a spatial grid.
491
+
492
+ Renders a 2D grid as a colored heatmap. When ``values`` is None,
493
+ color represents the altitude of each grid point. When ``values``
494
+ is provided, it is used as the color field instead (e.g. TDOA,
495
+ TDOA-dot, or any scalar quantity defined on the grid).
496
+
497
+ When ``lat_deg`` and ``lon_deg`` are provided they are used
498
+ directly as the axis coordinates, bypassing extraction from
499
+ ``sheet_xyz``. This is useful when the sheet contains NaN cells
500
+ (e.g. a visibility-masked grid) that would corrupt the
501
+ coordinate recovery.
489
502
 
490
- Renders a 2D grid as a colored heatmap where color represents
491
- the altitude (Z-height) of each grid point. Only supported in
492
- BLANK mode.
503
+ Only supported in BLANK mode.
493
504
 
494
505
  Args:
495
506
  sheet_xyz: Terrain sheet in ECEF meters, shape (M, N, 3).
507
+ values: Optional scalar field to color by, shape (M, N).
508
+ When None, altitude is used.
509
+ lat_deg: Optional latitude axis, shape (M,). When None,
510
+ extracted from sheet_xyz.
511
+ lon_deg: Optional longitude axis, shape (N,). When None,
512
+ extracted from sheet_xyz.
496
513
  colorscale: Plotly colorscale name. Default "earth".
497
514
  opacity: Heatmap opacity (0-1). Default 0.6.
498
515
  label: Colorbar title.
@@ -510,7 +527,11 @@ class FigureMap:
510
527
  sheet_xyz = np.asarray(sheet_xyz, dtype=np.float64)
511
528
  m, n, _ = sheet_xyz.shape
512
529
 
513
- if self._frame == "enu":
530
+ if lat_deg is not None and lon_deg is not None:
531
+ x_vals = np.asarray(lon_deg, dtype=np.float64)
532
+ y_vals = np.asarray(lat_deg, dtype=np.float64)
533
+ z_vals = values
534
+ elif self._frame == "enu":
514
535
  assert self._origin_xyz is not None # noqa: S101
515
536
  flat = sheet_xyz.reshape(-1, 3)
516
537
  enu = get_enu(self._origin_xyz, flat)
@@ -526,6 +547,9 @@ class FigureMap:
526
547
  y_vals = lats
527
548
  z_vals = alts
528
549
 
550
+ if values is not None:
551
+ z_vals = np.asarray(values, dtype=np.float64)
552
+
529
553
  trace = go.Heatmap(
530
554
  x=x_vals,
531
555
  y=y_vals,
@@ -580,8 +604,8 @@ class FigureMap:
580
604
  self._traces.append(go.Scattermap(**trace_kwargs))
581
605
  else:
582
606
  trace_kwargs = {
583
- "x": display[:, 0],
584
- "y": display[:, 1],
607
+ "x": display[:, 1],
608
+ "y": display[:, 0],
585
609
  "mode": mode,
586
610
  "marker": {"size": size, "color": color},
587
611
  "showlegend": False,
@@ -0,0 +1,15 @@
1
+ """Observable surface classes for geolocation visualization.
2
+
3
+ This module provides parametric surfaces for visualizing AOA cones,
4
+ line-of-sight rays, and range spheres.
5
+ """
6
+
7
+ from .aoa import AoaSurface
8
+ from .los import LosSurface
9
+ from .range_sphere import RangeSphere
10
+
11
+ __all__ = [
12
+ "AoaSurface",
13
+ "LosSurface",
14
+ "RangeSphere",
15
+ ]
@@ -21,26 +21,20 @@ class ImplicitShape(ABC):
21
21
  This ABC provides a unified interface for shapes that can be:
22
22
  - Evaluated via an implicit function (residual_fn)
23
23
  - Rendered as a Plotly Mesh3d trace
24
- - Used in intersection calculations
25
24
 
26
25
  Residual convention:
27
26
  - residual < 0: inside the shape
28
27
  - residual = 0: on the boundary
29
28
  - residual > 0: outside the shape
30
29
 
31
- The is_volume property determines intersection behavior:
32
- - True: interior (residual < 0) is the solution region
33
- - False: boundary (residual = 0) is the solution locus
30
+ The is_volume property describes whether the shape represents a solid
31
+ volume or a thin boundary surface.
34
32
  """
35
33
 
36
34
  @property
37
35
  @abstractmethod
38
36
  def is_volume(self) -> bool:
39
- """Whether this shape represents a volume (True) or surface (False).
40
-
41
- Volumes contribute to intersection everywhere inside (residual < 0).
42
- Surfaces contribute based on distance from boundary (|residual|).
43
- """
37
+ """Whether this shape represents a volume (True) or surface (False)."""
44
38
  ...
45
39
 
46
40
  @property
@@ -163,11 +157,8 @@ def expand_bounds(
163
157
  return center - half_extent * factor, center + half_extent * factor
164
158
 
165
159
 
166
- from .intersection import IntersectionField # noqa: E402
167
-
168
160
  __all__ = [
169
161
  "ImplicitShape",
170
- "IntersectionField",
171
162
  "expand_bounds",
172
163
  "merge_bounds",
173
164
  ]