From 4d1fe0df7fc1625f69ff516dbd8cf60e1d8ae94e Mon Sep 17 00:00:00 2001 From: LEO Yoon-Tsaw Date: Wed, 17 Aug 2022 20:24:32 -0400 Subject: [PATCH] Add sunrise/set and moonrise/set with complementing UI to set their colors Also add moon position on celestial circle to join other planets --- ChineseTime.xcodeproj/project.pbxproj | 4 +- ChineseTime/Data.swift | 3 + ChineseTime/Info.plist | 4 +- ChineseTime/Layout.swift | 27 ++- ChineseTime/Model.swift | 152 ++++++++++++--- ChineseTime/PlanetModel.swift | 114 +++++++++++- ChineseTime/WatchFace.swift | 31 +++- ChineseTime/en.lproj/Main.storyboard | 256 ++++++++++++++++---------- 8 files changed, 445 insertions(+), 146 deletions(-) diff --git a/ChineseTime.xcodeproj/project.pbxproj b/ChineseTime.xcodeproj/project.pbxproj index 9dad57e..0e99543 100644 --- a/ChineseTime.xcodeproj/project.pbxproj +++ b/ChineseTime.xcodeproj/project.pbxproj @@ -304,7 +304,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 31; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 28HU5A7B46; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = ChineseTime/Info.plist; @@ -329,7 +329,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 31; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 28HU5A7B46; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = ChineseTime/Info.plist; diff --git a/ChineseTime/Data.swift b/ChineseTime/Data.swift index 3c48f5f..9e910bd 100644 --- a/ChineseTime/Data.swift +++ b/ChineseTime/Data.swift @@ -5,6 +5,9 @@ // Created by LEO Yoon-Tsaw on 9/21/21. // +let earthSpeed = 360 / 86164.0989 +let moonSpeed = 360 / (27.321582 * 86400) + let moonData: [[Int8]] = [[0,6,6,6,6,6,5,6,3,5,4,4,5,4,6,6,6,6,6,2,6,1,6,5,6,7], [1,7,7,7,6,4,8,4,8,5,7,5,4,5,4,6,6,6,4,5,0,3,2,5,6,6], [0,6,6,4,4,5,4,7,5,8,7,6,7,5,5,6,4,5,4,4,4,4,3,4,4,5], diff --git a/ChineseTime/Info.plist b/ChineseTime/Info.plist index 17d38d4..d702dae 100644 --- a/ChineseTime/Info.plist +++ b/ChineseTime/Info.plist @@ -24,10 +24,10 @@ public.app-category.utilities LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) - NSLocationWhenInUseUsageDescription - Enable to automatically fill in location, for sunrise and sunset time calculation NSLocationUsageDescription Use location to calculate local sunrise and sunset time + NSLocationWhenInUseUsageDescription + Enable to automatically fill in location, for sunrise and sunset time calculation NSMainStoryboardFile Main NSPrincipalClass diff --git a/ChineseTime/Layout.swift b/ChineseTime/Layout.swift index 8b9edc5..a0c57f7 100644 --- a/ChineseTime/Layout.swift +++ b/ChineseTime/Layout.swift @@ -214,6 +214,7 @@ class WatchLayout { var oddSolarTermTickColorDark: NSColor var planetIndicator: [NSColor] var sunPositionIndicator: [NSColor] + var moonPositionIndicator: [NSColor] var eclipseIndicator: NSColor var fullmoonIndicator: NSColor var oddStermIndicator: NSColor @@ -262,11 +263,15 @@ class WatchLayout { NSColor(displayP3Red: 200/255, green: 190/255, blue: 170/255, alpha: 1.0), //Venus NSColor(displayP3Red: 210/255, green: 48/255, blue: 40/255, alpha: 1.0), //Mars NSColor(displayP3Red: 60/255, green: 180/255, blue: 90/255, alpha: 1.0), //Jupyter - NSColor(displayP3Red: 170/255, green: 150/255, blue: 50/255, alpha: 1.0)] //Saturn + NSColor(displayP3Red: 170/255, green: 150/255, blue: 50/255, alpha: 1.0), //Saturn + NSColor(displayP3Red: 220/255, green: 200/255, blue: 60/255, alpha: 1.0)] //Moon sunPositionIndicator = [NSColor(displayP3Red: 0/255, green: 0/255, blue: 0/255, alpha: 1.0), //Mid Night NSColor(displayP3Red: 255/255, green: 80/255, blue: 10/255, alpha: 1.0), //Sunrise NSColor(displayP3Red: 210/255, green: 170/255, blue: 120/255, alpha: 1.0), //Noon NSColor(displayP3Red: 230/255, green: 120/255, blue: 30/255, alpha: 1.0)] //Sunset + moonPositionIndicator = [NSColor(displayP3Red: 190/255, green: 210/255, blue: 30/255, alpha: 1.0), //Moon rise + NSColor(displayP3Red: 255/255, green: 255/255, blue: 50/255, alpha: 1.0), //Moon at meridian + NSColor(displayP3Red: 120/255, green: 30/255, blue: 150/255, alpha: 1.0)] //Moon set eclipseIndicator = NSColor(displayP3Red: 50/255, green: 68/255, blue: 96/255, alpha: 1.0) fullmoonIndicator = NSColor(displayP3Red: 255/255, green: 239/255, blue: 59/255, alpha: 1.0) oddStermIndicator = NSColor(displayP3Red: 153/255, green: 153/255, blue: 153/255, alpha:1.0) @@ -310,6 +315,7 @@ class WatchLayout { encoded += "oddStermIndicator: \(oddStermIndicator.hexCode)\n" encoded += "evenStermIndicator: \(evenStermIndicator.hexCode)\n" encoded += "sunPositionIndicator: \(sunPositionIndicator.map {$0.hexCode}.joined(separator: ", "))\n" + encoded += "moonPositionIndicator: \(moonPositionIndicator.map {$0.hexCode}.joined(separator: ", "))\n" encoded += "shadeAlpha: \(shadeAlpha)\n" encoded += "textFont: \(textFont.fontName)\n" encoded += "centerFont: \(centerFont.fontName)\n" @@ -380,6 +386,9 @@ class WatchLayout { if let colourList = readColorList(values["sunPositionIndicator"]), colourList.count == self.sunPositionIndicator.count { self.sunPositionIndicator = colourList } + if let colourList = readColorList(values["moonPositionIndicator"]), colourList.count == self.moonPositionIndicator.count { + self.moonPositionIndicator = colourList + } eclipseIndicator = values["eclipseIndicator"]?.colorValue ?? eclipseIndicator fullmoonIndicator = values["fullmoonIndicator"]?.colorValue ?? fullmoonIndicator oddStermIndicator = values["oddStermIndicator"]?.colorValue ?? oddStermIndicator @@ -465,6 +474,7 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { @IBOutlet weak var marsIndicatorColorPicker: NSColorWell! @IBOutlet weak var jupyterIndicatorColorPicker: NSColorWell! @IBOutlet weak var saturnIndicatorColorPicker: NSColorWell! + @IBOutlet weak var moonIndicatorColorPicker: NSColorWell! @IBOutlet weak var eclipseIndicatorColorPicker: NSColorWell! @IBOutlet weak var fullmoonIndicatorColorPicker: NSColorWell! @IBOutlet weak var oddStermIndicatorColorPicker: NSColorWell! @@ -473,6 +483,9 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { @IBOutlet weak var sunsetIndicatorColorPicker: NSColorWell! @IBOutlet weak var noonIndicatorColorPicker: NSColorWell! @IBOutlet weak var midnightIndicatorColorPicker: NSColorWell! + @IBOutlet weak var moonriseIndicatorColorPicker: NSColorWell! + @IBOutlet weak var moonsetIndicatorColorPicker: NSColorWell! + @IBOutlet weak var moonmeridianIndicatorColorPicker: NSColorWell! @IBOutlet weak var textFontFamilyPicker: NSPopUpButton! @IBOutlet weak var textFontTraitPicker: NSPopUpButton! @IBOutlet weak var centerTextFontFamilyPicker: NSPopUpButton! @@ -725,7 +738,8 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { venusIndicatorColorPicker.color, marsIndicatorColorPicker.color, jupyterIndicatorColorPicker.color, - saturnIndicatorColorPicker.color + saturnIndicatorColorPicker.color, + moonIndicatorColorPicker.color ] watchLayout.eclipseIndicator = eclipseIndicatorColorPicker.color watchLayout.fullmoonIndicator = fullmoonIndicatorColorPicker.color @@ -737,6 +751,11 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { noonIndicatorColorPicker.color, sunsetIndicatorColorPicker.color ] + watchLayout.moonPositionIndicator = [ + moonriseIndicatorColorPicker.color, + moonmeridianIndicatorColorPicker.color, + moonsetIndicatorColorPicker.color + ] watchLayout.textFont = readFont(family: textFontFamilyPicker, style: textFontTraitPicker) ?? watchLayout.textFont watchLayout.centerFont = readFont(family: centerTextFontFamilyPicker, style: centerTextFontTraitPicker) ?? watchLayout.centerFont watchLayout.watchSize = NSMakeSize(max(0, widthPicker.doubleValue), max(0, heightPicker.doubleValue)) @@ -810,6 +829,7 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { marsIndicatorColorPicker.color = watchLayout.planetIndicator[2] jupyterIndicatorColorPicker.color = watchLayout.planetIndicator[3] saturnIndicatorColorPicker.color = watchLayout.planetIndicator[4] + moonIndicatorColorPicker.color = watchLayout.planetIndicator[5] eclipseIndicatorColorPicker.color = watchLayout.eclipseIndicator fullmoonIndicatorColorPicker.color = watchLayout.fullmoonIndicator oddStermIndicatorColorPicker.color = watchLayout.oddStermIndicator @@ -818,6 +838,9 @@ class ConfigurationViewController: NSViewController, NSWindowDelegate { sunriseIndicatorColorPicker.color = watchLayout.sunPositionIndicator[1] noonIndicatorColorPicker.color = watchLayout.sunPositionIndicator[2] sunsetIndicatorColorPicker.color = watchLayout.sunPositionIndicator[3] + moonriseIndicatorColorPicker.color = watchLayout.moonPositionIndicator[0] + moonmeridianIndicatorColorPicker.color = watchLayout.moonPositionIndicator[1] + moonsetIndicatorColorPicker.color = watchLayout.moonPositionIndicator[2] populateFontFamilies(textFontFamilyPicker) textFontFamilyPicker.selectItem(withTitle: watchLayout.textFont.familyName!) populateFontMember(textFontTraitPicker, inFamily: textFontFamilyPicker) diff --git a/ChineseTime/Model.swift b/ChineseTime/Model.swift index 80defa2..a488ec1 100644 --- a/ChineseTime/Model.swift +++ b/ChineseTime/Model.swift @@ -230,8 +230,20 @@ private func get_month_day(time: Date, eclipse: [Date], calendar: Calendar) -> ( return (month_index, day_index, precise_month) } -private func intraday_solar_times(chineseCalendar: ChineseCalendar, latitude: CGFloat, longitude: CGFloat) -> [CGFloat?] { +private func fromJD2000(date: Date) -> CGFloat { + var dateComponents = utcCalendar.dateComponents([.year, .month, .day], from: date) + dateComponents.hour = 12 + let noon = utcCalendar.date(from: dateComponents)! + let j2000 = getJD(yyyy: dateComponents.year!, mm: dateComponents.month!, dd: dateComponents.day!) + let delta_j2000 = DeltaT(T: (j2000-2451545 + 365.25*0.5)/36525) + var offset = j2000 + delta_j2000 - 2451544.5 + offset += noon.distance(to: date) / 86400 + return offset +} + +private func intraday_solar_times(chineseCalendar: ChineseCalendar, latitude: CGFloat, longitude: CGFloat) -> [Date?] { let calendar = chineseCalendar.calendar + let eps: CGFloat = 0.409092610296857 // obliquity @ J2000 in rad func timeOfDate(date: Date, hour: Int) -> Date { var dateComponents = calendar.dateComponents([.year, .month, .day], from: date) dateComponents.hour = hour @@ -241,17 +253,6 @@ private func intraday_solar_times(chineseCalendar: ChineseCalendar, latitude: CG return newDate + delta } - func fromJD2000(date: Date) -> CGFloat { - var dateComponents = utcCalendar.dateComponents([.year, .month, .day], from: date) - dateComponents.hour = 12 - let noon = utcCalendar.date(from: dateComponents)! - let j2000 = getJD(yyyy: dateComponents.year!, mm: dateComponents.month!, dd: dateComponents.day!) - let delta_j2000 = DeltaT(T: (j2000-2451545 + 365.25*0.5)/36525) - var offset = j2000 + delta_j2000 - 2451544.5 - offset += noon.distance(to: date) / 86400 - return offset - } - let localNoon = timeOfDate(date: chineseCalendar.time, hour: 12) let noonTime = localNoon - equationOfTime(D: fromJD2000(date: localNoon)) / (2 * CGFloat.pi) * 86400 let priorMidNight = timeOfDate(date: chineseCalendar.time, hour: 0) @@ -259,24 +260,95 @@ private func intraday_solar_times(chineseCalendar: ChineseCalendar, latitude: CG let priorMidNightTime = priorMidNight - equationOfTime(D: fromJD2000(date: priorMidNight)) / (2 * CGFloat.pi) * 86400 let nextMidNightTime = nextMidNight - equationOfTime(D: fromJD2000(date: nextMidNight)) / (2 * CGFloat.pi) * 86400 - let sunriseSunsetOffset = daytimeOffset(latitude: latitude / 180 * CGFloat.pi, progressInYear: chineseCalendar.progressInYear * 2 * CGFloat.pi) / (2 * CGFloat.pi) * 86400 - let results: [CGFloat?] + let sunriseSunsetOffset = daytimeOffset(latitude: latitude / 180 * CGFloat.pi, progressInYear: chineseCalendar.progressInYear * 2 * CGFloat.pi, eps: eps) / (2 * CGFloat.pi) * 86400 + let results: [Date?] if sunriseSunsetOffset == CGFloat.infinity { //Extreme day - results = [nil, nil, CGFloat(calendar.timeInSeconds(for: noonTime)) / 86400, nil, nil] + results = [nil, nil, noonTime, nil, nil] } else if sunriseSunsetOffset == -CGFloat.infinity { //Extreme night - let solarTimes = [priorMidNightTime, nextMidNightTime].map { CGFloat(calendar.timeInSeconds(for: $0)) / 86400 } - results = [solarTimes[0] < 0.5 ? solarTimes[0] : nil, nil, nil, nil, solarTimes[1] > 0.5 ? solarTimes[1] : nil] + results = [priorMidNightTime, nil, nil, nil, nextMidNightTime] } else { var sunriseTime = localNoon - sunriseSunsetOffset var sunsetTime = localNoon + sunriseSunsetOffset sunriseTime -= equationOfTime(D: fromJD2000(date: sunriseTime)) / (2 * CGFloat.pi) * 86400 sunsetTime -= equationOfTime(D: fromJD2000(date: sunsetTime)) / (2 * CGFloat.pi) * 86400 - let solarTimes = [priorMidNightTime, sunriseTime, noonTime, sunsetTime, nextMidNightTime].map { CGFloat(calendar.timeInSeconds(for: $0)) / 86400 } - results = [solarTimes[0] < 0.5 ? solarTimes[0] : nil, solarTimes[1], solarTimes[2], solarTimes[3], solarTimes[4] > 0.5 ? solarTimes[4] : nil] + results = [priorMidNightTime, sunriseTime, noonTime, sunsetTime, nextMidNightTime] } return results } +private func intraday_lunar_times(chineseCalendar: ChineseCalendar, latitude: CGFloat, longitude: CGFloat) -> [Date?] { + + func riseAndSet(meridianTime: Date, latitude: CGFloat, light: Bool) -> ([Date?], CGFloat) { + let (_, dec) = moonEquatorPosition(D: fromJD2000(date: meridianTime)) + let delta = 50 / 60 / 180 * CGFloat.pi + let offsetMeridian = lunarTimeOffset(latitude: latitude / 180 * CGFloat.pi, declination: light ? dec : -dec, aeroAdj: light ? delta : -delta) + let moonrise = meridianTime - offsetMeridian / (2*CGFloat.pi) * 360 / (earthSpeed - moonSpeed) + let moonset = meridianTime + offsetMeridian / (2*CGFloat.pi) * 360 / (earthSpeed - moonSpeed) + if offsetMeridian == CGFloat.infinity { + return ([nil, meridianTime, nil], offsetMeridian) + } else if offsetMeridian == -CGFloat.infinity { + return ([nil, nil, nil], offsetMeridian) + } else { + return ([moonrise, meridianTime, moonset], offsetMeridian) + } + } + func roundHalf(_ num: CGFloat) -> CGFloat { + return num - 1 - floor(num - 0.5) + } + + let (ra, _) = moonEquatorPosition(D: fromJD2000(date: chineseCalendar.time)) + let progressInYear = chineseCalendar.progressInYear + var longitudeUnderMoon = -progressInYear - 1/4 - utcCalendar.timeInSeconds(for: chineseCalendar.time) / 86400 + ra / (2*CGFloat.pi) + let eot = equationOfTime(D: fromJD2000(date: chineseCalendar.time)) / (2*CGFloat.pi) + longitudeUnderMoon += eot + longitudeUnderMoon = roundHalf(longitudeUnderMoon) + var longitudeDiff = longitudeUnderMoon - longitude / 360 + longitudeDiff = roundHalf(longitudeDiff) + var timeUnderMeridianNext: Date, timeUnderMeridianPrevious: Date + if longitudeDiff >= 0 { + timeUnderMeridianNext = chineseCalendar.time + longitudeDiff * 360 / (earthSpeed - moonSpeed) + timeUnderMeridianPrevious = chineseCalendar.time + (longitudeDiff - 1) * 360 / (earthSpeed - moonSpeed) + } else { + timeUnderMeridianPrevious = chineseCalendar.time + longitudeDiff * 360 / (earthSpeed - moonSpeed) + timeUnderMeridianNext = chineseCalendar.time + (longitudeDiff + 1) * 360 / (earthSpeed - moonSpeed) + } + let beginOfToday = chineseCalendar.calendar.startOfDay(for: chineseCalendar.time) + let endOfToday = chineseCalendar.calendar.date(byAdding: .day, value: 1, to: beginOfToday)! + if timeUnderMeridianPrevious.advanced(by: 43200) < beginOfToday { + timeUnderMeridianPrevious += 360 / (earthSpeed - moonSpeed) + timeUnderMeridianNext += 360 / (earthSpeed - moonSpeed) + } else if timeUnderMeridianNext.advanced(by: -43200) >= endOfToday { + timeUnderMeridianPrevious -= 360 / (earthSpeed - moonSpeed) + timeUnderMeridianNext -= 360 / (earthSpeed - moonSpeed) + } + let (previousTimes, _) = riseAndSet(meridianTime: timeUnderMeridianPrevious, latitude: latitude, light: true) + let (nextTimes, _) = riseAndSet(meridianTime: timeUnderMeridianNext, latitude: latitude, light: true) + let midtime = timeUnderMeridianPrevious + timeUnderMeridianPrevious.distance(to: timeUnderMeridianNext) / 2 + let (midTimes, offset) = riseAndSet(meridianTime: midtime, latitude: latitude, light: false) + + var results = [previousTimes[0], previousTimes[1]] + if let set1 = previousTimes[2], let set2 = midTimes[0] { + results.append(set2 + set2.distance(to: set1) * (offset / CGFloat.pi / 2)) + } else { + if offset == -CGFloat.infinity { + results.append(nil) + } else { + results.append(previousTimes[2] ?? midTimes[0]) + } + } + if let set1 = midTimes[2], let set2 = nextTimes[0] { + results.append(set2 + set2.distance(to: set1) * (offset / CGFloat.pi / 2)) + } else { + if offset == -CGFloat.infinity { + results.append(nil) + } else { + results.append(nextTimes[0] ?? midTimes[2]) + } + } + results.append(contentsOf: [nextTimes[1], nextTimes[2]]) + return results +} + extension Date { static func from(year: Int, month: Int, day: Int, hour: Int, minute: Int, timezone: TimeZone?) -> Date? { var dateComponents = DateComponents() @@ -518,14 +590,42 @@ class ChineseCalendar { return _calendar } var progressInYear: CGFloat { + func interpolate(f1: CGFloat, f2: CGFloat, f3: CGFloat, y: CGFloat) -> CGFloat { + let a = f2 - f1 + let b = f3 - f2 - a + let ba = b - 2*a + return (ba + sqrt(pow(ba,2) + 8*b*(y-f1))) / (4*b) + } var i = 0 while (i+1 < _solarTerms.count) && (_time > _solarTerms[i+1]) { i += 1 } - return (CGFloat(i) + _solarTerms[i].distance(to: _time) / _solarTerms[i].distance(to: _solarTerms[i+1])) / 24 + if i <= _solarTerms.count / 2 { + return (CGFloat(i) + interpolate(f1: 0, f2: _solarTerms[i].distance(to: _solarTerms[i+1]), f3: _solarTerms[i].distance(to: _solarTerms[i+2]), y: _solarTerms[i].distance(to: _time)) * 2) / 24 + } else { + return (CGFloat(i) + (interpolate(f1: 0, f2: _solarTerms[i-1].distance(to: _solarTerms[i]), f3: _solarTerms[i-1].distance(to: _solarTerms[i+1]), y: _solarTerms[i-1].distance(to: _time)) - 0.5) * 2) / 24 + } } func sunPositions(latitude: CGFloat, longitude: CGFloat) -> [CGFloat?] { - return intraday_solar_times(chineseCalendar: self, latitude: latitude, longitude: longitude) + let sunTimes = intraday_solar_times(chineseCalendar: self, latitude: latitude, longitude: longitude) + let sunPos = sunTimes.map { date -> CGFloat? in + if let date = date, calendar.isDate(date, inSameDayAs: time) { + return CGFloat(calendar.timeInSeconds(for: date)) / 86400 + } else { + return nil + } + } + return sunPos + } + func moonrise(latitude: CGFloat, longitude: CGFloat) -> [CGFloat?] { + let moonTimes = intraday_lunar_times(chineseCalendar: self, latitude: latitude, longitude: longitude) + return moonTimes.map { date in + if let date = date, calendar.isDate(date, inSameDayAs: time) { + return calendar.timeInSeconds(for: date) / 86400 + } else { + return nil + } + } } var evenSolarTerms: [CGFloat] { var evenSolarTermsPositions = _evenSolarTerms.map { _year_start.distance(to: $0) / _year_length as CGFloat } @@ -613,12 +713,10 @@ class ChineseCalendar { } } var planetPosition: [CGFloat] { - let components = utcCalendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: _time) - var JD: CGFloat = getJD(yyyy: components.year!, mm: components.month!, dd: components.day!) - JD += (CGFloat(components.hour!) + (CGFloat(components.minute!) + CGFloat(components.second!) / 60.0) / 60.0) / 24.0 - let T = (JD - 2451545) / 36525 - let planetPosition = planetPos(T: T).map { ($0 / CGFloat.pi / 2 + 0.25) % 1.0 } - return planetPosition + var planetPosition = planetPos(T: fromJD2000(date: _time) / 36525) + let moonPosition = moonElipticPosition(D: fromJD2000(date: _time)) + planetPosition.append(moonPosition) + return planetPosition.map { ($0 / CGFloat.pi / 2 + 0.25) % 1.0 } } var eventInMonth: CelestialEvent { let monthStart: Date diff --git a/ChineseTime/PlanetModel.swift b/ChineseTime/PlanetModel.swift index 4b14b97..ef021f2 100644 --- a/ChineseTime/PlanetModel.swift +++ b/ChineseTime/PlanetModel.swift @@ -139,9 +139,9 @@ func planetPos(T: CGFloat) -> [CGFloat] { let pi2 = 2 * CGFloat.pi; // 1/light speed in century/AU let f1oc = 1.58125073358306e-07 - //let eps = 0.409092610296857 // obliquity @ J2000 in rad - let cosEps = 0.917482139208287 - let sinEps = 0.397776978008764 + let eps = 0.409092610296857 // obliquity @ J2000 in rad + let cosEps = cos(eps) + let sinEps = sin(eps) // Angles have been converted to radians let a0: [CGFloat], adot: [CGFloat], e0: [CGFloat],edot: [CGFloat], I0: [CGFloat], Idot: [CGFloat], L0: [CGFloat], Ldot: [CGFloat], pom0: [CGFloat], pomdot: [CGFloat], Omg0: [CGFloat], Omgdot: [CGFloat] @@ -264,6 +264,94 @@ func planetPos(T: CGFloat) -> [CGFloat] { return output } +func moonCoordinate(D: CGFloat) -> (CGFloat, CGFloat, CGFloat) { + var l = 0.606434 + 0.03660110129 * D + var m = 0.374897 + 0.03629164709 * D + var f = 0.259091 + 0.03674819520 * D + var d = 0.827362 + 0.03386319198 * D + var n = 0.347343 - 0.00014709391 * D + var g = 0.993126 + 0.00273777850 * D + + l = 2 * CGFloat.pi * (l - floor(l)) + m = 2 * CGFloat.pi * (m - floor(m)) + f = 2 * CGFloat.pi * (f - floor(f)) + d = 2 * CGFloat.pi * (d - floor(d)) + n = 2 * CGFloat.pi * (n - floor(n)) + g = 2 * CGFloat.pi * (g - floor(g)) + + var v: CGFloat, u: CGFloat, w: CGFloat + v = 0.39558 * sin(f + n) + + 0.08200 * sin(f) + + 0.03257 * sin(m - f - n) + + 0.01092 * sin(m + f + n) + + 0.00666 * sin(m - f) + - 0.00644 * sin(m + f - 2*d + n) + - 0.00331 * sin(f - 2*d + n) + - 0.00304 * sin(f - 2*d) + - 0.00240 * sin(m - f - 2*d - n) + + 0.00226 * sin(m + f) + - 0.00108 * sin(m + f - 2*d) + - 0.00079 * sin(f - n) + + 0.00078 * sin(f + 2*d + n) + + u = 1 + - 0.10828 * cos(m) + - 0.01880 * cos(m - 2*d) + - 0.01479 * cos(2*d) + + 0.00181 * cos(2*m - 2*d) + - 0.00147 * cos(2*m) + - 0.00105 * cos(2*d - g) + - 0.00075 * cos(m - 2*d + g) + + w = 0.10478 * sin(m) + - 0.04105 * sin(2*f + 2*n) + - 0.02130 * sin(m - 2*d) + - 0.01779 * sin(2*f + n) + + 0.01774 * sin(n) + + 0.00987 * sin(2*d) + - 0.00338 * sin(m - 2*f - 2*n) + - 0.00309 * sin(g) + - 0.00190 * sin(2*f) + - 0.00144 * sin(m + n) + - 0.00144 * sin(m - 2*f - n) + - 0.00113 * sin(m + 2*f + 2*n) + - 0.00094 * sin(m - 2*d + g) + - 0.00092 * sin(2*m - 2*d) + + var s: CGFloat + s = w / sqrt(u - v*v) + let rightAscension = l + atan(s / sqrt(1 - s*s)) + + s = v / sqrt(u) + let declination = atan(s / sqrt(1 - s*s)) + + var x = cos(rightAscension) * cos(declination) + var y = sin(rightAscension) * cos(declination) + var z = sin(declination) + + // RA and Dec with respect to J2000 + let p = precession_matrix(T0: 0,T: D/36525) + // precessed to the mean equator and equinox of the date + x = p.p11*x + p.p12*y + p.p13*z + y = p.p21*x + p.p22*y + p.p23*z + z = p.p31*x + p.p32*y + p.p33*z + + return (x: x, y: y, z: z) +} + +func moonElipticPosition(D: CGFloat) -> CGFloat { + let (x, y, z) = moonCoordinate(D: D) + let eps: CGFloat = 0.409092610296857 // obliquity @ J2000 in rad + // Back to eliptic + let yel = cos(eps) * y + sin(eps) * z + return atan2(yel, x) +} + +func moonEquatorPosition(D: CGFloat) -> (CGFloat, CGFloat) { + let (x, y, z) = moonCoordinate(D: D) + return (ra: atan2(y, x), dec: atan2(z, sqrt(x*x+y*y))) +} + func equationOfTime(D: CGFloat) -> CGFloat { let d = D / 36525 let epsilon = (23.4393 - 0.013 * d - 2e-7 * pow(d, 2) + 5e-7 * pow(d, 3)) / 180 * CGFloat.pi @@ -276,12 +364,11 @@ func equationOfTime(D: CGFloat) -> CGFloat { return deltaT } -func daytimeOffset(latitude: CGFloat, progressInYear: CGFloat) -> CGFloat { - let epsilon = 23.4393 / 180 * CGFloat.pi +func daytimeOffset(latitude: CGFloat, progressInYear: CGFloat, eps: CGFloat) -> CGFloat { let delta = 50 / 60 / 180 * CGFloat.pi - let denominator = sqrt(pow(cos(epsilon), 2) + pow(sin(epsilon) * sin(progressInYear), 2)) - let numerator = sin(latitude) * sin(epsilon) * cos(progressInYear) - let cosValue = (numerator / denominator - sin(delta)) / cos(latitude) + let denominator = sqrt(pow(cos(eps), 2) + pow(sin(eps) * sin(progressInYear), 2)) * cos(latitude) + let numerator = sin(latitude) * sin(eps) * cos(progressInYear) - sin(delta) + let cosValue = numerator / denominator if cosValue >= 1 { return -CGFloat.infinity } else if cosValue <= -1 { @@ -290,3 +377,14 @@ func daytimeOffset(latitude: CGFloat, progressInYear: CGFloat) -> CGFloat { return acos(cosValue) } } + +func lunarTimeOffset(latitude: CGFloat, declination: CGFloat, aeroAdj: CGFloat) -> CGFloat { + let cosValue = (sin(latitude) * sin(declination) - sin(aeroAdj)) / (cos(declination) * cos(latitude)) + if cosValue >= 1 { + return CGFloat.infinity + } else if cosValue <= -1 { + return -CGFloat.infinity + } else { + return CGFloat.pi - acos(cosValue) + } +} diff --git a/ChineseTime/WatchFace.swift b/ChineseTime/WatchFace.swift index 8850e3c..e6375b6 100644 --- a/ChineseTime/WatchFace.swift +++ b/ChineseTime/WatchFace.swift @@ -349,6 +349,8 @@ class WatchFaceView: NSView { private var timeString: String = "" private var calPlanetTime: Date = Date() private var planetPosition: [CGFloat] = [] + private var sunPositions: [CGFloat?] = [] + private var moonPositions: [CGFloat?] = [] private var eventInMonth = ChineseCalendar.CelestialEvent() private var eventInDay = ChineseCalendar.CelestialEvent() private var eventInHour = ChineseCalendar.CelestialEvent() @@ -916,23 +918,31 @@ class WatchFaceView: NSView { self.layer?.addSublayer(graphicArtifects.secondRingLayer!) self.layer?.addSublayer(graphicArtifects.secondRingMarks!) - let sunPositions = chineseCalendar.sunPositions(latitude: location.x, longitude: location.y) // Third Ring if (graphicArtifects.thirdRingLayer == nil) || (keyStates.day != chineseCalendar.currentDay) || (chineseCalendar.timezone != keyStates.timezone) { + sunPositions = chineseCalendar.sunPositions(latitude: location.x, longitude: location.y) + moonPositions = chineseCalendar.moonrise(latitude: location.x, longitude: location.y) if graphicArtifects.thirdRingLayer == nil { let (hourNamePositions, hourTick, quarterTick) = calHourParams() graphicArtifects.thirdRingLayer = drawRing(ringPath: graphicArtifects.thirdRingOuterPath!, roundedRect: graphicArtifects.thirdRingOuter!, gradient: watchLayout.thirdRing, minorTickPositions: quarterTick, majorTickPositions: hourTick, textPositions: hourNamePositions, texts: ChineseCalendar.terrestrial_branches, fontSize: fontSize, minorLineWidth: minorLineWidth, majorLineWidth: majorLineWidth) } graphicArtifects.thirdRingMarks = addMarks(position: eventInDay, on: graphicArtifects.thirdRingOuter!, maskPath: graphicArtifects.thirdRingOuterPath!, radius: 0.012 * shortEdge) - var sunPositionsInDay = [CGFloat]() - var sunPositionsInDayColors = [NSColor]() + var sunPositionsInDay = [CGFloat](), moonPositionsInDay = [CGFloat]() + var sunPositionsInDayColors = [NSColor](), moonPositionsInDayColors = [NSColor]() for i in 0.. - + - + @@ -728,7 +728,7 @@ - + @@ -737,7 +737,7 @@ - + @@ -746,11 +746,11 @@ - + - + @@ -759,7 +759,7 @@ - + - + @@ -794,7 +794,7 @@ - + @@ -803,7 +803,7 @@ - + @@ -812,11 +812,11 @@ - + - + @@ -825,7 +825,7 @@ - + @@ -834,7 +834,7 @@ - + - + @@ -858,27 +858,27 @@ - + - + - + - + - + @@ -887,12 +887,12 @@ - + - + @@ -901,12 +901,12 @@ - + - + @@ -915,12 +915,12 @@ - + - + @@ -938,12 +938,12 @@ - + - + @@ -952,12 +952,12 @@ - + - + @@ -966,17 +966,17 @@ - + - + - + @@ -985,12 +985,12 @@ - + - + @@ -999,12 +999,12 @@ - + - + @@ -1016,7 +1016,7 @@ - + @@ -1213,12 +1213,12 @@ - + - - + + @@ -1232,11 +1232,11 @@ - + - + @@ -1276,7 +1276,7 @@ - + @@ -1285,12 +1285,12 @@ - + - + @@ -1299,7 +1299,7 @@ - + @@ -1307,7 +1307,7 @@ - + @@ -1316,7 +1316,7 @@ - + @@ -1325,7 +1325,7 @@ - + @@ -1334,7 +1334,7 @@ - + @@ -1342,16 +1342,16 @@ - + - + - + @@ -1360,12 +1360,12 @@ - + - + @@ -1374,12 +1374,12 @@ - + - + @@ -1388,12 +1388,12 @@ - + - + @@ -1402,12 +1402,12 @@ - + - + @@ -1416,12 +1416,26 @@ - + + + + + + + + + + + + + + + - + @@ -1430,21 +1444,21 @@ - + - + - + - + @@ -1453,7 +1467,7 @@ - + @@ -1461,72 +1475,72 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1539,7 +1553,7 @@ - + @@ -1552,7 +1566,7 @@ - + @@ -1565,7 +1579,7 @@ - + @@ -1576,7 +1590,7 @@ - + @@ -1589,7 +1603,7 @@ - + @@ -1602,7 +1616,7 @@ - + @@ -1615,7 +1629,7 @@ - + @@ -1625,6 +1639,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1635,7 +1691,7 @@ - + @@ -1746,6 +1802,10 @@ DQ + + + +