├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SunshineKit.podspec ├── SunshineKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── SunshineKit.xcscheme ├── SunshineKit ├── Other │ └── Info.plist └── Source │ ├── Extension │ ├── Date+SunKitExtension.swift │ └── FractionOfDay+SunKitExtension.swift │ ├── Model │ ├── SunKitTypes.swift │ ├── SunPosition.swift │ ├── SunPositionFragment.swift │ ├── SunRiseSet.swift │ └── SunRiseSetFragment.swift │ └── SPA │ ├── NREL │ ├── NRELJulian.swift │ └── NRELSPA.swift │ ├── RadAndAngle.swift │ └── Wikipedia │ └── WikipediaSPA.swift └── SunshineKitTests ├── Info.plist └── Source ├── NREL ├── NRELJulianTests.swift ├── NRELSPATests.swift └── NRELSunriseTests.swift ├── RadAndAngleTests.swift └── WikipediaSPATests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode9.3 2 | language: objective-c 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | env: 9 | - DESTINATION='platform=iOS Simulator,name=iPhone 6S' POD_LINT="YES" 10 | 11 | before_install: 12 | - gem install xcpretty-travis-formatter 13 | 14 | script: 15 | - set -o pipefail 16 | - xcodebuild -scheme SunshineKit -destination "$DESTINATION" test | xcpretty -f `xcpretty-travis-formatter` 17 | 18 | - if [ "$POD_LINT" = "YES" ]; then 19 | pod lib lint; 20 | fi 21 | 22 | # Run release to master branch 23 | - if [ "$POD_LINT" = "YES" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 24 | pod spec lint; 25 | fi 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Oleg Müller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SunshineKit 2 | 3 | SunshineKit is a framework which calculates various Sun related data, such as: 4 | * sunrise, sunset and transit 5 | * Ascension 6 | * Zenith 7 | * Incidence 8 | * Azimuth 9 | * height 10 | * shadow length and direction 11 | 12 | SunshineKit supports two Solar Position Algorithms (SPA). One is from the german [Wikipedia](https://de.wikipedia.org/wiki/Sonnenstand), the other is from [NREL](http://rredc.nrel.gov/solar/codesandalgorithms/spa/). The NREL SPA is far more complex but also more accurate. 13 | 14 | You can use SunshineKit to calculate the data above for one point in time or if you use the NREL SPA, you can let SunshineKit calculate data points for every hour, every minute or every second of a day. SunshineKit is then using the Apple Accelerate framework for faster processing. 15 | 16 | If you do not need all data points, you can choose the needed data points with the Fragments-Enums. 17 | -------------------------------------------------------------------------------- /SunshineKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SunshineKit" 3 | s.summary = "Framework that calculates various Sun related data" 4 | s.version = "1.1.1" 5 | s.homepage = "https://github.com/scytalion/SunshineKit" 6 | s.license = 'MIT' 7 | s.author = { "Oleg Müller" => "oleg@bitgrainedbytes.com" } 8 | s.source = { 9 | :git => "https://github.com/scytalion/SunshineKit.git", 10 | :tag => s.version.to_s 11 | } 12 | s.ios.deployment_target = '10.0' 13 | s.source_files = 'SunshineKit/Source/**/*.swift' 14 | s.ios.frameworks = 'Foundation', 'Accelerate', 'CoreLocation' 15 | s.swift_version = '4.2' 16 | end 17 | -------------------------------------------------------------------------------- /SunshineKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7C1783E8207D70400043780D /* SunshineKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C1783DE207D70400043780D /* SunshineKit.framework */; }; 11 | 7C178409207D70780043780D /* Date+SunKitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1783FA207D70780043780D /* Date+SunKitExtension.swift */; }; 12 | 7C17840A207D70780043780D /* FractionOfDay+SunKitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1783FB207D70780043780D /* FractionOfDay+SunKitExtension.swift */; }; 13 | 7C17840B207D70780043780D /* WikipediaSPA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1783FE207D70780043780D /* WikipediaSPA.swift */; }; 14 | 7C17840C207D70780043780D /* RadAndAngle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1783FF207D70780043780D /* RadAndAngle.swift */; }; 15 | 7C17840D207D70780043780D /* NRELSPA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178401207D70780043780D /* NRELSPA.swift */; }; 16 | 7C17840E207D70780043780D /* NRELJulian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178402207D70780043780D /* NRELJulian.swift */; }; 17 | 7C17840F207D70780043780D /* SunPositionFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178404207D70780043780D /* SunPositionFragment.swift */; }; 18 | 7C178410207D70780043780D /* SunRiseSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178405207D70780043780D /* SunRiseSet.swift */; }; 19 | 7C178411207D70780043780D /* SunKitTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178406207D70780043780D /* SunKitTypes.swift */; }; 20 | 7C178412207D70780043780D /* SunPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178407207D70780043780D /* SunPosition.swift */; }; 21 | 7C178413207D70780043780D /* SunRiseSetFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178408207D70780043780D /* SunRiseSetFragment.swift */; }; 22 | 7C17841B207D70A30043780D /* RadAndAngleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178415207D70A30043780D /* RadAndAngleTests.swift */; }; 23 | 7C17841C207D70A30043780D /* WikipediaSPATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178416207D70A30043780D /* WikipediaSPATests.swift */; }; 24 | 7C17841D207D70A30043780D /* NRELSunriseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178418207D70A30043780D /* NRELSunriseTests.swift */; }; 25 | 7C17841E207D70A30043780D /* NRELSPATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C178419207D70A30043780D /* NRELSPATests.swift */; }; 26 | 7C17841F207D70A30043780D /* NRELJulianTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C17841A207D70A30043780D /* NRELJulianTests.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 7C1783E9207D70400043780D /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 7C1783D5207D703F0043780D /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 7C1783DD207D70400043780D; 35 | remoteInfo = SunKit; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 7C1783DE207D70400043780D /* SunshineKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SunshineKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 7C1783E2207D70400043780D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 7C1783E7207D70400043780D /* SunshineKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SunshineKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 7C1783EE207D70400043780D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 7C1783FA207D70780043780D /* Date+SunKitExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+SunKitExtension.swift"; sourceTree = ""; }; 45 | 7C1783FB207D70780043780D /* FractionOfDay+SunKitExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FractionOfDay+SunKitExtension.swift"; sourceTree = ""; }; 46 | 7C1783FE207D70780043780D /* WikipediaSPA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSPA.swift; sourceTree = ""; }; 47 | 7C1783FF207D70780043780D /* RadAndAngle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadAndAngle.swift; sourceTree = ""; }; 48 | 7C178401207D70780043780D /* NRELSPA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NRELSPA.swift; sourceTree = ""; }; 49 | 7C178402207D70780043780D /* NRELJulian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NRELJulian.swift; sourceTree = ""; }; 50 | 7C178404207D70780043780D /* SunPositionFragment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SunPositionFragment.swift; sourceTree = ""; }; 51 | 7C178405207D70780043780D /* SunRiseSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SunRiseSet.swift; sourceTree = ""; }; 52 | 7C178406207D70780043780D /* SunKitTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SunKitTypes.swift; sourceTree = ""; }; 53 | 7C178407207D70780043780D /* SunPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SunPosition.swift; sourceTree = ""; }; 54 | 7C178408207D70780043780D /* SunRiseSetFragment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SunRiseSetFragment.swift; sourceTree = ""; }; 55 | 7C178415207D70A30043780D /* RadAndAngleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadAndAngleTests.swift; sourceTree = ""; }; 56 | 7C178416207D70A30043780D /* WikipediaSPATests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSPATests.swift; sourceTree = ""; }; 57 | 7C178418207D70A30043780D /* NRELSunriseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NRELSunriseTests.swift; sourceTree = ""; }; 58 | 7C178419207D70A30043780D /* NRELSPATests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NRELSPATests.swift; sourceTree = ""; }; 59 | 7C17841A207D70A30043780D /* NRELJulianTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NRELJulianTests.swift; sourceTree = ""; }; 60 | 7C659BE52090D7A20029EE2D /* SunshineKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SunshineKit.podspec; sourceTree = SOURCE_ROOT; }; 61 | 7CACE4B22090DFF9006EC8F7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 7C1783DA207D70400043780D /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 7C1783E4207D70400043780D /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | 7C1783E8207D70400043780D /* SunshineKit.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 7C1783D4207D703F0043780D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 7C659BE52090D7A20029EE2D /* SunshineKit.podspec */, 87 | 7CACE4B22090DFF9006EC8F7 /* README.md */, 88 | 7C1783E0207D70400043780D /* SunshineKit */, 89 | 7C1783EB207D70400043780D /* SunshineKitTests */, 90 | 7C1783DF207D70400043780D /* Products */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 7C1783DF207D70400043780D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 7C1783DE207D70400043780D /* SunshineKit.framework */, 98 | 7C1783E7207D70400043780D /* SunshineKitTests.xctest */, 99 | ); 100 | name = Products; 101 | sourceTree = ""; 102 | }; 103 | 7C1783E0207D70400043780D /* SunshineKit */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 7C659BE4208FDDB10029EE2D /* Other */, 107 | 7C1783F8207D70780043780D /* Source */, 108 | ); 109 | path = SunshineKit; 110 | sourceTree = ""; 111 | }; 112 | 7C1783EB207D70400043780D /* SunshineKitTests */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 7C1783EE207D70400043780D /* Info.plist */, 116 | 7C178414207D70A30043780D /* Source */, 117 | ); 118 | path = SunshineKitTests; 119 | sourceTree = ""; 120 | }; 121 | 7C1783F8207D70780043780D /* Source */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 7C1783F9207D70780043780D /* Extension */, 125 | 7C1783FC207D70780043780D /* SPA */, 126 | 7C178403207D70780043780D /* Model */, 127 | ); 128 | path = Source; 129 | sourceTree = ""; 130 | }; 131 | 7C1783F9207D70780043780D /* Extension */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 7C1783FA207D70780043780D /* Date+SunKitExtension.swift */, 135 | 7C1783FB207D70780043780D /* FractionOfDay+SunKitExtension.swift */, 136 | ); 137 | path = Extension; 138 | sourceTree = ""; 139 | }; 140 | 7C1783FC207D70780043780D /* SPA */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 7C1783FD207D70780043780D /* Wikipedia */, 144 | 7C1783FF207D70780043780D /* RadAndAngle.swift */, 145 | 7C178400207D70780043780D /* NREL */, 146 | ); 147 | path = SPA; 148 | sourceTree = ""; 149 | }; 150 | 7C1783FD207D70780043780D /* Wikipedia */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 7C1783FE207D70780043780D /* WikipediaSPA.swift */, 154 | ); 155 | path = Wikipedia; 156 | sourceTree = ""; 157 | }; 158 | 7C178400207D70780043780D /* NREL */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 7C178401207D70780043780D /* NRELSPA.swift */, 162 | 7C178402207D70780043780D /* NRELJulian.swift */, 163 | ); 164 | path = NREL; 165 | sourceTree = ""; 166 | }; 167 | 7C178403207D70780043780D /* Model */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 7C178404207D70780043780D /* SunPositionFragment.swift */, 171 | 7C178405207D70780043780D /* SunRiseSet.swift */, 172 | 7C178406207D70780043780D /* SunKitTypes.swift */, 173 | 7C178407207D70780043780D /* SunPosition.swift */, 174 | 7C178408207D70780043780D /* SunRiseSetFragment.swift */, 175 | ); 176 | path = Model; 177 | sourceTree = ""; 178 | }; 179 | 7C178414207D70A30043780D /* Source */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 7C178415207D70A30043780D /* RadAndAngleTests.swift */, 183 | 7C178416207D70A30043780D /* WikipediaSPATests.swift */, 184 | 7C178417207D70A30043780D /* NREL */, 185 | ); 186 | path = Source; 187 | sourceTree = ""; 188 | }; 189 | 7C178417207D70A30043780D /* NREL */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 7C178418207D70A30043780D /* NRELSunriseTests.swift */, 193 | 7C178419207D70A30043780D /* NRELSPATests.swift */, 194 | 7C17841A207D70A30043780D /* NRELJulianTests.swift */, 195 | ); 196 | path = NREL; 197 | sourceTree = ""; 198 | }; 199 | 7C659BE4208FDDB10029EE2D /* Other */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 7C1783E2207D70400043780D /* Info.plist */, 203 | ); 204 | path = Other; 205 | sourceTree = ""; 206 | }; 207 | /* End PBXGroup section */ 208 | 209 | /* Begin PBXHeadersBuildPhase section */ 210 | 7C1783DB207D70400043780D /* Headers */ = { 211 | isa = PBXHeadersBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXHeadersBuildPhase section */ 218 | 219 | /* Begin PBXNativeTarget section */ 220 | 7C1783DD207D70400043780D /* SunshineKit */ = { 221 | isa = PBXNativeTarget; 222 | buildConfigurationList = 7C1783F2207D70400043780D /* Build configuration list for PBXNativeTarget "SunshineKit" */; 223 | buildPhases = ( 224 | 7C1783D9207D70400043780D /* Sources */, 225 | 7C1783DA207D70400043780D /* Frameworks */, 226 | 7C1783DB207D70400043780D /* Headers */, 227 | 7C1783DC207D70400043780D /* Resources */, 228 | ); 229 | buildRules = ( 230 | ); 231 | dependencies = ( 232 | ); 233 | name = SunshineKit; 234 | productName = SunKit; 235 | productReference = 7C1783DE207D70400043780D /* SunshineKit.framework */; 236 | productType = "com.apple.product-type.framework"; 237 | }; 238 | 7C1783E6207D70400043780D /* SunshineKitTests */ = { 239 | isa = PBXNativeTarget; 240 | buildConfigurationList = 7C1783F5207D70400043780D /* Build configuration list for PBXNativeTarget "SunshineKitTests" */; 241 | buildPhases = ( 242 | 7C1783E3207D70400043780D /* Sources */, 243 | 7C1783E4207D70400043780D /* Frameworks */, 244 | 7C1783E5207D70400043780D /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | 7C1783EA207D70400043780D /* PBXTargetDependency */, 250 | ); 251 | name = SunshineKitTests; 252 | productName = SunKitTests; 253 | productReference = 7C1783E7207D70400043780D /* SunshineKitTests.xctest */; 254 | productType = "com.apple.product-type.bundle.unit-test"; 255 | }; 256 | /* End PBXNativeTarget section */ 257 | 258 | /* Begin PBXProject section */ 259 | 7C1783D5207D703F0043780D /* Project object */ = { 260 | isa = PBXProject; 261 | attributes = { 262 | LastSwiftUpdateCheck = 0930; 263 | LastUpgradeCheck = 0930; 264 | ORGANIZATIONNAME = "Oleg Mueller"; 265 | TargetAttributes = { 266 | 7C1783DD207D70400043780D = { 267 | CreatedOnToolsVersion = 9.3; 268 | LastSwiftMigration = 1000; 269 | }; 270 | 7C1783E6207D70400043780D = { 271 | CreatedOnToolsVersion = 9.3; 272 | LastSwiftMigration = 1000; 273 | }; 274 | }; 275 | }; 276 | buildConfigurationList = 7C1783D8207D703F0043780D /* Build configuration list for PBXProject "SunshineKit" */; 277 | compatibilityVersion = "Xcode 9.3"; 278 | developmentRegion = en; 279 | hasScannedForEncodings = 0; 280 | knownRegions = ( 281 | en, 282 | ); 283 | mainGroup = 7C1783D4207D703F0043780D; 284 | productRefGroup = 7C1783DF207D70400043780D /* Products */; 285 | projectDirPath = ""; 286 | projectRoot = ""; 287 | targets = ( 288 | 7C1783DD207D70400043780D /* SunshineKit */, 289 | 7C1783E6207D70400043780D /* SunshineKitTests */, 290 | ); 291 | }; 292 | /* End PBXProject section */ 293 | 294 | /* Begin PBXResourcesBuildPhase section */ 295 | 7C1783DC207D70400043780D /* Resources */ = { 296 | isa = PBXResourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | 7C1783E5207D70400043780D /* Resources */ = { 303 | isa = PBXResourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXResourcesBuildPhase section */ 310 | 311 | /* Begin PBXSourcesBuildPhase section */ 312 | 7C1783D9207D70400043780D /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | 7C178410207D70780043780D /* SunRiseSet.swift in Sources */, 317 | 7C17840F207D70780043780D /* SunPositionFragment.swift in Sources */, 318 | 7C178409207D70780043780D /* Date+SunKitExtension.swift in Sources */, 319 | 7C17840C207D70780043780D /* RadAndAngle.swift in Sources */, 320 | 7C178411207D70780043780D /* SunKitTypes.swift in Sources */, 321 | 7C17840B207D70780043780D /* WikipediaSPA.swift in Sources */, 322 | 7C17840A207D70780043780D /* FractionOfDay+SunKitExtension.swift in Sources */, 323 | 7C178412207D70780043780D /* SunPosition.swift in Sources */, 324 | 7C178413207D70780043780D /* SunRiseSetFragment.swift in Sources */, 325 | 7C17840D207D70780043780D /* NRELSPA.swift in Sources */, 326 | 7C17840E207D70780043780D /* NRELJulian.swift in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | 7C1783E3207D70400043780D /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 7C17841C207D70A30043780D /* WikipediaSPATests.swift in Sources */, 335 | 7C17841E207D70A30043780D /* NRELSPATests.swift in Sources */, 336 | 7C17841D207D70A30043780D /* NRELSunriseTests.swift in Sources */, 337 | 7C17841F207D70A30043780D /* NRELJulianTests.swift in Sources */, 338 | 7C17841B207D70A30043780D /* RadAndAngleTests.swift in Sources */, 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | /* End PBXSourcesBuildPhase section */ 343 | 344 | /* Begin PBXTargetDependency section */ 345 | 7C1783EA207D70400043780D /* PBXTargetDependency */ = { 346 | isa = PBXTargetDependency; 347 | target = 7C1783DD207D70400043780D /* SunshineKit */; 348 | targetProxy = 7C1783E9207D70400043780D /* PBXContainerItemProxy */; 349 | }; 350 | /* End PBXTargetDependency section */ 351 | 352 | /* Begin XCBuildConfiguration section */ 353 | 7C1783F0207D70400043780D /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_SEARCH_USER_PATHS = NO; 357 | CLANG_ANALYZER_NONNULL = YES; 358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_ENABLE_OBJC_WEAK = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 377 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 380 | CLANG_WARN_STRICT_PROTOTYPES = YES; 381 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 382 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | CODE_SIGN_IDENTITY = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | CURRENT_PROJECT_VERSION = 1; 388 | DEBUG_INFORMATION_FORMAT = dwarf; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | ENABLE_TESTABILITY = YES; 391 | GCC_C_LANGUAGE_STANDARD = gnu11; 392 | GCC_DYNAMIC_NO_PIC = NO; 393 | GCC_NO_COMMON_BLOCKS = YES; 394 | GCC_OPTIMIZATION_LEVEL = 0; 395 | GCC_PREPROCESSOR_DEFINITIONS = ( 396 | "DEBUG=1", 397 | "$(inherited)", 398 | ); 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 406 | MTL_ENABLE_DEBUG_INFO = YES; 407 | ONLY_ACTIVE_ARCH = YES; 408 | SDKROOT = iphoneos; 409 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 410 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 411 | VERSIONING_SYSTEM = "apple-generic"; 412 | VERSION_INFO_PREFIX = ""; 413 | }; 414 | name = Debug; 415 | }; 416 | 7C1783F1207D70400043780D /* Release */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ALWAYS_SEARCH_USER_PATHS = NO; 420 | CLANG_ANALYZER_NONNULL = YES; 421 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 422 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 423 | CLANG_CXX_LIBRARY = "libc++"; 424 | CLANG_ENABLE_MODULES = YES; 425 | CLANG_ENABLE_OBJC_ARC = YES; 426 | CLANG_ENABLE_OBJC_WEAK = YES; 427 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 428 | CLANG_WARN_BOOL_CONVERSION = YES; 429 | CLANG_WARN_COMMA = YES; 430 | CLANG_WARN_CONSTANT_CONVERSION = YES; 431 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 432 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 433 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 434 | CLANG_WARN_EMPTY_BODY = YES; 435 | CLANG_WARN_ENUM_CONVERSION = YES; 436 | CLANG_WARN_INFINITE_RECURSION = YES; 437 | CLANG_WARN_INT_CONVERSION = YES; 438 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 440 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 441 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 442 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 443 | CLANG_WARN_STRICT_PROTOTYPES = YES; 444 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 445 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 446 | CLANG_WARN_UNREACHABLE_CODE = YES; 447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 448 | CODE_SIGN_IDENTITY = "iPhone Developer"; 449 | COPY_PHASE_STRIP = NO; 450 | CURRENT_PROJECT_VERSION = 1; 451 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 452 | ENABLE_NS_ASSERTIONS = NO; 453 | ENABLE_STRICT_OBJC_MSGSEND = YES; 454 | GCC_C_LANGUAGE_STANDARD = gnu11; 455 | GCC_NO_COMMON_BLOCKS = YES; 456 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 457 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 458 | GCC_WARN_UNDECLARED_SELECTOR = YES; 459 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 460 | GCC_WARN_UNUSED_FUNCTION = YES; 461 | GCC_WARN_UNUSED_VARIABLE = YES; 462 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 463 | MTL_ENABLE_DEBUG_INFO = NO; 464 | SDKROOT = iphoneos; 465 | SWIFT_COMPILATION_MODE = wholemodule; 466 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 467 | VALIDATE_PRODUCT = YES; 468 | VERSIONING_SYSTEM = "apple-generic"; 469 | VERSION_INFO_PREFIX = ""; 470 | }; 471 | name = Release; 472 | }; 473 | 7C1783F3207D70400043780D /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | CODE_SIGN_IDENTITY = ""; 477 | CODE_SIGN_STYLE = Automatic; 478 | DEFINES_MODULE = YES; 479 | DYLIB_COMPATIBILITY_VERSION = 1; 480 | DYLIB_CURRENT_VERSION = 1; 481 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 482 | INFOPLIST_FILE = "$(SRCROOT)/SunshineKit/Other/Info.plist"; 483 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 484 | LD_RUNPATH_SEARCH_PATHS = ( 485 | "$(inherited)", 486 | "@executable_path/Frameworks", 487 | "@loader_path/Frameworks", 488 | ); 489 | PRODUCT_BUNDLE_IDENTIFIER = com.olegmueller.SunshineKit; 490 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 491 | SKIP_INSTALL = YES; 492 | SWIFT_VERSION = 4.2; 493 | TARGETED_DEVICE_FAMILY = "1,2"; 494 | }; 495 | name = Debug; 496 | }; 497 | 7C1783F4207D70400043780D /* Release */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | CODE_SIGN_IDENTITY = ""; 501 | CODE_SIGN_STYLE = Automatic; 502 | DEFINES_MODULE = YES; 503 | DYLIB_COMPATIBILITY_VERSION = 1; 504 | DYLIB_CURRENT_VERSION = 1; 505 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 506 | INFOPLIST_FILE = "$(SRCROOT)/SunshineKit/Other/Info.plist"; 507 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 508 | LD_RUNPATH_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "@executable_path/Frameworks", 511 | "@loader_path/Frameworks", 512 | ); 513 | PRODUCT_BUNDLE_IDENTIFIER = com.olegmueller.SunshineKit; 514 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 515 | SKIP_INSTALL = YES; 516 | SWIFT_VERSION = 4.2; 517 | TARGETED_DEVICE_FAMILY = "1,2"; 518 | }; 519 | name = Release; 520 | }; 521 | 7C1783F6207D70400043780D /* Debug */ = { 522 | isa = XCBuildConfiguration; 523 | buildSettings = { 524 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 525 | CODE_SIGN_STYLE = Automatic; 526 | INFOPLIST_FILE = SunshineKitTests/Info.plist; 527 | LD_RUNPATH_SEARCH_PATHS = ( 528 | "$(inherited)", 529 | "@executable_path/Frameworks", 530 | "@loader_path/Frameworks", 531 | ); 532 | PRODUCT_BUNDLE_IDENTIFIER = com.olegmueller.SunshineKitTests; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SWIFT_VERSION = 4.2; 535 | TARGETED_DEVICE_FAMILY = "1,2"; 536 | }; 537 | name = Debug; 538 | }; 539 | 7C1783F7207D70400043780D /* Release */ = { 540 | isa = XCBuildConfiguration; 541 | buildSettings = { 542 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 543 | CODE_SIGN_STYLE = Automatic; 544 | INFOPLIST_FILE = SunshineKitTests/Info.plist; 545 | LD_RUNPATH_SEARCH_PATHS = ( 546 | "$(inherited)", 547 | "@executable_path/Frameworks", 548 | "@loader_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = com.olegmueller.SunshineKitTests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_VERSION = 4.2; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | }; 555 | name = Release; 556 | }; 557 | /* End XCBuildConfiguration section */ 558 | 559 | /* Begin XCConfigurationList section */ 560 | 7C1783D8207D703F0043780D /* Build configuration list for PBXProject "SunshineKit" */ = { 561 | isa = XCConfigurationList; 562 | buildConfigurations = ( 563 | 7C1783F0207D70400043780D /* Debug */, 564 | 7C1783F1207D70400043780D /* Release */, 565 | ); 566 | defaultConfigurationIsVisible = 0; 567 | defaultConfigurationName = Release; 568 | }; 569 | 7C1783F2207D70400043780D /* Build configuration list for PBXNativeTarget "SunshineKit" */ = { 570 | isa = XCConfigurationList; 571 | buildConfigurations = ( 572 | 7C1783F3207D70400043780D /* Debug */, 573 | 7C1783F4207D70400043780D /* Release */, 574 | ); 575 | defaultConfigurationIsVisible = 0; 576 | defaultConfigurationName = Release; 577 | }; 578 | 7C1783F5207D70400043780D /* Build configuration list for PBXNativeTarget "SunshineKitTests" */ = { 579 | isa = XCConfigurationList; 580 | buildConfigurations = ( 581 | 7C1783F6207D70400043780D /* Debug */, 582 | 7C1783F7207D70400043780D /* Release */, 583 | ); 584 | defaultConfigurationIsVisible = 0; 585 | defaultConfigurationName = Release; 586 | }; 587 | /* End XCConfigurationList section */ 588 | }; 589 | rootObject = 7C1783D5207D703F0043780D /* Project object */; 590 | } 591 | -------------------------------------------------------------------------------- /SunshineKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SunshineKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SunshineKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SunshineKit.xcodeproj/xcshareddata/xcschemes/SunshineKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /SunshineKit/Other/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SunshineKit/Source/Extension/Date+SunKitExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+SunshineKitExtension.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 04.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Date { 13 | public var yesterday: Date { 14 | let components = Set([.year, .month, .day, .hour, .minute, .second]) 15 | var dateComponents = Calendar.current.dateComponents(components, from: self) 16 | dateComponents.day = (dateComponents.day ?? 1) - 1 17 | 18 | guard let yesterday = Calendar.current.date(from: dateComponents) else { return self } 19 | 20 | return yesterday 21 | } 22 | 23 | 24 | /** 25 | Creates date objects for the given JulianDayResolution. 26 | 27 | - parameter resolution: The desired time resolution 28 | - parameter hour: Set if you need date objects only for this hour 29 | - returns: Date objects for the whole day with the given resolution. If hour is set, only date objects for this hour are returned. 30 | */ 31 | func allDatesForDateWith(resolution: JulianDayResolution, for hour: Int? = nil) -> [Date] { 32 | 33 | 34 | func appendDateFromComponents(_ dateComponents: DateComponents, toDates dates: inout [Date]) { 35 | if let date = Calendar.current.date(from: dateComponents) { 36 | dates.append(date) 37 | } 38 | } 39 | 40 | 41 | let components = Set([.year, .month, .day]) 42 | var dateComponents = Calendar.current.dateComponents(components, from: self) 43 | 44 | var dates = [Date]() 45 | 46 | if let hour = hour { 47 | switch resolution { 48 | case .second: 49 | for hour in hour - 1.. Int { 19 | let hours = Int(self) 20 | return hours 21 | } 22 | 23 | 24 | func minutes() -> Int { 25 | let minutes = doubleMinutes() 26 | return Int(minutes) 27 | } 28 | 29 | 30 | func seconds() -> Int { 31 | let seconds = 60*(doubleMinutes() - Double(Int(doubleMinutes()))) 32 | return Int(seconds) 33 | } 34 | 35 | 36 | func dateByAdding(_ date: Date) -> Date { 37 | let hours = self.hours() 38 | let minutes = self.minutes() 39 | let seconds = self.seconds() 40 | 41 | var dateComponents = Calendar.current.dateComponents([.era, .year, .month, .day], from: date) 42 | dateComponents.hour = hours 43 | dateComponents.minute = minutes 44 | dateComponents.second = seconds 45 | 46 | let date = Calendar.current.date(from: dateComponents) ?? date 47 | 48 | return date 49 | } 50 | 51 | 52 | // MARK: - private 53 | 54 | 55 | private func doubleMinutes() -> Double { 56 | let minutes = (self - Double(Int(self)))*60 57 | return minutes 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SunshineKit/Source/Model/SunKitTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SunshineKitTypes.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 04.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public typealias Rad = Double 13 | public typealias Angle = Double 14 | 15 | /// Represents a fraction of a day in hours, e.g. 23 is 23 h / 11 pm 16 | public typealias FractionOfDay = Double 17 | -------------------------------------------------------------------------------- /SunshineKit/Source/Model/SunPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SunPosition.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 14.05.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public struct SunPosition: Equatable { 13 | public struct Shadow: Equatable { 14 | public let length: Double? 15 | public let direction: Double? 16 | } 17 | 18 | 19 | public let ascension: Angle? 20 | public let zenith: Angle? 21 | public let incidence: Angle? 22 | public let azimuth: Angle? 23 | public let height: Angle? 24 | public let shadow: Shadow? 25 | public let date: Date 26 | 27 | 28 | public static var empty: SunPosition { 29 | return SunPosition(date: Date()) 30 | } 31 | 32 | 33 | public init(date: Date, ascension: Angle? = nil, azimuth: Angle? = nil, height: Angle? = nil, zenith: Angle? = nil, incidence: Angle? = nil, shadowDirection: Angle? = nil, shadowLength: Double? = nil) { 34 | self.ascension = ascension 35 | self.azimuth = azimuth 36 | self.height = height 37 | self.date = date 38 | self.zenith = zenith 39 | self.incidence = incidence 40 | 41 | if shadowDirection != nil || shadowLength != nil { 42 | self.shadow = Shadow(length: shadowLength, direction: shadowDirection) 43 | } else { 44 | self.shadow = nil 45 | } 46 | } 47 | } 48 | 49 | 50 | public func ==(lhs: SunPosition, rhs: SunPosition) -> Bool { 51 | return lhs.ascension == rhs.ascension 52 | && lhs.azimuth == rhs.azimuth 53 | && lhs.height == rhs.height 54 | && lhs.date.timeIntervalSince1970 == rhs.date.timeIntervalSince1970 55 | && lhs.zenith == rhs.zenith 56 | && lhs.incidence == rhs.incidence 57 | && lhs.shadow == rhs.shadow 58 | } 59 | 60 | 61 | public func ==(lhs: SunPosition.Shadow, rhs: SunPosition.Shadow) -> Bool { 62 | return lhs.direction == rhs.direction 63 | && lhs.length == rhs.length 64 | } 65 | -------------------------------------------------------------------------------- /SunshineKit/Source/Model/SunPositionFragment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SunPositionFragment.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 11.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum SunPositionFragment { 13 | 14 | 15 | public enum Shadow { 16 | case length 17 | case direction 18 | } 19 | 20 | 21 | case ascension 22 | case azimuth 23 | case height 24 | case incidence 25 | case zenith 26 | case shadow([Shadow]) 27 | } 28 | 29 | 30 | let FullSunPositionFragments: [SunPositionFragment] = [.ascension, .azimuth, .height, .incidence, .zenith, .shadow([ 31 | SunPositionFragment.Shadow.direction, SunPositionFragment.Shadow.length 32 | ])] 33 | -------------------------------------------------------------------------------- /SunshineKit/Source/Model/SunRiseSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SunRiseSet.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 07.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public struct SunRiseSet { 13 | public struct DateHeight { 14 | public let date: Date? 15 | public let height: Angle? 16 | } 17 | 18 | 19 | public let sunrise: DateHeight? 20 | public let sunset: DateHeight? 21 | public let transit: DateHeight? 22 | 23 | 24 | public static var empty: SunRiseSet { 25 | return SunRiseSet(sunriseDate: nil, sunriseHeight: nil, sunsetDate: nil, sunsetHeight: nil, transitDate: nil, transitHeight: nil) 26 | } 27 | 28 | 29 | public func isDaylight(at sunPosition: SunPosition, with resolution: JulianDayResolution = .minute) -> Bool { 30 | let resolutionValue: Double 31 | 32 | switch resolution { 33 | case .hour: 34 | resolutionValue = 3600 35 | case .minute: 36 | resolutionValue = 60 37 | case .second: 38 | resolutionValue = 0 39 | } 40 | 41 | if sunrise?.date?.timeIntervalSince(sunPosition.date) ?? 0 <= resolutionValue && sunset?.date?.timeIntervalSince(sunPosition.date) ?? 0 > 0 { 42 | return true 43 | } else { 44 | return false 45 | } 46 | } 47 | 48 | 49 | // MARK: - internal 50 | 51 | 52 | init(sunriseDate: Date?, sunriseHeight: Angle?, sunsetDate: Date?, sunsetHeight: Angle?, transitDate: Date?, transitHeight: Angle?) { 53 | self.sunrise = DateHeight(date: sunriseDate, height: sunriseHeight) 54 | self.sunset = DateHeight(date: sunsetDate, height: sunsetHeight) 55 | self.transit = DateHeight(date: transitDate, height: transitHeight) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SunshineKit/Source/Model/SunRiseSetFragment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SunRiseSet+Fragments.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 11.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum SunRiseSetFragment { 13 | public enum DateHeightFragment { 14 | case date 15 | case height 16 | } 17 | 18 | 19 | case sunrise([DateHeightFragment]) 20 | case sunset([DateHeightFragment]) 21 | case transit([DateHeightFragment]) 22 | } 23 | 24 | 25 | let FullSunRiseSetFragments: [SunRiseSetFragment] = [.transit([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height]), 26 | .sunrise([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height]), 27 | .sunset([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height])] 28 | -------------------------------------------------------------------------------- /SunshineKit/Source/SPA/NREL/NRELJulian.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Julian.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Accelerate 11 | 12 | 13 | // MARK: - JulianDay 14 | 15 | 16 | public enum JulianDayResolution { 17 | case hour 18 | case minute 19 | case second 20 | } 21 | 22 | 23 | // MARK: - internal 24 | 25 | 26 | typealias JulianDay = Double 27 | 28 | 29 | let ΔT = 67.0 30 | 31 | 32 | /** 33 | Calculates Julian Days for the whole day or hour with the desired resolution (one Julian Day per hour or per minute or per second). 34 | 35 | - parameter date: The point in time (day) to calculate the Julian Day for 36 | - parameter forHour: An optional hour, if set Julian Days are only calculated for this hour 37 | - parameter timeZoneOffset: Offset from UTC in hours 38 | - parameter withResolution: The desired resolution 39 | 40 | - returns: Julian Days for the selected date with the given resolution 41 | */ 42 | func julianDays(for date: Date, forHour: Int? = nil, timeZoneOffset: Int, withResolution resolution: JulianDayResolution) -> [JulianDay] { 43 | 44 | 45 | func addHourPart(to day_decimals: inout [JulianDay], day: Int, hour: Int = 0) { 46 | let length = vDSP_Length(day_decimals.count) 47 | var temp = day_decimals 48 | 49 | // add timezone offset 50 | var negativeTimeZoneOffset = Double(hour) - Double(timeZoneOffset) 51 | vDSP_vsaddD(&temp, 1, &negativeTimeZoneOffset, &day_decimals, 1, length) 52 | 53 | // divide through 24 54 | var twentyFour = 24.0 55 | vDSP_vsdivD(&day_decimals, 1, &twentyFour, &temp, 1, length) 56 | 57 | // add hourPart to day 58 | var dayDouble = Double(day) 59 | vDSP_vsaddD(&temp, 1, &dayDouble, &day_decimals, 1, length) 60 | } 61 | 62 | 63 | func divideThroughSixty(_ day_decimals: inout [JulianDay]) { 64 | let length = vDSP_Length(day_decimals.count) 65 | var sixty = 60.0 66 | var temp = day_decimals 67 | vDSP_vsdivD(&temp, 1, &sixty, &day_decimals, 1, length) 68 | } 69 | 70 | let components = Set([.year, .month, .day]) 71 | let dateComponents = Calendar.current.dateComponents(components, from: date) 72 | var year = dateComponents.year ?? 2016 73 | var month = dateComponents.month ?? 1 74 | let day = dateComponents.day ?? 1 75 | 76 | if month < 3 { 77 | month += 12 78 | year -= 1 79 | } 80 | 81 | let left = Int(365.25*Double(year + 4716)) 82 | let right = Int(30.6001*Double(month + 1)) 83 | let leftAndRight = Double(left + right) 84 | 85 | let secondsPerMinute = 60 86 | let minutesPerHour = 60 87 | 88 | let fromHour = forHour == nil ? 0 : (forHour! - 1 < 0 ? 0 : forHour! - 1 ) 89 | let toHour = forHour == nil ? 24 : forHour! + 1 90 | 91 | let hoursPerDay = forHour == nil ? 24 : toHour - fromHour 92 | 93 | let count: Int 94 | switch resolution { 95 | case .second: 96 | count = hoursPerDay*minutesPerHour*secondsPerMinute 97 | case .minute: 98 | count = hoursPerDay*minutesPerHour 99 | case .hour: 100 | count = hoursPerDay 101 | } 102 | 103 | var julianDates = Array(repeating: leftAndRight, count: count) 104 | 105 | var A = 0.0 106 | var B = 1.0 107 | var subArrayCount: vDSP_Length = 0 108 | 109 | switch resolution { 110 | case .second: 111 | subArrayCount = vDSP_Length(secondsPerMinute) 112 | 113 | var index = 0 114 | for hour in fromHour..(repeating: 0, count: Int(subArrayCount)) 117 | 118 | // create second vector: 0...59 119 | vDSP_vrampD(&A, &B, &day_decimals, 1, subArrayCount) 120 | 121 | divideThroughSixty(&day_decimals) 122 | 123 | // create minute vector 124 | let minutesArray = Array(repeating: Double(minute), count: Int(subArrayCount)) 125 | // add minutes to second part 126 | vDSP_vaddD(minutesArray, 1, day_decimals, 1, &day_decimals, 1, subArrayCount) 127 | 128 | divideThroughSixty(&day_decimals) 129 | 130 | addHourPart(to: &day_decimals, day: day, hour: hour) 131 | 132 | // add day_decimal 133 | let value1 = Int(index*minutesPerHour*secondsPerMinute + minute*secondsPerMinute) 134 | vDSP_vaddD(&julianDates + value1, 1, &day_decimals, 1, &julianDates[index*minutesPerHour*secondsPerMinute + minute*secondsPerMinute], 1, subArrayCount) 135 | } 136 | 137 | index += 1 138 | } 139 | case .minute: 140 | subArrayCount = vDSP_Length(minutesPerHour) 141 | 142 | var index = 0 143 | for hour in fromHour..(repeating: 0, count: Int(subArrayCount)) 145 | 146 | // create minute vector: 0...59 147 | vDSP_vrampD(&A, &B, &day_decimals, 1, subArrayCount) 148 | 149 | divideThroughSixty(&day_decimals) 150 | 151 | addHourPart(to: &day_decimals, day: day, hour: hour) 152 | 153 | // add day_decimal 154 | vDSP_vaddD(&julianDates + Int(index*minutesPerHour), 1, &day_decimals, 1, &julianDates[index*minutesPerHour], 1, subArrayCount) 155 | 156 | index += 1 157 | } 158 | case .hour: 159 | subArrayCount = vDSP_Length(hoursPerDay) 160 | var day_decimals = Array(repeating: 0, count: Int(subArrayCount)) 161 | 162 | // create hour vector: 0...23 163 | var index = 0 164 | for hour in fromHour.. 2299160.0 { 184 | let a = Int(year/100) 185 | var addValue = Double(2 - a + Int(a/4)) 186 | vDSP_vsaddD(&julianDates, 1, &addValue, &temp, 1, length) 187 | 188 | return temp 189 | } else { 190 | return julianDates 191 | } 192 | } 193 | 194 | 195 | /** 196 | Calculates Julian Day for the given date 197 | 198 | - parameter date: The point in time (day) to calculate the Julian Day for 199 | - parameter timeZoneOffset: Offset from UTC in hours 200 | 201 | - returns: Julian Day for the selected date 202 | */ 203 | func julianDay(for date: Date, timeZoneOffset: Int) -> JulianDay { 204 | let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) 205 | 206 | var year = dateComponents.year ?? 2016 207 | var month = dateComponents.month ?? 1 208 | let hour = dateComponents.hour ?? 0 209 | let minute = dateComponents.minute ?? 0 210 | let second = dateComponents.second ?? 0 211 | let day = dateComponents.day ?? 0 212 | 213 | let secondPart = (Double(second))/60.0 214 | let minutePart = (Double(minute) + secondPart)/60.0 215 | let hourPart = (Double(hour - timeZoneOffset) + minutePart)/24.0 216 | let day_decimal = Double(day) + hourPart 217 | 218 | if month < 3 { 219 | month += 12 220 | year -= 1 221 | } 222 | 223 | let left = Int(365.25*Double(year + 4716)) 224 | let right = Int(30.6001*Double(month + 1)) 225 | var julian_day = Double(left + right) + day_decimal - 1524.5 226 | 227 | if julian_day > 2299160.0 { 228 | let a = Int(year/100) 229 | julian_day += Double(2 - a + Int(a/4)) 230 | } 231 | 232 | return julian_day 233 | } 234 | 235 | 236 | // MARK: - JulianCentury 237 | 238 | 239 | typealias JulianCentury = Double 240 | 241 | 242 | /** 243 | Calculates Julian Century for the given Julian Day 244 | 245 | - parameter day: The Julian Day to calculate the Julian Century for 246 | 247 | - returns: Julian Century for the selected Julian Day 248 | */ 249 | func julianCentury(for day: JulianDay) -> JulianCentury { 250 | let century = (day - 2451545.0)/36525.0 251 | return century 252 | } 253 | 254 | 255 | /** 256 | Calculates Julian Centuries for the given Julian Days 257 | 258 | - parameter days: The Julian Days to calculate the Julian Centuries for 259 | 260 | - returns: Julian Centuries for the given Julian Days 261 | */ 262 | func julianCenturies(for days: [JulianDay]) -> [JulianCentury] { 263 | // create vector of -2451545.0 values 264 | var centuries = Array(repeating: -2451545.0, count: days.count) 265 | var temp = centuries 266 | 267 | // add days vector to centuries vector 268 | let length = vDSP_Length(days.count) 269 | vDSP_vaddD(days, 1, &temp, 1, ¢uries, 1, length) 270 | 271 | // divide with 36525.0 272 | var divider = 36525.0 273 | vDSP_vsdivD(¢uries, 1, ÷r, &temp, 1, length) 274 | 275 | return temp 276 | } 277 | 278 | 279 | // MARK: - JulianEphemeris Day, Century and Millenium 280 | 281 | 282 | typealias JulianEphemerisDay = Double 283 | 284 | 285 | /** 286 | Calculates Julian Ephemeris Day for the given Julian Day 287 | 288 | - parameter day: The Julian Day to calculate the Julian Ephemeris for 289 | 290 | - returns: Julian Ephemeris for the selected Julian Day 291 | */ 292 | func julianEphemerisDay(for day: JulianDay) -> JulianEphemerisDay { 293 | let ephemerisDay = day + ΔT/86400.0 294 | return ephemerisDay 295 | } 296 | 297 | 298 | /** 299 | Calculates Julian Ephemerises Day for the given Julian Days 300 | 301 | - parameter days: The Julian Days to calculate the Julian Ephemeris for 302 | 303 | - returns: Julian Ephemerises for the selected Julian Days 304 | */ 305 | func julianEphemerisDays(for days: [JulianDay]) -> [JulianEphemerisDay] { 306 | // create vector of ΔT/86400.0 values 307 | var ephemerisDays = Array(repeating: ΔT/86400.0, count: days.count) 308 | var temp = ephemerisDays 309 | 310 | // add days vector to ephemerisDays vector 311 | let length = vDSP_Length(days.count) 312 | vDSP_vaddD(days, 1, &temp, 1, &ephemerisDays, 1, length) 313 | 314 | return ephemerisDays 315 | } 316 | 317 | 318 | typealias JulianEphemerisMillenium = Double 319 | 320 | 321 | /** 322 | Calculates Julian Ephemeris Millenium for the given Julian Century 323 | 324 | - parameter century: The Julian Century to calculate the Julian Ephemeris Millenium for 325 | 326 | - returns: Julian Ephemeris Millenium for the selected Julian Century 327 | */ 328 | func julianEphemerisMillenium(for century: JulianCentury) -> JulianEphemerisMillenium { 329 | let millenium = century/10.0 330 | return millenium 331 | } 332 | 333 | 334 | /** 335 | Calculates Julian Ephemeris Millenias for the given Julian Centuries 336 | 337 | - parameter centuries: The Julian Centuries to calculate the Julian Ephemeris Millenias for 338 | 339 | - returns: Julian Ephemeris Millenias for the selected Julian Centuries 340 | */ 341 | func julianEphemerisMillenias(for centuries: [JulianCentury]) -> [JulianEphemerisMillenium] { 342 | var millenias = Array(repeating: 0, count: centuries.count) 343 | 344 | let length = vDSP_Length(centuries.count) 345 | 346 | var ten = 10.0 347 | vDSP_vsdivD(centuries, 1, &ten, &millenias, 1, length) 348 | 349 | return millenias 350 | } 351 | -------------------------------------------------------------------------------- /SunshineKit/Source/SPA/NREL/NRELSPA.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPACalculator.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 25.06.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | import CoreLocation 12 | import Accelerate 13 | 14 | 15 | /// Used for unit tests 16 | public let SunshineKitAccuracy = 0.000001 17 | 18 | public let SunshineKitDefaultPressure = 1010.0 19 | public let SunshineKitDefaultTemperature = 10.0 20 | public let SunshineKitDefaultSlope = 30.0 21 | public let SunshineKitDefaultSurfaceAzimuth = -10.0 22 | public let SunshineKitDefaultBuildingHeight = 10.0 23 | 24 | 25 | // MARK: - SPA 26 | 27 | 28 | /** 29 | Calculates SunPositions for the whole day or hour with the desired resolution (one SunPosition per hour or per minute or per second). 30 | 31 | Source is the NREL [Article](http://rredc.nrel.gov/solar/codesandalgorithms/spa/) 32 | 33 | - parameter date: The point in time (day) to calculate the sun positions for 34 | - parameter forHour: An optional hour, if set SunPositions are only calculated for this hour 35 | - parameter withResolution: The desired resolution 36 | - parameter timeZoneOffset: Offset from UTC in hours 37 | - parameter coordinate: The point on earth to calculate the sun positions for 38 | - parameter elevation: The height above Zero for the given location 39 | - parameter fragments: The selected SunPosition properties you need to calculate (fewer means faster processing) 40 | - parameter pressure: In Millibar, defaults to 1010 41 | - parameter temperature: In Celcius, defaults to 10 42 | - parameter slope: In Meter, defaults to 30 43 | - parameter surfaceAzimuth: In Meter, defaults to -10 44 | - parameter buildingHeight: In Meter, defaults to 10 (for shadow calculation) 45 | 46 | - returns: One SunPosition object per date with the given resolution and selected properties (Fragments) 47 | */ 48 | public func NRELSunPositions(for date: Date, forHour hour: Int? = nil, withResolution resolution: JulianDayResolution, timeZoneOffset: Int, coordinate: CLLocationCoordinate2D, elevation: Double, fragments: [SunPositionFragment], pressure: Double = SunshineKitDefaultPressure, temperature: Double = SunshineKitDefaultTemperature, slope: Double = SunshineKitDefaultSlope, surfaceAzimuth: Double = SunshineKitDefaultSurfaceAzimuth, buildingHeight: Double = SunshineKitDefaultBuildingHeight) -> [SunPosition] { 49 | let calculation_tupel = bool(for: fragments) 50 | 51 | var JD = julianDays(for: date, forHour: hour, timeZoneOffset: timeZoneOffset, withResolution: resolution) // 2452930.312847 52 | 53 | let count = JD.count 54 | 55 | // 3.1.2. Calculate the Julian Ephemeris Day (JDE) 56 | var JDE = julianEphemerisDays(for: JD) // 2452930.3136226851 57 | 58 | // 3.1.3. Calculate the Julian century (JC) and the Julian Ephemeris Century (JCE) for the 2000 standard epoch 59 | var JC = julianCenturies(for: JD) // 0.03792779869191517 0.03792779869191517 60 | var JCE = julianCenturies(for: JDE) // 0.037927819922933585 61 | 62 | // free memory 63 | JDE = [] 64 | 65 | // 3.1.4. Calculate the Julian Ephemeris Millennium (JME) for the 2000 standard epoch 66 | var JME = julianEphemerisMillenias(for: JCE) // 0.0037927819922933584 67 | 68 | 69 | var tempArray = Array(repeating: 0, count: count) 70 | var secondTempArray = tempArray 71 | let length = vDSP_Length(count) 72 | var int32Count = Int32(count) 73 | 74 | 75 | // 3.2.1. and 3.2.2. Calculate the term L0 (in radians) 76 | var L0 = calculateTerms(with: L_TERMS, index: 0, ephemerisMillenia: JME) // 172067561.52658555 77 | // 3.2.3. Calculate the terms L1, L2, L3, L4, and L5 78 | var L1 = calculateTerms(with: L_TERMS, index: 1, ephemerisMillenia: JME) // 628332010650.05115 79 | var L2 = calculateTerms(with: L_TERMS, index: 2, ephemerisMillenia: JME) // 61368.682493387161 80 | var L3 = calculateTerms(with: L_TERMS, index: 3, ephemerisMillenia: JME) // -26.90281881244934 81 | var L4 = calculateTerms(with: L_TERMS, index: 4, ephemerisMillenia: JME) // -121.27953627276293 82 | var L5 = calculateTerms(with: L_TERMS, index: 5, ephemerisMillenia: JME) // -0.9999987317275395 83 | var L = Array(repeating: 0, count: count) 84 | // 3.2.4. Calculate the Earth heliocentric longitude, L (in radians) 85 | // multiply L1 with JME add L0 vector and save in L 86 | vDSP_vmaD(L1, 1, JME, 1, L0, 1, &L, 1, length) 87 | // jme power 2 88 | var power2Array = Array(repeating: 2.0, count: count) 89 | vvpow(&tempArray, &power2Array, JME, &int32Count) 90 | // multiply L2 with JME*2 and add to L vector 91 | secondTempArray = L 92 | vDSP_vmaD(L2, 1, &tempArray, 1, &secondTempArray, 1, &L, 1, length) 93 | // jme power 3 94 | var power3Array = Array(repeating: 3.0, count: count) 95 | vvpow(&tempArray, &power3Array, JME, &int32Count) 96 | // multiply L3 with JME*3 and add to L vector 97 | secondTempArray = L 98 | vDSP_vmaD(L3, 1, &tempArray, 1, &secondTempArray, 1, &L, 1, length) 99 | // jme power 4 100 | var power4Array = Array(repeating: 4.0, count: count) 101 | vvpow(&tempArray, &power4Array, JME, &int32Count) 102 | // multiply L4 with JME*4 and add to L vector 103 | secondTempArray = L 104 | vDSP_vmaD(L4, 1, &tempArray, 1, &secondTempArray, 1, &L, 1, length) 105 | // jme power 5 106 | var power5Array = Array(repeating: 5.0, count: count) 107 | vvpow(&tempArray, &power5Array, JME, &int32Count) 108 | // multiply L5 with JME*5 and add to L vector 109 | secondTempArray = L 110 | vDSP_vmaD(L5, 1, &tempArray, 1, &secondTempArray, 1, &L, 1, length) // 2555193897.5843773 111 | // divide L through 100000000.0 112 | var divider = 100000000.0 113 | secondTempArray = L 114 | vDSP_vsdivD(&secondTempArray, 1, ÷r, &L, 1, length) 115 | // 3.2.5. Calculate L in degrees, 116 | var L_degree_values = radsToAngles(L) // 25.551938975843772 117 | // 3.2.6. Limit L to the range from 0/ to 360/ 118 | L_degree_values = clampAnglesToThreeSixty(L_degree_values) // 24.018261691679399 119 | 120 | // free memory 121 | L0 = [] 122 | L1 = [] 123 | L2 = [] 124 | L3 = [] 125 | L4 = [] 126 | L5 = [] 127 | L = [] 128 | 129 | 130 | // 3.2.7. Calculate the Earth heliocentric latitude, 131 | var B = Array(repeating: 0, count: count) // -0.0000017649105337201631 132 | var B0 = calculateTerms(with: B_TERMS, index: 0, ephemerisMillenia: JME) 133 | B0 = radsToAngles(B0) 134 | var B1 = calculateTerms(with: B_TERMS, index: 1, ephemerisMillenia: JME) 135 | B1 = radsToAngles(B1) 136 | // multiply B1Values with JME and add it to B0Values 137 | vDSP_vmaD(B1, 1, JME, 1, B0, 1, &B, 1, length) 138 | // divide B through 100000000.0 139 | secondTempArray = B 140 | vDSP_vsdivD(&secondTempArray, 1, ÷r, &B, 1, length) 141 | 142 | // free memory 143 | B0 = [] 144 | B1 = [] 145 | 146 | 147 | // 3.2.8. Calculate the Earth radius vector, R 148 | var R = Array(repeating: 0, count: count) 149 | var R0 = calculateTerms(with: R_TERMS, index: 0, ephemerisMillenia: JME) 150 | var R1 = calculateTerms(with: R_TERMS, index: 1, ephemerisMillenia: JME) 151 | var R2 = calculateTerms(with: R_TERMS, index: 2, ephemerisMillenia: JME) 152 | var R3 = calculateTerms(with: R_TERMS, index: 3, ephemerisMillenia: JME) 153 | var R4 = calculateTerms(with: R_TERMS, index: 4, ephemerisMillenia: JME) 154 | // add R0 vector 155 | vDSP_vaddD(R0, 1, R, 1, &R, 1, length) 156 | // multiply R1 with JME and add to R vector 157 | vDSP_vmaD(R1, 1, JME, 1, R, 1, &R, 1, length) 158 | // jme power 2 159 | vvpow(&tempArray, &power2Array, JME, &int32Count) 160 | // multiply R2 with JME*2 and add to R vector 161 | secondTempArray = R 162 | vDSP_vmaD(R2, 1, &tempArray, 1, &secondTempArray, 1, &R, 1, length) 163 | // jme power 3 164 | vvpow(&tempArray, &power3Array, JME, &int32Count) 165 | // multiply R3 with JME*3 and add to R vector 166 | secondTempArray = R 167 | vDSP_vmaD(R3, 1, &tempArray, 1, &secondTempArray, 1, &R, 1, length) 168 | // jme power 4 169 | vvpow(&tempArray, &power4Array, JME, &int32Count) 170 | // multiply R4 with JME*4 and add to R vector 171 | secondTempArray = R 172 | vDSP_vmaD(R4, 1, &tempArray, 1, &secondTempArray, 1, &R, 1, length) 173 | // divide R through 100000000.0 174 | secondTempArray = R 175 | vDSP_vsdivD(&secondTempArray, 1, ÷r, &R, 1, length) 176 | 177 | // make some memory free again 178 | R0 = [] 179 | R1 = [] 180 | R2 = [] 181 | R3 = [] 182 | R4 = [] 183 | power2Array = [] 184 | power3Array = [] 185 | power4Array = [] 186 | power5Array = [] 187 | 188 | 189 | // 3.3.1. Calculate the geocentric longitude, 1 (in degrees) 190 | var Θ: [Angle] = Array(repeating: 0, count: count) 191 | var oneHundredEighty = 180.0 192 | vDSP_vsaddD(L_degree_values, 1, &oneHundredEighty, &Θ, 1, length) 193 | 194 | 195 | // free memory 196 | L_degree_values = [] 197 | 198 | 199 | // 3.3.2. Limit 1 to the range from 0 to 360 200 | Θ = clampAnglesToThreeSixty(Θ) 201 | //let Θ_rad: [Rad] = anglesToRads(Θ) 202 | 203 | 204 | // 3.3.3. Calculate the geocentric latitude, beta (in degrees), 205 | var βValues: [Angle] = Array(repeating: 0, count: count) 206 | var minusOne = -1.0 207 | vDSP_vsmulD(B, 1, &minusOne, &βValues, 1, length) 208 | var β_rad = anglesToRads(βValues) 209 | 210 | // free memory 211 | βValues = [] 212 | B = [] 213 | 214 | 215 | // 3.4.1. Calculate the mean elongation of the moon from the sun, X0 (in degrees) // ((a*x + b)*x + c)*x + d; 216 | var X0: [Angle] = Array(repeating: 0, count: count) 217 | let X0_coefficients = [1.0/189474.0, -0.0019142, 445267.111480, 297.85036] 218 | vDSP_vpolyD(X0_coefficients, 1, JCE, 1, &X0, 1, length, vDSP_Length(X0_coefficients.count - 1)) 219 | 220 | 221 | // 3.4.2. Calculate the mean anomaly of the sun (Earth), X1 (in degrees) 222 | var X1: [Angle] = Array(repeating: 0, count: count) 223 | let X1_coefficients = [-1.0/300000.0, -0.0001603, 35999.050340, 357.52772] 224 | vDSP_vpolyD(X1_coefficients, 1, JCE, 1, &X1, 1, length, vDSP_Length(X1_coefficients.count - 1)) 225 | 226 | 227 | // 3.4.3. Calculate the mean anomaly of the moon, X2 (in degrees) 228 | var X2: [Angle] = Array(repeating: 0, count: count) 229 | let X2_coefficients = [1.0/56250.0, 0.0086972, 477198.867398, 134.96298] 230 | vDSP_vpolyD(X2_coefficients, 1, JCE, 1, &X2, 1, length, vDSP_Length(X2_coefficients.count - 1)) 231 | 232 | 233 | // 3.4.4. Calculate the moon’s argument of latitude, X3 (in degrees) 234 | var X3: [Angle] = Array(repeating: 0, count: count) 235 | let X3_coefficients = [1.0/327270.0, -0.0036825, 483202.017538, 93.27191] 236 | vDSP_vpolyD(X3_coefficients, 1, JCE, 1, &X3, 1, length, vDSP_Length(X3_coefficients.count - 1)) 237 | 238 | 239 | // 3.4.5. Calculate the longitude of the ascending node of the moon’s mean orbit on the ecliptic, measured from the mean equinox of the date, X4 (in degrees) 240 | var X4: [Angle] = Array(repeating: 0, count: count) 241 | let X4_coefficients = [1.0/450000.0, 0.0020708, -1934.136261, 125.04452] 242 | vDSP_vpolyD(X4_coefficients, 1, JCE, 1, &X4, 1, length, vDSP_Length(X4_coefficients.count - 1)) 243 | 244 | 245 | // 3.4.6. to 3.4.8 246 | var ΔΨ: [Angle] = Array(repeating: 0, count: count) 247 | var Δε: [Angle] = Array(repeating: 0, count: count) 248 | 249 | for i in 0..(repeating: 0, count: count) 251 | var ε: [Angle] = Array(repeating: 0, count: count) 252 | 253 | var sum: [Angle] = Array(repeating: 0, count: count) 254 | 255 | let y_array = Y_TERMS[i] 256 | var y0 = y_array[0] 257 | var y1 = y_array[1] 258 | var y2 = y_array[2] 259 | var y3 = y_array[3] 260 | var y4 = y_array[4] 261 | 262 | let pe_array = PE_TERMS[i] 263 | var a = pe_array[0] 264 | var b = pe_array[1] 265 | var c = pe_array[2] 266 | var d = pe_array[3] 267 | 268 | // X0*y0 269 | vDSP_vsmulD(X0, 1, &y0, &sum, 1, length) 270 | // + X1*y1 271 | tempArray = sum 272 | vDSP_vsmaD(X1, 1, &y1, &tempArray, 1, &sum, 1, length) 273 | // + X2*y2 274 | tempArray = sum 275 | vDSP_vsmaD(X2, 1, &y2, &tempArray, 1, &sum, 1, length) 276 | // + X3*y3 277 | tempArray = sum 278 | vDSP_vsmaD(X3, 1, &y3, &tempArray, 1, &sum, 1, length) 279 | // + X4*y4 280 | tempArray = sum 281 | vDSP_vsmaD(X4, 1, &y4, &tempArray, 1, &sum, 1, length) 282 | 283 | let sum_rad: [Rad] = anglesToRads(sum) 284 | 285 | // (a + b*JCE) 286 | vDSP_vsmulD(JCE, 1, &b, &sum, 1, length) 287 | tempArray = sum 288 | vDSP_vsaddD(&tempArray, 1, &a, &sum, 1, length) 289 | 290 | var temp: [Rad] = Array(repeating: 0, count: count) 291 | // sin(sum_rad) 292 | vvsin(&temp, sum_rad, &int32Count) 293 | 294 | // (a + b*JCE)*sin(sum_rad) 295 | vDSP_vmulD(&sum, 1, &temp, 1, &Ψ, 1, length) 296 | // ΔΨ += Ψ 297 | vDSP_vaddD(ΔΨ, 1, Ψ, 1, &ΔΨ, 1, length) 298 | 299 | // cos(sum_rad) 300 | vvcos(&temp, sum_rad, &int32Count) 301 | 302 | // (c + d*JCE) 303 | vDSP_vsmulD(JCE, 1, &d, &sum, 1, length) 304 | tempArray = sum 305 | vDSP_vsaddD(&tempArray, 1, &c, &sum, 1, length) 306 | 307 | // (c + d*JCE)*cos(sum_rad) 308 | vDSP_vmulD(&sum, 1, &temp, 1, &ε, 1, length) 309 | // Δε += ε 310 | vDSP_vaddD(Δε, 1, ε, 1, &Δε, 1, length) 311 | } 312 | 313 | // free memory 314 | JCE = [] 315 | X0 = [] 316 | X1 = [] 317 | X2 = [] 318 | X3 = [] 319 | X4 = [] 320 | 321 | // ΔΨ = ΔΨ/36000000.0 // -0.003998404 322 | divider = 36000000.0 323 | vDSP_vsdivD(ΔΨ, 1, ÷r, &ΔΨ, 1, length) 324 | // Δε = Δε/36000000.0 // 0.001666568 325 | vDSP_vsdivD(Δε, 1, ÷r, &Δε, 1, length) 326 | 327 | 328 | // 3.5.1. Calculate the mean obliquity of the ecliptic, use tempArray as "U" 329 | divider = 10.0 330 | vDSP_vsdivD(JME, 1, ÷r, &tempArray, 1, length) 331 | var ε0: [Angle] = Array(repeating: 0, count: count) 332 | let ε0_coefficients = [2.45, 5.79, 27.87, 7.12, -39.05, -249.67, -51.38, 1999.25, -1.55, -4680.93, 84381.448] 333 | vDSP_vpolyD(ε0_coefficients, 1, tempArray, 1, &ε0, 1, length, vDSP_Length(ε0_coefficients.count - 1)) 334 | 335 | // free memory 336 | JME = [] 337 | 338 | // 3.5.2. Calculate the true obliquity of the ecliptic, 339 | var ε: [Angle] = Array(repeating: 0, count: count) 340 | divider = 3600.0 341 | // ε0/3600.0 342 | vDSP_vsdivD(ε0, 1, ÷r, &ε, 1, length) 343 | // ε0/3600.0 + Δε // 23.440465 344 | vDSP_vaddD(ε, 1, Δε, 1, &ε, 1, length) 345 | var ε_rad: [Rad] = anglesToRads(ε) // 0.409113 346 | 347 | 348 | // free memory 349 | ε = [] 350 | Δε = [] 351 | ε0 = [] 352 | 353 | 354 | // 3.6. Calculate the aberration correction 355 | var Δτ: [Angle] = Array(repeating: 0, count: count) 356 | var multiplier = 3600.0 357 | vDSP_vsmulD(R, 1, &multiplier, &Δτ, 1, length) 358 | tempArray = Array(repeating: -20.4898, count: count) 359 | vDSP_vdivD(Δτ, 1, tempArray, 1, &Δτ, 1, length) 360 | 361 | 362 | // 3.7 Calculate the apparent sun longitude 363 | var λ: [Angle] = Array(repeating: 0, count: count) 364 | // Θ + ΔΨ + Δτ // 204.008552 365 | vDSP_vaddD(Θ, 1, ΔΨ, 1, &λ, 1, length) 366 | vDSP_vaddD(λ, 1, Δτ, 1, &λ, 1, length) 367 | var λ_rad: [Rad] = anglesToRads(λ) // 3.560621 368 | 369 | // free memory 370 | Θ = [] 371 | Δτ = [] 372 | λ = [] 373 | 374 | 375 | // 3.8.1. Calculate the mean sidereal time at Greenwich 376 | // 280.46061837 + 360.98564736629*(JD - 2451545.0) + JC*JC*(0.000387933 - JC/38710000.0) 377 | var ν0: [Angle] = Array(repeating: 0, count: count) 378 | // (JD - 2451545.0) 379 | var addValue = -2451545.0 380 | vDSP_vsaddD(JD, 1, &addValue, &tempArray, 1, length) 381 | // 360.98564736629*(JD - 2451545.0) 382 | var multiplyValue = 360.98564736629 383 | vDSP_vsmulD(tempArray, 1, &multiplyValue, &tempArray, 1, length) 384 | // 280.46061837 + 360.98564736629*(JD - 2451545.0) 385 | addValue = 280.46061837 386 | vDSP_vsaddD(tempArray, 1, &addValue, &ν0, 1, length) 387 | // JC*JC 388 | vDSP_vmulD(JC, 1, JC, 1, &tempArray, 1, length) 389 | // JC/38710000.0 390 | divider = -38710000.0 391 | vDSP_vsdivD(JC, 1, ÷r, &secondTempArray, 1, length) 392 | // (0.000387933 - JC/38710000.0) 393 | addValue = 0.000387933 394 | vDSP_vsaddD(secondTempArray, 1, &addValue, &secondTempArray, 1, length) 395 | // JC*JC*(0.000387933 - JC/38710000.0) 396 | vDSP_vmulD(tempArray, 1, &secondTempArray, 1, &tempArray, 1, length) 397 | // 280.46061837 + 360.98564736629*(JD - 2451545.0) + JC*JC*(0.000387933 - JC/38710000.0) 398 | vDSP_vaddD(ν0, 1, &tempArray, 1, &ν0, 1, length) 399 | 400 | // free memory 401 | JD = [] 402 | JC = [] 403 | 404 | // 3.8.2. Limit v_zero to the range from 0 to 360 405 | ν0 = clampAnglesToThreeSixty(ν0) 406 | 407 | 408 | // 3.8.3. Calculate the apparent sidereal time at Greenwich 409 | var ν: [Angle] = Array(repeating: 0, count: count) 410 | vDSP_vaddD(ν, 1, ν0, 1, &ν, 1, length) 411 | // cos(ε_rad) 412 | var ε_cos: [Rad] = Array(repeating: 0, count: count) 413 | vvcos(&ε_cos, ε_rad, &int32Count) 414 | // ΔΨ*cos(ε_rad) 415 | vDSP_vmulD(&ε_cos, 1, ΔΨ, 1, &secondTempArray, 1, length) 416 | // ν0 + ΔΨ*cos(ε_rad) 417 | vDSP_vaddD(ν0, 1, secondTempArray, 1, &ν, 1, length) 418 | 419 | 420 | // free memory 421 | ΔΨ = [] 422 | ν0 = [] 423 | 424 | 425 | // 3.9.1. Calculate the sun right ascension 426 | var atan_nominator: [Rad] = Array(repeating: 0, count: count) 427 | // sin(λ_rad) 428 | var λ_sin: [Rad] = Array(repeating: 0, count: count) 429 | vvsin(&λ_sin, &λ_rad, &int32Count) 430 | // sin(λ_rad)*cos(ε_rad), tempArray still constains cos(ε_rad) 431 | vDSP_vmulD(ε_cos, 1, λ_sin, 1, &atan_nominator, 1, length) 432 | // tan(β_rad) 433 | vvtan(&tempArray, β_rad, &int32Count) 434 | // sin(ε_rad) 435 | var ε_sin: [Rad] = Array(repeating: 0, count: count) 436 | vvsin(&ε_sin, ε_rad, &int32Count) 437 | // tan(β_rad)*sin(ε_rad) 438 | vDSP_vmulD(tempArray, 1, ε_sin, 1, &tempArray, 1, length) 439 | // sin(λ_rad)*cos(ε_rad) - tan(β_rad)*sin(ε_rad) 440 | vDSP_vsubD(tempArray, 1, atan_nominator, 1, &atan_nominator, 1, length) 441 | // cos(λ_rad) 442 | vvcos(&tempArray, λ_rad, &int32Count) 443 | var α: [Rad] = Array(repeating: 0, count: count) 444 | // atan2(atan_nominator, atan_denominator) 445 | vvatan2(&α, atan_nominator, tempArray, &int32Count) 446 | 447 | // free memory 448 | atan_nominator = [] 449 | 450 | 451 | // 3.9.2. Calculate alhpa in degrees using Equation 12, then limit it to the range from 0 to 360 452 | var α_degrees = radsToAngles(α) 453 | α_degrees = clampAnglesToThreeSixty(α_degrees) // 454 | 455 | // free memory 456 | α = [] 457 | 458 | 459 | // 3.10. Calculate the geocentric sun declination 460 | // sin(β_rad) 461 | vvsin(&tempArray, β_rad, &int32Count) 462 | // cos(β_rad) 463 | vvcos(&secondTempArray, β_rad, &int32Count) 464 | var δ: [Rad] = Array(repeating: 0, count: count) 465 | // sin(β_rad)*cos(ε_rad) 466 | vDSP_vmulD(tempArray, 1, ε_cos, 1, &tempArray, 1, length) 467 | // cos(β_rad)*sin(ε_rad) 468 | vDSP_vmulD(secondTempArray, 1, ε_sin, 1, &secondTempArray, 1, length) 469 | // cos(β_rad)*sin(ε_rad)*sin(λ_rad) 470 | vDSP_vmulD(secondTempArray, 1, λ_sin, 1, &secondTempArray, 1, length) 471 | // sin(β_rad)*cos(ε_rad) + cos(β_rad)*sin(ε_rad)*sin(λ_rad) 472 | vDSP_vaddD(tempArray, 1, secondTempArray, 1, &tempArray, 1, length) 473 | // asin(sin(β_rad)*cos(ε_rad) + cos(β_rad)*sin(ε_rad)*sin(λ_rad)) 474 | vvasin(&δ, tempArray, &int32Count) 475 | 476 | // free memory 477 | β_rad = [] 478 | ε_rad = [] 479 | λ_rad = [] 480 | ε_cos = [] 481 | λ_sin = [] 482 | ε_sin = [] 483 | 484 | 485 | // 3.11. Calculate the observer local hour angle 486 | var H: [Angle] = Array(repeating: coordinate.longitude, count: count) 487 | // ν + coordinate.longitude 488 | vDSP_vaddD(ν, 1, H, 1, &H, 1, length) 489 | // ν + coordinate.longitude - α_degrees 490 | vDSP_vsubD(α_degrees, 1, H, 1, &H, 1, length) 491 | H = clampAnglesToThreeSixty(H) // 11.105902 492 | var H_rad = anglesToRads(H) // 0.193835 493 | 494 | // free memory 495 | ν = [] 496 | 497 | 498 | // 3.12.1. Calculate the equatorial horizontal parallax of the sun 499 | var ξ: [Angle] = Array(repeating: 8.794, count: count) 500 | // (3600.0*R) 501 | multiplier = 3600.0 502 | vDSP_vsmulD(R, 1, &multiplier, &tempArray, 1, length) 503 | // 8.794/(3600.0*R) 504 | vDSP_vdivD(tempArray, 1, ξ, 1, &ξ, 1, length) 505 | var ξ_rad = anglesToRads(ξ) // 0.000043 506 | 507 | 508 | // free memory 509 | ξ = [] 510 | R = [] 511 | 512 | 513 | // 3.12.2. Calculate the term u 514 | let latitude_rad = angleToRad(coordinate.latitude) // 0.693637 515 | var u: [Rad] = Array(repeating: latitude_rad, count: count) 516 | // tan(latitude_rad) 517 | vvtan(&u, u, &int32Count) 518 | // 0.99664719*tan(latitude_rad) 519 | multiplier = 0.99664719 520 | tempArray = u 521 | vDSP_vsmulD(&tempArray, 1, &multiplier, &u, 1, length) 522 | // atan(0.99664719*tan(latitude_rad)) 523 | vvatan(&u, u, &int32Count) 524 | 525 | 526 | // 3.12.3. Calculate the term x 527 | var latitude_cos: [Rad] = Array(repeating: latitude_rad, count: count) 528 | // cos(latitude_rad) 529 | vvcos(&latitude_cos, latitude_cos, &int32Count) 530 | // cos(u) 531 | vvcos(&tempArray, u, &int32Count) 532 | // elevation/6378140.0 533 | multiplier = elevation/6378140.0 534 | // elevation/6378140.0*cos(latitude_rad) 535 | var x: [Rad] = Array(repeating: 0, count: count) 536 | vDSP_vsmulD(latitude_cos, 1, &multiplier, &x, 1, length) 537 | // cos(u) + elevation/6378140.0*cos(latitude_rad) 538 | vDSP_vaddD(tempArray, 1, x, 1, &x, 1, length) 539 | 540 | 541 | // 3.12.4. Calculate the term y 542 | var latitude_sin: [Rad] = Array(repeating: latitude_rad, count: count) 543 | // sin(latitude_rad) 544 | vvsin(&latitude_sin, latitude_sin, &int32Count) 545 | // sin(u) 546 | vvsin(&tempArray, u, &int32Count) 547 | // 0.99664719*sin(u) 548 | multiplier = 0.99664719 549 | secondTempArray = tempArray 550 | vDSP_vsmulD(&secondTempArray, 1, &multiplier, &tempArray, 1, length) 551 | // elevation/6378140.0*sin(latitude_rad) 552 | var y: [Rad] = Array(repeating: 0, count: count) 553 | multiplier = elevation/6378140.0 554 | vDSP_vsmulD(latitude_sin, 1, &multiplier, &y, 1, length) 555 | // 0.99664719*sin(u) + elevation/6378140.0*sin(latitude_rad) 556 | vDSP_vaddD(&tempArray, 1, y, 1, &y, 1, length) 557 | 558 | 559 | // 3.12.5. Calculate the parallax in the sun right ascension 560 | var ξ_sin = Array(repeating: 0, count: count) 561 | // sin(ξ_rad) 562 | vvsin(&ξ_sin, ξ_rad, &int32Count) 563 | // sin(H_rad) 564 | vvsin(&tempArray, H_rad, &int32Count) 565 | // cos(δ) 566 | vvcos(&secondTempArray, δ, &int32Count) 567 | var thirdTempArray: [Rad] = Array(repeating: 0, count: count) 568 | // cos(H_rad) 569 | vvcos(&thirdTempArray, H_rad, &int32Count) 570 | var fourthTempArray: [Rad] = Array(repeating: -1, count: count) 571 | // -x 572 | vDSP_vmulD(fourthTempArray, 1, x, 1, &fourthTempArray, 1, length) 573 | // -x*sin(H_rad) 574 | vDSP_vmulD(fourthTempArray, 1, tempArray, 1, &tempArray, 1, length) 575 | // -x*sin(H_rad)*sin(ξ_rad) 576 | vDSP_vmulD(tempArray, 1, ξ_sin, 1, &tempArray, 1, length) 577 | // -x*sin(ξ_rad) 578 | vDSP_vmulD(fourthTempArray, 1, ξ_sin, 1, &fourthTempArray, 1, length) 579 | // - x*sin(ξ_rad)*cos(H_rad) 580 | vDSP_vmulD(fourthTempArray, 1, thirdTempArray, 1, &fourthTempArray, 1, length) 581 | // cos(δ) - x*sin(ξ_rad)*cos(H_rad) 582 | vDSP_vaddD(secondTempArray, 1, fourthTempArray, 1, &fourthTempArray, 1, length) 583 | // atan2(-x*sin(ξ_rad)*sin(H_rad), cos(δ) - x*sin(ξ_rad)*cos(H_rad)) 584 | var Δα: [Rad] = Array(repeating: 0, count: count) 585 | vvatan2(&Δα, tempArray, fourthTempArray, &int32Count) 586 | var Δα_degrees: [Angle] = radsToAngles(Δα) 587 | 588 | 589 | // 3.12.6. Calculate the topocentric sun right ascension 590 | var α_new: [Angle]? 591 | if calculation_tupel.doCalculateAscension { 592 | var _α_new: [Angle] = Array(repeating: 0, count: count) 593 | // α_degrees + Δα_degrees // 202.227039 594 | vDSP_vaddD(α_degrees, 1, Δα_degrees, 1, &_α_new, 1, length) 595 | α_new = _α_new 596 | } 597 | // free memory 598 | α_degrees = [] 599 | 600 | 601 | // 3.12.7. Calculate the topocentric sun declination 602 | // sin(δ) 603 | vvsin(&tempArray, δ, &int32Count) 604 | // y*sin(ξ_rad) 605 | vDSP_vmulD(y, 1, ξ_sin, 1, &secondTempArray, 1, length) 606 | // sin(δ) - y*sin(ξ_rad) 607 | vDSP_vsubD(secondTempArray, 1, tempArray, 1, &tempArray, 1, length) 608 | // cos(Δα) 609 | vvcos(&secondTempArray, Δα, &int32Count) 610 | // (sin(δ) - y*sin(ξ_rad))*cos(Δα) 611 | vDSP_vmulD(tempArray, 1, secondTempArray, 1, &tempArray, 1, length) 612 | var δ_new: [Rad] = Array(repeating: 0, count: count) 613 | // atan2((sin(δ) - y*sin(ξ_rad))*cos(Δα), cos(δ) - x*sin(ξ_rad)*cos(H_rad)) // -9.316179 614 | vvatan2(&δ_new, tempArray, fourthTempArray, &int32Count) 615 | 616 | // free memory 617 | ξ_sin = [] 618 | δ = [] 619 | ξ_rad = [] 620 | Δα = [] 621 | 622 | 623 | // 3.13. Calculate the topocentric local hour angle 624 | // H - Δα_degrees // 11.106271 625 | vDSP_vsubD(Δα_degrees, 1, H, 1, &tempArray, 1, length) 626 | var H_new_rad: [Rad] = anglesToRads(tempArray) 627 | 628 | // free memory 629 | Δα_degrees = [] 630 | 631 | 632 | // 3.14.1. Calculate the topocentric elevation angle without atmospheric refraction correction 633 | var cos_H_new: [Rad] = Array(repeating: 0, count: count) 634 | // cos(H_new_rad) 635 | vvcos(&cos_H_new, H_new_rad, &int32Count) 636 | var e: [Angle]? 637 | if calculation_tupel.doCalculateHeight || calculation_tupel.doCalculateIncidence || calculation_tupel.doCalculateZenith || calculation_tupel.doCalculateShadowLength { 638 | // sin(δ_new) 639 | vvsin(&secondTempArray, δ_new, &int32Count) 640 | // cos(δ_new) 641 | vvcos(&thirdTempArray, δ_new, &int32Count) 642 | // sin(latitude_rad)*sin(δ_new) 643 | vDSP_vmulD(latitude_sin, 1, secondTempArray, 1, &secondTempArray, 1, length) 644 | // cos(latitude_rad)*cos(δ_new) 645 | vDSP_vmulD(latitude_cos, 1, thirdTempArray, 1, &thirdTempArray, 1, length) 646 | // cos(latitude_rad)*cos(δ_new)*cos(H_new_rad)) 647 | vDSP_vmulD(thirdTempArray, 1, cos_H_new, 1, &thirdTempArray, 1, length) 648 | // sin(latitude_rad)*sin(δ_new) + cos(latitude_rad)*cos(δ_new)*cos(H_new_rad) 649 | vDSP_vaddD(secondTempArray, 1, thirdTempArray, 1, &thirdTempArray, 1, length) 650 | var e0: [Rad] = Array(repeating: 0, count: count) 651 | // asin(sin(latitude_rad)*sin(δ_new) + cos(latitude_rad)*cos(δ_new)*cos(H_new_rad)) 652 | vvasin(&e0, thirdTempArray, &int32Count) 653 | var e0_degrees: [Angle] = radsToAngles(e0) 654 | 655 | 656 | // 3.14.2. Calculate the atmospheric refraction correction 657 | var Δe: [Rad] = Array(repeating: 0, count: count) 658 | var pressure_temp = (pressure/1010.0)*(283.0/(273.0 + temperature)) 659 | // (e0_degrees + 5.11) 660 | addValue = 5.11 661 | vDSP_vsaddD(e0_degrees, 1, &addValue, &tempArray, 1, length) 662 | // 10.3/(e0_degrees + 5.11) 663 | secondTempArray = Array(repeating: 10.3, count: count) 664 | vDSP_vdivD(tempArray, 1, secondTempArray, 1, &tempArray, 1, length) 665 | // e0_degrees + 10.3/(e0_degrees + 5.11)) 666 | vDSP_vaddD(e0_degrees, 1, tempArray, 1, &tempArray, 1, length) 667 | tempArray = anglesToRads(tempArray) 668 | // tan(angleToRad(e0_degrees + 10.3/(e0_degrees + 5.11)))) 669 | vvtan(&tempArray, tempArray, &int32Count) 670 | // 60.0*tan(angleToRad(e0_degrees + 10.3/(e0_degrees + 5.11)))) 671 | multiplier = 60.0 672 | vDSP_vsmulD(tempArray, 1, &multiplier, &tempArray, 1, length) 673 | tempArray = radsToAngles(tempArray) 674 | // 1.02/(radToAngle(60.0*tan(angleToRad(e0_degrees + 10.3/(e0_degrees + 5.11))))) 675 | secondTempArray = Array(repeating: 1.02, count: count) 676 | vDSP_vdivD(tempArray, 1, secondTempArray, 1, &tempArray, 1, length) 677 | // (pressure/1010.0)*(283.0/(273.0 + temperature))*1.02/(radToAngle(60.0*tan(angleToRad(e0_degrees + 10.3/(e0_degrees + 5.11))))) // 0.016332 678 | vDSP_vsmulD(tempArray, 1, &pressure_temp, &Δe, 1, length) 679 | // FIXME: find vectorized solution 680 | // not in paper 681 | for index in 0..(repeating: 0, count: count) 695 | // e0_degrees + Δe_degrees // 39.888378 696 | vDSP_vaddD(e0_degrees, 1, Δe_degrees, 1, &_e, 1, length) 697 | e = _e 698 | 699 | // free memory 700 | e0_degrees = [] 701 | Δe_degrees = [] 702 | } 703 | 704 | 705 | // 3.14.4. Calculate the topocentric zenith angle 706 | var zenith: [Angle]? 707 | var zenith_rad: [Rad]? 708 | if let e = e , calculation_tupel.doCalculateZenith || calculation_tupel.doCalculateIncidence { 709 | var _zenith: [Angle] = Array(repeating: 90, count: count) 710 | // 90.0 - e // 50.111622 711 | vDSP_vsubD(e, 1, _zenith, 1, &_zenith, 1, length) 712 | zenith_rad = anglesToRads(_zenith) 713 | zenith = _zenith 714 | } 715 | 716 | 717 | // 3.15.1. Calculate the topocentric astronomers azimuth angle 718 | var Γ_degrees: [Angle]? 719 | var Φ: [Angle]? 720 | if calculation_tupel.doCalculateAzimuth || calculation_tupel.doCalculateIncidence || calculation_tupel.doCalculateShadowDirection { 721 | // sin(H_new_rad) 722 | vvsin(&tempArray, H_new_rad, &int32Count) 723 | // tan(δ_new) 724 | vvtan(&secondTempArray, δ_new, &int32Count) 725 | // cos(H_new_rad)*sin(latitude_rad) 726 | vDSP_vmulD(cos_H_new, 1, latitude_sin, 1, &thirdTempArray, 1, length) 727 | // tan(δ_new)*cos(latitude_rad) 728 | vDSP_vmulD(secondTempArray, 1, latitude_cos, 1, &secondTempArray, 1, length) 729 | // cos(H_new_rad)*sin(latitude_rad) - tan(δ_new)*cos(latitude_rad) 730 | vDSP_vsubD(secondTempArray, 1, thirdTempArray, 1, &secondTempArray, 1, length) 731 | var Γ: [Rad] = Array(repeating: 0, count: count) 732 | // atan2(sin(H_new_rad), cos(H_new_rad)*sin(latitude_rad) - tan(δ_new)*cos(latitude_rad)) 733 | vvatan2(&Γ, tempArray, secondTempArray, &int32Count) 734 | var Γ_deg: [Angle] = radsToAngles(Γ) 735 | Γ_deg = clampAnglesToThreeSixty(Γ_deg) 736 | Γ_degrees = Γ_deg 737 | 738 | // free memory 739 | latitude_cos = [] 740 | latitude_sin = [] 741 | H_rad = [] 742 | δ_new = [] 743 | H_new_rad = [] 744 | Γ = [] 745 | cos_H_new = [] 746 | 747 | 748 | // 3.15.2. Calculate the topocentric azimuth angle, M for navigators and solar radiation users 749 | var _Φ: [Angle] = Array(repeating: 0, count: count) 750 | // Γ_degrees + 180.0 751 | addValue = 180.0 752 | vDSP_vsaddD(Γ_deg, 1, &addValue, &_Φ, 1, length) 753 | Φ = clampAnglesToThreeSixty(_Φ) // 194.340241 754 | } 755 | 756 | // 3.16. Calculate the incidence angle for a surface oriented in any direction 757 | var I_degrees: [Angle]? 758 | if let zenith_rad = zenith_rad, let Γ_degrees = Γ_degrees , calculation_tupel.doCalculateIncidence { 759 | let slope_rad: Rad = angleToRad(slope) 760 | var cos_slope = cos(slope_rad) 761 | var sin_slope = sin(slope_rad) 762 | // cos(zenith_rad) 763 | vvcos(&tempArray, zenith_rad, &int32Count) 764 | // sin(zenith_rad) 765 | vvsin(&secondTempArray, zenith_rad, &int32Count) 766 | // Γ_degrees - surfaceAzimuth 767 | addValue = -surfaceAzimuth 768 | vDSP_vsaddD(Γ_degrees, 1, &addValue, &thirdTempArray, 1, length) 769 | // angleToRad(Γ_degrees - surfaceAzimuth) 770 | fourthTempArray = anglesToRads(thirdTempArray) 771 | // cos(angleToRad(Γ_degrees - surfaceAzimuth)) 772 | vvcos(&thirdTempArray, fourthTempArray, &int32Count) 773 | // sin(zenith_rad)*cos(angleToRad(Γ_degrees - surfaceAzimuth)) 774 | vDSP_vmulD(secondTempArray, 1, thirdTempArray, 1, &fourthTempArray, 1, length) 775 | // sin(slope_rad)*sin(zenith_rad)*cos(angleToRad(Γ_degrees - surfaceAzimuth)) 776 | vDSP_vsmulD(fourthTempArray, 1, &sin_slope, &fourthTempArray, 1, length) 777 | // cos(zenith_rad)*cos(slope_rad) 778 | vDSP_vsmulD(tempArray, 1, &cos_slope, &tempArray, 1, length) 779 | // cos(zenith_rad)*cos(slope_rad) + sin(slope_rad)*sin(zenith_rad)*cos(angleToRad(Γ_degrees - surfaceAzimuth)) 780 | vDSP_vaddD(tempArray, 1, fourthTempArray, 1, &tempArray, 1, length) 781 | var I: [Rad] = Array(repeating: 0, count: count) 782 | // acos(cos(zenith_rad)*cos(slope_rad) + sin(slope_rad)*sin(zenith_rad)*cos(angleToRad(Γ_degrees - surfaceAzimuth))) 783 | vvacos(&I, tempArray, &int32Count) 784 | I_degrees = radsToAngles(I) // 25.187000 785 | } 786 | 787 | 788 | var shadowDirections: [Angle]? 789 | if let Φ = Φ , calculation_tupel.doCalculateShadowDirection { 790 | var _shadowDirections = Array(repeating: 0, count: count) 791 | var minus_one_eighty = -180.0 792 | vDSP_vsaddD(Φ, 1, &minus_one_eighty, &_shadowDirections, 1, length) 793 | shadowDirections = _shadowDirections 794 | } 795 | 796 | 797 | var shadowLengths: [Double]? 798 | if let e = e , calculation_tupel.doCalculateShadowLength { 799 | // shadowLength = buildingHeight*(sin(angleToRad(90) - e_rad)/sin(e_rad)) 800 | // e_rad 801 | tempArray = anglesToRads(e) 802 | // angleToRad(90) 803 | secondTempArray = Array(repeating: 90, count: count) 804 | secondTempArray = anglesToRads(secondTempArray) 805 | // angleToRad(90) - e_rad) 806 | vDSP_vsubD(tempArray, 1, secondTempArray, 1, &thirdTempArray, 1, length) 807 | // sin(angleToRad(90) - e_rad) 808 | vvsin(&thirdTempArray, thirdTempArray, &int32Count) 809 | // sin(e_rad) 810 | vvsin(&tempArray, tempArray, &int32Count) 811 | // sin(angleToRad(90) - e_rad)/sin(e_rad) 812 | vDSP_vdivD(tempArray, 1, thirdTempArray, 1, &tempArray, 1, length) 813 | // buildingHeight*(sin(angleToRad(90) - e_rad)/sin(e_rad)) 814 | var buildingHeight = 10.0 // m 815 | vDSP_vsmulD(tempArray, 1, &buildingHeight, &tempArray, 1, length) 816 | shadowLengths = tempArray 817 | } 818 | 819 | let dates = date.allDatesForDateWith(resolution: resolution, for: hour) 820 | var sunPositions = [SunPosition]() 821 | 822 | for index in 0.. SunPosition { 887 | let calculation_tupel = bool(for: fragments) 888 | 889 | let JD = julianDay(for: date, timeZoneOffset: timeZoneOffset) // 2452930.3128472222 890 | 891 | // 3.1.2. Calculate the Julian Ephemeris Day (JDE) 892 | let JDE = julianEphemerisDay(for: JD) // 2452930.3136226851 893 | 894 | // 3.1.3. Calculate the Julian century (JC) and the Julian Ephemeris Century (JCE) for the 2000 standard epoch 895 | let JC = julianCentury(for: JD) // 0.03792779869191517 896 | let JCE = julianCentury(for: JDE) // 0.037927819922933585 897 | 898 | // 3.1.4. Calculate the Julian Ephemeris Millennium (JME) for the 2000 standard epoch 899 | let JME = julianEphemerisMillenium(for: JCE) // 0.0037927819922933584 900 | 901 | // 3.2.8. Calculate the Earth radius vector, R 902 | let R0 = calculateTerm(with: R_TERMS, index: 0, ephemerisMillenium: JME) 903 | let R1 = calculateTerm(with: R_TERMS, index: 1, ephemerisMillenium: JME) 904 | let R2 = calculateTerm(with: R_TERMS, index: 2, ephemerisMillenium: JME) 905 | let R3 = calculateTerm(with: R_TERMS, index: 3, ephemerisMillenium: JME) 906 | let R4 = calculateTerm(with: R_TERMS, index: 4, ephemerisMillenium: JME) 907 | let R_nominator = R0 + R1*JME + R2*pow(JME, 2) + R3*pow(JME, 3) + R4*pow(JME, 4) 908 | let R = R_nominator/100000000.0 // 0.996542 909 | 910 | // 3.3.1. Calculate the geocentric longitude, 1 (in degrees) 911 | let Θ: Angle = geocentricLongitude(for: JME) 912 | 913 | // 3.3.3. Calculate the geocentric latitude, beta (in degrees), 914 | let β_rad = geocentricLatitude(for: JME) 915 | 916 | let tupel = trueObliquityEcliptic(for: JCE, JME: JME) 917 | let ΔΨ: Angle = tupel.ΔΨ.angle 918 | let ε_rad: Rad = tupel.ε_rad 919 | 920 | // 3.7 Calculate the apparent sun longitude 921 | let λ_rad = apparentSunLongitude(for: JME, Θ: Θ, ΔΨ: ΔΨ) 922 | 923 | // 3.8.1. Calculate the mean sidereal time at Greenwich 924 | let left = 280.46061837 + 360.98564736629*(JD - 2451545.0) 925 | let right = JC*JC*(0.000387933 - JC/38710000.0) 926 | var ν0: Angle = left + right // 318.515578 927 | 928 | // 3.8.2. Limit v_zero to the range from 0 to 360 929 | ν0 = clampAngleToThreeSixty(ν0) 930 | 931 | // 3.8.3. Calculate the apparent sidereal time at Greenwich 932 | let ν: Angle = apparentSiderealTimeAtGreenwich(for: JD, JC: JC, ε_rad: ε_rad, ΔΨ: ΔΨ) 933 | 934 | // 3.9.1. Calculate the sun right ascension 935 | let α = sunRightAscension(for: β_rad, ε_rad: ε_rad, λ_rad: λ_rad) 936 | 937 | // 3.9.2. Calculate alhpa in degrees using Equation 12, then limit it to the range from 0 to 360 938 | let α_degrees = clampAngleToThreeSixty(α.angle) // 939 | 940 | // 3.10. Calculate the geocentric sun declination 941 | let δ: Rad = geocentricSunDeclination(for: β_rad, ε_rad: ε_rad, λ_rad: λ_rad).rad 942 | 943 | // 3.11. Calculate the observer local hour angle 944 | var H: Angle = ν + coordinate.longitude - α_degrees 945 | H = clampAngleToThreeSixty(H) // 11.105902 946 | let H_rad = angleToRad(H) // 0.193835 947 | 948 | // 3.12.1. Calculate the equatorial horizontal parallax of the sun 949 | let ξ: Angle = 8.794/(3600.0*R) 950 | let ξ_rad = angleToRad(ξ) // 0.000043 951 | 952 | // 3.12.2. Calculate the term u 953 | let latitude_rad = angleToRad(coordinate.latitude) // 0.693637 954 | let u: Rad = atan(0.99664719*tan(latitude_rad)) 955 | 956 | // 3.12.3. Calculate the term x 957 | let x: Rad = cos(u) + elevation/6378140.0*cos(latitude_rad) 958 | 959 | // 3.12.4. Calculate the term y 960 | let y: Rad = 0.99664719*sin(u) + elevation/6378140.0*sin(latitude_rad) 961 | 962 | // 3.12.5. Calculate the parallax in the sun right ascension 963 | let Δα: Rad = atan2(-x*sin(ξ_rad)*sin(H_rad), cos(δ) - x*sin(ξ_rad)*cos(H_rad)) 964 | let Δα_degrees: Angle = radToAngle(Δα) // -0.000369 965 | 966 | // 3.12.6. Calculate the topocentric sun right ascension 967 | var α_new: Angle? 968 | if calculation_tupel.doCalculateAscension { 969 | α_new = α_degrees + Δα_degrees // 202.227039 970 | } 971 | 972 | // 3.12.7. Calculate the topocentric sun declination 973 | let δ_new: Rad = atan2((sin(δ) - y*sin(ξ_rad))*cos(Δα), cos(δ) - x*sin(ξ_rad)*cos(H_rad)) // -9.316179 974 | 975 | // 3.13. Calculate the topocentric local hour angle 976 | let H_new: Angle = H - Δα_degrees // 11.106271 977 | let H_new_rad: Rad = angleToRad(H_new) // 978 | 979 | var e: Angle? 980 | if calculation_tupel.doCalculateHeight || calculation_tupel.doCalculateZenith || calculation_tupel.doCalculateIncidence || calculation_tupel.doCalculateShadowLength { 981 | // 3.14.1. Calculate the topocentric elevation angle without atmospheric refraction correction 982 | let e0: Rad = asin(sin(latitude_rad)*sin(δ_new) + cos(latitude_rad)*cos(δ_new)*cos(H_new_rad)) 983 | let e0_degrees: Angle = radToAngle(e0) // 39.872046 984 | 985 | // 3.14.2. Calculate the atmospheric refraction correction 986 | var Δe: Rad = 0 987 | if e0 >= -1*(SUN_RADIUS + 0.5667) { // not in paper 988 | Δe = (pressure/1010.0)*(283.0/(273.0 + temperature))*1.02/(radToAngle(60.0*tan(angleToRad(e0_degrees + 10.3/(e0_degrees + 5.11))))) // 0.016332 989 | } 990 | let Δe_degrees: Angle = radToAngle(Δe) 991 | 992 | // 3.14.3. Calculate the topocentric elevation angle 993 | e = e0_degrees + Δe_degrees // 39.888378 994 | } 995 | 996 | // 3.14.4. Calculate the topocentric zenith angle 997 | var z: Angle? 998 | var zenith_rad: Angle? 999 | if let e = e , calculation_tupel.doCalculateZenith || calculation_tupel.doCalculateIncidence { 1000 | let zenith: Angle = 90.0 - e // 50.111622 1001 | zenith_rad = angleToRad(zenith) 1002 | z = zenith 1003 | } 1004 | 1005 | var Γ_degrees: Angle? 1006 | var Φ: Angle? 1007 | if calculation_tupel.doCalculateAzimuth || calculation_tupel.doCalculateIncidence || calculation_tupel.doCalculateShadowDirection { 1008 | // 3.15.1. Calculate the topocentric astronomers azimuth angle 1009 | let Γ: Rad = atan2(sin(H_new_rad), cos(H_new_rad)*sin(latitude_rad) - tan(δ_new)*cos(latitude_rad)) 1010 | var Γ_deg = radToAngle(Γ) 1011 | Γ_deg = clampAngleToThreeSixty(Γ_deg) 1012 | Γ_degrees = Γ_deg 1013 | 1014 | // 3.15.2. Calculate the topocentric azimuth angle, M for navigators and solar radiation users 1015 | let _Φ = Γ_deg + 180.0 1016 | Φ = clampAngleToThreeSixty(_Φ) // 194.340241 1017 | } 1018 | 1019 | // 3.16. Calculate the incidence angle for a surface oriented in any direction 1020 | var i: Angle? 1021 | if let zenith_rad = zenith_rad, let Γ_degrees = Γ_degrees , calculation_tupel.doCalculateIncidence { 1022 | let slope_rad: Rad = angleToRad(slope) 1023 | let I: Rad = acos(cos(zenith_rad)*cos(slope_rad) + sin(slope_rad)*sin(zenith_rad)*cos(angleToRad(Γ_degrees - surfaceAzimuth))) 1024 | i = radToAngle(I) // 25.187000 1025 | } 1026 | 1027 | var shadowLength: Double? 1028 | if let e = e , calculation_tupel.doCalculateShadowLength { 1029 | let e_rad = angleToRad(e) 1030 | shadowLength = buildingHeight*(sin(angleToRad(90) - e_rad)/sin(e_rad)) 1031 | } 1032 | 1033 | var shadowDirection: Angle? 1034 | if let Φ = Φ , calculation_tupel.doCalculateShadowDirection { 1035 | shadowDirection = Φ - 180 1036 | } 1037 | 1038 | return SunPosition(date: date, ascension: α_new, azimuth: Φ, height: e, zenith: z, incidence: i, shadowDirection: shadowDirection, shadowLength: shadowLength) 1039 | } 1040 | 1041 | 1042 | // MARK: - Sunrise, Sunset 1043 | 1044 | 1045 | /** 1046 | Calculates SunRiseSet for a given day. 1047 | 1048 | Source is the NREL [Article](http://rredc.nrel.gov/solar/codesandalgorithms/spa/) 1049 | 1050 | - parameter date: The point in time to calculate the SunRiseSet for 1051 | - parameter timeZoneOffset: Offset from UTC in hours 1052 | - parameter coordinate: The point on earth to calculate the SunRiseSet for 1053 | - parameter fragments: The selected SunRiseSet properties you need to calculate (fewer means faster processing) 1054 | 1055 | - returns: SunRiseSet object for the date with the selected properties (Fragments) 1056 | */ 1057 | public func NRELSunrise(for date: Date, timeZoneOffset: Int, coordinate: CLLocationCoordinate2D, fragments: [SunRiseSetFragment]) -> SunRiseSet { 1058 | let start = Date() 1059 | 1060 | func fractionOfDayToLocalHour(_ fractionOfDay: FractionOfDay, timeZoneOffset: Int) -> FractionOfDay { 1061 | return 24.0*clampBetweenOneAndZero(fractionOfDay + Double(timeZoneOffset)/24.0) 1062 | } 1063 | 1064 | var doCalculateSunriseDate = false 1065 | var doCalculateSunriseSunHeight = false 1066 | 1067 | var doCalculateSunsetDate = false 1068 | var doCalculateSunsetSunHeight = false 1069 | 1070 | var doCalculateTransitDate = false 1071 | var doCalculateTransitSunHeight = false 1072 | 1073 | for fragment in fragments { 1074 | switch fragment { 1075 | case .sunrise(let array): 1076 | for subFragment in array { 1077 | switch subFragment { 1078 | case .date: 1079 | doCalculateSunriseDate = true 1080 | case .height: 1081 | doCalculateSunriseSunHeight = true 1082 | } 1083 | } 1084 | case .sunset(let array): 1085 | for subFragment in array { 1086 | switch subFragment { 1087 | case .date: 1088 | doCalculateSunsetDate = true 1089 | case .height: 1090 | doCalculateSunsetSunHeight = true 1091 | } 1092 | } 1093 | case .transit(let array): 1094 | for subFragment in array { 1095 | switch subFragment { 1096 | case .date: 1097 | doCalculateTransitDate = true 1098 | case .height: 1099 | doCalculateTransitSunHeight = true 1100 | } 1101 | } 1102 | } 1103 | } 1104 | 1105 | 1106 | var transitDate: Date? 1107 | var sunriseDate: Date? 1108 | var sunsetDate: Date? 1109 | 1110 | 1111 | var h_0: Angle? 1112 | var h_1: Angle? 1113 | var h_2: Angle? 1114 | 1115 | 1116 | let components = Set([.year, .month, .day]) 1117 | let dateComponents = Calendar.current.dateComponents(components, from: date) 1118 | guard let zeroDate = Calendar.current.date(from: dateComponents) else { return SunRiseSet.empty } 1119 | 1120 | let JD_today = julianDay(for: zeroDate, timeZoneOffset: 0) 1121 | let JDE_today = julianEphemerisDay(for: JD_today) 1122 | let JC_today = julianCentury(for: JD_today) 1123 | let JCE_today = julianCentury(for: JDE_today) 1124 | let JME_today = julianEphemerisMillenium(for: JCE_today) 1125 | 1126 | let JD_yesterday = JD_today - 1.0 1127 | let JDE_yesterday = julianEphemerisDay(for: JD_yesterday) 1128 | let JCE_yesterday = julianCentury(for: JDE_yesterday) 1129 | let JME_yesterday = julianEphemerisMillenium(for: JCE_yesterday) 1130 | 1131 | let JD_tomorrow = JD_today + 1.0 1132 | let JDE_tomorrow = julianEphemerisDay(for: JD_tomorrow) 1133 | let JCE_tomorrow = julianCentury(for: JDE_tomorrow) 1134 | let JME_tomorrow = julianEphemerisMillenium(for: JCE_tomorrow) 1135 | 1136 | 1137 | // A.2.1. 1138 | let tupel_today = trueObliquityEcliptic(for: JCE_today, JME: JME_today) 1139 | let ν: Angle = apparentSiderealTimeAtGreenwich(for: JD_today, JC: JC_today, ε_rad: tupel_today.ε_rad, ΔΨ: tupel_today.ΔΨ.angle) 1140 | 1141 | 1142 | // A.2.2. 1143 | // today 1144 | let Θ_0 = geocentricLongitude(for: JME_today) 1145 | let β_rad_0 = geocentricLatitude(for: JME_today) 1146 | let λ_rad_0 = apparentSunLongitude(for: JME_today, Θ: Θ_0, ΔΨ: tupel_today.ΔΨ.angle) 1147 | let α_0 = sunRightAscension(for: β_rad_0, ε_rad: tupel_today.ε_rad, λ_rad: λ_rad_0) 1148 | let δ_0 = geocentricSunDeclination(for: β_rad_0, ε_rad: tupel_today.ε_rad, λ_rad: λ_rad_0) 1149 | // yesterday 1150 | let tupel_yesterday = trueObliquityEcliptic(for: JCE_yesterday, JME: JME_yesterday) 1151 | let Θ_yesterday = geocentricLongitude(for: JME_yesterday) 1152 | let β_rad_yesterday = geocentricLatitude(for: JME_yesterday) 1153 | let λ_rad_yesterday = apparentSunLongitude(for: JME_yesterday, Θ: Θ_yesterday, ΔΨ: tupel_yesterday.ΔΨ.angle) 1154 | let α_yesterday = sunRightAscension(for: β_rad_yesterday, ε_rad: tupel_yesterday.ε_rad, λ_rad: λ_rad_yesterday) 1155 | let δ_yesterday = geocentricSunDeclination(for: β_rad_yesterday, ε_rad: tupel_yesterday.ε_rad, λ_rad: λ_rad_yesterday) 1156 | // tomorrow 1157 | let tupel_tomorrow = trueObliquityEcliptic(for: JCE_tomorrow, JME: JME_tomorrow) 1158 | let Θ_tomorrow = geocentricLongitude(for: JME_tomorrow) 1159 | let β_rad_tomorrow = geocentricLatitude(for: JME_tomorrow) 1160 | let λ_rad_tomorrow = apparentSunLongitude(for: JME_tomorrow, Θ: Θ_tomorrow, ΔΨ: tupel_tomorrow.ΔΨ.angle) 1161 | let α_tomorrow = sunRightAscension(for: β_rad_tomorrow, ε_rad: tupel_tomorrow.ε_rad, λ_rad: λ_rad_tomorrow) 1162 | let δ_tomorrow = geocentricSunDeclination(for: β_rad_tomorrow, ε_rad: tupel_tomorrow.ε_rad, λ_rad: λ_rad_tomorrow) 1163 | 1164 | 1165 | // A.2.3. 1166 | var m_0: Angle = (α_0.angle - coordinate.longitude - ν)/360.0 1167 | 1168 | 1169 | // A.2.4. 1170 | let latitude_rad: Rad = angleToRad(coordinate.latitude) 1171 | let h_0_angle = -0.83337 1172 | let h_0_prime_rad: Rad = angleToRad(h_0_angle) 1173 | let argmument = (sin(h_0_prime_rad) - sin(latitude_rad)*sin(δ_0.rad))/(cos(latitude_rad)*cos(δ_0.rad)) 1174 | 1175 | if argmument < -1 || argmument > 1 { 1176 | return SunRiseSet.empty 1177 | } 1178 | 1179 | let H_0 = acos(argmument) 1180 | 1181 | var H_0_degree: Angle = radToAngle(H_0) 1182 | H_0_degree = clampAngleToOneEighty(H_0_degree) 1183 | 1184 | 1185 | // A.2.7. 1186 | m_0 = clampBetweenOneAndZero(m_0) 1187 | 1188 | 1189 | // A.2.10. 1190 | var a: Angle = α_0.angle - α_yesterday.angle 1191 | if fabs(a) > 2 { 1192 | a = clampBetweenOneAndZero(a) 1193 | } 1194 | var b: Angle = α_tomorrow.angle - α_0.angle 1195 | if fabs(b) > 2 { 1196 | b = clampBetweenOneAndZero(b) 1197 | } 1198 | let c: Angle = b - a 1199 | var a_: Angle = δ_0.angle - δ_yesterday.angle 1200 | if fabs(a_) > 2 { 1201 | a_ = clampBetweenOneAndZero(a_) 1202 | } 1203 | var b_: Angle = δ_tomorrow.angle - δ_0.angle 1204 | if fabs(b_) > 2 { 1205 | b_ = clampBetweenOneAndZero(b_) 1206 | } 1207 | let c_: Angle = b_ - a_ 1208 | 1209 | 1210 | var H_transit: Angle = 0 1211 | if doCalculateTransitSunHeight || doCalculateTransitDate { 1212 | // A.2.8. 1213 | let ν_0: Angle = ν + 360.985647*m_0 1214 | 1215 | 1216 | // A.2.9. 1217 | let n_0: Angle = m_0 + ΔT/86400.0 1218 | 1219 | 1220 | // A.2.10. 1221 | let δ_0_new: Rad = angleToRad(δ_0.angle + (n_0*(a_ + b_ + c_*n_0))/2.0) 1222 | let α_0_new: Angle = α_0.angle + (n_0*(a + b + c*n_0))/2.0 1223 | 1224 | 1225 | 1226 | // A.2.11. 1227 | H_transit = clampAngleToOneEightyPM(ν_0 + coordinate.longitude - α_0_new) 1228 | let H_transit_rad: Rad = angleToRad(H_transit) 1229 | 1230 | 1231 | // A.2.12. 1232 | h_0 = radToAngle(asin(sin(latitude_rad)*sin(δ_0_new) + cos(latitude_rad)*cos(δ_0_new)*cos(H_transit_rad))) 1233 | } 1234 | 1235 | var m_1: Angle = 0 1236 | var δ_1: Rad = 0 1237 | var H_sunrise_rad: Rad = 0 1238 | if doCalculateSunriseSunHeight || doCalculateSunriseDate { 1239 | // A.2.5. 1240 | m_1 = m_0 - H_0_degree/360.0 1241 | 1242 | // A.2.7. 1243 | m_1 = clampBetweenOneAndZero(m_1) 1244 | 1245 | 1246 | // A.2.8. 1247 | let ν_1: Angle = ν + 360.985647*m_1 1248 | 1249 | 1250 | // A.2.9. 1251 | let n_1: Angle = m_1 + ΔT/86400.0 1252 | 1253 | 1254 | // A.2.10. 1255 | let α_1: Angle = α_0.angle + (n_1*(a + b + c*n_1))/2.0 1256 | δ_1 = angleToRad(δ_0.angle + (n_1*(a_ + b_ + c_*n_1))/2.0) 1257 | 1258 | 1259 | // A.2.11. 1260 | let H_sunrise: Angle = clampAngleToOneEightyPM(ν_1 + coordinate.longitude - α_1) 1261 | 1262 | 1263 | // A.2.12. 1264 | H_sunrise_rad = angleToRad(H_sunrise) 1265 | h_1 = radToAngle(asin(sin(latitude_rad)*sin(δ_1) + cos(latitude_rad)*cos(δ_1)*cos(H_sunrise_rad))) 1266 | } 1267 | 1268 | var m_2: Angle = 0 1269 | var δ_2: Rad = 0 1270 | var H_sunset_rad: Rad = 0 1271 | if doCalculateSunsetSunHeight || doCalculateSunsetDate { 1272 | // A.2.6. 1273 | m_2 = m_0 + H_0_degree/360.0 1274 | 1275 | 1276 | // A.2.7. 1277 | m_2 = clampBetweenOneAndZero(m_2) 1278 | 1279 | 1280 | // A.2.8. 1281 | let ν_2: Angle = ν + 360.985647*m_2 1282 | 1283 | 1284 | // A.2.9. 1285 | let n_2: Angle = m_2 + ΔT/86400.0 1286 | 1287 | 1288 | // A.2.10. 1289 | let α_2: Angle = α_0.angle + (n_2*(a + b + c*n_2))/2.0 1290 | δ_2 = angleToRad(δ_0.angle + (n_2*(a_ + b_ + c_*n_2))/2.0) 1291 | 1292 | // A.2.11. 1293 | let H_sunset: Angle = clampAngleToOneEightyPM(ν_2 + coordinate.longitude - α_2) 1294 | H_sunset_rad = angleToRad(H_sunset) 1295 | 1296 | 1297 | // A.2.12. 1298 | h_2 = radToAngle(asin(sin(latitude_rad)*sin(δ_2) + cos(latitude_rad)*cos(δ_2)*cos(H_sunset_rad))) 1299 | } 1300 | 1301 | 1302 | // A.2.13. 1303 | if doCalculateTransitDate { 1304 | var T: FractionOfDay = m_0 - H_transit/360.0 1305 | T = fractionOfDayToLocalHour(T, timeZoneOffset: timeZoneOffset) 1306 | transitDate = T.dateByAdding(date) 1307 | } 1308 | 1309 | 1310 | // A.2.14. 1311 | if let h_1 = h_1 , doCalculateSunriseDate { 1312 | var R: FractionOfDay = m_1 + (h_1 - h_0_angle)/(360.0*cos(δ_1)*cos(latitude_rad)*sin(H_sunrise_rad)) 1313 | R = fractionOfDayToLocalHour(R, timeZoneOffset: timeZoneOffset) 1314 | sunriseDate = R.dateByAdding(date) 1315 | } 1316 | 1317 | 1318 | // A.2.15. 1319 | if let h_2 = h_2 , doCalculateSunsetDate { 1320 | var S: FractionOfDay = m_2 + (h_2 - h_0_angle)/(360.0*cos(δ_2)*cos(latitude_rad)*sin(H_sunset_rad)) 1321 | S = fractionOfDayToLocalHour(S, timeZoneOffset: timeZoneOffset) 1322 | sunsetDate = S.dateByAdding(date) 1323 | } 1324 | 1325 | 1326 | return SunRiseSet(sunriseDate: sunriseDate, sunriseHeight: h_1, sunsetDate: sunsetDate, sunsetHeight: h_2, transitDate: transitDate, transitHeight: h_0) 1327 | } 1328 | 1329 | 1330 | // MARK: - Helper 1331 | 1332 | 1333 | func calculateTerm(with array: [[[Double]]], index: Int, ephemerisMillenium: JulianEphemerisMillenium) -> Rad { 1334 | var sum = 0.0 1335 | let row_array = array[index] 1336 | for values in row_array { 1337 | let A = values[0] 1338 | let B = values[1] 1339 | let C = values[2] 1340 | let result = A*cos(B + C*ephemerisMillenium) 1341 | sum += result 1342 | } 1343 | 1344 | return sum 1345 | } 1346 | 1347 | 1348 | func calculateTerms(with array: [[[Double]]], index: Int, ephemerisMillenia: [JulianEphemerisMillenium]) -> [Rad] { 1349 | let row_array = array[index] 1350 | 1351 | let length = vDSP_Length(ephemerisMillenia.count) 1352 | var sums = Array(repeating: 0, count: ephemerisMillenia.count) 1353 | var valuesArray = Array(repeating: 0, count: ephemerisMillenia.count) 1354 | 1355 | for index in 0.. Double { 1388 | var limited = 0.0 1389 | 1390 | let degrees = angle/360.0 1391 | limited = 360.0*(degrees - Double(Int(degrees))) 1392 | 1393 | if limited < 0 { 1394 | limited += 360.0 1395 | } 1396 | 1397 | return limited 1398 | } 1399 | 1400 | 1401 | func clampAnglesToThreeSixty(_ angles: [Double]) -> [Double] { 1402 | var clampedAngles = Array(repeating: 0, count: angles.count) 1403 | 1404 | let length = vDSP_Length(angles.count) 1405 | 1406 | // divide through 360 1407 | var threeSixty = 360.0 1408 | vDSP_vsdivD(angles, 1, &threeSixty, &clampedAngles, 1, length) 1409 | 1410 | // get int part from clampedAngles 1411 | var degrees_double = Array(repeating: 0, count: angles.count) 1412 | var count = Int32(angles.count) 1413 | vvfloor(°rees_double, &clampedAngles, &count) 1414 | 1415 | // remove double_degrees from clampedAngles 1416 | vDSP_vsubD(degrees_double, 1, clampedAngles, 1, &clampedAngles, 1, length) 1417 | 1418 | // multiply clampedAngles with 360 1419 | vDSP_vsmulD(clampedAngles, 1, &threeSixty, &clampedAngles, 1, length) 1420 | 1421 | // add 360 if smaller 0 1422 | // FIXME: find vectorized solution 1423 | for index in 0.. Double { 1436 | var limited = 0.0 1437 | 1438 | let degrees = angle/180.0 1439 | limited = 180.0*(degrees - Double(Int(degrees))) 1440 | 1441 | if limited < 0 { 1442 | limited += 180.0 1443 | } 1444 | 1445 | return limited 1446 | } 1447 | 1448 | 1449 | func clampAngleToOneEightyPM(_ angle: Double) -> Double { 1450 | let degrees = angle/360.0 1451 | var limited = 360.0*(degrees - floor(degrees)) 1452 | if limited < -180.0 { 1453 | limited += 360.0; 1454 | } else if limited > 180.0 { 1455 | limited -= 360.0; 1456 | } 1457 | 1458 | return limited; 1459 | } 1460 | 1461 | 1462 | func clampBetweenOneAndZero(_ value: Double) -> Double { 1463 | var limited = value - floor(value) 1464 | 1465 | if limited < 0 { 1466 | limited += 1.0 1467 | } 1468 | 1469 | return limited 1470 | } 1471 | 1472 | 1473 | // MARK: - private 1474 | 1475 | 1476 | private let SUN_RADIUS = 0.26667 1477 | private let L_TERMS: [[[Double]]] = [ 1478 | [ 1479 | [175347046.0,0,0], 1480 | [3341656.0,4.6692568,6283.07585], 1481 | [34894.0,4.6261,12566.1517], 1482 | [3497.0,2.7441,5753.3849], 1483 | [3418.0,2.8289,3.5231], 1484 | [3136.0,3.6277,77713.7715], 1485 | [2676.0,4.4181,7860.4194], 1486 | [2343.0,6.1352,3930.2097], 1487 | [1324.0,0.7425,11506.7698], 1488 | [1273.0,2.0371,529.691], 1489 | [1199.0,1.1096,1577.3435], 1490 | [990,5.233,5884.927], 1491 | [902,2.045,26.298], 1492 | [857,3.508,398.149], 1493 | [780,1.179,5223.694], 1494 | [753,2.533,5507.553], 1495 | [505,4.583,18849.228], 1496 | [492,4.205,775.523], 1497 | [357,2.92,0.067], 1498 | [317,5.849,11790.629], 1499 | [284,1.899,796.298], 1500 | [271,0.315,10977.079], 1501 | [243,0.345,5486.778], 1502 | [206,4.806,2544.314], 1503 | [205,1.869,5573.143], 1504 | [202,2.458,6069.777], 1505 | [156,0.833,213.299], 1506 | [132,3.411,2942.463], 1507 | [126,1.083,20.775], 1508 | [115,0.645,0.98], 1509 | [103,0.636,4694.003], 1510 | [102,0.976,15720.839], 1511 | [102,4.267,7.114], 1512 | [99,6.21,2146.17], 1513 | [98,0.68,155.42], 1514 | [86,5.98,161000.69], 1515 | [85,1.3,6275.96], 1516 | [85,3.67,71430.7], 1517 | [80,1.81,17260.15], 1518 | [79,3.04,12036.46], 1519 | [75,1.76,5088.63], 1520 | [74,3.5,3154.69], 1521 | [74,4.68,801.82], 1522 | [70,0.83,9437.76], 1523 | [62,3.98,8827.39], 1524 | [61,1.82,7084.9], 1525 | [57,2.78,6286.6], 1526 | [56,4.39,14143.5], 1527 | [56,3.47,6279.55], 1528 | [52,0.19,12139.55], 1529 | [52,1.33,1748.02], 1530 | [51,0.28,5856.48], 1531 | [49,0.49,1194.45], 1532 | [41,5.37,8429.24], 1533 | [41,2.4,19651.05], 1534 | [39,6.17,10447.39], 1535 | [37,6.04,10213.29], 1536 | [37,2.57,1059.38], 1537 | [36,1.71,2352.87], 1538 | [36,1.78,6812.77], 1539 | [33,0.59,17789.85], 1540 | [30,0.44,83996.85], 1541 | [30,2.74,1349.87], 1542 | [25,3.16,4690.48] 1543 | ], 1544 | [ 1545 | [628331966747.0,0,0], 1546 | [206059.0,2.678235,6283.07585], 1547 | [4303.0,2.6351,12566.1517], 1548 | [425.0,1.59,3.523], 1549 | [119.0,5.796,26.298], 1550 | [109.0,2.966,1577.344], 1551 | [93,2.59,18849.23], 1552 | [72,1.14,529.69], 1553 | [68,1.87,398.15], 1554 | [67,4.41,5507.55], 1555 | [59,2.89,5223.69], 1556 | [56,2.17,155.42], 1557 | [45,0.4,796.3], 1558 | [36,0.47,775.52], 1559 | [29,2.65,7.11], 1560 | [21,5.34,0.98], 1561 | [19,1.85,5486.78], 1562 | [19,4.97,213.3], 1563 | [17,2.99,6275.96], 1564 | [16,0.03,2544.31], 1565 | [16,1.43,2146.17], 1566 | [15,1.21,10977.08], 1567 | [12,2.83,1748.02], 1568 | [12,3.26,5088.63], 1569 | [12,5.27,1194.45], 1570 | [12,2.08,4694], 1571 | [11,0.77,553.57], 1572 | [10,1.3,6286.6], 1573 | [10,4.24,1349.87], 1574 | [9,2.7,242.73], 1575 | [9,5.64,951.72], 1576 | [8,5.3,2352.87], 1577 | [6,2.65,9437.76], 1578 | [6,4.67,4690.48] 1579 | ], 1580 | [ 1581 | [52919.0,0,0], 1582 | [8720.0,1.0721,6283.0758], 1583 | [309.0,0.867,12566.152], 1584 | [27,0.05,3.52], 1585 | [16,5.19,26.3], 1586 | [16,3.68,155.42], 1587 | [10,0.76,18849.23], 1588 | [9,2.06,77713.77], 1589 | [7,0.83,775.52], 1590 | [5,4.66,1577.34], 1591 | [4,1.03,7.11], 1592 | [4,3.44,5573.14], 1593 | [3,5.14,796.3], 1594 | [3,6.05,5507.55], 1595 | [3,1.19,242.73], 1596 | [3,6.12,529.69], 1597 | [3,0.31,398.15], 1598 | [3,2.28,553.57], 1599 | [2,4.38,5223.69], 1600 | [2,3.75,0.98] 1601 | ], 1602 | [ 1603 | [289.0,5.844,6283.076], 1604 | [35,0,0], 1605 | [17,5.49,12566.15], 1606 | [3,5.2,155.42], 1607 | [1,4.72,3.52], 1608 | [1,5.3,18849.23], 1609 | [1,5.97,242.73] 1610 | ], 1611 | [ 1612 | [114.0,3.142,0], 1613 | [8,4.13,6283.08], 1614 | [1,3.84,12566.15] 1615 | ], 1616 | [ 1617 | [1,3.14,0] 1618 | ] 1619 | ] 1620 | private let B_TERMS: [[[Double]]] = [ 1621 | [ 1622 | [280.0,3.199,84334.662], 1623 | [102.0,5.422,5507.553], 1624 | [80,3.88,5223.69], 1625 | [44,3.7,2352.87], 1626 | [32,4,1577.34] 1627 | ], 1628 | [ 1629 | [9,3.9,5507.55], 1630 | [6,1.73,5223.69] 1631 | ] 1632 | ] 1633 | private let R_TERMS: [[[Double]]] = [ 1634 | [ 1635 | [100013989.0,0,0], 1636 | [1670700.0,3.0984635,6283.07585], 1637 | [13956.0,3.05525,12566.1517], 1638 | [3084.0,5.1985,77713.7715], 1639 | [1628.0,1.1739,5753.3849], 1640 | [1576.0,2.8469,7860.4194], 1641 | [925.0,5.453,11506.77], 1642 | [542.0,4.564,3930.21], 1643 | [472.0,3.661,5884.927], 1644 | [346.0,0.964,5507.553], 1645 | [329.0,5.9,5223.694], 1646 | [307.0,0.299,5573.143], 1647 | [243.0,4.273,11790.629], 1648 | [212.0,5.847,1577.344], 1649 | [186.0,5.022,10977.079], 1650 | [175.0,3.012,18849.228], 1651 | [110.0,5.055,5486.778], 1652 | [98,0.89,6069.78], 1653 | [86,5.69,15720.84], 1654 | [86,1.27,161000.69], 1655 | [65,0.27,17260.15], 1656 | [63,0.92,529.69], 1657 | [57,2.01,83996.85], 1658 | [56,5.24,71430.7], 1659 | [49,3.25,2544.31], 1660 | [47,2.58,775.52], 1661 | [45,5.54,9437.76], 1662 | [43,6.01,6275.96], 1663 | [39,5.36,4694], 1664 | [38,2.39,8827.39], 1665 | [37,0.83,19651.05], 1666 | [37,4.9,12139.55], 1667 | [36,1.67,12036.46], 1668 | [35,1.84,2942.46], 1669 | [33,0.24,7084.9], 1670 | [32,0.18,5088.63], 1671 | [32,1.78,398.15], 1672 | [28,1.21,6286.6], 1673 | [28,1.9,6279.55], 1674 | [26,4.59,10447.39] 1675 | ], 1676 | [ 1677 | [103019.0,1.10749,6283.07585], 1678 | [1721.0,1.0644,12566.1517], 1679 | [702.0,3.142,0], 1680 | [32,1.02,18849.23], 1681 | [31,2.84,5507.55], 1682 | [25,1.32,5223.69], 1683 | [18,1.42,1577.34], 1684 | [10,5.91,10977.08], 1685 | [9,1.42,6275.96], 1686 | [9,0.27,5486.78] 1687 | ], 1688 | [ 1689 | [4359.0,5.7846,6283.0758], 1690 | [124.0,5.579,12566.152], 1691 | [12,3.14,0], 1692 | [9,3.63,77713.77], 1693 | [6,1.87,5573.14], 1694 | [3,5.47,18849.23] 1695 | ], 1696 | [ 1697 | [145.0,4.273,6283.076], 1698 | [7,3.92,12566.15] 1699 | ], 1700 | [ 1701 | [4,2.56,6283.08] 1702 | ] 1703 | ] 1704 | private let Y_TERMS: [[Double]] = [ 1705 | [0,0,0,0,1], 1706 | [-2,0,0,2,2], 1707 | [0,0,0,2,2], 1708 | [0,0,0,0,2], 1709 | [0,1,0,0,0], 1710 | [0,0,1,0,0], 1711 | [-2,1,0,2,2], 1712 | [0,0,0,2,1], 1713 | [0,0,1,2,2], 1714 | [-2,-1,0,2,2], 1715 | [-2,0,1,0,0], 1716 | [-2,0,0,2,1], 1717 | [0,0,-1,2,2], 1718 | [2,0,0,0,0], 1719 | [0,0,1,0,1], 1720 | [2,0,-1,2,2], 1721 | [0,0,-1,0,1], 1722 | [0,0,1,2,1], 1723 | [-2,0,2,0,0], 1724 | [0,0,-2,2,1], 1725 | [2,0,0,2,2], 1726 | [0,0,2,2,2], 1727 | [0,0,2,0,0], 1728 | [-2,0,1,2,2], 1729 | [0,0,0,2,0], 1730 | [-2,0,0,2,0], 1731 | [0,0,-1,2,1], 1732 | [0,2,0,0,0], 1733 | [2,0,-1,0,1], 1734 | [-2,2,0,2,2], 1735 | [0,1,0,0,1], 1736 | [-2,0,1,0,1], 1737 | [0,-1,0,0,1], 1738 | [0,0,2,-2,0], 1739 | [2,0,-1,2,1], 1740 | [2,0,1,2,2], 1741 | [0,1,0,2,2], 1742 | [-2,1,1,0,0], 1743 | [0,-1,0,2,2], 1744 | [2,0,0,2,1], 1745 | [2,0,1,0,0], 1746 | [-2,0,2,2,2], 1747 | [-2,0,1,2,1], 1748 | [2,0,-2,0,1], 1749 | [2,0,0,0,1], 1750 | [0,-1,1,0,0], 1751 | [-2,-1,0,2,1], 1752 | [-2,0,0,0,1], 1753 | [0,0,2,2,1], 1754 | [-2,0,2,0,1], 1755 | [-2,1,0,2,1], 1756 | [0,0,1,-2,0], 1757 | [-1,0,1,0,0], 1758 | [-2,1,0,0,0], 1759 | [1,0,0,0,0], 1760 | [0,0,1,2,0], 1761 | [0,0,-2,2,2], 1762 | [-1,-1,1,0,0], 1763 | [0,1,1,0,0], 1764 | [0,-1,1,2,2], 1765 | [2,-1,-1,2,2], 1766 | [0,0,3,2,2], 1767 | [2,-1,0,2,2], 1768 | ] 1769 | private let PE_TERMS: [[Double]] = [ 1770 | [-171996,-174.2,92025,8.9], 1771 | [-13187,-1.6,5736,-3.1], 1772 | [-2274,-0.2,977,-0.5], 1773 | [2062,0.2,-895,0.5], 1774 | [1426,-3.4,54,-0.1], 1775 | [712,0.1,-7,0], 1776 | [-517,1.2,224,-0.6], 1777 | [-386,-0.4,200,0], 1778 | [-301,0,129,-0.1], 1779 | [217,-0.5,-95,0.3], 1780 | [-158,0,0,0], 1781 | [129,0.1,-70,0], 1782 | [123,0,-53,0], 1783 | [63,0,0,0], 1784 | [63,0.1,-33,0], 1785 | [-59,0,26,0], 1786 | [-58,-0.1,32,0], 1787 | [-51,0,27,0], 1788 | [48,0,0,0], 1789 | [46,0,-24,0], 1790 | [-38,0,16,0], 1791 | [-31,0,13,0], 1792 | [29,0,0,0], 1793 | [29,0,-12,0], 1794 | [26,0,0,0], 1795 | [-22,0,0,0], 1796 | [21,0,-10,0], 1797 | [17,-0.1,0,0], 1798 | [16,0,-8,0], 1799 | [-16,0.1,7,0], 1800 | [-15,0,9,0], 1801 | [-13,0,7,0], 1802 | [-12,0,6,0], 1803 | [11,0,0,0], 1804 | [-10,0,5,0], 1805 | [-8,0,3,0], 1806 | [7,0,-3,0], 1807 | [-7,0,0,0], 1808 | [-7,0,3,0], 1809 | [-7,0,3,0], 1810 | [6,0,0,0], 1811 | [6,0,-3,0], 1812 | [6,0,-3,0], 1813 | [-6,0,3,0], 1814 | [-6,0,3,0], 1815 | [5,0,0,0], 1816 | [-5,0,3,0], 1817 | [-5,0,3,0], 1818 | [-5,0,3,0], 1819 | [4,0,0,0], 1820 | [4,0,0,0], 1821 | [4,0,0,0], 1822 | [-4,0,0,0], 1823 | [-4,0,0,0], 1824 | [-4,0,0,0], 1825 | [3,0,0,0], 1826 | [-3,0,0,0], 1827 | [-3,0,0,0], 1828 | [-3,0,0,0], 1829 | [-3,0,0,0], 1830 | [-3,0,0,0], 1831 | [-3,0,0,0], 1832 | [-3,0,0,0], 1833 | ] 1834 | 1835 | 1836 | private func thirdOrderPolynom(for a: Double, b: Double, c: Double, d: Double, x: Double) -> Double { 1837 | return ((a*x + b)*x + c)*x + d 1838 | } 1839 | 1840 | 1841 | private func apparentSunLongitude(for JME: JulianEphemerisMillenium, Θ: Angle, ΔΨ: Angle) -> Rad { 1842 | // 3.2.8. Calculate the Earth radius vector, R 1843 | let R0 = calculateTerm(with: R_TERMS, index: 0, ephemerisMillenium: JME) 1844 | let R1 = calculateTerm(with: R_TERMS, index: 1, ephemerisMillenium: JME) 1845 | let R2 = calculateTerm(with: R_TERMS, index: 2, ephemerisMillenium: JME) 1846 | let R3 = calculateTerm(with: R_TERMS, index: 3, ephemerisMillenium: JME) 1847 | let R4 = calculateTerm(with: R_TERMS, index: 4, ephemerisMillenium: JME) 1848 | let R_nominator = R0 + R1*JME + R2*pow(JME, 2) + R3*pow(JME, 3) + R4*pow(JME, 4) 1849 | let R = R_nominator/100000000.0 // 0.996542 1850 | 1851 | // 3.6. Calculate the aberration correction 1852 | let Δτ: Angle = -20.4898/(3600.0*R) 1853 | 1854 | // 3.7 Calculate the apparent sun longitude 1855 | let λ: Angle = Θ + ΔΨ + Δτ // 204.008552 1856 | let λ_rad = angleToRad(λ) // 3.560621 1857 | 1858 | return λ_rad 1859 | } 1860 | 1861 | 1862 | private func geocentricLongitude(for JME: JulianEphemerisMillenium) -> Angle { 1863 | // 3.2.1. and 3.2.2. Calculate the term L0 (in radians) 1864 | let L0: Rad = calculateTerm(with: L_TERMS, index: 0, ephemerisMillenium: JME) // 1865 | 1866 | // 3.2.3. Calculate the terms L1, L2, L3, L4, and L5 1867 | let L1: Rad = calculateTerm(with: L_TERMS, index: 1, ephemerisMillenium: JME) // 1868 | let L2: Rad = calculateTerm(with: L_TERMS, index: 2, ephemerisMillenium: JME) // 1869 | let L3: Rad = calculateTerm(with: L_TERMS, index: 3, ephemerisMillenium: JME) // 1870 | let L4: Rad = calculateTerm(with: L_TERMS, index: 4, ephemerisMillenium: JME) // 1871 | let L5: Rad = calculateTerm(with: L_TERMS, index: 5, ephemerisMillenium: JME) // 1872 | 1873 | // 3.2.4. Calculate the Earth heliocentric longitude, L (in radians) 1874 | let L_nominator: Rad = L0 + L1*JME + L2*pow(JME, 2) + L3*pow(JME, 3) + L4*pow(JME, 4) + L5*pow(JME, 5) 1875 | let L: Rad = L_nominator/100000000.0 1876 | 1877 | // 3.2.5. Calculate L in degrees, 1878 | let L_degrees = radToAngle(L) // 24.01826 1879 | 1880 | // 3.3.1. Calculate the geocentric longitude, 1 (in degrees) 1881 | var Θ: Angle = L_degrees + 180.0 1882 | 1883 | // 3.3.2. Limit 1 to the range from 0 to 360 1884 | Θ = clampAngleToThreeSixty(Θ) // 1885 | 1886 | return Θ 1887 | } 1888 | 1889 | 1890 | private func geocentricLatitude(for JME: JulianEphemerisMillenium) -> Rad { 1891 | // 3.2.7. Calculate the Earth heliocentric latitude, 1892 | let B0: Angle = radToAngle(calculateTerm(with: B_TERMS, index: 0, ephemerisMillenium: JME)) // 1893 | let B1: Angle = radToAngle(calculateTerm(with: B_TERMS, index: 1, ephemerisMillenium: JME)) // 1894 | let B_nominator: Angle = B0 + B1*JME 1895 | let B: Angle = B_nominator/100000000.0 // -0.0001011219 1896 | 1897 | // 3.3.3. Calculate the geocentric latitude, beta (in degrees), 1898 | let β: Angle = -B // 1899 | let β_rad: Rad = angleToRad(β) // 0.000002 1900 | 1901 | return β_rad 1902 | } 1903 | 1904 | 1905 | private func geocentricSunDeclination(for β_rad: Rad, ε_rad: Rad, λ_rad: Rad) -> (rad: Rad, angle: Angle) { 1906 | // 3.10. Calculate the geocentric sun declination 1907 | let δ: Rad = asin(sin(β_rad)*cos(ε_rad) + cos(β_rad)*sin(ε_rad)*sin(λ_rad)) // 1908 | 1909 | return (δ, radToAngle(δ)) 1910 | } 1911 | 1912 | 1913 | private func sunRightAscension(for β_rad: Rad, ε_rad: Rad, λ_rad: Rad) -> (rad: Rad, angle: Angle) { 1914 | // 3.9.1. Calculate the sun right ascension 1915 | let atan_nominator = sin(λ_rad)*cos(ε_rad) - tan(β_rad)*sin(ε_rad) 1916 | let atan_denominator = cos(λ_rad) 1917 | let α: Rad = atan2(atan_nominator, atan_denominator) 1918 | 1919 | // 3.9.2. Calculate alhpa in degrees using Equation 12, then limit it to the range from 0 to 360 1920 | var α_degrees = radToAngle(α) 1921 | α_degrees = clampAngleToThreeSixty(α_degrees) // 1922 | 1923 | return (α, α_degrees) 1924 | } 1925 | 1926 | 1927 | private func apparentSiderealTimeAtGreenwich(for JD: JulianDay, JC: JulianCentury, ε_rad: Rad, ΔΨ: Angle) -> Angle { 1928 | // 3.8.1. Calculate the mean sidereal time at Greenwich 1929 | let left = 280.46061837 + 360.98564736629*(JD - 2451545.0) 1930 | let right = JC*JC*(0.000387933 - JC/38710000.0) 1931 | var ν0: Angle = left + right // 318.515578 1932 | 1933 | // 3.8.2. Limit v_zero to the range from 0 to 360 1934 | ν0 = clampAngleToThreeSixty(ν0) 1935 | 1936 | // 3.8.3. Calculate the apparent sidereal time at Greenwich 1937 | let ν: Angle = ν0 + ΔΨ*cos(ε_rad) 1938 | 1939 | return ν 1940 | } 1941 | 1942 | 1943 | private func trueObliquityEcliptic(for JCE: JulianCentury, JME: JulianEphemerisMillenium) -> (ΔΨ: (angle: Angle, rad: Rad), Δε: Rad, ε_rad: Rad) { 1944 | // 3.4.1. Calculate the mean elongation of the moon from the sun, X0 (in degrees) // ((a*x + b)*x + c)*x + d; 1945 | let X0: Angle = thirdOrderPolynom(for: 1.0/189474.0, b: -0.0019142, c: 445267.111480, d: 297.85036, x: JCE) 1946 | 1947 | // 3.4.2. Calculate the mean anomaly of the sun (Earth), X1 (in degrees) 1948 | let X1: Angle = thirdOrderPolynom(for: -1.0/300000.0, b: -0.0001603, c: 35999.050340, d: 357.52772, x: JCE) 1949 | 1950 | // 3.4.3. Calculate the mean anomaly of the moon, X2 (in degrees) 1951 | let X2: Angle = thirdOrderPolynom(for: 1.0/56250.0, b: 0.0086972, c: 477198.867398, d: 134.96298, x: JCE) 1952 | 1953 | // 3.4.4. Calculate the moon’s argument of latitude, X3 (in degrees) 1954 | let X3: Angle = thirdOrderPolynom(for: 1.0/327270.0, b: -0.0036825, c: 483202.017538, d: 93.27191, x: JCE) 1955 | 1956 | // 3.4.5. Calculate the longitude of the ascending node of the moon’s mean orbit on the ecliptic, measured from the mean equinox of the date, X4 (in degrees) 1957 | let X4: Angle = thirdOrderPolynom(for: 1.0/450000.0, b: 0.0020708, c: -1934.136261, d: 125.04452, x: JCE) 1958 | 1959 | // 3.4.6. to 3.4.8 1960 | var ΔΨ: Angle = 0.0 1961 | var Δε: Angle = 0.0 1962 | for i in 0.. (doCalculateAscension: Bool, doCalculateAzimuth: Bool, doCalculateHeight: Bool, doCalculateIncidence: Bool, doCalculateZenith: Bool, doCalculateShadowLength: Bool, doCalculateShadowDirection: Bool) { 2004 | var doCalculateAscension = false 2005 | var doCalculateAzimuth = false 2006 | var doCalculateHeight = false 2007 | var doCalculateIncidence = false 2008 | var doCalculateZenith = false 2009 | var doCalculateShadowDirection = false 2010 | var doCalculateShadowLength = false 2011 | 2012 | for fragment in fragments { 2013 | switch fragment { 2014 | case .ascension: 2015 | doCalculateAscension = true 2016 | case .azimuth: 2017 | doCalculateAzimuth = true 2018 | case .height: 2019 | doCalculateHeight = true 2020 | case .incidence: 2021 | doCalculateIncidence = true 2022 | case .zenith: 2023 | doCalculateZenith = true 2024 | case .shadow(let array): 2025 | for subFragment in array { 2026 | switch subFragment { 2027 | case .direction: 2028 | doCalculateShadowDirection = true 2029 | case .length: 2030 | doCalculateShadowLength = true 2031 | } 2032 | } 2033 | } 2034 | } 2035 | 2036 | return (doCalculateAscension, doCalculateAzimuth, doCalculateHeight, doCalculateIncidence, doCalculateZenith, doCalculateShadowLength, doCalculateShadowDirection) 2037 | } 2038 | -------------------------------------------------------------------------------- /SunshineKit/Source/SPA/RadAndAngle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadAndAngle.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Accelerate 11 | 12 | 13 | public func radToAngle(_ rad: Rad) -> Angle { 14 | return (180.0/Double.pi)*rad 15 | } 16 | 17 | 18 | func radsToAngles(_ rads: [Rad]) -> [Angle] { 19 | var angles = Array(repeating: 180.0, count: rads.count) 20 | 21 | let length = vDSP_Length(rads.count) 22 | var divider = Double.pi 23 | 24 | // divide through pi 25 | vDSP_vsdivD(angles, 1, ÷r, &angles, 1, length) 26 | 27 | // multiply with rads 28 | vDSP_vmulD(angles, 1, rads, 1, &angles, 1, length) 29 | 30 | return angles 31 | } 32 | 33 | 34 | public func angleToRad(_ angle: Angle) -> Rad { 35 | return (Double.pi/180.0)*angle 36 | } 37 | 38 | 39 | func anglesToRads(_ angles: [Angle]) -> [Rad] { 40 | var rads = Array(repeating: Double.pi, count: angles.count) 41 | 42 | let length = vDSP_Length(angles.count) 43 | var divider = 180.0 44 | 45 | // divide pi through 180 46 | vDSP_vsdivD(rads, 1, ÷r, &rads, 1, length) 47 | 48 | // multiply with angles 49 | vDSP_vmulD(rads, 1, angles, 1, &rads, 1, length) 50 | 51 | return rads 52 | } 53 | -------------------------------------------------------------------------------- /SunshineKit/Source/SPA/Wikipedia/WikipediaSPA.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WikipediaSPA.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | /** 13 | Calculates the SunPosition for a date and location. 14 | 15 | Source is a German Wikipedia [Article](https://de.wikipedia.org/wiki/Sonnenstand) 16 | 17 | - parameter date: The point in time to calculate the sun position 18 | - parameter coordinate: The point on earth to calculate the sun position for 19 | - returns: A SunPosition object with date, azimuth and height 20 | */ 21 | func WikipediaSunPosition(for date: Date, coordinate: CLLocationCoordinate2D) -> SunPosition { 22 | let components = Set([.year, .month, .day, .hour, .minute, .second]) 23 | 24 | var calendar = Calendar(identifier: .gregorian) 25 | calendar.timeZone = TimeZone(secondsFromGMT: 0)! 26 | var dateComponents = calendar.dateComponents(components, from: date) 27 | let hour = dateComponents.hour ?? 0 28 | let minute = dateComponents.minute 29 | let second = dateComponents.second 30 | 31 | dateComponents.hour = 0 32 | dateComponents.minute = 0 33 | dateComponents.second = 0 34 | 35 | guard let zerothDate = calendar.date(from: dateComponents) else { return SunPosition.empty } 36 | 37 | let jDay = julianDay(from: date) 38 | let n = jDay - 2451545 39 | let L: Angle = 280.46 + 0.9856474*n 40 | let g: Angle = 357.528 + 0.9856003*n 41 | let delta: Rad = angleToRad(L) + angleToRad(1.915)*sin(angleToRad(g)) + angleToRad(0.01997)*sin(angleToRad(2*g)) 42 | let epsilon: Rad = angleToRad(23.439 - 0.0000004*n) 43 | 44 | let alphaNominator = cos(delta) 45 | var alpha: Angle = radToAngle(atan((cos(epsilon)*sin(delta))/alphaNominator)) 46 | 47 | if radToAngle(alphaNominator) < 0 { 48 | alpha += 180.0 49 | } 50 | 51 | let gamma: Rad = asin(sin(epsilon)*sin(delta)) 52 | 53 | let zerothJulianDay = julianDay(from: zerothDate) 54 | 55 | let timeZero = (zerothJulianDay - 2451545.0)/36525.0 56 | 57 | let hours: Double = Double(hour) + Double(minute!)/60.0 + Double(second!)/3600.0 // hours of day, e.g. 17,75 for 17:45h 58 | 59 | let meanRaiseTime = 6.697376 + 2400.05134*timeZero + 1.002738*hours 60 | 61 | let greenwichHourAngleSpringpoint: Angle = meanRaiseTime*15.0 62 | 63 | let hourAngleSpringPoint: Angle = greenwichHourAngleSpringpoint + coordinate.longitude 64 | 65 | let tau: Rad = angleToRad(hourAngleSpringPoint - alpha) 66 | 67 | let nominatorForAzimuth: Rad = cos(tau)*sin(angleToRad(coordinate.latitude)) - tan(gamma)*cos(angleToRad(coordinate.latitude)) 68 | 69 | var azimuth: Angle = radToAngle(atan(sin(tau)/nominatorForAzimuth)) 70 | 71 | if radToAngle(nominatorForAzimuth) < 0 { 72 | azimuth -= 180.0; 73 | } 74 | 75 | azimuth += 180.0 76 | 77 | if azimuth < 0 { 78 | azimuth += 360.0 79 | } 80 | 81 | var height: Angle = radToAngle(asin(cos(gamma)*cos(tau)*cos(angleToRad(coordinate.latitude)) + sin(gamma)*sin(angleToRad(coordinate.latitude)))) 82 | 83 | // corrected height for refraction 84 | let r: Angle = 1.02/tan(angleToRad(height + 10.3/(height + 5.11))) 85 | height = height + r/60.0; 86 | 87 | let sunPosition = SunPosition(date: date, azimuth: azimuth, height: height) 88 | 89 | return sunPosition 90 | } 91 | 92 | 93 | /** 94 | Calculates the Julian Day for a Date object. 95 | 96 | - parameter date: The point in time 97 | - returns: The Julian Day 98 | */ 99 | func julianDay(from date: Date) -> Double { 100 | let julianDay = date.timeIntervalSince1970/86400.0 + 2440587.5 101 | var intpart = 0.0 102 | let fractpart = modf(julianDay, &intpart) 103 | 104 | switch fractpart { 105 | case let fractpart where fractpart <= 0.25: 106 | return intpart + 0.25 107 | case let fractpart where fractpart <= 0.5: 108 | return intpart + 0.5 109 | case let fractpart where fractpart <= 0.75: 110 | return intpart + 0.75 111 | default: 112 | return intpart + 1 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /SunshineKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SunshineKitTests/Source/NREL/NRELJulianTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NRELJulianTests.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SunshineKit 11 | 12 | 13 | final class NRELJulianTests: XCTestCase { 14 | func testJulianEphemerisMilleniumForJulianCentury() { 15 | var dateComponents1 = DateComponents() 16 | dateComponents1.year = 2010 17 | dateComponents1.month = 5 18 | dateComponents1.day = 3 19 | dateComponents1.hour = 12 20 | 21 | let date = Calendar.current.date(from: dateComponents1)! 22 | 23 | let jd = julianDay(for: date, timeZoneOffset: 0) 24 | 25 | let century = julianCentury(for: jd) 26 | 27 | let millenium = julianEphemerisMillenium(for: century) 28 | XCTAssertEqual(millenium, 0.010335386721423683, accuracy: SunshineKitAccuracy) 29 | } 30 | 31 | 32 | func testJulianEphemerisMilleniaForJulianCenturies() { 33 | var dateComponents1 = DateComponents() 34 | dateComponents1.year = 2010 35 | dateComponents1.month = 5 36 | dateComponents1.day = 3 37 | dateComponents1.hour = 12 38 | 39 | let date = Calendar.current.date(from: dateComponents1)! 40 | 41 | let jd = julianDay(for: date, timeZoneOffset: 0) 42 | 43 | let century = julianCentury(for: jd) 44 | 45 | let millenium = julianEphemerisMillenium(for: century) 46 | 47 | let centuries = Array(repeating: century, count: 100) 48 | 49 | let otherMillenias = julianEphemerisMillenias(for: centuries) 50 | XCTAssertEqual(otherMillenias.count, 100) 51 | for m in otherMillenias { 52 | XCTAssertEqual(m, millenium, accuracy: SunshineKitAccuracy) 53 | } 54 | } 55 | 56 | 57 | func testJulianEphemerisDayForJulianDay() { 58 | var dateComponents1 = DateComponents() 59 | dateComponents1.year = 2000 60 | dateComponents1.month = 1 61 | dateComponents1.day = 1 62 | dateComponents1.hour = 12 63 | 64 | let date = Calendar.current.date(from: dateComponents1)! 65 | 66 | let jd = julianDay(for: date, timeZoneOffset: 0) 67 | 68 | let day = julianEphemerisDay(for: jd) 69 | XCTAssertEqual(day, 2451545.0007754629, accuracy: SunshineKitAccuracy) 70 | } 71 | 72 | 73 | func testJulianEphemerisDaysForJulianDays() { 74 | var dateComponents1 = DateComponents() 75 | dateComponents1.year = 2000 76 | dateComponents1.month = 1 77 | dateComponents1.day = 1 78 | dateComponents1.hour = 12 79 | 80 | let date = Calendar.current.date(from: dateComponents1)! 81 | 82 | let jd = julianDay(for: date, timeZoneOffset: 0) 83 | let ephemerisDay = julianEphemerisDay(for: jd) 84 | let julianDays = Array(repeating: jd, count: 100) 85 | 86 | let otherDays = julianEphemerisDays(for: julianDays) 87 | XCTAssertEqual(otherDays.count, 100) 88 | 89 | for e in otherDays { 90 | XCTAssertEqual(e, ephemerisDay, accuracy: SunshineKitAccuracy) 91 | } 92 | } 93 | 94 | 95 | func testJulianCenturyForJulianDay() { 96 | var dateComponents1 = DateComponents() 97 | dateComponents1.year = 2000 98 | dateComponents1.month = 1 99 | dateComponents1.day = 1 100 | dateComponents1.hour = 12 101 | 102 | let date = Calendar.current.date(from: dateComponents1)! 103 | 104 | let jd = julianDay(for: date, timeZoneOffset: 0) 105 | 106 | let century = julianCentury(for: jd) 107 | XCTAssertEqual(0, century) 108 | } 109 | 110 | 111 | func testJulianCenturiesForJulianDays() { 112 | var dateComponents1 = DateComponents() 113 | dateComponents1.year = 2003 114 | dateComponents1.month = 10 115 | dateComponents1.day = 17 116 | dateComponents1.hour = 12 117 | dateComponents1.minute = 30 118 | dateComponents1.second = 30 119 | 120 | let date = Calendar.current.date(from: dateComponents1)! 121 | 122 | let jd = julianDay(for: date, timeZoneOffset: 0) 123 | let century = julianCentury(for: jd) 124 | 125 | let julianDays = Array(repeating: jd, count: 100) 126 | 127 | let centuries = julianCenturies(for: julianDays) 128 | XCTAssertEqual(centuries.count, 100) 129 | 130 | for c in centuries { 131 | XCTAssertEqual(c, century, accuracy: SunshineKitAccuracy) 132 | } 133 | } 134 | 135 | 136 | func testJulianDayFromDate() { 137 | var dateComponents1 = DateComponents() 138 | dateComponents1.year = 2000 139 | dateComponents1.month = 1 140 | dateComponents1.day = 1 141 | dateComponents1.hour = 12 142 | 143 | let date = Calendar.current.date(from: dateComponents1)! 144 | 145 | let jd = julianDay(for: date, timeZoneOffset: 0) 146 | 147 | XCTAssertEqual(jd, 2451545.0, accuracy: SunshineKitAccuracy) 148 | 149 | var dateComponents2 = DateComponents() 150 | dateComponents2.year = 1988 151 | dateComponents2.month = 6 152 | dateComponents2.day = 19 153 | dateComponents2.hour = 12 154 | 155 | let date2 = Calendar.current.date(from: dateComponents2)! 156 | 157 | let julianDay2 = julianDay(for: date2, timeZoneOffset: 0) 158 | 159 | XCTAssertEqual(julianDay2, 2447332.0, accuracy: SunshineKitAccuracy) 160 | 161 | var dateComponents3 = DateComponents() 162 | dateComponents3.year = 2003 163 | dateComponents3.month = 10 164 | dateComponents3.day = 17 165 | dateComponents3.hour = 12 166 | dateComponents3.minute = 30 167 | dateComponents3.second = 30 168 | 169 | let date3 = Calendar.current.date(from: dateComponents3)! 170 | 171 | let julianDay3 = julianDay(for: date3, timeZoneOffset: -7) 172 | 173 | XCTAssertEqual(julianDay3, 2452930.312847, accuracy: SunshineKitAccuracy) 174 | } 175 | 176 | 177 | func testJulianDaysForDateWithSecondResolution() { 178 | var julianDates = [Double]() 179 | 180 | let now = Date() 181 | 182 | let nowDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: now) 183 | 184 | var dateComponents = DateComponents() 185 | dateComponents.year = nowDateComponents.year 186 | dateComponents.month = nowDateComponents.month 187 | dateComponents.day = nowDateComponents.day 188 | 189 | for hour in 0..<24 { 190 | dateComponents.hour = hour 191 | 192 | for minute in 0..<60 { 193 | dateComponents.minute = minute 194 | 195 | for second in 0..<60 { 196 | dateComponents.second = second 197 | 198 | let date = Calendar.current.date(from: dateComponents)! 199 | 200 | let julianDate = julianDay(for: date, timeZoneOffset: 0) 201 | 202 | julianDates.append(julianDate) 203 | } 204 | } 205 | } 206 | 207 | let fromDate = Calendar.current.date(from: nowDateComponents)! 208 | 209 | let otherJulianDates = julianDays(for: fromDate, timeZoneOffset: 0, withResolution: .second) 210 | XCTAssertEqual(otherJulianDates.count, 24*60*60) 211 | 212 | for i in 0..<24*60*60 { 213 | let value1 = julianDates[i] 214 | let value2 = otherJulianDates[i] 215 | 216 | XCTAssertEqual(value1, value2) 217 | } 218 | } 219 | 220 | 221 | func testJulianDaysForDateWithMinuteResolution() { 222 | var julianDates = [Double]() 223 | 224 | let now = Date() 225 | 226 | let nowDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: now) 227 | 228 | for hour in 0..<24 { 229 | for minute in 0..<60 { 230 | var dateComponents = DateComponents() 231 | dateComponents.year = nowDateComponents.year 232 | dateComponents.month = nowDateComponents.month 233 | dateComponents.day = nowDateComponents.day 234 | dateComponents.hour = hour 235 | dateComponents.minute = minute 236 | 237 | let date = Calendar.current.date(from: dateComponents)! 238 | 239 | let julianDate = julianDay(for: date, timeZoneOffset: 0) 240 | 241 | julianDates.append(julianDate) 242 | } 243 | } 244 | 245 | let fromDate = Calendar.current.date(from: nowDateComponents)! 246 | 247 | let otherJulianDates = julianDays(for: fromDate, timeZoneOffset: 0, withResolution: .minute) 248 | XCTAssertEqual(otherJulianDates.count, 1440) 249 | 250 | for i in 0..<24*60 { 251 | let value1 = julianDates[i] 252 | let value2 = otherJulianDates[i] 253 | 254 | XCTAssertEqual(value1, value2) 255 | } 256 | } 257 | 258 | 259 | func testJulianDaysForDateWithHourResolution() { 260 | var julianDates = [Double]() 261 | 262 | let now = Date() 263 | 264 | let nowDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: now) 265 | 266 | for hour in 0..<24 { 267 | var dateComponents = DateComponents() 268 | dateComponents.year = nowDateComponents.year 269 | dateComponents.month = nowDateComponents.month 270 | dateComponents.day = nowDateComponents.day 271 | dateComponents.hour = hour 272 | 273 | let date = Calendar.current.date(from: dateComponents)! 274 | 275 | let julianDate = julianDay(for: date, timeZoneOffset: 0) 276 | 277 | julianDates.append(julianDate) 278 | } 279 | 280 | let fromDate = Calendar.current.date(from: nowDateComponents)! 281 | 282 | let otherJulianDates = julianDays(for: fromDate, timeZoneOffset: 0, withResolution: .hour) 283 | XCTAssertEqual(otherJulianDates.count, 24) 284 | 285 | for i in 0..<24 { 286 | let value1 = julianDates[i] 287 | let value2 = otherJulianDates[i] 288 | 289 | XCTAssertEqual(value1, value2) 290 | } 291 | } 292 | 293 | 294 | func testJulianDaysForHour() { 295 | let now = Date() 296 | 297 | let nowDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: now) 298 | let fromDate = Calendar.current.date(from: nowDateComponents)! 299 | 300 | let otherJulianDates1 = julianDays(for: fromDate, forHour: 8, timeZoneOffset: 0, withResolution: .hour) 301 | XCTAssertEqual(otherJulianDates1.count, 2) 302 | var dateComponents = DateComponents() 303 | var index = 0 304 | for hour in 7..<9 { 305 | dateComponents.hour = hour 306 | let date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 307 | let julianDate = julianDay(for: date, timeZoneOffset: 0) 308 | XCTAssertEqual(julianDate, otherJulianDates1[index]) 309 | index += 1 310 | } 311 | 312 | 313 | let otherJulianDates2 = julianDays(for: fromDate, forHour: 0, timeZoneOffset: 0, withResolution: .hour) 314 | XCTAssertEqual(otherJulianDates2.count, 1) 315 | dateComponents = DateComponents() 316 | dateComponents.hour = 0 317 | var date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 318 | var julianDate = julianDay(for: date, timeZoneOffset: 0) 319 | XCTAssertEqual(julianDate, otherJulianDates2[0]) 320 | 321 | 322 | let otherJulianDates3 = julianDays(for: fromDate, forHour: 23, timeZoneOffset: 0, withResolution: .hour) 323 | XCTAssertEqual(otherJulianDates3.count, 2) 324 | dateComponents = DateComponents() 325 | index = 0 326 | for hour in 22..<24 { 327 | dateComponents.hour = hour 328 | let date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 329 | let julianDate = julianDay(for: date, timeZoneOffset: 0) 330 | XCTAssertEqual(julianDate, otherJulianDates3[index]) 331 | index += 1 332 | } 333 | 334 | 335 | let otherJulianDates4 = julianDays(for: fromDate, forHour: 8, timeZoneOffset: 0, withResolution: .minute) 336 | XCTAssertEqual(otherJulianDates4.count, 2*60) 337 | dateComponents = DateComponents() 338 | index = 0 339 | for hour in 7..<9 { 340 | dateComponents.hour = hour 341 | for minute in 0..<60 { 342 | dateComponents.minute = minute 343 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 344 | julianDate = julianDay(for: date, timeZoneOffset: 0) 345 | XCTAssertEqual(julianDate, otherJulianDates4[index*60 + minute]) 346 | } 347 | index += 1 348 | } 349 | 350 | 351 | let otherJulianDates5 = julianDays(for: fromDate, forHour: 0, timeZoneOffset: 0, withResolution: .minute) 352 | XCTAssertEqual(otherJulianDates5.count, 1*60) 353 | dateComponents = DateComponents() 354 | dateComponents.hour = 0 355 | for minute in 0..<60 { 356 | dateComponents.minute = minute 357 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 358 | julianDate = julianDay(for: date, timeZoneOffset: 0) 359 | XCTAssertEqual(julianDate, otherJulianDates5[minute]) 360 | } 361 | 362 | 363 | let otherJulianDates6 = julianDays(for: fromDate, forHour: 23, timeZoneOffset: 0, withResolution: .minute) 364 | XCTAssertEqual(otherJulianDates6.count, 2*60) 365 | dateComponents = DateComponents() 366 | index = 0 367 | for hour in 22..<24 { 368 | dateComponents.hour = hour 369 | 370 | for minute in 0..<60 { 371 | dateComponents.minute = minute 372 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 373 | julianDate = julianDay(for: date, timeZoneOffset: 0) 374 | XCTAssertEqual(julianDate, otherJulianDates6[index*60 + minute]) 375 | } 376 | 377 | index += 1 378 | } 379 | 380 | 381 | let otherJulianDates7 = julianDays(for: fromDate, forHour: 8, timeZoneOffset: 0, withResolution: .second) 382 | XCTAssertEqual(otherJulianDates7.count, 2*60*60) 383 | dateComponents = DateComponents() 384 | index = 0 385 | for hour in 7..<9 { 386 | dateComponents.hour = hour 387 | 388 | for minute in 0..<60 { 389 | dateComponents.minute = minute 390 | 391 | for second in 0..<60 { 392 | dateComponents.second = second 393 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 394 | julianDate = julianDay(for: date, timeZoneOffset: 0) 395 | XCTAssertEqual(julianDate, otherJulianDates7[index*3600 + minute*60 + second]) 396 | } 397 | } 398 | 399 | index += 1 400 | } 401 | 402 | 403 | let otherJulianDates8 = julianDays(for: fromDate, forHour: 0, timeZoneOffset: 0, withResolution: .second) 404 | XCTAssertEqual(otherJulianDates8.count, 1*60*60) 405 | dateComponents = DateComponents() 406 | dateComponents.hour = 0 407 | for minute in 0..<60 { 408 | dateComponents.minute = minute 409 | 410 | for second in 0..<60 { 411 | dateComponents.second = second 412 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 413 | julianDate = julianDay(for: date, timeZoneOffset: 0) 414 | XCTAssertEqual(julianDate, otherJulianDates8[minute*60 + second]) 415 | } 416 | } 417 | 418 | let otherJulianDates9 = julianDays(for: fromDate, forHour: 23, timeZoneOffset: 0, withResolution: .second) 419 | XCTAssertEqual(otherJulianDates9.count, 2*60*60) 420 | dateComponents = DateComponents() 421 | index = 0 422 | for hour in 22..<24 { 423 | dateComponents.hour = hour 424 | for minute in 0..<60 { 425 | dateComponents.minute = minute 426 | 427 | for second in 0..<60 { 428 | dateComponents.second = second 429 | date = Calendar.current.date(byAdding: dateComponents, to: fromDate)! 430 | julianDate = julianDay(for: date, timeZoneOffset: 0) 431 | XCTAssertEqual(julianDate, otherJulianDates9[index*3600 + minute*60 + second]) 432 | } 433 | } 434 | 435 | index += 1 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /SunshineKitTests/Source/NREL/NRELSPATests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPATests.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 25.06.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SunshineKit 11 | import CoreLocation 12 | 13 | 14 | final class NRELSPATests: XCTestCase { 15 | 16 | 17 | // MARK: - other tests 18 | 19 | 20 | func testClampedAnglesToThreeSixty() { 21 | let angle1 = 480.0 22 | let clampedAngle1 = clampAngleToThreeSixty(angle1) 23 | 24 | let anglesToClamp1 = Array(repeating: angle1, count: 100) 25 | 26 | let clampedAngles1 = clampAnglesToThreeSixty(anglesToClamp1) 27 | XCTAssertEqual(clampedAngles1.count, 100) 28 | 29 | for angle in clampedAngles1 { 30 | XCTAssertEqual(angle, clampedAngle1, accuracy: SunshineKitAccuracy) 31 | } 32 | 33 | 34 | let angle2 = -380.0 35 | let clampedAngle2 = clampAngleToThreeSixty(angle2) 36 | 37 | let anglesToClamp2 = Array(repeating: angle2, count: 100) 38 | 39 | let clampedAngles2 = clampAnglesToThreeSixty(anglesToClamp2) 40 | XCTAssertEqual(clampedAngles2.count, 100) 41 | 42 | for angle in clampedAngles2 { 43 | XCTAssertEqual(angle, clampedAngle2, accuracy: SunshineKitAccuracy) 44 | } 45 | } 46 | 47 | 48 | func testClampAngleToThreeSixty() { 49 | let angle1 = 480.0 50 | let value1 = clampAngleToThreeSixty(angle1) 51 | 52 | XCTAssertEqual(value1, 120.0, accuracy: SunshineKitAccuracy) 53 | 54 | let angle2 = -380.0 55 | let value2 = clampAngleToThreeSixty(angle2) 56 | XCTAssertEqual(value2, 340.0, accuracy: SunshineKitAccuracy) 57 | } 58 | 59 | 60 | func testCalculateTermsWithArray() { 61 | let B_TERMS: [[[Double]]] = [ 62 | [ 63 | [280.0,3.199,84334.662], 64 | [102.0,5.422,5507.553], 65 | [80,3.88,5223.69], 66 | [44,3.7,2352.87], 67 | [32,4,1577.34] 68 | ], 69 | [ 70 | [9,3.9,5507.55], 71 | [6,1.73,5223.69] 72 | ] 73 | ] 74 | 75 | let JME = 0.0065948002066062907 76 | let jmeArray = Array(repeating: JME, count: 100) 77 | 78 | let expectedResult = 310.28125065315311 79 | 80 | let results = calculateTerms(with: B_TERMS, index: 0, ephemerisMillenia: jmeArray) 81 | XCTAssertEqual(results.count, 100) 82 | 83 | for result in results { 84 | XCTAssertEqual(expectedResult, result, accuracy: SunshineKitAccuracy) 85 | } 86 | } 87 | 88 | 89 | func testCalculateTermWithArray() { 90 | let B_TERMS: [[[Double]]] = [ 91 | [ 92 | [280.0,3.199,84334.662], 93 | [102.0,5.422,5507.553], 94 | [80,3.88,5223.69], 95 | [44,3.7,2352.87], 96 | [32,4,1577.34] 97 | ], 98 | [ 99 | [9,3.9,5507.55], 100 | [6,1.73,5223.69] 101 | ] 102 | ] 103 | 104 | let JME = 0.0065948002066062907 105 | 106 | let expectedResult = 310.28125065315311 107 | 108 | let result = calculateTerm(with: B_TERMS, index: 0, ephemerisMillenium: JME) 109 | XCTAssertEqual(expectedResult, result, accuracy: SunshineKitAccuracy) 110 | } 111 | 112 | 113 | // MARK: - SPA tests 114 | 115 | 116 | func testSPAWithPaperExample() { 117 | var dateComponents = DateComponents() 118 | dateComponents.year = 2003 119 | dateComponents.month = 10 120 | dateComponents.day = 17 121 | dateComponents.hour = 12 122 | dateComponents.minute = 30 123 | dateComponents.second = 30 124 | 125 | let date = Calendar.current.date(from: dateComponents)! 126 | 127 | let coordinate = CLLocationCoordinate2D(latitude: 39.742476, longitude: -105.1786) 128 | let pressure = 820.0 129 | let elevation = 1830.14 130 | let temperature = 11.0 131 | let surfaceAzimuth = SunshineKitDefaultSurfaceAzimuth 132 | let sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 133 | 134 | XCTAssertEqual(sunPosition.ascension!, 202.22703929, accuracy: SunshineKitAccuracy) 135 | XCTAssertEqual(sunPosition.height!, 39.888378, accuracy: SunshineKitAccuracy) 136 | XCTAssertEqual(sunPosition.azimuth!, 194.340241, accuracy: SunshineKitAccuracy) 137 | XCTAssertEqual(sunPosition.zenith!, 50.111622024, accuracy: SunshineKitAccuracy) 138 | XCTAssertEqual(sunPosition.incidence!, 25.187, accuracy: SunshineKitAccuracy) 139 | XCTAssertEqual(sunPosition.shadow!.direction!, 14.3402405, accuracy: SunshineKitAccuracy) 140 | XCTAssertEqual(sunPosition.shadow!.length!, 11.9647968, accuracy: SunshineKitAccuracy) 141 | } 142 | 143 | 144 | func testSunPositionFragmentsSelection() { 145 | var dateComponents = DateComponents() 146 | dateComponents.year = 2003 147 | dateComponents.month = 10 148 | dateComponents.day = 17 149 | dateComponents.hour = 12 150 | dateComponents.minute = 30 151 | dateComponents.second = 30 152 | let date = Calendar.current.date(from: dateComponents)! 153 | let coordinate = CLLocationCoordinate2D(latitude: 39.742476, longitude: -105.1786) 154 | let pressure = 820.0 155 | let elevation = 1830.14 156 | let temperature = 11.0 157 | let surfaceAzimuth = SunshineKitDefaultSurfaceAzimuth 158 | 159 | 160 | let onlyAscensionFragment: [SunPositionFragment] = [.ascension] 161 | var sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: onlyAscensionFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 162 | XCTAssertNotNil(sunPosition.ascension) 163 | XCTAssertNil(sunPosition.azimuth) 164 | XCTAssertNil(sunPosition.incidence) 165 | XCTAssertNil(sunPosition.height) 166 | XCTAssertNil(sunPosition.zenith) 167 | XCTAssertNil(sunPosition.shadow) 168 | XCTAssertEqual(sunPosition.ascension!, 202.22703929, accuracy: SunshineKitAccuracy) 169 | 170 | 171 | let onlyAzimuthFragment: [SunPositionFragment] = [.azimuth] 172 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: onlyAzimuthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 173 | XCTAssertNil(sunPosition.ascension) 174 | XCTAssertNotNil(sunPosition.azimuth) 175 | XCTAssertNil(sunPosition.incidence) 176 | XCTAssertNil(sunPosition.height) 177 | XCTAssertNil(sunPosition.zenith) 178 | XCTAssertNil(sunPosition.shadow) 179 | XCTAssertEqual(sunPosition.azimuth!, 194.340241, accuracy: SunshineKitAccuracy) 180 | 181 | 182 | let onlyHeightFragment: [SunPositionFragment] = [.height] 183 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: onlyHeightFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 184 | XCTAssertNil(sunPosition.ascension) 185 | XCTAssertNil(sunPosition.azimuth) 186 | XCTAssertNil(sunPosition.incidence) 187 | XCTAssertNotNil(sunPosition.height) 188 | XCTAssertNil(sunPosition.zenith) 189 | XCTAssertNil(sunPosition.shadow) 190 | XCTAssertEqual(sunPosition.height!, 39.888378, accuracy: SunshineKitAccuracy) 191 | 192 | 193 | let onlyIncidenceFragment: [SunPositionFragment] = [.incidence] // implies calculation of azmiuth, height and zenith 194 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: onlyIncidenceFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 195 | XCTAssertNil(sunPosition.ascension) 196 | XCTAssertNotNil(sunPosition.azimuth) 197 | XCTAssertNotNil(sunPosition.incidence) 198 | XCTAssertNotNil(sunPosition.height) 199 | XCTAssertNotNil(sunPosition.zenith) 200 | XCTAssertNil(sunPosition.shadow) 201 | XCTAssertEqual(sunPosition.incidence!, 25.187, accuracy: SunshineKitAccuracy) 202 | 203 | 204 | let onlyZenithFragment: [SunPositionFragment] = [.zenith] // implies calculation of height 205 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: onlyZenithFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 206 | XCTAssertNil(sunPosition.ascension) 207 | XCTAssertNil(sunPosition.azimuth) 208 | XCTAssertNil(sunPosition.incidence) 209 | XCTAssertNotNil(sunPosition.height) 210 | XCTAssertNotNil(sunPosition.zenith) 211 | XCTAssertNil(sunPosition.shadow) 212 | XCTAssertEqual(sunPosition.zenith!, 50.111622024, accuracy: SunshineKitAccuracy) 213 | 214 | 215 | let heightAndAzimuthFragment: [SunPositionFragment] = [.azimuth, .height] // implies calculation of height 216 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: heightAndAzimuthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 217 | XCTAssertNil(sunPosition.ascension) 218 | XCTAssertNotNil(sunPosition.azimuth) 219 | XCTAssertNil(sunPosition.incidence) 220 | XCTAssertNotNil(sunPosition.height) 221 | XCTAssertNil(sunPosition.zenith) 222 | XCTAssertNil(sunPosition.shadow) 223 | XCTAssertEqual(sunPosition.azimuth!, 194.340241, accuracy: SunshineKitAccuracy) 224 | XCTAssertEqual(sunPosition.height!, 39.888378, accuracy: SunshineKitAccuracy) 225 | 226 | 227 | let shadowFragment: [SunPositionFragment] = [.shadow([SunPositionFragment.Shadow.direction, SunPositionFragment.Shadow.length])] 228 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: shadowFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 229 | XCTAssertNil(sunPosition.ascension) 230 | XCTAssertNotNil(sunPosition.azimuth) 231 | XCTAssertNil(sunPosition.incidence) 232 | XCTAssertNotNil(sunPosition.height) 233 | XCTAssertNil(sunPosition.zenith) 234 | XCTAssertNotNil(sunPosition.shadow) 235 | XCTAssertEqual(sunPosition.azimuth!, 194.340241, accuracy: SunshineKitAccuracy) 236 | XCTAssertEqual(sunPosition.height!, 39.888378, accuracy: SunshineKitAccuracy) 237 | XCTAssertEqual(sunPosition.shadow!.direction!, 14.3402405, accuracy: SunshineKitAccuracy) 238 | XCTAssertEqual(sunPosition.shadow!.length!, 11.9647968, accuracy: SunshineKitAccuracy) 239 | 240 | 241 | let shadowDirectionFragment: [SunPositionFragment] = [.shadow([SunPositionFragment.Shadow.direction])] 242 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: shadowDirectionFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 243 | XCTAssertNil(sunPosition.ascension) 244 | XCTAssertNotNil(sunPosition.azimuth) 245 | XCTAssertNil(sunPosition.incidence) 246 | XCTAssertNil(sunPosition.height) 247 | XCTAssertNil(sunPosition.zenith) 248 | XCTAssertNil(sunPosition.shadow!.length) 249 | XCTAssertNotNil(sunPosition.shadow!.direction) 250 | XCTAssertEqual(sunPosition.azimuth!, 194.340241, accuracy: SunshineKitAccuracy) 251 | XCTAssertEqual(sunPosition.shadow!.direction!, 14.3402405, accuracy: SunshineKitAccuracy) 252 | 253 | 254 | let shadowLengthFragment: [SunPositionFragment] = [.shadow([SunPositionFragment.Shadow.length])] 255 | sunPosition = NRELSunPosition(for: date, timeZoneOffset: -7, coordinate: coordinate, elevation: elevation, fragments: shadowLengthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 256 | XCTAssertNil(sunPosition.ascension) 257 | XCTAssertNil(sunPosition.azimuth) 258 | XCTAssertNil(sunPosition.incidence) 259 | XCTAssertNotNil(sunPosition.height) 260 | XCTAssertNil(sunPosition.zenith) 261 | XCTAssertNotNil(sunPosition.shadow!.length) 262 | XCTAssertNil(sunPosition.shadow!.direction) 263 | XCTAssertEqual(sunPosition.height!, 39.888378, accuracy: SunshineKitAccuracy) 264 | XCTAssertEqual(sunPosition.shadow!.length!, 11.9647968, accuracy: SunshineKitAccuracy) 265 | 266 | 267 | let now = Date() 268 | let nowDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: now) 269 | let hourNow = Calendar.current.date(from: nowDateComponents)! 270 | let nowSunPosition = NRELSunPosition(for: hourNow, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: FullSunPositionFragments) 271 | 272 | var vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyAscensionFragment) 273 | XCTAssertNotNil(vdspSunPositions[0].ascension) 274 | XCTAssertNil(vdspSunPositions[0].azimuth) 275 | XCTAssertNil(vdspSunPositions[0].incidence) 276 | XCTAssertNil(vdspSunPositions[0].height) 277 | XCTAssertNil(vdspSunPositions[0].zenith) 278 | XCTAssertNil(vdspSunPositions[0].shadow) 279 | XCTAssertEqual(vdspSunPositions[0].ascension!, nowSunPosition.ascension!, accuracy: SunshineKitAccuracy) 280 | 281 | 282 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyAzimuthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 283 | XCTAssertNil(vdspSunPositions[0].ascension) 284 | XCTAssertNotNil(vdspSunPositions[0].azimuth) 285 | XCTAssertNil(vdspSunPositions[0].incidence) 286 | XCTAssertNil(vdspSunPositions[0].height) 287 | XCTAssertNil(vdspSunPositions[0].zenith) 288 | XCTAssertNil(vdspSunPositions[0].shadow) 289 | XCTAssertEqual(vdspSunPositions[0].azimuth!, nowSunPosition.azimuth!, accuracy: SunshineKitAccuracy) 290 | 291 | 292 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyHeightFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 293 | XCTAssertNil(vdspSunPositions[0].ascension) 294 | XCTAssertNil(vdspSunPositions[0].azimuth) 295 | XCTAssertNil(vdspSunPositions[0].incidence) 296 | XCTAssertNotNil(vdspSunPositions[0].height) 297 | XCTAssertNil(vdspSunPositions[0].zenith) 298 | XCTAssertNil(vdspSunPositions[0].shadow) 299 | // TODO: why is the accuracy lower? 300 | XCTAssertEqual(vdspSunPositions[0].height!, nowSunPosition.height!, accuracy: SunshineKitAccuracy*100000) 301 | 302 | 303 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyIncidenceFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 304 | XCTAssertNil(vdspSunPositions[0].ascension) 305 | XCTAssertNotNil(vdspSunPositions[0].azimuth) 306 | XCTAssertNotNil(vdspSunPositions[0].incidence) 307 | XCTAssertNotNil(vdspSunPositions[0].height) 308 | XCTAssertNotNil(vdspSunPositions[0].zenith) 309 | XCTAssertNil(vdspSunPositions[0].shadow) 310 | // TODO: why is the accuracy lower? 311 | XCTAssertEqual(vdspSunPositions[0].incidence!, nowSunPosition.incidence!, accuracy: SunshineKitAccuracy*100000) 312 | 313 | 314 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyZenithFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 315 | XCTAssertNil(vdspSunPositions[0].ascension) 316 | XCTAssertNil(vdspSunPositions[0].azimuth) 317 | XCTAssertNil(vdspSunPositions[0].incidence) 318 | XCTAssertNotNil(vdspSunPositions[0].height) 319 | XCTAssertNotNil(vdspSunPositions[0].zenith) 320 | XCTAssertNil(vdspSunPositions[0].shadow) 321 | // TODO: why is the accuracy lower? 322 | XCTAssertEqual(vdspSunPositions[0].zenith!, nowSunPosition.zenith!, accuracy: SunshineKitAccuracy*1000000) 323 | 324 | 325 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: heightAndAzimuthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 326 | XCTAssertNil(vdspSunPositions[0].ascension) 327 | XCTAssertNotNil(vdspSunPositions[0].azimuth) 328 | XCTAssertNil(vdspSunPositions[0].incidence) 329 | XCTAssertNotNil(vdspSunPositions[0].height) 330 | XCTAssertNil(vdspSunPositions[0].zenith) 331 | XCTAssertNil(vdspSunPositions[0].shadow) 332 | XCTAssertEqual(vdspSunPositions[0].azimuth!, nowSunPosition.azimuth!, accuracy: SunshineKitAccuracy) 333 | // TODO: why is the accuracy lower? 334 | XCTAssertEqual(vdspSunPositions[0].height!, nowSunPosition.height!, accuracy: SunshineKitAccuracy*100000) 335 | 336 | 337 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: shadowFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 338 | XCTAssertNil(vdspSunPositions[0].ascension) 339 | XCTAssertNotNil(vdspSunPositions[0].azimuth) 340 | XCTAssertNil(vdspSunPositions[0].incidence) 341 | XCTAssertNotNil(vdspSunPositions[0].height) 342 | XCTAssertNil(vdspSunPositions[0].zenith) 343 | XCTAssertNotNil(vdspSunPositions[0].shadow!.direction!) 344 | XCTAssertNotNil(vdspSunPositions[0].shadow!.length!) 345 | XCTAssertEqual(vdspSunPositions[0].azimuth!, nowSunPosition.azimuth!, accuracy: SunshineKitAccuracy) 346 | // TODO: why is the accuracy lower? 347 | XCTAssertEqual(vdspSunPositions[0].height!, nowSunPosition.height!, accuracy: SunshineKitAccuracy*100000) 348 | XCTAssertEqual(vdspSunPositions[0].shadow!.direction!, nowSunPosition.shadow!.direction!, accuracy: SunshineKitAccuracy) 349 | //XCTAssertEqual(vdspSunPositions[0].shadow!.length!, nowSunPosition.shadow!.length!, accuracy: SunshineKitAccuracy*100000) 350 | 351 | 352 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: shadowDirectionFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 353 | XCTAssertNil(vdspSunPositions[0].ascension) 354 | XCTAssertNotNil(vdspSunPositions[0].azimuth) 355 | XCTAssertNil(vdspSunPositions[0].incidence) 356 | XCTAssertNil(vdspSunPositions[0].height) 357 | XCTAssertNil(vdspSunPositions[0].zenith) 358 | XCTAssertNotNil(vdspSunPositions[0].shadow!.direction!) 359 | XCTAssertNil(vdspSunPositions[0].shadow!.length) 360 | XCTAssertEqual(vdspSunPositions[0].azimuth!, nowSunPosition.azimuth!, accuracy: SunshineKitAccuracy) 361 | XCTAssertEqual(vdspSunPositions[0].shadow!.direction!, nowSunPosition.shadow!.direction!, accuracy: SunshineKitAccuracy) 362 | 363 | 364 | vdspSunPositions = NRELSunPositions(for: hourNow, withResolution: .hour, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: shadowLengthFragment, pressure: pressure, temperature: temperature, surfaceAzimuth: surfaceAzimuth) 365 | XCTAssertNil(vdspSunPositions[0].ascension) 366 | XCTAssertNil(vdspSunPositions[0].azimuth) 367 | XCTAssertNil(vdspSunPositions[0].incidence) 368 | XCTAssertNotNil(vdspSunPositions[0].height) 369 | XCTAssertNil(vdspSunPositions[0].zenith) 370 | XCTAssertNil(vdspSunPositions[0].shadow!.direction) 371 | XCTAssertNotNil(vdspSunPositions[0].shadow!.length) 372 | // FIXME: why is the accuracy lower? 373 | XCTAssertEqual(vdspSunPositions[0].height!, nowSunPosition.height!, accuracy: SunshineKitAccuracy*100000) 374 | //XCTAssertEqual(vdspSunPositions[0].shadow!.length!, nowSunPosition.shadow!.length!, accuracy: SunshineKitAccuracy*100000) 375 | } 376 | 377 | 378 | func testAllDaySPAForDateWithSecondResolution() { 379 | var dateComponents = DateComponents() 380 | dateComponents.year = 2003 381 | dateComponents.month = 10 382 | dateComponents.day = 17 383 | 384 | let coordinate = CLLocationCoordinate2D(latitude: 39.742476, longitude: -105.1786) 385 | let offset = -7 386 | let elevation = 1830.14 387 | 388 | let date = Calendar.current.date(from: dateComponents)! 389 | 390 | var slowPositions = [SunPosition]() 391 | 392 | for hour in 0..<24 { 393 | dateComponents.hour = hour 394 | 395 | for minute in 0..<60 { 396 | dateComponents.minute = minute 397 | 398 | for second in 0..<60 { 399 | dateComponents.second = second 400 | 401 | let date = Calendar.current.date(from: dateComponents)! 402 | 403 | let sunPosition = NRELSunPosition(for: date, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 404 | slowPositions.append(sunPosition) 405 | } 406 | } 407 | } 408 | 409 | let fastPositions = NRELSunPositions(for: date, withResolution: .second, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 410 | 411 | XCTAssertEqual(fastPositions.count, 24*60*60) 412 | 413 | for index in 0..<24*60*60 { 414 | let value1 = slowPositions[index] 415 | let value2 = fastPositions[index] 416 | 417 | XCTAssertEqual(value1.ascension!, value2.ascension!, accuracy: SunshineKitAccuracy) 418 | XCTAssertEqual(value1.azimuth!, value2.azimuth!, accuracy: SunshineKitAccuracy) 419 | // TODO: why is the accuracy lower? 420 | XCTAssertEqual(value1.height!, value2.height!, accuracy: SunshineKitAccuracy*10, "index \(index) failed") 421 | XCTAssertEqual(value1.incidence!, value2.incidence!, accuracy: SunshineKitAccuracy*10, "index \(index) failed") 422 | XCTAssertEqual(value1.zenith!, value2.zenith!, accuracy: SunshineKitAccuracy*10, "index \(index) failed") 423 | XCTAssertEqual(value1.shadow!.direction!, value2.shadow!.direction!, accuracy: SunshineKitAccuracy) 424 | XCTAssertEqual(value1.shadow!.length!, value2.shadow!.length!, accuracy: SunshineKitAccuracy*10000, "index \(index) failed") 425 | } 426 | } 427 | 428 | 429 | func testAllDaySPAForDateWithMinuteResolution() { 430 | var dateComponents = DateComponents() 431 | dateComponents.year = 2003 432 | dateComponents.month = 10 433 | dateComponents.day = 17 434 | 435 | let coordinate = CLLocationCoordinate2D(latitude: 39.742476, longitude: -105.1786) 436 | let offset = -7 437 | let elevation = 1830.14 438 | 439 | let date = Calendar.current.date(from: dateComponents)! 440 | 441 | var slowPositions = [SunPosition]() 442 | 443 | for hour in 0..<24 { 444 | dateComponents.hour = hour 445 | 446 | for minute in 0..<60 { 447 | dateComponents.minute = minute 448 | 449 | let date = Calendar.current.date(from: dateComponents)! 450 | 451 | let sunPosition = NRELSunPosition(for: date, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 452 | slowPositions.append(sunPosition) 453 | } 454 | } 455 | 456 | let fastPositions = NRELSunPositions(for: date, withResolution: .minute, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 457 | 458 | XCTAssertEqual(fastPositions.count, 1440) 459 | 460 | for index in 0..<24*60 { 461 | let value1 = slowPositions[index] 462 | let value2 = fastPositions[index] 463 | 464 | XCTAssertEqual(value1.ascension!, value2.ascension!, accuracy: SunshineKitAccuracy) 465 | XCTAssertEqual(value1.azimuth!, value2.azimuth!, accuracy: SunshineKitAccuracy) 466 | XCTAssertEqual(value1.height!, value2.height!, accuracy: SunshineKitAccuracy) 467 | XCTAssertEqual(value1.incidence!, value2.incidence!, accuracy: SunshineKitAccuracy) 468 | XCTAssertEqual(value1.zenith!, value2.zenith!, accuracy: SunshineKitAccuracy) 469 | XCTAssertEqual(value1.shadow!.direction!, value2.shadow!.direction!, accuracy: SunshineKitAccuracy) 470 | XCTAssertEqual(value1.shadow!.length!, value2.shadow!.length!, accuracy: SunshineKitAccuracy) 471 | } 472 | } 473 | 474 | 475 | func testAllDaySPAForDateWithHourResolution() { 476 | var dateComponents = DateComponents() 477 | dateComponents.year = 2003 478 | dateComponents.month = 10 479 | dateComponents.day = 17 480 | 481 | let coordinate = CLLocationCoordinate2D(latitude: 39.742476, longitude: -105.1786) 482 | let offset = -7 483 | let elevation = 1830.14 484 | 485 | let date = Calendar.current.date(from: dateComponents)! 486 | 487 | var slowPositions = [SunPosition]() 488 | 489 | for hour in 0..<24 { 490 | dateComponents.hour = hour 491 | 492 | let date = Calendar.current.date(from: dateComponents)! 493 | 494 | let sunPosition = NRELSunPosition(for: date, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 495 | slowPositions.append(sunPosition) 496 | } 497 | 498 | let fastPositions = NRELSunPositions(for: date, withResolution: .hour, timeZoneOffset: offset, coordinate: coordinate, elevation: elevation, fragments: FullSunPositionFragments) 499 | 500 | XCTAssertEqual(fastPositions.count, 24) 501 | 502 | for index in 0..<24 { 503 | let value1 = slowPositions[index] 504 | let value2 = fastPositions[index] 505 | 506 | XCTAssertEqual(value1.ascension!, value2.ascension!, accuracy: SunshineKitAccuracy) 507 | XCTAssertEqual(value1.azimuth!, value2.azimuth!, accuracy: SunshineKitAccuracy) 508 | XCTAssertEqual(value1.height!, value2.height!, accuracy: SunshineKitAccuracy) 509 | XCTAssertEqual(value1.incidence!, value2.incidence!, accuracy: SunshineKitAccuracy) 510 | XCTAssertEqual(value1.zenith!, value2.zenith!, accuracy: SunshineKitAccuracy) 511 | XCTAssertEqual(value1.shadow!.direction!, value2.shadow!.direction!, accuracy: SunshineKitAccuracy) 512 | XCTAssertEqual(value1.shadow!.length!, value2.shadow!.length!, accuracy: SunshineKitAccuracy) 513 | } 514 | } 515 | 516 | 517 | // MARK: - Performance tests 518 | 519 | 520 | func testFullSunPositionPerformance() { 521 | let now = Date() 522 | let coordinate = CLLocationCoordinate2D(latitude: 13.0, longitude: 53.0) 523 | 524 | measure { 525 | for _ in 0..<60 { 526 | _ = NRELSunPosition(for: now, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: FullSunPositionFragments) 527 | } 528 | } 529 | } 530 | 531 | 532 | func testOnePropertySunPositionPerformance() { 533 | let now = Date() 534 | let coordinate = CLLocationCoordinate2D(latitude: 13.0, longitude: 53.0) 535 | let onlyAzimuthFragment: [SunPositionFragment] = [.azimuth] 536 | 537 | measure { 538 | for _ in 0..<60 { 539 | _ = NRELSunPosition(for: now, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyAzimuthFragment) 540 | } 541 | } 542 | } 543 | 544 | 545 | func testSunPositionVDSPPerformance() { 546 | let now = Date() 547 | let coordinate = CLLocationCoordinate2D(latitude: 13.0, longitude: 53.0) 548 | 549 | measure { 550 | _ = NRELSunPositions(for: now, forHour: 12, withResolution: .minute, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: FullSunPositionFragments) 551 | } 552 | } 553 | 554 | 555 | func testOnePropertySunPositionVDSPPerformance() { 556 | let now = Date() 557 | let coordinate = CLLocationCoordinate2D(latitude: 13.0, longitude: 53.0) 558 | let onlyAzimuthFragment: [SunPositionFragment] = [.azimuth] 559 | 560 | measure { 561 | _ = NRELSunPositions(for: now, forHour: 12, withResolution: .minute, timeZoneOffset: 0, coordinate: coordinate, elevation: 0, fragments: onlyAzimuthFragment) 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /SunshineKitTests/Source/NREL/NRELSunriseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NRELSunriseTests.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 11.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | import CoreLocation 12 | @testable import SunshineKit 13 | 14 | 15 | final class NRELSunriseTests: XCTestCase { 16 | func testNRELSunsetForSanktPeterOrding() { 17 | var dateComponents = DateComponents() 18 | dateComponents.year = 2016 19 | dateComponents.month = 7 20 | dateComponents.day = 18 21 | let date = Calendar.current.date(from: dateComponents)! 22 | let coordinate = CLLocationCoordinate2D(latitude: 54.339262, longitude: 8.600417) 23 | 24 | let fragments: [SunRiseSetFragment] = [.sunset([SunRiseSetFragment.DateHeightFragment.date])] 25 | let sunriseset = NRELSunrise(for: date, timeZoneOffset: 2, coordinate: coordinate, fragments: fragments) 26 | let components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunset!.date!) 27 | XCTAssertEqual(components.hour, 21) 28 | XCTAssertEqual(components.minute, 47) 29 | XCTAssertEqual(components.second, 16) // seen live: 50 30 | } 31 | 32 | 33 | func testNRELSunriseWithSubsetFragmentsForDate() { 34 | var dateComponents = DateComponents() 35 | dateComponents.year = 1994 36 | dateComponents.month = 1 37 | dateComponents.day = 2 38 | let date = Calendar.current.date(from: dateComponents)! 39 | let coordinate = CLLocationCoordinate2D(latitude: 35, longitude: 0) 40 | 41 | 42 | let transitDateFragments: [SunRiseSetFragment] = [.transit([SunRiseSetFragment.DateHeightFragment.date])] 43 | var sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: transitDateFragments) 44 | var components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.transit!.date!) 45 | XCTAssertEqual(components.hour, 12) 46 | XCTAssertEqual(components.minute, 4) 47 | XCTAssertEqual(components.second, 0) 48 | 49 | let transitHeightFragments: [SunRiseSetFragment] = [.transit([SunRiseSetFragment.DateHeightFragment.height])] 50 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: transitHeightFragments) 51 | XCTAssertEqual(sunriseset.transit!.height!, 32.0912504, accuracy: SunshineKitAccuracy) 52 | 53 | let transitBothFragments: [SunRiseSetFragment] = [.transit([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height])] 54 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: transitBothFragments) 55 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.transit!.date!) 56 | XCTAssertEqual(components.hour, 12) 57 | XCTAssertEqual(components.minute, 4) 58 | XCTAssertEqual(components.second, 0) 59 | XCTAssertEqual(sunriseset.transit!.height!, 32.0912504, accuracy: SunshineKitAccuracy) 60 | 61 | 62 | let sunsetDateFragments: [SunRiseSetFragment] = [.sunset([SunRiseSetFragment.DateHeightFragment.date])] 63 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunsetDateFragments) 64 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunset!.date!) 65 | XCTAssertEqual(components.hour, 16) 66 | XCTAssertEqual(components.minute, 59) 67 | XCTAssertEqual(components.second, 56) 68 | 69 | let sunsetHeightFragments: [SunRiseSetFragment] = [.sunset([SunRiseSetFragment.DateHeightFragment.height])] 70 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunsetHeightFragments) 71 | XCTAssertEqual(sunriseset.sunset!.height!, -0.73393324, accuracy: SunshineKitAccuracy) 72 | 73 | let sunsetBothFragments: [SunRiseSetFragment] = [.sunset([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height])] 74 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunsetBothFragments) 75 | XCTAssertEqual(sunriseset.sunset!.height!, -0.73393324, accuracy: SunshineKitAccuracy) 76 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunset!.date!) 77 | XCTAssertEqual(components.hour, 16) 78 | XCTAssertEqual(components.minute, 59) 79 | XCTAssertEqual(components.second, 56) 80 | 81 | 82 | let sunriseDateFragments: [SunRiseSetFragment] = [.sunrise([SunRiseSetFragment.DateHeightFragment.date])] 83 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunriseDateFragments) 84 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunrise!.date!) 85 | XCTAssertEqual(components.hour, 7) 86 | XCTAssertEqual(components.minute, 8) 87 | XCTAssertEqual(components.second, 13) 88 | 89 | let sunriseHeightFragments: [SunRiseSetFragment] = [.sunrise([SunRiseSetFragment.DateHeightFragment.height])] 90 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunriseHeightFragments) 91 | XCTAssertEqual(sunriseset.sunrise!.height!, -0.843002484, accuracy: SunshineKitAccuracy) 92 | 93 | let sunriseBothFragments: [SunRiseSetFragment] = [.sunrise([SunRiseSetFragment.DateHeightFragment.date, SunRiseSetFragment.DateHeightFragment.height])] 94 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: sunriseBothFragments) 95 | XCTAssertEqual(sunriseset.sunrise!.height!, -0.843002484, accuracy: SunshineKitAccuracy) 96 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunrise!.date!) 97 | XCTAssertEqual(components.hour, 7) 98 | XCTAssertEqual(components.minute, 8) 99 | XCTAssertEqual(components.second, 13) 100 | } 101 | 102 | 103 | func testNRELSunriseWithALlFragmentsForDate() { 104 | var dateComponents = DateComponents() 105 | dateComponents.year = 1994 106 | dateComponents.month = 1 107 | dateComponents.day = 2 108 | 109 | var date = Calendar.current.date(from: dateComponents)! 110 | 111 | var coordinate = CLLocationCoordinate2D(latitude: 35, longitude: 0) 112 | 113 | var sunriseset = NRELSunrise(for: date, timeZoneOffset: 0, coordinate: coordinate, fragments: FullSunRiseSetFragments) 114 | 115 | var components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunrise!.date!) 116 | XCTAssertEqual(components.hour, 7) 117 | XCTAssertEqual(components.minute, 8) 118 | XCTAssertEqual(components.second, 13) 119 | 120 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.transit!.date!) 121 | XCTAssertEqual(components.hour, 12) 122 | XCTAssertEqual(components.minute, 4) 123 | XCTAssertEqual(components.second, 0) 124 | 125 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunset!.date!) 126 | XCTAssertEqual(components.hour, 16) 127 | XCTAssertEqual(components.minute, 59) 128 | XCTAssertEqual(components.second, 56) 129 | 130 | dateComponents.year = 2016 131 | dateComponents.month = 7 132 | dateComponents.day = 9 133 | 134 | date = Calendar.current.date(from: dateComponents)! 135 | 136 | coordinate = CLLocationCoordinate2D(latitude: 53.3249, longitude: 10) 137 | 138 | sunriseset = NRELSunrise(for: date, timeZoneOffset: 2, coordinate: coordinate, fragments: FullSunRiseSetFragments) 139 | 140 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunrise!.date!) 141 | XCTAssertEqual(components.hour, 5) 142 | XCTAssertEqual(components.minute, 4) 143 | XCTAssertEqual(components.second, 11) 144 | 145 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.transit!.date!) 146 | XCTAssertEqual(components.hour, 13) 147 | XCTAssertEqual(components.minute, 25) 148 | XCTAssertEqual(components.second, 17) 149 | 150 | components = Calendar.current.dateComponents([.hour, .minute, .second], from: sunriseset.sunset!.date!) 151 | XCTAssertEqual(components.hour, 21) 152 | XCTAssertEqual(components.minute, 45) 153 | XCTAssertEqual(components.second, 43) 154 | } 155 | 156 | 157 | func testSunRiseSetPerformance() { 158 | let now = Date() 159 | let coordinate = CLLocationCoordinate2D(latitude: 13.0, longitude: 53.0) 160 | 161 | measure { 162 | _ = NRELSunrise(for: now, timeZoneOffset: 0, coordinate: coordinate, fragments: FullSunRiseSetFragments) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /SunshineKitTests/Source/RadAndAngleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadAndAngleTests.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SunshineKit 12 | 13 | 14 | final class RadAndAngleTests: XCTestCase { 15 | func testAngleToRadAndRadToAngle() { 16 | let angle1 = 45.0 17 | 18 | let rad = angleToRad(angle1) 19 | 20 | let angle2 = radToAngle(rad) 21 | 22 | XCTAssertEqual(angle1, angle2) 23 | } 24 | 25 | 26 | func testAnglesToRadsAndRadsToAngles() { 27 | let angle1 = 33.0 28 | let angles1 = Array(repeating: angle1, count: 100) 29 | 30 | let rads1 = anglesToRads(angles1) 31 | XCTAssertEqual(rads1.count, 100) 32 | 33 | let angles2 = radsToAngles(rads1) 34 | XCTAssertEqual(angles2.count, 100) 35 | 36 | for angle in angles2 { 37 | XCTAssertEqual(angle1, angle) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SunshineKitTests/Source/WikipediaSPATests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WikipediaSPATests.swift 3 | // SunshineKit 4 | // 5 | // Created by Oleg Mueller on 06.07.16. 6 | // Copyright © 2016 Oleg Mueller. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SunshineKit 11 | import CoreLocation 12 | 13 | 14 | final class WikipediaSPATests: XCTestCase { 15 | var coordinate: CLLocationCoordinate2D { 16 | return CLLocationCoordinate2D(latitude: 48.1, longitude: 11.6) 17 | } 18 | 19 | 20 | func testSPAWithMunichWikipediaExample() { 21 | var dateComponents = DateComponents() 22 | dateComponents.year = 2006 23 | dateComponents.month = 8 24 | dateComponents.day = 6 25 | dateComponents.hour = 6 26 | dateComponents.minute = 0 27 | dateComponents.second = 0 28 | 29 | var calendar = Calendar(identifier: .gregorian) 30 | calendar.timeZone = TimeZone(secondsFromGMT: 0)! 31 | let date = calendar.date(from: dateComponents)! 32 | 33 | let coordinate = CLLocationCoordinate2D(latitude: 48.1, longitude: 11.6) 34 | 35 | let sunPosition = WikipediaSunPosition(for: date, coordinate: coordinate) 36 | 37 | XCTAssertEqual(sunPosition.height!, 19.109, accuracy: 0.001) 38 | XCTAssertEqual(sunPosition.azimuth!, 85.938, accuracy: 0.001) 39 | } 40 | 41 | 42 | func testSunPositionPerformance() { 43 | measure { 44 | for _ in 0..<60 { 45 | _ = WikipediaSunPosition(for: Date(), coordinate: self.coordinate) 46 | } 47 | } 48 | } 49 | } 50 | --------------------------------------------------------------------------------