First off, I’m new to Swift, however have been a software program programs engineer for the final 40 years. I’m fluent in C, C++, Java and Python.
I’ve applied a model of the next resolution in Python utilizing inverse_haversine(), however I want to make it extra correct and hopefully make the most of among the CLLocation framework and any built-in or out there libraries that calculate geodetics.
So, that being mentioned, right here is the issue I’m attempting to unravel utilizing Swift:
Given:
-
Observer’s Latitude (𝝏1)
-
Observer’s Longitude (𝝄1)
-
Observer’s Peak above sea stage (h)
-
Radius of the Earth (R)
And:
-
Bearing from Observer (O) to Goal (T)
-
The angle shaped by the straight line between Observer (O) and Goal (T) relative to the tangent of the earth’s circumference on the location of the Observer (O)
Calculate:
- The Latitude (𝝏2) and Longitude(𝝄2) of Goal
enter picture description right here Illustration of the issue
As talked about above, I’ve written a program in Python that performs most of those calculations, nonetheless as an alternative of utilizing the angle shaped by the observer’s downward viewing angle and the tangent to the sphere on the location of the observer, I take advantage of the reticle of binoculars measuring the variety of mils between the horizon and the goal object.
I want to not depend on the reticle of the binoculars, however fairly use the gyro within the iPhone to find out the above angle, and utilizing that within the calculations as an alternative.
For reference, right here is the Python code I’ve written:
<
mport sys
from math import sin, cos, asin, acos, atan2, sqrt, pi
from haversine import haversine, inverse_haversine, inverse_haversine_vector, Unit, Path
from numpy import arange
#===================================================================================#
# UNIT CONVERSIONS FACTOR CONSTANTS #
#===================================================================================#
feetToMiles = 0.000189394
feetToMeters = 0.30480030800000379454
feetToKilometers = 0.0003048000097536
feetToNauticalMiles = 0.000164579
milesToFeet = 5280
milesToMeters = 1609.34
milesToKilometers = 1.60934
milesToNauticalMiles = 0.868976
metersToFeet = 3.28084
metersToMiles = 0.000621371
metersToKilometers = 0.001
metersToNauticalMiles = 0.000539957349081371537
kilometersToFeet = 3280.84
kilometersToMiles = 0.621371
kilometersToMeters = 1000
kilometersToNauticalMiles = 0.53995734908137149599
nauticalMilesToFeet = 6076.11568
nauticalMilesToMiles = 1.15078
nauticalMilesToMeters = 1852.000059264
nauticalMilesToKilometers = 1.852
degreesToRadians = pi / 180
radiansToDegrees = 180 / pi
englishHorizonCoefficient = 1.22459
metricHorizonCoefficient = 3.56972
#===================================================================================#
# CLASSES FOR LOCATION, BEARING AND DISTANCE #
#===================================================================================#
class Bearing:
def __init__(self, bearing: float, unit:Unit = None):
self.bearing = bearing
self.unit = unit
def __str__(self):
return f"Bearing: {spherical(self.bearing, 4):3} {self.unit.worth}"
def levels(self):
if self.unit == Unit.DEGREES:
return self.bearing
else:
return self.bearing * radiansToDegrees
def radians(self):
if (self.unit == Unit.RADIANS):
return self.bearing
else:
return self.bearing * degreesToRadians
def convertTo(self, unit: Unit):
if unit == self.unit:
return self
match unit:
case Unit.DEGREES:
return Bearing(self.levels(), unit)
case Unit.RADIANS:
return Bearing(self.radians(), unit)
case _:
elevate Exception(f"Invalid Bearing unit specified: {unit}")
class Distance:
def __init__(self, distance: float, unit: Unit = Unit.MILES):
self.distance = distance
self.unit = unit
def __str__(self):
return f"Distance: {spherical(self.distance, 4):8.4} {self.unit.worth}"
def toes(self):
match self.unit:
case Unit.FEET:
return self.distance
case Unit.MILES:
return self.distance * milesToFeet
case Unit.METERS:
return self.distance * metersToFeet
case Unit.KILOMETERS:
return self.distance * kilometersToFeet
case Unit.NAUTICAL_MILES:
return self.distance * nauticalMilesToFeet
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
def miles(self):
match self.unit:
case Unit.FEET:
return self.distance * feetToMiles
case Unit.MILES:
return self.distance
case Unit.METERS:
return self.distance * metersToMiles
case Unit.KILOMETERS:
return self.distance * kilometersToMiles
case Unit.NAUTICAL_MILES:
return self.distance * nauticalMilesToMiles
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
def meters(self):
match self.unit:
case Unit.FEET:
return self.distance * feetToMeters
case Unit.MILES:
return self.distance * milesToMeters
case Unit.METERS:
return self.distance
case Unit.KILOMETERS:
return self.distance * kilometersToMeters
case Unit.NAUTICAL_MILES:
return self.distance * nauticalMilesToMeters
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
def kilometers(self):
match self.unit:
case Unit.FEET:
return self.distance * feetToKilometers
case Unit.MILES:
return self.distance * milesToKilometers
case Unit.METERS:
return self.distance * metersToKilometers
case Unit.KILOMETERS:
return self.distance
case Unit.NAUTICAL_MILES:
return self.distance * nauticalMilesToKilometers
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
def nauticalMiles(self):
match self.unit:
case Unit.FEET:
return self.distance * feetToNauticalMiles
case Unit.MILES:
return self.distance * milesToNauticalMiles
case Unit.METERS:
return self.distance * metersToNauticalMiles
case Unit.KILOMETERS:
return self.distance * kilometersToNauticalMiles
case Unit.NAUTICAL_MILES:
return self.distance
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
def convertTo(self, unit: Unit):
if unit == self.unit:
return self
match unit:
case Unit.FEET:
return Distance(self.toes(), unit)
case Unit.MILES:
return Distance(self.miles(), unit)
case Unit.METERS:
return Distance(self.meters(), unit)
case Unit.KILOMETERS:
return Distance(self.kilometers(), unit)
case Unit.NAUTICAL_MILES:
return Distance(self.nauticalMiles(), unit)
case _:
elevate Exception(f"Invalid Distance unit specified: {unit}")
class Peak:
def __init__(self, peak: float, unit = Unit.MILES):
self.peak = peak
self.unit = unit
def __str__(self):
return f"Peak: {spherical(self.peak, 1)} {self.unit.worth}"
def toes(self):
match self.unit:
case Unit.FEET:
return self.peak
case Unit.MILES:
return self.peak * milesToFeet
case Unit.METERS:
return self.peak * metersToFeet
case Unit.KILOMETERS:
return self.peak * kilometersToFeet
case Unit.NAUTICAL_MILES:
return self.peak * nauticalMilesToFeet
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
def miles(self):
match self.unit:
case Unit.FEET:
return self.peak * feetToMiles
case Unit.MILES:
return self.peak
case Unit.METERS:
return self.peak * metersToMiles
case Unit.KILOMETERS:
return self.peak * kilometersToMiles
case Unit.NAUTICAL_MILES:
return self.peak * nauticalMilesToMiles
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
def meters(self):
match self.unit:
case Unit.FEET:
return self.peak * feetToMeters
case Unit.MILES:
return self.peak * milesToMeters
case Unit.METERS:
return self.peak
case Unit.KILOMETERS:
return self.peak * kilometersToMeters
case Unit.NAUTICAL_MILES:
return self.peak * nauticalMilesToMeters
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
def kilometers(self):
match self.unit:
case Unit.FEET:
return self.peak * feetToKilometers
case Unit.MILES:
return self.peak * milesToKilometers
case Unit.METERS:
return self.peak * metersToKilometers
case Unit.KILOMETERS:
return self.peak
case Unit.NAUTICAL_MILES:
return self.peak * nauticalMilesToKilometers
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
def nauticalMiles(self):
match self.unit:
case Unit.FEET:
return self.peak * feetToNauticalMiles
case Unit.MILES:
return self.peak * milesToNauticalMiles
case Unit.METERS:
return self.peak * metersToNauticalMiles
case Unit.KILOMETERS:
return self.peak * kilometersToNauticalMiles
case Unit.NAUTICAL_MILES:
return self.peak
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
def convertTo(self, unit: Unit):
if unit == self.unit:
return self
match unit:
case Unit.FEET:
return Peak(self.toes(), unit)
case Unit.MILES:
return Peak(self.miles(), unit)
case Unit.METERS:
return Peak(self.meters(), unit)
case Unit.KILOMETERS:
return Peak(self.kilometers(), unit)
case Unit.NAUTICAL_MILES:
return Peak(self.nauticalMiles(), unit)
case _:
elevate Exception(f"Invalid Peak unit specified: {unit}")
class Coordinate:
def __init__(self, coord: float, unit: Unit = Unit.RADIANS):
self.coord = coord
self.unit = unit
def __str__(self):
return f"Coordinate: {spherical(self.coord, 4)} {self.unit.worth:4.4}"
def radians(self):
match self.unit:
case Unit.DEGREES:
return self.coord * degreesToRadians
case Unit.RADIANS:
return self.coord
def levels(self):
match self.unit:
case Unit.DEGREES:
return self.coord
case Unit.RADIANS:
return self.coord * radiansToDegrees
def convertTo(self, unit: Unit):
if unit == self.unit:
return self
match unit:
case Unit.DEGREES:
return Coordinate(self.levels(), unit)
case Unit.RADIANS:
return Coordinate(self.radians(), unit)
case _:
elevate Exception(f"Invalid Coordinate unit specified: {unit}")
class Location:
def __init__(self, lat: float, lon: float, h: Peak, cUnit: Unit = Unit.DEGREES):
self.lat = Coordinate(lat % 360, cUnit)
self.lon = Coordinate(lon % 360, cUnit)
self.h = h
def __str__(self):
return f"Location: {self.lat}, {self.lon}, {self.h}"
def latRad(self):
return self.lat.radians()
def latDeg(self):
return self.lat.levels()
def lonRad(self):
return self.lon.radians()
def lonDeg(self):
return self.lon.levels()
def latLonRad(self):
return self.latRad(), self.lonRad()
def latLonDeg(self):
return self.latDeg(), self.lonDeg()
def peak(self):
return self.h
def convertTo(self, unit: Unit):
match unit:
case Unit.DEGREES:
return Location(self.lat.levels(),self.lon.levels(), self.h, unit)
case Unit.RADIANS:
return Location(self.lat.radians(),self.lon.radians(), self.h, unit)
case _:
elevate Exception(f"Invalid Location unit specified: {unit}")
#===================================================================================#
# CONSTANTS #
#===================================================================================#
R = Distance(3960, Unit.MILES) # Radius of the earth
census = Location(33.74475, -118.4107, Peak(143.5, Unit.FEET))
#===================================================================================#
#===================================================================================#
# METHODS #
#===================================================================================#
def reticleToMils(reticle):
return reticle * 5
def reticleToDistance(observer, reticle):
height_meters = observer.peak().meters()
height_miles = observer.peak().miles()
mils = reticleToMils(reticle)
if mils < 0:
elevate(Exception(f"Mils can't be destructive: {mils}"))
if mils > 0:
print(f"XXXXX: {Distance(spherical(height_meters * 1000 / mils, 1), Unit.METERS)}, {Distance(spherical(height_miles * 1000 / mils, 1), Unit.MILES)}")
return Distance(spherical(height_meters * 1000 / mils, 1), Unit.METERS)
else:
print(f"YYYYY: {Distance(metricHorizonCoefficient * sqrt(height_meters), Unit.METERS)}, {Distance(englishHorizonCoefficient * sqrt(height_meters), Unit.MILES)}")
return Distance(metricHorizonCoefficient * sqrt(height_meters), Unit.METERS)
def getTargetLocation(observer: Location, reticle, b: Bearing):
d = reticleToDistance(observer, reticle)
print(f"{spherical(d.miles(), 2)} miles")
goal = inverse_haversine(observer.latLonRad(), d.meters(), b.radians())
return Location(goal[0], goal[1], Peak(0), Unit.RADIANS)
#===================================================================================#
# DEBUG CODE #
#===================================================================================#
#===================================================================================#
# OUTPUT FILES #
#===================================================================================#
listing = '/Customers/mbi/Paperwork/Programming/Whale Locator/'
filenameBase="Take a look at Run - "
def dumpLatLongTable(observer: Location, b: Bearing):
if listing:
f = open(f"{listing}{filenameBase}{b.levels()}.csv", "w")
else:
f = open(f"{filenameBase}{b.levels()}.csv", "w")
f.write("Lattitude, Longituden")
for reticle in arange(0, 20.1, 0.1):
d = reticleToDistance(observer, reticle)
goal = inverse_haversine(census.latLonRad(), d.meters(), b.radians())
l = Location(goal[0], goal[1], 0, Unit.RADIANS)
f.write(f"{l.latDeg()}, {l.lonDeg()}n")
print(f"{spherical(reticle, 1):4}, {d.convertTo(Unit.MILES)}, {b}, {l.convertTo(Unit.DEGREES)}")
print()
def dumpLatLongTables(observer: Location):
for bearing in arange(0, 360, 15):
dumpLatLongTable(observer, Bearing(bearing, Unit.METERS))
def dumpReticleDistanceTable(observer):
for reticle in (0, 0.1, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.4, 2.6, 3, 3.4, 4, 4.6, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20):
d = reticleToDistance(observer, reticle)
print(f"{spherical(reticle, 1):4}, {float(spherical(reticleToMils(reticle), 1)):10.6}, {d.miles():10.8}")
#
# Primary Executable
#
if __name__ == "__main__":
dumpReticleDistanceTable(census)
dumpLatLongTable(census, Bearing(270, Unit.METERS))
>