import 'dart:math' as math; import 'package:intaleq_maps/intaleq_maps.dart'; /// Military Coordinates Utility (WGS84 <-> UTM Zone 36N & MGRS Grid) /// Specifically optimized for Jordan & Levantine military operations (Zone 36N / 37N) class MilitaryGridUtils { MilitaryGridUtils._(); static const double _a = 6378137.0; // WGS84 semi-major axis static const double _f = 1 / 298.257223563; // WGS84 flattening static const double _b = _a * (1.0 - _f); static const double _eSq = (_a * _a - _b * _b) / (_a * _a); static const double _ePrimeSq = (_a * _a - _b * _b) / (_b * _b); static const double _k0 = 0.9996; // UTM scale factor static const double earthRadiusM = 6371000.0; /// Haversine Great Circle Distance in meters static double haversineDistance(double lat1, double lng1, double lat2, double lng2) { final dLat = (lat2 - lat1) * (math.pi / 180.0); final dLng = (lng2 - lng1) * (math.pi / 180.0); final a = math.sin(dLat / 2.0) * math.sin(dLat / 2.0) + math.cos(lat1 * math.pi / 180.0) * math.cos(lat2 * math.pi / 180.0) * math.sin(dLng / 2.0) * math.sin(dLng / 2.0); final c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a)); return earthRadiusM * c; } /// Initial Great Circle Bearing in degrees (0..360) static double calculateBearing(double lat1, double lng1, double lat2, double lng2) { final phi1 = lat1 * (math.pi / 180.0); final phi2 = lat2 * (math.pi / 180.0); final deltaLambda = (lng2 - lng1) * (math.pi / 180.0); final y = math.sin(deltaLambda) * math.cos(phi2); final x = math.cos(phi1) * math.sin(phi2) - math.sin(phi1) * math.cos(phi2) * math.cos(deltaLambda); final theta = math.atan2(y, x); return (theta * (180.0 / math.pi) + 360.0) % 360.0; } /// Convert Azimuth Degrees to Arabic Cardinal Name static String azimuthToCardinalArabic(double azimuthDeg) { final deg = (azimuthDeg % 360.0 + 360.0) % 360.0; if (deg >= 337.5 || deg < 22.5) return 'شمال (N)'; if (deg >= 22.5 && deg < 67.5) return 'شمال شرق (NE)'; if (deg >= 67.5 && deg < 112.5) return 'شرق (E)'; if (deg >= 112.5 && deg < 157.5) return 'جنوب شرق (SE)'; if (deg >= 157.5 && deg < 202.5) return 'جنوب (S)'; if (deg >= 202.5 && deg < 247.5) return 'جنوب غرب (SW)'; if (deg >= 247.5 && deg < 292.5) return 'غرب (W)'; return 'شمال غرب (NW)'; } /// Convert LatLng to standard MGRS String representation (Zone + Band + 100k Square ID + Easting/Northing) static String latLngToMgrs(double lat, double lng, {int accuracyDigits = 4}) { if (lat < -80.0 || lat > 84.0) { return 'OUT_OF_BOUNDS'; } final zone = ((lng + 180.0) / 6.0).floor() + 1; final coords = fromLatLng(LatLng(lat, lng), zone: zone); final bandLetter = _getLatitudeBandLetter(lat); final squareId = _get100kSquareId(coords.easting, coords.northing, zone); final eInt = coords.easting.round() % 100000; final nInt = coords.northing.round() % 100000; // Pad to 5 digits then trim to desired accuracy final e5 = eInt.toString().padLeft(5, '0'); final n5 = nInt.toString().padLeft(5, '0'); final eStr = e5.substring(0, math.min(5, accuracyDigits)); final nStr = n5.substring(0, math.min(5, accuracyDigits)); return '$zone$bandLetter $squareId $eStr $nStr'; } /// Latitude band letter (8-degree bands from -80° to +84°: C to X, skipping I and O) static String _getLatitudeBandLetter(double lat) { const bands = 'CDEFGHJKLMNPQRSTUVWX'; if (lat >= 84.0) return 'X'; if (lat < -80.0) return 'C'; int index = ((lat + 80.0) / 8.0).floor(); if (index < 0) index = 0; if (index >= bands.length) index = bands.length - 1; return bands[index]; } /// 100,000-meter Square Identification (2 letters) per US DMA TM 8358.1 / WGS84 standard static String _get100kSquareId(double easting, double northing, int zone) { const setColOrigin = 'AJSAJS'; const setRowOrigin = 'AFAFAF'; const aCode = 65; // 'A' const iCode = 73; // 'I' const oCode = 79; // 'O' const vCode = 86; // 'V' const zCode = 90; // 'Z' final setParm = (zone % 6 == 0) ? 6 : (zone % 6); final setIndex = setParm - 1; final colOrigin = setColOrigin.codeUnitAt(setIndex); final rowOrigin = setRowOrigin.codeUnitAt(setIndex); final col = (easting / 100000.0).floor(); final row = (northing / 100000.0).floor() % 20; var colInt = colOrigin + col - 1; var rowInt = rowOrigin + row; var colRollover = false; if (colInt > zCode) { colInt = colInt - zCode + aCode - 1; colRollover = true; } if (colInt == iCode || (colOrigin < iCode && colInt > iCode) || ((colInt > iCode || colOrigin < iCode) && colRollover)) { colInt++; } if (colInt == oCode || (colOrigin < oCode && colInt > oCode) || ((colInt > oCode || colOrigin < oCode) && colRollover)) { colInt++; if (colInt == iCode) colInt++; } if (colInt > zCode) { colInt = colInt - zCode + aCode - 1; } var rowRollover = false; if (rowInt > vCode) { rowInt = rowInt - vCode + aCode - 1; rowRollover = true; } if (rowInt == iCode || (rowOrigin < iCode && rowInt > iCode) || ((rowInt > iCode || rowOrigin < iCode) && rowRollover)) { rowInt++; } if (rowInt == oCode || (rowOrigin < oCode && rowInt > oCode) || ((rowInt > oCode || rowOrigin < oCode) && rowRollover)) { rowInt++; if (rowInt == iCode) rowInt++; } if (rowInt > vCode) { rowInt = rowInt - vCode + aCode - 1; } return String.fromCharCode(colInt) + String.fromCharCode(rowInt); } /// Convert WGS84 Lat/Lng to UTM Zone 36N Easting (شرقيات) and Northing (شماليات) static MilitaryCoordinates fromLatLng(LatLng latLng, {int zone = 36}) { final lat = latLng.latitude; final lng = latLng.longitude; final latRad = lat * (math.pi / 180.0); final lngRad = lng * (math.pi / 180.0); final centralMeridianDeg = (zone - 1) * 6 - 180 + 3; final centralMeridianRad = centralMeridianDeg * (math.pi / 180.0); final n = _a / math.sqrt(1.0 - _eSq * math.sin(latRad) * math.sin(latRad)); final t = math.tan(latRad) * math.tan(latRad); final c = _ePrimeSq * math.cos(latRad) * math.cos(latRad); final aCoeff = math.cos(latRad) * (lngRad - centralMeridianRad); final m = _a * ((1.0 - _eSq / 4.0 - 3.0 * _eSq * _eSq / 64.0 - 5.0 * _eSq * _eSq * _eSq / 256.0) * latRad - (3.0 * _eSq / 8.0 + 3.0 * _eSq * _eSq / 32.0 + 45.0 * _eSq * _eSq * _eSq / 1024.0) * math.sin(2.0 * latRad) + (15.0 * _eSq * _eSq / 256.0 + 45.0 * _eSq * _eSq * _eSq / 1024.0) * math.sin(4.0 * latRad) - (35.0 * _eSq * _eSq * _eSq / 3072.0) * math.sin(6.0 * latRad)); final easting = 500000.0 + _k0 * n * (aCoeff + (1.0 - t + c) * math.pow(aCoeff, 3) / 6.0 + (5.0 - 18.0 * t + t * t + 72.0 * c - 58.0 * _ePrimeSq) * math.pow(aCoeff, 5) / 120.0); final northing = _k0 * (m + n * math.tan(latRad) * (aCoeff * aCoeff / 2.0 + (5.0 - t + 9.0 * c + 4.0 * c * c) * math.pow(aCoeff, 4) / 24.0 + (61.0 - 58.0 * t + t * t + 600.0 * c - 330.0 * _ePrimeSq) * math.pow(aCoeff, 6) / 720.0)); return MilitaryCoordinates( lat: lat, lng: lng, easting: easting, northing: northing, zone: zone, ); } /// Convert UTM Zone 36N Easting (شرقيات) and Northing (شماليات) to WGS84 Lat/Lng static LatLng toLatLng({ required double easting, required double northing, int zone = 36, }) { final e1 = (1.0 - math.sqrt(1.0 - _eSq)) / (1.0 + math.sqrt(1.0 - _eSq)); final x = easting - 500000.0; final y = northing; final m = y / _k0; final mu = m / (_a * (1.0 - _eSq / 4.0 - 3.0 * _eSq * _eSq / 64.0 - 5.0 * _eSq * _eSq * _eSq / 256.0)); final phi1Rad = mu + (3.0 * e1 / 2.0 - 27.0 * math.pow(e1, 3) / 32.0) * math.sin(2.0 * mu) + (21.0 * e1 * e1 / 16.0 - 55.0 * math.pow(e1, 4) / 32.0) * math.sin(4.0 * mu) + (151.0 * math.pow(e1, 3) / 96.0) * math.sin(6.0 * mu) + (1097.0 * math.pow(e1, 4) / 512.0) * math.sin(8.0 * mu); final n1 = _a / math.sqrt(1.0 - _eSq * math.sin(phi1Rad) * math.sin(phi1Rad)); final t1 = math.tan(phi1Rad) * math.tan(phi1Rad); final c1 = _ePrimeSq * math.cos(phi1Rad) * math.cos(phi1Rad); final r1 = _a * (1.0 - _eSq) / math.pow(1.0 - _eSq * math.sin(phi1Rad) * math.sin(phi1Rad), 1.5); final d = x / (n1 * _k0); final latRad = phi1Rad - (n1 * math.tan(phi1Rad) / r1) * (d * d / 2.0 - (5.0 + 3.0 * t1 + 10.0 * c1 - 4.0 * c1 * c1 - 9.0 * _ePrimeSq) * math.pow(d, 4) / 24.0 + (61.0 + 90.0 * t1 + 298.0 * c1 + 45.0 * t1 * t1 - 252.0 * _ePrimeSq - 3.0 * c1 * c1) * math.pow(d, 6) / 720.0); final centralMeridianDeg = (zone - 1) * 6 - 180 + 3; final centralMeridianRad = centralMeridianDeg * (math.pi / 180.0); final lngRad = centralMeridianRad + (d - (1.0 + 2.0 * t1 + c1) * math.pow(d, 3) / 6.0 + (5.0 - 2.0 * c1 + 28.0 * t1 - 3.0 * c1 * c1 + 8.0 * _ePrimeSq + 24.0 * t1 * t1) * math.pow(d, 5) / 120.0) / math.cos(phi1Rad); return LatLng( latRad * (180.0 / math.pi), lngRad * (180.0 / math.pi), ); } } class MilitaryCoordinates { final double lat; final double lng; final double easting; final double northing; final int zone; MilitaryCoordinates({ required this.lat, required this.lng, required this.easting, required this.northing, this.zone = 36, }); /// Short 6-digit Easting / Northing string for tactical voice transmission (e.g. 845 412) String get shortGrid => '${(easting.round() % 100000 ~/ 100).toString().padLeft(3, '0')} ${(northing.round() % 100000 ~/ 100).toString().padLeft(3, '0')}'; /// Standard Arabic military format (شرقيات: 784520 م • شماليات: 3541280 م) String get arabicFullFormat => 'شرقيات: ${easting.round()} م • شماليات: ${northing.round()} م (${zone}N)'; String get eastingStr => '${easting.round()}'; String get northingStr => '${northing.round()}'; }