Skip to content

Instantly share code, notes, and snippets.

@tgherzog
Created December 12, 2017 13:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tgherzog/c5a1b951510c6ac6ef254175c2424352 to your computer and use it in GitHub Desktop.
Save tgherzog/c5a1b951510c6ac6ef254175c2424352 to your computer and use it in GitHub Desktop.
SailBuddy code
//
// SBData.swift
// Sail Buddy
//
// Created by Tim on 6/27/15.
// Copyright (c) 2015 Zog Software. All rights reserved.
//
import Foundation
import CoreLocation
@objc(SBData)
class SBData : NSObject /*, NSCoding */ {
var location:CLLocation?
// this is only set and implemented at recording time to ignore GPS data where hAccuracy is too high
var gpsTolerance = 0
var _apparentWindSpeed: CLLocationSpeed?, _trueWindSpeed: CLLocationSpeed?
var _apparentWindAngle: CLLocationDirection? {
didSet {
if _apparentWindAngle != nil {
_apparentWindAngle = normalizeAngle(angle: _apparentWindAngle!)
}
}
}
var _trueWindAngle: CLLocationDirection? {
didSet {
if _trueWindAngle != nil {
_trueWindAngle = normalizeAngle(angle: _trueWindAngle!)
}
}
}
var course:CLLocationDirection?
var speed:CLLocationSpeed?
var depthBelowKeel:Double?, depthBelowTransducer:Double?, depthBelowSurface:Double?
var lastGPSUpdate:Date? // timestamp of last location update, including COG and SOG
var lastWindDataUpdate:Date?
var lastDepthDataUpdate:Date?
var windBatteryLevel:Int?
func startInstruments() {
location = nil
course = nil
speed = nil
_apparentWindSpeed = nil
_apparentWindAngle = nil
_trueWindSpeed = nil
_trueWindAngle = nil
depthBelowKeel = nil
depthBelowTransducer = nil
depthBelowSurface = nil
lastGPSUpdate = nil
lastWindDataUpdate = nil
lastDepthDataUpdate = nil
}
func normalizeAngle(angle:CLLocationDirection) -> CLLocationDirection {
var angle = angle
while angle < 0 {
angle += 360.0
}
while angle >= 360.0 {
angle -= 360.0
}
return angle
}
/*
override init() {
}
required init(coder adecoder: NSCoder) {
course = adecoder.decodeObjectForKey("cog") as? CLLocationDirection
speed = adecoder.decodeObjectForKey("sog") as? CLLocationSpeed
apparentWindAngle = adecoder.decodeObjectForKey("awa") as? CLLocationDirection
apparentWindSpeed = adecoder.decodeObjectForKey("aws") as? CLLocationSpeed
lastCourseUpdate = adecoder.decodeObjectForKey("lastCourseUpdate") as? Date
lastWindDataUpdate = adecoder.decodeObjectForKey("lastWindDataUpdate") as? Date
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(course, forKey: "cog")
coder.encodeObject(speed, forKey: "sog")
coder.encodeObject(apparentWindSpeed, forKey: "awa")
coder.encodeObject(apparentWindAngle, forKey: "aws")
if let location = self.location {
coder.encodeObject(location.coordinate.latitude, forKey: "lat")
coder.encodeObject(location.coordinate.longitude, forKey: "lon")
coder.encodeObject(location.timestamp.timeIntervalSinceReferenceDate, forKey: "ts")
}
coder.encodeObject(lastCourseUpdate, forKey: "lastCourseUpdate")
coder.encodeObject(lastWindDataUpdate, forKey: "lastWindDataUpdate")
}
*/
func didReceiveLocation(location:CLLocation) {
self.location = location
self.lastGPSUpdate = location.timestamp
if location.speed >= 0 {
self.speed = location.speed
}
if location.course >= 0 {
self.course = location.course
}
}
var apparentWindSpeed: CLLocationSpeed? {
if _apparentWindSpeed != nil {
return _apparentWindSpeed
}
// TODO: calculate apparent wind speed
return nil
}
var apparentWindAngle: CLLocationDirection? {
if _apparentWindAngle != nil {
return _apparentWindAngle
}
// TODO: calculate apparent wind angle
return nil
}
// Using these calculations: https://en.wikipedia.org/wiki/Apparent_wind
var trueWindSpeed:CLLocationSpeed? {
if _trueWindSpeed != nil {
return _trueWindSpeed
}
if let aws = _apparentWindSpeed, let awa = _apparentWindAngle, let sog = self.speed {
let speed:Double = aws*aws + sog*sog - 2.0*aws*sog * cos(deg2rad(degrees: awa))
return sqrt(speed)
}
return nil
}
var trueWindAngle:CLLocationDirection? {
if _trueWindAngle != nil {
return _trueWindAngle
}
if let aws = _apparentWindSpeed, let awa = _apparentWindAngle, let sog = self.speed, let tws = trueWindSpeed {
// trap for div by 0
if tws == 0.0 {
return 0
}
var angle:Double = rad2deg( rad: acos( (aws*cos(deg2rad(degrees: awa)) - sog) / tws) )
if awa > 180 {
angle *= -1
}
return normalizeAngle(angle: angle)
}
return nil
}
func angleFormatted(angle:CLLocationDirection, settings:SBSettings) -> CLLocationDirection {
var angle = angle
if settings.angleRelativity == .North, let cog = course {
angle += cog
}
return normalizeAngle(angle: angle)
}
func cogFormatted(settings:SBSettings, units:Bool=false) -> String {
return cogExport
}
func sogFormatted(settings:SBSettings, units:Bool=false) -> String {
return speedFormatted(value: speed, settings: settings, units:units)
}
func awaFormatted(settings:SBSettings, units:Bool=false) -> String {
if let awa = apparentWindAngle {
return String(format: "%.0f", angleFormatted(angle: awa, settings: settings))
}
return ""
}
func twaFormatted(settings:SBSettings, units:Bool=false) -> String {
if let twa = trueWindAngle {
return String(format: "%.0f", angleFormatted(angle: twa, settings: settings))
}
return ""
}
func awsFormatted(settings:SBSettings, units:Bool=false) -> String {
return speedFormatted(value: apparentWindSpeed, settings: settings, units:units)
}
func twsFormatted(settings:SBSettings, units:Bool=false) -> String {
return speedFormatted(value: trueWindSpeed, settings: settings, units:units)
}
func speedFormatted(value:CLLocationSpeed?, settings:SBSettings, units:Bool=false) -> String {
if let x = value {
return String(format: "%.1f", x * settings.msToSpeed) + (units ? settings.speedUnitsLabel : "")
}
return ""
}
func distanceFormatted(value:Double?, settings:SBSettings, units:Bool=false) -> String {
if let x = value {
return String(format: "%.1f", x * settings.mToDistance) + (units ? settings.distUnitsLabel : "")
}
return ""
}
func degreeFormatted(degree:CLLocationDegrees?, settings:SBSettings) -> String {
if let d = degree {
if settings.degreeUnits == .DMS {
let degrees = Int(d)
let fraction = (d - Double(degrees)) * (d < 0 ? -1 : 1)
let minutes = Int(fraction*60)
let seconds = fraction*3600 - Double(minutes*60)
return String(format: "%d\u{00b0}%d'%.0f\"", degrees, minutes, seconds)
}
return String(format: "%.6f", d)
}
return ""
}
func depthFormatted(value:Double?, settings:SBSettings, units:Bool=false) -> String {
if let depth = value {
return String(format: "%.1f", depth * settings.mToDepth) + (units ? settings.depthUnitsLabel : "")
}
return ""
}
/* Exported varaibles always use the same units of measurement regardless of configuration settings.
velocities are in meters/second since that's what gpx specifies
distances would be in meters
angles are in degrees (0 to 360)
lon/lat in decimal degrees (-180 to +180)
current wind directions are relative to cog, not compass north. I haven't found any information that
there is a standard for this one way or another. But cog makes sense to me because: 1) if cog is undefined
then wind directions relative to compass north would also be undefined, which would mean less data, and 2)
it's easy for users to calculate wind relative to cog if both are given.
*/
var cogExport:String {
if let cog = course {
return String(format: "%.0f", cog)
}
return ""
}
var awaExport:String {
if let awa = apparentWindAngle {
return String(format: "%.0f", awa)
}
return ""
}
var twaExport:String {
if let twa = trueWindAngle {
return String(format: "%.0f", twa)
}
return ""
}
var sogExport:String {
if let sog = speed {
return String(format: "%.3f", sog * SB.kMetersPerSecond)
}
return ""
}
var awsExport:String {
if let aws = apparentWindSpeed {
return String(format: "%.3f", aws * SB.kMetersPerSecond)
}
return ""
}
var twsExport:String {
if let tws = trueWindSpeed {
return String(format: "%.3f", tws * SB.kMetersPerSecond)
}
return ""
}
var latExport:String {
if let loc = location {
return String(format: "%.6f", loc.coordinate.latitude)
}
return ""
}
var lonExport:String {
if let loc = location {
return String(format: "%.6f", loc.coordinate.longitude)
}
return ""
}
func depthObservation(index:Int) -> (key:String, label:String, value:Double)? {
let obs = [(SB.Fields.dbk, "Below Keel" /*"ΔKeel"*/, depthBelowKeel), (SB.Fields.dbs, "Below Surface" /*"ΔSurface"*/, depthBelowSurface), (SB.Fields.dbt, "Below Transducer" /*"ΔTrans."*/, depthBelowTransducer)]
var n=0
for i in 0..<obs.count {
if obs[i].2 == nil {
continue
}
if n == index {
return (obs[i].0, obs[i].1, obs[i].2!)
}
n += 1
}
return nil
}
}
func deg2rad(degrees: CLLocationDirection) -> Double {
return degrees * 3.14159 / 180.0
}
func rad2deg(rad: Double) -> CLLocationDirection {
return round(rad * 180.0 / 3.14159)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment