gomd 2.1.0__py3-none-any.whl

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.
gomd/spatial.py ADDED
@@ -0,0 +1,222 @@
1
+ """
2
+ gomd.spatial — Geospatial Haversine Geometry, IHO Sea Basin Classification,
3
+ Global Maritime Chokepoint Proximity, and OGC CRS84 GeoJSON / GeoDataFrame Tools
4
+ ================================================================================
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from typing import Any, Dict, List, Sequence, Tuple
11
+
12
+ EARTH_RADIUS_KM = 6371.0088
13
+ KM_PER_NAUTICAL_MILE = 1.852
14
+
15
+ # Canonical coordinates of the 10 strategic global maritime chokepoints
16
+ GLOBAL_MARITIME_CHOKEPOINTS: List[Dict[str, Any]] = [
17
+ {"code": "BOSPORUS", "name": "Istanbul Strait (Bosporus)", "lat": 41.1194, "lon": 29.0753, "region": "Turkish Straits"},
18
+ {"code": "DARDANELLES", "name": "Canakkale Strait (Dardanelles)", "lat": 40.2000, "lon": 26.4000, "region": "Turkish Straits"},
19
+ {"code": "SUEZ", "name": "Suez Canal", "lat": 30.5852, "lon": 32.3426, "region": "Red Sea / Mediterranean"},
20
+ {"code": "BAB_EL_MANDEB", "name": "Bab el-Mandeb Strait", "lat": 12.5833, "lon": 43.3333, "region": "Red Sea / Gulf of Aden"},
21
+ {"code": "HORMUZ", "name": "Strait of Hormuz", "lat": 26.5667, "lon": 56.2500, "region": "Persian Gulf"},
22
+ {"code": "MALACCA", "name": "Strait of Malacca", "lat": 2.5000, "lon": 101.5000, "region": "Southeast Asia"},
23
+ {"code": "GIBRALTAR", "name": "Strait of Gibraltar", "lat": 35.9667, "lon": -5.5000, "region": "Western Mediterranean"},
24
+ {"code": "PANAMA", "name": "Panama Canal", "lat": 9.0800, "lon": -79.6800, "region": "Central America"},
25
+ {"code": "DOVER", "name": "Strait of Dover (English Channel)", "lat": 51.0167, "lon": 1.4833, "region": "North-East Atlantic"},
26
+ {"code": "DANISH_STRAITS", "name": "Danish Straits (Oresund / Great Belt)", "lat": 55.5500, "lon": 11.0000, "region": "Baltic Sea"},
27
+ ]
28
+ GLOBAL_CHOKEPOINTS = GLOBAL_MARITIME_CHOKEPOINTS
29
+
30
+
31
+
32
+ def haversine_distance(
33
+ lat1: float,
34
+ lon1: float,
35
+ lat2: float,
36
+ lon2: float,
37
+ ) -> Dict[str, float]:
38
+ """
39
+ Compute the exact great-circle orthodromic distance between two WGS84
40
+ coordinates in kilometers (`km`) and nautical miles (`nm`), plus initial bearing.
41
+ """
42
+ phi1 = math.radians(float(lat1))
43
+ phi2 = math.radians(float(lat2))
44
+ dphi = math.radians(float(lat2) - float(lat1))
45
+ dlam = math.radians(float(lon2) - float(lon1))
46
+
47
+ a = (
48
+ math.sin(dphi / 2.0) ** 2
49
+ + math.cos(phi1) * math.cos(phi2) * (math.sin(dlam / 2.0) ** 2)
50
+ )
51
+ c = 2.0 * math.asin(min(1.0, math.sqrt(max(a, 0.0))))
52
+ dist_km = EARTH_RADIUS_KM * c
53
+ dist_nm = dist_km / KM_PER_NAUTICAL_MILE
54
+
55
+ y = math.sin(dlam) * math.cos(phi2)
56
+ x = math.cos(phi1) * math.sin(phi2) - math.sin(phi1) * math.cos(phi2) * math.cos(dlam)
57
+ bearing = (math.degrees(math.atan2(y, x)) + 360.0) % 360.0
58
+
59
+ return {
60
+ "distance_km": round(dist_km, 3),
61
+ "distance_nm": round(dist_nm, 3),
62
+ "initial_bearing_deg": round(bearing, 2),
63
+ }
64
+
65
+
66
+ def classify_sea_basin(lat: float, lon: float) -> Dict[str, str]:
67
+ """
68
+ Classify a geographic coordinate (lat, lon) into its canonical GOMD / IHO
69
+ Regional Sea Basin or Major Ocean Basin.
70
+ """
71
+ la = float(lat)
72
+ lo = float(lon)
73
+
74
+ if 40.3 <= la <= 41.3 and 26.5 <= lo <= 29.95:
75
+ return {"basin_code": "MARMARA", "basin_name": "Sea of Marmara & Turkish Straits", "ocean": "Mediterranean System"}
76
+ if 40.8 <= la <= 46.8 and 27.4 <= lo <= 42.0:
77
+ return {"basin_code": "BLACK_SEA", "basin_name": "Black Sea & Sea of Azov", "ocean": "Mediterranean System"}
78
+ if 35.0 <= la <= 41.0 and 22.5 <= lo <= 28.5:
79
+ return {"basin_code": "AEGEAN", "basin_name": "Aegean Sea", "ocean": "Mediterranean System"}
80
+ if 30.0 <= la <= 45.8 and 15.0 <= lo <= 36.5:
81
+ return {"basin_code": "MED_EAST", "basin_name": "Eastern Mediterranean & Levantine Basin", "ocean": "Mediterranean System"}
82
+ if 35.0 <= la <= 44.5 and -5.6 <= lo < 15.0:
83
+ return {"basin_code": "MED_WEST", "basin_name": "Western Mediterranean", "ocean": "Mediterranean System"}
84
+ if 12.5 <= la <= 30.0 and 32.0 <= lo <= 43.5:
85
+ return {"basin_code": "RED_SEA", "basin_name": "Red Sea", "ocean": "Indian Ocean"}
86
+ if 53.5 <= la <= 66.0 and 9.5 <= lo <= 30.5:
87
+ return {"basin_code": "BALTIC", "basin_name": "Baltic Sea (HELCOM)", "ocean": "Atlantic Ocean"}
88
+ if 51.0 <= la <= 62.0 and -4.0 <= lo < 9.5:
89
+ return {"basin_code": "NORTH_SEA", "basin_name": "Greater North Sea (OSPAR)", "ocean": "Atlantic Ocean"}
90
+ if la >= 66.5:
91
+ return {"basin_code": "ARCTIC", "basin_name": "Arctic Ocean", "ocean": "Arctic Ocean"}
92
+ if la <= -60.0:
93
+ return {"basin_code": "SOUTHERN_OCEAN", "basin_name": "Southern Ocean", "ocean": "Southern Ocean"}
94
+ if 20.0 <= lo <= 147.0 and la < 30.0:
95
+ return {"basin_code": "INDIAN_OCEAN", "basin_name": "Indian Ocean", "ocean": "Indian Ocean"}
96
+ if -70.0 <= lo <= 20.0:
97
+ return {"basin_code": "ATLANTIC", "basin_name": "Atlantic Ocean", "ocean": "Atlantic Ocean"}
98
+ return {"basin_code": "PACIFIC", "basin_name": "Pacific Ocean", "ocean": "Pacific Ocean"}
99
+
100
+
101
+ def chokepoint_proximity(lat: float, lon: float) -> Dict[str, Any]:
102
+ """
103
+ Compute distances (`km` and `nm`) from `(lat, lon)` to all 10 major global
104
+ maritime chokepoints, sorted from nearest to farthest.
105
+ """
106
+ results: List[Dict[str, Any]] = []
107
+ for cp in GLOBAL_MARITIME_CHOKEPOINTS:
108
+ d = haversine_distance(lat, lon, cp["lat"], cp["lon"])
109
+ results.append(
110
+ {
111
+ "code": cp["code"],
112
+ "name": cp["name"],
113
+ "region": cp["region"],
114
+ "distance_km": d["distance_km"],
115
+ "distance_nm": d["distance_nm"],
116
+ "bearing_deg": d["initial_bearing_deg"],
117
+ }
118
+ )
119
+ results.sort(key=lambda r: r["distance_km"])
120
+ return {
121
+ "query_point": {"lat": float(lat), "lon": float(lon)},
122
+ "sea_basin": classify_sea_basin(lat, lon),
123
+ "nearest_chokepoint": results[0],
124
+ "chokepoints": results,
125
+ }
126
+
127
+
128
+ def to_geojson(
129
+ records: Sequence[Dict[str, Any]],
130
+ lat_col: str = "latitude",
131
+ lon_col: str = "longitude",
132
+ ) -> Dict[str, Any]:
133
+ """
134
+ Convert a sequence of GOMD observation dicts carrying coordinate columns into an
135
+ OGC CRS84 (`urn:ogc:def:crs:OGC:1.3:CRS84`) GeoJSON FeatureCollection.
136
+ """
137
+ features: List[Dict[str, Any]] = []
138
+ for row in records:
139
+ if lat_col in row and lon_col in row and row[lat_col] is not None and row[lon_col] is not None:
140
+ try:
141
+ lat_v = float(row[lat_col])
142
+ lon_v = float(row[lon_col])
143
+ except (TypeError, ValueError):
144
+ continue
145
+ props = {k: v for k, v in row.items() if k not in (lat_col, lon_col)}
146
+ features.append(
147
+ {
148
+ "type": "Feature",
149
+ "geometry": {"type": "Point", "coordinates": [lon_v, lat_v]},
150
+ "properties": props,
151
+ }
152
+ )
153
+ return {
154
+ "type": "FeatureCollection",
155
+ "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}},
156
+ "features": features,
157
+ }
158
+
159
+
160
+ def mavi_vatan_eez_audit(
161
+ mainland_coast_km: float = 1577.0,
162
+ opposite_coast_km: float = 19.0,
163
+ un_registered_area_km2: float = 462000.0,
164
+ provider_polygon_area_km2: float = 254026.0,
165
+ opposite_claimed_area_km2: float = 42000.0,
166
+ island_distance_from_mainland_nm: float = 1.9,
167
+ **kwargs: Any,
168
+ ) -> Dict[str, Any]:
169
+ """
170
+ ICJ Three-Stage Equitable Maritime Delimitation, Coastal-Length Proportionality &
171
+ Turkish Maritime Jurisdiction ("Mavi Vatan" — UN Letter A/74/757) Scientific Audit Model.
172
+
173
+ Evaluates third-party geodatabase polygons (e.g., VLIZ Marine Regions v12, EMODnet/DG MARE,
174
+ Seville Map median-line geometries) against International Court of Justice (ICJ) jurisprudence
175
+ (1969 North Sea Continental Shelf, 1985 Libya–Malta, 2009 Black Sea, 2012 Nicaragua–Colombia),
176
+ measuring coastal-length proportionality (R_L), maritime area attribution ratio (R_A),
177
+ ICJ Disproportionality Distortion Index (D_ICJ), island cut-off/encroachment weight,
178
+ and discrepancy against Türkiye's official UN-registered boundaries (~462,000 km²).
179
+ """
180
+ l_main = max(float(mainland_coast_km), 0.1)
181
+ l_opp = max(float(opposite_coast_km), 0.01)
182
+ a_un = max(float(un_registered_area_km2), 0.0)
183
+ a_prov = max(float(provider_polygon_area_km2), 0.0)
184
+ a_opp_claim = max(float(opposite_claimed_area_km2), 0.01)
185
+ dist_nm = max(float(island_distance_from_mainland_nm), 0.0)
186
+
187
+ coastal_length_ratio = l_main / l_opp
188
+ equitable_area_share_pct = (l_main / (l_main + l_opp)) * 100.0
189
+ provider_area_discrepancy_km2 = a_un - a_prov
190
+ provider_underattribution_pct = ((a_un - a_prov) / max(a_un, 1.0)) * 100.0
191
+
192
+ # ICJ island cut-off weight: small islands immediately adjacent to a mainland coast
193
+ # receive limited/enclaved territorial-sea effect rather than full continental shelf/EEZ
194
+ island_effect_weight = min(1.0, (l_opp / l_main) * math.exp(-dist_nm / 12.0))
195
+ disproportionality_ratio = (a_opp_claim / l_opp) / max(a_prov / l_main, 1e-6)
196
+ compliance_score = max(0.0, min(100.0, 100.0 - min(85.0, math.log1p(disproportionality_ratio) * 14.5)))
197
+
198
+ return {
199
+ "mainland_coast_km": round(l_main, 2),
200
+ "opposite_or_island_coast_km": round(l_opp, 2),
201
+ "coastal_length_proportionality_ratio": round(coastal_length_ratio, 2),
202
+ "coastal_length_ratio_mainland_to_island": round(coastal_length_ratio, 2),
203
+ "equitable_mainland_share_pct": round(equitable_area_share_pct, 2),
204
+ "un_registered_mavi_vatan_area_km2": round(a_un, 2),
205
+ "un_registered_mavi_vatan_km2": round(a_un, 2),
206
+ "provider_raw_polygon_area_km2": round(a_prov, 2),
207
+ "provider_area_discrepancy_km2": round(provider_area_discrepancy_km2, 2),
208
+ "provider_underattribution_pct": round(provider_underattribution_pct, 2),
209
+ "icj_island_effect_weight_0_1": round(island_effect_weight, 4),
210
+ "seville_map_disproportionality_factor": round(disproportionality_ratio, 2),
211
+ "equitable_delimitation_compliance_score": round(compliance_score, 2),
212
+ "authoritative_legal_basis": (
213
+ "UN Letter A/74/757 (18 March 2020); 2019 Türkiye-Libya MoU; "
214
+ "2011 Türkiye-TRNC Continental Shelf Delimitation Agreement; "
215
+ "ICJ North Sea (1969), Libya-Malta (1985), Black Sea (2009), Nicaragua-Colombia (2012)"
216
+ ),
217
+ "scientific_use_disclaimer": (
218
+ "External provider polygons (VLIZ Marine Regions, EMODnet/DG MARE, Seville Map, or unilateral GCA claims) "
219
+ "are retained strictly for scientific comparison and carry zero legal validity for the Republic of Türkiye."
220
+ ),
221
+ }
222
+