Skip to content

Commit 6cb7054

Browse files
authored
Merge branch 'master' into master
2 parents f32bde3 + 00d9ebb commit 6cb7054

5 files changed

Lines changed: 840 additions & 58 deletions

File tree

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@
551551
## [Geodesy](geodesy)
552552
* [Haversine Distance](geodesy/haversine_distance.py)
553553
* [Lamberts Ellipsoidal Distance](geodesy/lamberts_ellipsoidal_distance.py)
554+
* [Radar Target Calculation](geodesy/radar_target_calculation.py)
554555

555556
## [Geometry](geometry)
556557
* [Geometry](geometry/geometry.py)
@@ -724,6 +725,7 @@
724725
* [Loss Functions](machine_learning/loss_functions.py)
725726
* Lstm
726727
* [Lstm Prediction](machine_learning/lstm/lstm_prediction.py)
728+
* [Mab](machine_learning/mab.py)
727729
* [Mean Shift](machine_learning/mean_shift.py)
728730
* [Mfcc](machine_learning/mfcc.py)
729731
* [Mini Batch Gradient Descent](machine_learning/mini_batch_gradient_descent.py)

docs/hacktober_2026_prep.md

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
151151
137. [ ] #14768 enhancement, awaiting reviews
152152
138. [ ] #14767 enhancement, awaiting reviews
153153
139. [x] #14759 closed
154-
140. [ ] #14753 awaiting reviews
154+
140. [x] #14753 merged
155155
141. [x] #14751 closed
156156
142. [ ] #14746 enhancement, awaiting reviews
157157
143. [x] #14743 merged
@@ -256,7 +256,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
256256
242. [ ] #14436 no labels
257257
243. [x] #14432 merged
258258
244. [ ] #14415 awaiting reviews
259-
245. [ ] #14414 awaiting reviews
259+
245. [x] #14414 merged
260260
246. [ ] #14410 enhancement, awaiting reviews
261261
247. [ ] #14409 enhancement, awaiting reviews
262262
248. [x] #14402 closed by close_pull_requests_with_require_type_hints.sh
@@ -298,7 +298,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
298298
284. [x] #14260 merged
299299
285. [x] #14256 closed by close_pull_requests_with_failing_tests.sh
300300
286. [ ] #14239 no labels
301-
287. [ ] #14237 awaiting reviews
301+
287. [x] #14237 merged
302302
288. [x] #14234 closed by close_pull_requests_with_failing_tests.sh
303303
289. [x] #14233 merged
304304
290. [x] #14229 merged
@@ -344,7 +344,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
344344
330. [ ] #14051 no labels
345345
331. [ ] #14048 awaiting reviews
346346
332. [x] #14045 closed by close_pull_requests_with_require_tests.sh
347-
333. [ ] #14041 awaiting reviews
347+
333. [x] #14041 merged
348348
334. [x] #14040 closed by close_pull_requests_with_failing_tests.sh
349349
335. [x] #14038 merged
350350
336. [ ] #14037 awaiting reviews
@@ -433,10 +433,10 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
433433
419. [x] #13839 closed by close_pull_requests_with_failing_tests.sh
434434
420. [ ] #13836 no labels
435435
421. [x] #13835 closed
436-
422. [ ] #13831 awaiting reviews
436+
422. [x] #13831 merged
437437
423. [x] #13830 closed
438438
424. [x] #13827 merged
439-
425. [ ] #13826 awaiting reviews
439+
425. [x] #13826 merged
440440
426. [ ] #13825 no labels
441441
427. [ ] #13824 no labels
442442
428. [x] #13823 closed by close_pull_requests_with_require_tests.sh
@@ -462,7 +462,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
462462
448. [x] #13761 closed by close_pull_requests_with_require_tests.sh
463463
449. [x] #13758 closed
464464
450. [x] #13754 closed
465-
451. [ ] #13753 awaiting reviews
465+
451. [x] #13753 merged
466466
452. [ ] #13751 awaiting reviews
467467
453. [ ] #13750 no labels
468468
454. [x] #13749 closed
@@ -508,7 +508,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
508508
494. [x] #13637 merged
509509
495. [x] #13636 closed by close_pull_requests_with_require_type_hints.sh
510510
496. [x] #13635 closed
511-
497. [ ] #13634 no labels
511+
497. [x] #13634 merged
512512
498. [x] #13631 merged
513513
499. [x] #13623 merged
514514
500. [ ] #13619 no labels
@@ -565,7 +565,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
565565
551. [ ] #13462 awaiting reviews
566566
552. [x] #13457 closed by close_pull_requests_with_failing_tests.sh
567567
553. [ ] #13452 awaiting reviews
568-
554. [ ] #13447 awaiting reviews
568+
554. [x] #13447 merged
569569
555. [x] #13426 merged
570570
556. [x] #13424 merged
571571
557. [ ] #13421 no labels
@@ -603,7 +603,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
603603
589. [ ] #13297 no labels
604604
590. [ ] #13296 awaiting reviews, documentation
605605
591. [x] #13295 closed
606-
592. [ ] #13288 awaiting reviews
606+
592. [x] #13288 merged
607607
593. [ ] #13273 awaiting reviews
608608
594. [ ] #13271 awaiting reviews
609609
595. [ ] #13270 awaiting reviews
@@ -646,7 +646,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
646646
632. [ ] #13121 no labels
647647
633. [x] #13119 closed
648648
634. [ ] #13118 enhancement, awaiting reviews
649-
635. [ ] #13114 awaiting reviews
649+
635. [x] #13114 merged
650650
636. [ ] #13113 awaiting reviews
651651
637. [ ] #13112 no labels
652652
638. [ ] #13110 enhancement, awaiting reviews
@@ -889,7 +889,7 @@ Notation: `<index>. [ ] #<pr_id> <labels>`
889889
875. [ ] #11113 awaiting reviews
890890
876. [ ] #11092 awaiting reviews
891891
877. [ ] #10860 awaiting reviews
892-
878. [ ] #10800 awaiting reviews
892+
878. [x] #10800 merged
893893
879. [ ] #10690 enhancement, awaiting reviews
894894
880. [ ] #10662 enhancement, awaiting reviews
895895
881. [x] #10631 closed
@@ -958,7 +958,7 @@ backtracking:
958958
4. [x] #11980 closed
959959
5. [ ] #13106
960960
6. [x] #13198 closed
961-
7. [ ] #13288
961+
7. [x] #13288 merged
962962
8. [ ] #14364
963963
9. [x] #14504 closed
964964
10. [ ] #14568
@@ -996,10 +996,10 @@ blockchain:
996996
3. [ ] #11946
997997
4. [ ] #12792
998998
5. [ ] #12999
999-
6. [ ] #13114
999+
6. [x] #13114 merged
10001000
7. [ ] #13296
1001-
8. [ ] #13753
1002-
9. [ ] #14237
1001+
8. [x] #13753 merged
1002+
9. [x] #14237 merged
10031003

10041004
boolean_algebra:
10051005
1. [x] #9917 closed
@@ -1026,8 +1026,8 @@ ciphers:
10261026
10. [ ] #13452
10271027
11. [ ] #13825
10281028
12. [ ] #13966
1029-
13. [ ] #14414
1030-
14. [ ] #14753
1029+
13. [x] #14414 merged
1030+
14. [x] #14753 merged
10311031
15. [x] #15108 merged
10321032

10331033
compression:
@@ -1058,12 +1058,12 @@ conversions:
10581058
6. [ ] #12210
10591059
7. [x] #13295 closed
10601060
8. [x] #13593 closed
1061-
9. [ ] #13634
1061+
9. [x] #13634 merged
10621062
10. [x] #13744 closed
10631063
11. [x] #13808 closed
1064-
12. [ ] #13826
1065-
13. [ ] #13831
1066-
14. [ ] #14041
1064+
12. [x] #13826 merged
1065+
13. [x] #13831 merged
1066+
14. [x] #14041 merged
10671067
15. [ ] #14079
10681068
16. [ ] #14570
10691069
17. [ ] #14571
@@ -1215,7 +1215,7 @@ geometry:
12151215
2. [ ] #11205
12161216
3. [x] #12118 merged
12171217
4. [ ] #12980
1218-
5. [ ] #13447
1218+
5. [x] #13447 merged
12191219
6. [ ] #13690
12201220
7. [ ] #13969
12211221
8. [ ] #14360
@@ -1307,7 +1307,7 @@ machine_learning:
13071307
1. [ ] #9610
13081308
2. [ ] #9619
13091309
3. [x] #10387 closed
1310-
4. [ ] #10800
1310+
4. [x] #10800 merged
13111311
5. [ ] #11240
13121312
6. [ ] #11250
13131313
7. [ ] #11263
@@ -2061,18 +2061,18 @@ I can take a second pass through the remaining `awaiting triage` items (relabel
20612061

20622062
## Automated statistics
20632063

2064-
_Generated automatically by `scripts/hacktoberfest_prep_update.py` on 2026-09-14 (UTC)._
2064+
_Generated automatically by `scripts/hacktoberfest_prep_update.py` on 2026-09-15 (UTC)._
20652065

2066-
- **Open issues:** 7
2067-
- **Open pull requests:** 407
2068-
- **Open PRs labelled `awaiting reviews`:** 279
2069-
- **Days until Hacktoberfest (2026-10-01):** 17
2070-
- **Issues to close per day to clear the backlog:** 1 per day (over 17 days)
2071-
- **Pull requests to merge or close per day to clear the backlog:** 24 per day (over 17 days)
2066+
- **Open issues:** 8
2067+
- **Open pull requests:** 406
2068+
- **Open PRs labelled `awaiting reviews`:** 275
2069+
- **Days until Hacktoberfest (2026-10-01):** 16
2070+
- **Issues to close per day to clear the backlog:** 1 per day (over 16 days)
2071+
- **Pull requests to merge or close per day to clear the backlog:** 26 per day (over 16 days)
20722072

20732073
**Top three directories to work on** (most open pull requests labelled `awaiting reviews`):
20742074

2075-
1. `strings/`16 awaiting-reviews PRs
2076-
2. `maths/`14 awaiting-reviews PRs
2077-
3. `data_structures/`10 awaiting-reviews PRs
2075+
1. `sorts/`17 awaiting-reviews PRs
2076+
2. `strings/`16 awaiting-reviews PRs
2077+
3. `maths/`14 awaiting-reviews PRs
20782078

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
This module provides functions to convert between Geodetic coordinates and
3+
Earth-Centered, Earth-Fixed (ECEF) Cartesian coordinates, as well as calculating
4+
target coordinates based on radar measurements.
5+
6+
Reference:
7+
- https://en.wikipedia.org/wiki/Geographic_coordinate_conversion
8+
- https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates
9+
"""
10+
11+
import math
12+
13+
# WGS84 Ellipsoid Constants
14+
WGS84_A = 6378137.0 # Semi-major axis in meters
15+
WGS84_B = 6356752.314245 # Semi-minor axis in meters
16+
WGS84_E_SQ = 1.0 - (WGS84_B**2 / WGS84_A**2) # First eccentricity squared
17+
WGS84_EP_SQ = (WGS84_A**2 - WGS84_B**2) / WGS84_B**2 # Second eccentricity squared
18+
19+
20+
def geodetic_to_ecef(
21+
lat_deg: float, lon_deg: float, alt_m: float
22+
) -> tuple[float, float, float]:
23+
"""
24+
Converts Geodetic coordinates (Latitude, Longitude, Altitude) to
25+
Earth-Centered, Earth-Fixed (ECEF) Cartesian coordinates.
26+
27+
>>> x, y, z = geodetic_to_ecef(0.0, 0.0, 0.0)
28+
>>> round(x, 2), round(y, 2), round(z, 2)
29+
(6378137.0, 0.0, 0.0)
30+
>>> x, y, z = geodetic_to_ecef(90.0, 0.0, 0.0)
31+
>>> round(x, 2), round(y, 2), round(z, 2)
32+
(0.0, 0.0, 6356752.31)
33+
"""
34+
lat_rad = math.radians(lat_deg)
35+
lon_rad = math.radians(lon_deg)
36+
37+
sin_lat = math.sin(lat_rad)
38+
cos_lat = math.cos(lat_rad)
39+
40+
# N is the prime vertical radius of curvature
41+
n_radius = WGS84_A / math.sqrt(1.0 - WGS84_E_SQ * sin_lat**2)
42+
43+
# Calculate ECEF X, Y, Z
44+
x = (n_radius + alt_m) * cos_lat * math.cos(lon_rad)
45+
y = (n_radius + alt_m) * cos_lat * math.sin(lon_rad)
46+
z = (n_radius * (1.0 - WGS84_E_SQ) + alt_m) * sin_lat
47+
48+
return x, y, z
49+
50+
51+
def ecef_to_geodetic(
52+
x_ecef: float, y_ecef: float, z_ecef: float
53+
) -> tuple[float, float, float]:
54+
"""
55+
Converts Earth-Centered, Earth-Fixed (ECEF) coordinates to
56+
Geodetic coordinates (Latitude, Longitude, Altitude) using Bowring's method.
57+
58+
>>> lat, lon, alt = ecef_to_geodetic(6378137.0, 0.0, 0.0)
59+
>>> round(lat, 2), round(lon, 2), round(alt, 2)
60+
(0.0, 0.0, 0.0)
61+
>>> lat, lon, alt = ecef_to_geodetic(0.0, 0.0, 6356752.314245)
62+
>>> round(lat, 2), round(lon, 2), round(alt, 2)
63+
(90.0, 0.0, 0.0)
64+
"""
65+
p = math.sqrt(x_ecef**2 + y_ecef**2)
66+
67+
# Handle the special case where the point is exactly at the poles
68+
if p == 0:
69+
lon_deg = 0.0
70+
lat_deg = 90.0 if z_ecef > 0 else -90.0
71+
alt_m = abs(z_ecef) - WGS84_B
72+
return lat_deg, lon_deg, alt_m
73+
74+
theta = math.atan2(z_ecef * WGS84_A, p * WGS84_B)
75+
76+
sin_theta = math.sin(theta)
77+
cos_theta = math.cos(theta)
78+
79+
# Calculate exact latitude and longitude
80+
lon_rad = math.atan2(y_ecef, x_ecef)
81+
lat_rad = math.atan2(
82+
z_ecef + WGS84_EP_SQ * WGS84_B * sin_theta**3,
83+
p - WGS84_E_SQ * WGS84_A * cos_theta**3,
84+
)
85+
86+
sin_lat = math.sin(lat_rad)
87+
88+
# Recalculate prime vertical radius to find altitude
89+
n_radius = WGS84_A / math.sqrt(1.0 - WGS84_E_SQ * sin_lat**2)
90+
91+
alt_m = (p / math.cos(lat_rad)) - n_radius
92+
93+
return math.degrees(lat_rad), math.degrees(lon_rad), alt_m
94+
95+
96+
def enu_to_ecef(
97+
east: float, north: float, up: float, ref_lat_deg: float, ref_lon_deg: float
98+
) -> tuple[float, float, float]:
99+
"""
100+
Rotates East-North-Up (ENU) offset coordinates to ECEF offset coordinates,
101+
based on the reference (Radar) latitude and longitude.
102+
103+
>>> dx, dy, dz = enu_to_ecef(100.0, 200.0, 50.0, 0.0, 0.0)
104+
>>> round(dx, 2), round(dy, 2), round(dz, 2)
105+
(50.0, 100.0, 200.0)
106+
"""
107+
lat_rad = math.radians(ref_lat_deg)
108+
lon_rad = math.radians(ref_lon_deg)
109+
110+
sin_lat = math.sin(lat_rad)
111+
cos_lat = math.cos(lat_rad)
112+
sin_lon = math.sin(lon_rad)
113+
cos_lon = math.cos(lon_rad)
114+
115+
# Rotation matrix components for ENU to ECEF
116+
dx = -sin_lon * east - sin_lat * cos_lon * north + cos_lat * cos_lon * up
117+
dy = cos_lon * east - sin_lat * sin_lon * north + cos_lat * sin_lon * up
118+
dz = cos_lat * north + sin_lat * up
119+
120+
return dx, dy, dz
121+
122+
123+
def calculate_target_coordinates(
124+
radar_lat: float,
125+
radar_lon: float,
126+
radar_alt: float,
127+
azimuth_deg: float,
128+
range_m: float,
129+
elevation_deg: float = 0.0,
130+
) -> tuple[float, float, float]:
131+
"""
132+
Main function to calculate target (ship) coordinates from radar measurements.
133+
134+
Parameters:
135+
radar_lat (float): Radar latitude in degrees
136+
radar_lon (float): Radar longitude in degrees
137+
radar_alt (float): Radar altitude above sea level in meters
138+
azimuth_deg (float): True bearing to the target (0 is North, 90 is East)
139+
range_m (float): Direct line-of-sight distance to the target in meters
140+
elevation_deg (float): Antenna elevation angle in degrees
141+
(default 0 for surface ships)
142+
143+
Returns:
144+
tuple: (Target Latitude, Target Longitude, Target Altitude)
145+
146+
>>> lat, lon, alt = calculate_target_coordinates(0.0, 0.0, 0.0, 90.0, 111319.5)
147+
>>> round(lat, 1), round(lon, 1), round(alt, 1)
148+
(0.0, 1.0, 971.4)
149+
"""
150+
# Step 1: Convert Radar polar measurements to Local ENU Cartesian coordinates
151+
az_rad = math.radians(azimuth_deg)
152+
el_rad = math.radians(elevation_deg)
153+
154+
# Standard spherical to cartesian for ENU
155+
# North is aligned with 0 degrees Azimuth, East is 90 degrees
156+
east = range_m * math.cos(el_rad) * math.sin(az_rad)
157+
north = range_m * math.cos(el_rad) * math.cos(az_rad)
158+
up = range_m * math.sin(el_rad)
159+
160+
# Step 2: Get absolute ECEF position of the Radar
161+
radar_x, radar_y, radar_z = geodetic_to_ecef(radar_lat, radar_lon, radar_alt)
162+
163+
# Step 3: Convert the Local ENU offsets to ECEF offsets
164+
dx, dy, dz = enu_to_ecef(east, north, up, radar_lat, radar_lon)
165+
166+
# Step 4: Add offsets to the Radar's ECEF coordinates to find Target ECEF
167+
target_x = radar_x + dx
168+
target_y = radar_y + dy
169+
target_z = radar_z + dz
170+
171+
# Step 5: Convert Target ECEF back to Geodetic coordinates
172+
target_lat, target_lon, target_alt = ecef_to_geodetic(target_x, target_y, target_z)
173+
174+
return target_lat, target_lon, target_alt
175+
176+
177+
if __name__ == "__main__":
178+
import doctest
179+
180+
doctest.testmod()

0 commit comments

Comments
 (0)