95_Astro.pm 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586
  1. ########################################################################################
  2. #
  3. # 95_Astro.pm
  4. #
  5. # Collection of various routines for astronomical data
  6. # Prof. Dr. Peter A. Henning
  7. #
  8. # Equations from "Practical Astronomy with your Calculator" by Peter Duffett-Smith
  9. # Program skeleton (with some errors) by Arnold Barmettler
  10. # http://lexikon.astronomie.info/java/sunmoon/
  11. #
  12. # $Id: 95_Astro.pm 17517 2018-10-12 15:45:49Z phenning $
  13. #
  14. ########################################################################################
  15. #
  16. # This programm is free software; you can redistribute it and/or modify
  17. # it under the terms of the GNU General Public License as published by
  18. # the Free Software Foundation; either version 2 of the License, or
  19. # (at your option) any later version.
  20. #
  21. # The GNU General Public License can be found at
  22. # http://www.gnu.org/copyleft/gpl.html.
  23. # A copy is found in the textfile GPL.txt and important notices to the license
  24. # from the author is found in LICENSE.txt distributed with these scripts.
  25. #
  26. # This script is distributed in the hope that it will be useful,
  27. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. # GNU General Public License for more details.
  30. #
  31. ########################################################################################
  32. package main;
  33. use strict;
  34. use warnings;
  35. use POSIX;
  36. use Math::Trig;
  37. use Time::Local;
  38. #use Data::Dumper;
  39. my $DEG = pi/180.0;
  40. my $RAD = 180./pi;
  41. my $deltaT = 65; # Correction time in s
  42. my %Astro;
  43. my %Date;
  44. my $astroversion = 1.50;
  45. #-- These we may get on request
  46. my %gets = (
  47. "version" => "V",
  48. "json" => "J",
  49. "text" => "T"
  50. );
  51. my $astro_tt;
  52. my %astro_transtable_EN = (
  53. "overview" => "Summary",
  54. "name" => "Name",
  55. "time" => "Time",
  56. "action" => "Action",
  57. "type" => "Type",
  58. "description" => "Description",
  59. "profile" => "Profile",
  60. #--
  61. "coord" => "Coordinates",
  62. "position" => "Position",
  63. "longitude" => "Longitude",
  64. "latitude" => "Latitude",
  65. "altitude" => "Height above sea",
  66. "lonecl" => "Ecliptical longitude",
  67. "latecl" => "Ecliptical latitude",
  68. "ra" => "Right ascension",
  69. "dec" => "Declination",
  70. "az" => "Azimuth",
  71. "alt" => "Horizontal altitude",
  72. "age" => "Age",
  73. "rise" => "Rise",
  74. "set" => "Set",
  75. "transit" => "Transit",
  76. "distance" => "Distance",
  77. "diameter" => "Diameter",
  78. "toobs" => "to observer",
  79. "toce" => "to Earth center",
  80. "twilightcivil" => "Civil twilight",
  81. "twilightnautic" => "Nautical twilight",
  82. "twilightastro" => "Astronomical twilight",
  83. "twilightcustom" => "Custom twilight",
  84. "sign" => "Zodiac sign",
  85. "dst" => "daylight saving time",
  86. #--
  87. "today" => "Today",
  88. "tomorrow" => "Tomorrow",
  89. "weekday" => "Day of Week",
  90. "date" => "Date",
  91. "jdate" => "Julian date",
  92. "dayofyear" => "day of year",
  93. "days" => "days",
  94. "timezone" => "Time Zone",
  95. "lmst" => "Local Sidereal Time",
  96. #--
  97. "monday" => ["Monday","Mon"],
  98. "tuesday" => ["Tuesday","Tue"],
  99. "wednesday" => ["Wednesday","Wed"],
  100. "thursday" => ["Thursday","Thu"],
  101. "friday" => ["Friday","Fri"],
  102. "saturday" => ["Saturday","Sat"],
  103. "sunday" => ["Sunday","Sun"],
  104. #--
  105. "season" => "Season",
  106. "spring" => "Spring",
  107. "summer" => "Summer",
  108. "fall" => "Fall",
  109. "winter" => "Winter",
  110. #--
  111. "aries" => "Ram",
  112. "taurus" => "Bull",
  113. "gemini" => "Twins",
  114. "cancer" => "Crab",
  115. "leo" => "Lion",
  116. "virgo" => "Maiden",
  117. "libra" => "Scales",
  118. "scorpio" => "Scorpion",
  119. "sagittarius" => "Archer",
  120. "capricorn" => "Goat",
  121. "aquarius" => "Water Bearer",
  122. "pisces" => "Fish",
  123. #--
  124. "sun" => "Sun",
  125. #--
  126. "moon" => "Moon",
  127. "phase" => "Phase",
  128. "newmoon" => "New Moon",
  129. "waxingcrescent" => "Waxing Crescent",
  130. "firstquarter" => "First Quarter",
  131. "waxingmoon" => "Waxing Moon",
  132. "fullmoon" => "Full Moon",
  133. "waningmoon" => "Waning Moon",
  134. "lastquarter" => "Last Quarter",
  135. "waningcrescent" => "Waning Crescent"
  136. );
  137. my %astro_transtable_DE = (
  138. "overview" => "Zusammenfassung",
  139. "name" => "Name",
  140. "time" => "Zeit",
  141. "action" => "Aktion",
  142. "type" => "Typ",
  143. "description" => "Beschreibung",
  144. "profile" => "Profil",
  145. #--
  146. "coord" => "Koordinaten",
  147. "position" => "Position",
  148. "longitude" => "Länge",
  149. "latitude" => "Breite",
  150. "altitude" => "Höhe ü.M.",
  151. "lonecl" => "Eklipt. Länge",
  152. "latecl" => "Eklipt. Breite",
  153. "ra" => "Rektaszension",
  154. "dec" => "Deklination",
  155. "az" => "Azimut",
  156. "alt" => "Horizontwinkel",
  157. "age" => "Alter",
  158. "phase" => "Phase",
  159. "rise" => "Aufgang",
  160. "set" => "Untergang",
  161. "transit" => "Kulmination",
  162. "distance" => "Entfernung",
  163. "diameter" => "Durchmesser",
  164. "toobs" => "z. Beobachter",
  165. "toce" => "z. Erdmittelpunkt",
  166. "twilightcivil" => "Bürgerliche Dämmerung",
  167. "twilightnautic" => "Nautische Dämmerung",
  168. "twilightastro" => "Astronomische Dämmerung",
  169. "twilightcustom" => "Konfigurierte Dämmerung",
  170. "sign" => "Tierkreiszeichen",
  171. "dst" => "Sommerzeit",
  172. #--
  173. "today" => "Heute",
  174. "tomorrow" => "Morgen",
  175. "weekday" => "Wochentag",
  176. "date" => "Datum",
  177. "jdate" => "Julianisches Datum",
  178. "dayofyear" => "Tag d. Jahres",
  179. "days" => "Tage",
  180. "timezone" => "Zeitzone",
  181. "lmst" => "Lokale Sternzeit",
  182. #--
  183. "monday" => ["Montag","Mo"],
  184. "tuesday" => ["Dienstag","Di"],
  185. "wednesday" => ["Mittwoch","Mi"],
  186. "thursday" => ["Donnerstag","Do"],
  187. "friday" => ["Freitag","Fr"],
  188. "saturday" => ["Samstag","Sa"],
  189. "sunday" => ["Sonntag","So"],
  190. #--
  191. "season" => "Jahreszeit",
  192. "spring" => "Frühling",
  193. "summer" => "Sommer",
  194. "fall" => "Herbst",
  195. "winter" => "Winter",
  196. #--
  197. "aries" => "Widder",
  198. "taurus" => "Stier",
  199. "gemini" => "Zwillinge",
  200. "cancer" => "Krebs",
  201. "leo" => "Löwe",
  202. "virgo" => "Jungfrau",
  203. "libra" => "Waage",
  204. "scorpio" => "Skorpion",
  205. "sagittarius" => "Schütze",
  206. "capricorn" => "Steinbock",
  207. "aquarius" => "Wassermann",
  208. "pisces" => "Fische",
  209. #--
  210. "sun" => "Sonne",
  211. #--
  212. "moon" => "Mond",
  213. "phase" => "Phase",
  214. "newmoon" => "Neumond",
  215. "waxingcrescent" => "Zunehmende Sichel",
  216. "firstquarter" => "Erstes Viertel",
  217. "waxingmoon" => "Zunehmender Mond",
  218. "fullmoon" => "Vollmond",
  219. "waningmoon" => "Abnehmender Mond",
  220. "lastquarter" => "Letztes Viertel",
  221. "waningcrescent" => "Abnehmende Sichel"
  222. );
  223. my @zodiac=("aries","taurus","gemini","cancer","leo","virgo",
  224. "libra","scorpio","sagittarius","capricorn","aquarius","pisces");
  225. my @phases = ("newmoon","waxingcrescent", "firstquarter", "waxingmoon",
  226. "fullmoon", "waningmoon", "lastquarter", "waningcrescent");
  227. my @seasons = (
  228. "winter","spring","summer","fall");
  229. my %seasonn = (
  230. "spring" => [80,172], #21./22.3. - 20.6.
  231. "summer" => [173,265], #21.06. bis 21./22.09.
  232. "fall" => [266,353], #22./23.09. bis 20./21.12.
  233. "winter" => [354,79]
  234. );
  235. sub Astro_SunRise($$$$$$);
  236. sub Astro_MoonRise($$$$$$$);
  237. ########################################################################################################
  238. #
  239. # Astro_Initialize
  240. #
  241. # Parameter hash = hash of device addressed
  242. #
  243. ########################################################################################################
  244. sub Astro_Initialize ($) {
  245. my ($hash) = @_;
  246. $hash->{DefFn} = "Astro_Define";
  247. #$hash->{SetFn} = "Astro_Set";
  248. $hash->{GetFn} = "Astro_Get";
  249. $hash->{UndefFn} = "Astro_Undef";
  250. $hash->{AttrFn} = "Astro_Attr";
  251. $hash->{AttrList} = "interval longitude latitude altitude horizon ".$readingFnAttributes;;
  252. $data{FWEXT}{"/Astro_moonwidget"}{FUNC} = "Astro_moonwidget";
  253. $data{FWEXT}{"/Astr_moonwidget"}{FORKABLE} = 0;
  254. return undef;
  255. }
  256. ########################################################################################################
  257. #
  258. # Astro_Define - Implements DefFn function
  259. #
  260. # Parameter hash = hash of device addressed, def = definition string
  261. #
  262. ########################################################################################################
  263. sub Astro_Define ($$) {
  264. my ($hash, $def) = @_;
  265. #my $now = time();
  266. my $name = $hash->{NAME};
  267. $hash->{VERSION} = $astroversion;
  268. readingsSingleUpdate( $hash, "state", "Initialized", 1 );
  269. $modules{Astro}{defptr}{$name} = $hash;
  270. RemoveInternalTimer($hash);
  271. #-- Call us in n seconds again.
  272. InternalTimer(gettimeofday()+ 60, "Astro_Update", $hash,0);
  273. return undef;
  274. }
  275. ########################################################################################################
  276. #
  277. # Astro_Undef - Implements Undef function
  278. #
  279. # Parameter hash = hash of device addressed, def = definition string
  280. #
  281. ########################################################################################################
  282. sub Astro_Undef ($$) {
  283. my ($hash,$arg) = @_;
  284. RemoveInternalTimer($hash);
  285. return undef;
  286. }
  287. ########################################################################################################
  288. #
  289. # Astro_Attr - Implements Attr function
  290. #
  291. # Parameter hash = hash of device addressed, ???
  292. #
  293. ########################################################################################################
  294. sub Astro_Attr(@) {
  295. my ($do,$name,$key,$value) = @_;
  296. my $hash = $defs{$name};
  297. my $ret;
  298. if ( $do eq "set") {
  299. ARGUMENT_HANDLER: {
  300. #-- interval modified at runtime
  301. $key eq "interval" and do {
  302. #-- check value
  303. return "[Astro] set $name interval must be >= 0" if(int($value) < 0);
  304. #-- update timer
  305. $hash->{INTERVAL} = int($value);
  306. if ($init_done) {
  307. RemoveInternalTimer($hash);
  308. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Astro_Update", $hash, 0);
  309. }
  310. last;
  311. };
  312. }
  313. }
  314. return $ret;
  315. }
  316. sub Astro_mod($$) { my ($a,$b)=@_;if( $a =~ /\d*\.\d*/){return($a-floor($a/$b)*$b)}else{return undef}; }
  317. sub Astro_mod2Pi($) { my ($x)=@_;$x = Astro_mod($x, 2.*pi);return($x); }
  318. sub Astro_round($$) { my ($x,$n)=@_; return int(10**$n*$x+0.5)/10**$n};
  319. sub Astro_tzoffset($) {
  320. my ($t) = @_;
  321. my $utc = mktime(gmtime($t));
  322. #-- the following does not properly calculate dst
  323. my $local = mktime(localtime($t));
  324. #-- this is the correction
  325. my $isdst = (localtime($t))[8];
  326. #-- correction
  327. if($isdst == 1){
  328. $local+=3600;
  329. }
  330. return (($local - $utc)/36);
  331. }
  332. ########################################################################################################
  333. #
  334. # time fragments into minutes, seconds
  335. #
  336. ########################################################################################################
  337. sub Astro_HHMM($){
  338. my ($hh) = @_;
  339. return("---")
  340. if (!defined($hh) || $hh !~ /\d*\.\d*/) ;
  341. my $h = floor($hh);
  342. my $m = ($hh-$h)*60.;
  343. return sprintf("%02d:%02d",$h,$m);
  344. }
  345. sub Astro_HHMMSS($){
  346. my ($hh) = @_;
  347. return("")
  348. if ($hh==0) ;
  349. my $m = ($hh-floor($hh))*60.;
  350. my $s = ($m-floor($m))*60;
  351. my $h = floor($hh);
  352. return sprintf("%02d:%02d:%02d",$h,$m,$s);
  353. }
  354. ########################################################################################################
  355. #
  356. # Astro_CalcJD - Calculate Julian date: valid only from 1.3.1901 to 28.2.2100
  357. #
  358. ########################################################################################################
  359. sub Astro_CalcJD($$$) {
  360. my ($day,$month,$year) = @_;
  361. my $jd = 2415020.5-64; # 1.1.1900 - correction of algorithm
  362. if ($month<=2) {
  363. $year--;
  364. $month += 12;
  365. }
  366. $jd += int( ($year-1900)*365.25 );
  367. $jd += int( 30.6001*(1+$month) );
  368. return($jd + $day);
  369. }
  370. ########################################################################################################
  371. #
  372. # Astro_GMST - Julian Date to Greenwich Mean Sidereal Time
  373. #
  374. ########################################################################################################
  375. sub Astro_GMST($){
  376. my ($JD) = @_;
  377. my $UT = ($JD-0.5) - int($JD-0.5);
  378. $UT = $UT*24.; # UT in hours
  379. $JD = floor($JD-0.5)+0.5; # JD at 0 hours UT
  380. my $T = ($JD-2451545.0)/36525.0;
  381. my $T0 = 6.697374558 + $T*(2400.051336 + $T*0.000025862);
  382. return( Astro_mod($T0+$UT*1.002737909,24.));
  383. }
  384. ########################################################################################################
  385. #
  386. # Astro_GMST2UT - Convert Greenweek mean sidereal time to UT
  387. #
  388. ########################################################################################################
  389. sub Astro_GMST2UT($$){
  390. my ($JD, $gmst) = @_;
  391. $JD = floor($JD-0.5)+0.5; # JD at 0 hours UT
  392. my $T = ($JD-2451545.0)/36525.0;
  393. my $T0 = Astro_mod(6.697374558 + $T*(2400.051336 + $T*0.000025862), 24.);
  394. my $UT = 0.9972695663*(($gmst-$T0));
  395. return($UT);
  396. }
  397. ########################################################################################################
  398. #
  399. # Astro_GMST2LMST - Local Mean Sidereal Time, geographical longitude in radians,
  400. # East is positive
  401. #
  402. ########################################################################################################
  403. sub Astro_GMST2LMST($$){
  404. my ($gmst, $lon) = @_;
  405. my $lmst = Astro_mod($gmst+$RAD*$lon/15, 24.);
  406. return( $lmst );
  407. }
  408. ########################################################################################################
  409. #
  410. # Astro_Ecl2Equ - Transform ecliptical coordinates (lon/lat) to equatorial coordinates (RA/dec)
  411. #
  412. ########################################################################################################
  413. sub Astro_Ecl2Equ($$$){
  414. my ($lon, $lat, $TDT) = @_;
  415. my $T = ($TDT-2451545.0)/36525.; # Epoch 2000 January 1.5
  416. my $eps = (23.+(26+21.45/60.)/60. + $T*(-46.815 +$T*(-0.0006 + $T*0.00181) )/3600. )*$DEG;
  417. my $coseps = cos($eps);
  418. my $sineps = sin($eps);
  419. my $sinlon = sin($lon);
  420. my $ra = Astro_mod2Pi(atan2( ($sinlon*$coseps-tan($lat)*$sineps), cos($lon) ));
  421. my $dec = asin( sin($lat)*$coseps + cos($lat)*$sineps*$sinlon );
  422. return ($ra,$dec);
  423. }
  424. ########################################################################################################
  425. #
  426. # Astro_Equ2Altaz - Transform equatorial coordinates (RA/Dec) to horizonal coordinates
  427. # (azimuth/altitude). Refraction is ignored
  428. #
  429. ########################################################################################################
  430. sub Astro_Equ2Altaz($$$$$){
  431. my ($ra, $dec, $TDT, $lat, $lmst)=@_;
  432. my $cosdec = cos($dec);
  433. my $sindec = sin($dec);
  434. my $lha = $lmst - $ra;
  435. my $coslha = cos($lha);
  436. my $sinlha = sin($lha);
  437. my $coslat = cos($lat);
  438. my $sinlat = sin($lat);
  439. my $N = -$cosdec * $sinlha;
  440. my $D = $sindec * $coslat - $cosdec * $coslha * $sinlat;
  441. my $az = Astro_mod2Pi( atan2($N, $D) );
  442. my $alt = asin( $sindec * $sinlat + $cosdec * $coslha * $coslat );
  443. return ($az,$alt);
  444. }
  445. ########################################################################################################
  446. #
  447. # Astro_GeoEqu2TopoEqu - Transform geocentric equatorial coordinates (RA/Dec) to
  448. # topocentric equatorial coordinates
  449. #
  450. ########################################################################################################
  451. sub Astro_GeoEqu2TopoEqu($$$$$$$){
  452. my ($ra, $dec, $distance, $lon, $lat, $radius, $lmst) = @_;
  453. my $cosdec = cos($dec);
  454. my $sindec = sin($dec);
  455. my $coslst = cos($lmst);
  456. my $sinlst = sin($lmst);
  457. my $coslat = cos($lat); # we should use geocentric latitude, not geodetic latitude
  458. my $sinlat = sin($lat);
  459. my $rho = $radius; # observer-geocenter in km
  460. my $x = $distance*$cosdec*cos($ra) - $rho*$coslat*$coslst;
  461. my $y = $distance*$cosdec*sin($ra) - $rho*$coslat*$sinlst;
  462. my $z = $distance*$sindec - $rho*$sinlat;
  463. my $distanceTopocentric = sqrt($x*$x + $y*$y + $z*$z);
  464. my $decTopocentric = asin($z/$distanceTopocentric);
  465. my $raTopocentric = Astro_mod2Pi( atan2($y, $x) );
  466. return ( ($distanceTopocentric,$decTopocentric,$raTopocentric) );
  467. }
  468. ########################################################################################################
  469. #
  470. # Astro_EquPolar2Cart - Calculate cartesian from polar coordinates
  471. #
  472. ########################################################################################################
  473. sub Astro_EquPolar2Cart($$$){
  474. my ($lon,$lat,$distance) = @_;
  475. my $rcd = cos($lat)*$distance;
  476. my $x = $rcd*cos($lon);
  477. my $y = $rcd*sin($lon);
  478. my $z = sin($lat)*$distance;
  479. return( ($x,$y,$z) );
  480. }
  481. ########################################################################################################
  482. #
  483. # Astro_Observer2EquCart - Calculate observers cartesian equatorial coordinates (x,y,z in celestial frame)
  484. # from geodetic coordinates (longitude, latitude, height above WGS84 ellipsoid)
  485. # Currently only used to calculate distance of a body from the observer
  486. #
  487. ########################################################################################################
  488. sub Astro_Observer2EquCart($$$$){
  489. my ($lon, $lat, $height, $gmst ) = @_;
  490. my $flat = 298.257223563; # WGS84 flatening of earth
  491. my $aearth = 6378.137; # GRS80/WGS84 semi major axis of earth ellipsoid
  492. #-- Calculate geocentric latitude from geodetic latitude
  493. my $co = cos ($lat);
  494. my $si = sin ($lat);
  495. $si = $si * $si;
  496. my $fl = 1.0 - 1.0 / $flat;
  497. $fl = $fl * $fl;
  498. my $u = 1.0 / sqrt ($co * $co + $fl * $si);
  499. my $a = $aearth * $u + $height;
  500. my $b = $aearth * $fl * $u + $height;
  501. my $radius = sqrt ($a * $a * $co *$co + $b *$b * $si); # geocentric distance from earth center
  502. my $y = acos ($a * $co / $radius); # geocentric latitude, rad
  503. my $x = $lon; # longitude stays the same
  504. my $z;
  505. if ($lat < 0.0) { $y = -$y; } # adjust sign
  506. #-- convert from geocentric polar to geocentric cartesian, with regard to Greenwich
  507. ($x,$y,$z) = Astro_EquPolar2Cart( $x, $y, $radius );
  508. #-- rotate around earth's polar axis to align coordinate system from Greenwich to vernal equinox
  509. my $rotangle = $gmst/24*2*pi; # sideral time gmst given in hours. Convert to radians
  510. my $x2 = $x*cos($rotangle) - $y*sin($rotangle);
  511. my $y2 = $x*sin($rotangle) + $y*cos($rotangle);
  512. return( ($x2,$y2,$z,$radius) );
  513. }
  514. ########################################################################################################
  515. #
  516. # Astro_SunPosition - Calculate coordinates for Sun
  517. # Coordinates are accurate to about 10s (right ascension)
  518. # and a few minutes of arc (declination)
  519. #
  520. ########################################################################################################
  521. sub Astro_SunPosition($$$){
  522. my ($TDT, $observerlat, $lmst)=@_;
  523. my $D = $TDT-2447891.5;
  524. my $eg = 279.403303*$DEG;
  525. my $wg = 282.768422*$DEG;
  526. my $e = 0.016713;
  527. my $a = 149598500; # km
  528. #-- mean angular diameter of sun
  529. my $diameter0 = 0.533128*$DEG;
  530. my $MSun = 360*$DEG/365.242191*$D+$eg-$wg;
  531. my $nu = $MSun + 360.*$DEG/pi*$e*sin($MSun);
  532. my %sunCoor;
  533. $sunCoor{lon} = Astro_mod2Pi($nu+$wg);
  534. $sunCoor{lat} = 0;
  535. $sunCoor{anomalyMean} = $MSun;
  536. my $distance = (1-$e*$e)/(1+$e*cos($nu)); # distance in astronomical units
  537. $sunCoor{diameter} = $diameter0/$distance; # angular diameter
  538. $sunCoor{distance} = $distance*$a; # distance in km
  539. $sunCoor{parallax} = 6378.137/$sunCoor{distance}; # horizonal parallax
  540. ($sunCoor{ra},$sunCoor{dec}) = Astro_Ecl2Equ($sunCoor{lon}, $sunCoor{lat}, $TDT);
  541. #-- calculate horizonal coordinates of sun, if geographic positions is given
  542. if (defined($observerlat) && defined($lmst) ) {
  543. ($sunCoor{az},$sunCoor{alt}) = Astro_Equ2Altaz($sunCoor{ra}, $sunCoor{dec}, $TDT, $observerlat, $lmst);
  544. }
  545. $sunCoor{sig} = $zodiac[floor($sunCoor{lon}*$RAD/30)];
  546. return ( \%sunCoor );
  547. }
  548. ########################################################################################################
  549. #
  550. # Astro_MoonPosition - Calculate data and coordinates for the Moon
  551. # Coordinates are accurate to about 1/5 degree (in ecliptic coordinates)
  552. #
  553. ########################################################################################################
  554. sub Astro_MoonPosition($$$$$$$){
  555. my ($sunlon, $sunanomalyMean, $TDT, $observerlon, $observerlat, $observerradius, $lmst) = @_;
  556. my $D = $TDT-2447891.5;
  557. #-- Mean Moon orbit elements as of 1990.0
  558. my $l0 = 318.351648*$DEG;
  559. my $P0 = 36.340410*$DEG;
  560. my $N0 = 318.510107*$DEG;
  561. my $i = 5.145396*$DEG;
  562. my $e = 0.054900;
  563. my $a = 384401; # km
  564. my $diameter0 = 0.5181*$DEG; # angular diameter of Moon at a distance
  565. my $parallax0 = 0.9507*$DEG; # parallax at distance a
  566. my $l = 13.1763966*$DEG*$D+$l0;
  567. my $MMoon = $l-0.1114041*$DEG*$D-$P0; # Moon's mean anomaly M
  568. my $N = $N0-0.0529539*$DEG*$D; # Moon's mean ascending node longitude
  569. my $C = $l-$sunlon;
  570. my $Ev = 1.2739*$DEG*sin(2*$C-$MMoon);
  571. my $Ae = 0.1858*$DEG*sin($sunanomalyMean);
  572. my $A3 = 0.37*$DEG*sin($sunanomalyMean);
  573. my $MMoon2 = $MMoon+$Ev-$Ae-$A3; # corrected Moon anomaly
  574. my $Ec = 6.2886*$DEG*sin($MMoon2); # equation of centre
  575. my $A4 = 0.214*$DEG*sin(2*$MMoon2);
  576. my $l2 = $l+$Ev+$Ec-$Ae+$A4; # corrected Moon's longitude
  577. my $V = 0.6583*$DEG*sin(2*($l2-$sunlon));
  578. my $l3 = $l2+$V; # true orbital longitude;
  579. my $N2 = $N-0.16*$DEG*sin($sunanomalyMean);
  580. my %moonCoor;
  581. $moonCoor{lon} = Astro_mod2Pi( $N2 + atan2( sin($l3-$N2)*cos($i), cos($l3-$N2) ) );
  582. $moonCoor{lat} = asin( sin($l3-$N2)*sin($i) );
  583. $moonCoor{orbitLon} = $l3;
  584. ($moonCoor{ra},$moonCoor{dec}) = Astro_Ecl2Equ($moonCoor{lon},$moonCoor{lat},$TDT);
  585. #-- relative distance to semi mayor axis of lunar oribt
  586. my $distance = (1-$e*$e) / (1+$e*cos($MMoon2+$Ec) );
  587. $moonCoor{diameter} = $diameter0/$distance; # angular diameter in radians
  588. $moonCoor{parallax} = $parallax0/$distance; # horizontal parallax in radians
  589. $moonCoor{distance} = $distance*$a; # distance in km
  590. #-- Calculate horizonal coordinates of moon, if geographic positions is given
  591. #-- backup geocentric coordinates
  592. $moonCoor{raGeocentric} = $moonCoor{ra};
  593. $moonCoor{decGeocentric} = $moonCoor{dec};
  594. $moonCoor{distanceGeocentric} = $moonCoor{distance};
  595. if (defined($observerlat) && defined($observerlon) && defined($lmst) ) {
  596. #-- transform geocentric coordinates into topocentric (==observer based) coordinates
  597. my ($distanceTopocentric,$decTopocentric,$raTopocentric) =
  598. Astro_GeoEqu2TopoEqu($moonCoor{ra}, $moonCoor{dec}, $moonCoor{distance}, $observerlon, $observerlat, $observerradius, $lmst);
  599. #-- now ra and dec are topocentric
  600. $moonCoor{ra} = $raTopocentric;
  601. $moonCoor{dec} = $decTopocentric;
  602. ($moonCoor{az},$moonCoor{alt})= Astro_Equ2Altaz($moonCoor{ra}, $moonCoor{dec}, $TDT, $observerlat, $lmst);
  603. }
  604. #-- Age of Moon in radians since New Moon (0) - Full Moon (pi)
  605. $moonCoor{age} = Astro_mod2Pi($l3-$sunlon);
  606. $moonCoor{phasen} = 0.5*(1-cos($moonCoor{age})); # Moon phase numerical, 0-1
  607. my $mainPhase = 1./29.53*360*$DEG; # show 'Newmoon, 'Quarter' for +/-1 day around the actual event
  608. my $p = Astro_mod($moonCoor{age}, 90.*$DEG);
  609. if ($p < $mainPhase || $p > 90*$DEG-$mainPhase){
  610. $p = 2*floor($moonCoor{age} / (90.*$DEG)+0.5);
  611. }else{
  612. $p = 2*floor($moonCoor{age} / (90.*$DEG))+1;
  613. }
  614. $p = $p % 8;
  615. $moonCoor{phases} = $phases[$p];
  616. $moonCoor{phasei} = $p;
  617. $moonCoor{sig} = $zodiac[floor($moonCoor{lon}*$RAD/30)];
  618. return ( \%moonCoor );
  619. }
  620. ########################################################################################################
  621. #
  622. # Astro_Refraction - Input true altitude in radians, Output: increase in altitude in degrees
  623. #
  624. ########################################################################################################
  625. sub Astro_Refraction($){
  626. my ($alt) = @_;
  627. my $altdeg = $alt*$RAD;
  628. if ($altdeg<-2 || $altdeg>=90){
  629. return(0);
  630. }
  631. my $pressure = 1015;
  632. my $temperature = 10;
  633. if ($altdeg>15){
  634. return( 0.00452*$pressure/( (273+$temperature)*tan($alt)) );
  635. }
  636. my $y = $alt;
  637. my $D = 0.0;
  638. my $P = ($pressure-80.)/930.;
  639. my $Q = 0.0048*($temperature-10.);
  640. my $y0 = $y;
  641. my $D0 = $D;
  642. my $N;
  643. for (my $i=0; $i<3; $i++) {
  644. $N = $y+(7.31/($y+4.4));
  645. $N = 1./tan($N*$DEG);
  646. $D = $N*$P/(60.+$Q*($N+39.));
  647. $N = $y-$y0;
  648. $y0 = $D-$D0-$N;
  649. if (($N != 0.) && ($y0 != 0.)) {
  650. $N = $y-$N*($alt+$D-$y)/$y0;
  651. } else {
  652. $N = $alt+$D;
  653. }
  654. $y0 = $y;
  655. $D0 = $D;
  656. $y = $N;
  657. }
  658. return( $D );
  659. }
  660. ########################################################################################################
  661. #
  662. # Astro_GMSTRiseSet - returns Greenwich sidereal time (hours) of time of rise
  663. # and set of object with coordinates ra/dec
  664. # at geographic position lon/lat (all values in radians)
  665. # Correction for refraction and semi-diameter/parallax of body is taken care of in function RiseSet
  666. # h is used to calculate the twilights. It gives the required elevation of the disk center of the sun
  667. #
  668. ########################################################################################################
  669. sub Astro_GMSTRiseSet($$$$$){
  670. my ($ra, $dec, $lon, $lat, $h) = @_;
  671. $h = (defined($h)) ? $h : 0.0; # set default value
  672. #Log 1,"-------------------> Called Astro_GMSTRiseSet with $ra $dec $lon $lat $h";
  673. # my $tagbogen = acos(-tan(lat)*tan(coor.dec)); // simple formula if twilight is not required
  674. my $tagbarg = (sin($h) - sin($lat)*sin($dec)) / (cos($lat)*cos($dec));
  675. if( ($tagbarg > 1.000000) || ($tagbarg < -1.000000) ){
  676. Log 5,"[Astro_GMSTRiseSet] Parameters $ra $dec $lon $lat $h give complex angle";
  677. return( ("---","---","---") );
  678. };
  679. my $tagbogen = acos($tagbarg);
  680. my $transit = $RAD/15*( +$ra-$lon);
  681. my $rise = 24.+$RAD/15*(-$tagbogen+$ra-$lon); # calculate GMST of rise of object
  682. my $set = $RAD/15*(+$tagbogen+$ra-$lon); # calculate GMST of set of object
  683. #--Using the modulo function Astro_mod, the day number goes missing. This may get a problem for the moon
  684. $transit = Astro_mod($transit, 24);
  685. $rise = Astro_mod($rise, 24);
  686. $set = Astro_mod($set, 24);
  687. return( ($transit, $rise, $set) );
  688. }
  689. ########################################################################################################
  690. #
  691. # Astro_InterpolateGMST - Find GMST of rise/set of object from the two calculated
  692. # (start)points (day 1 and 2) and at midnight UT(0)
  693. #
  694. ########################################################################################################
  695. sub Astro_InterpolateGMST($$$$){
  696. my ($gmst0, $gmst1, $gmst2, $timefactor) = @_;
  697. return( ($timefactor*24.07*$gmst1- $gmst0*($gmst2-$gmst1)) / ($timefactor*24.07+$gmst1-$gmst2) );
  698. }
  699. ########################################################################################################
  700. #
  701. # Astro_RiseSet
  702. # // JD is the Julian Date of 0h UTC time (midnight)
  703. #
  704. ########################################################################################################
  705. sub Astro_RiseSet($$$$$$$$$$$){
  706. my ($jd0UT, $diameter, $parallax, $ra1, $dec1, $ra2, $dec2, $lon, $lat, $timeinterval, $altip) = @_;
  707. #--altitude of sun center: semi-diameter, horizontal parallax and (standard) refraction of 34'
  708. # true height of sun center for sunrise and set calculation. Is kept 0 for twilight (ie. altitude given):
  709. my $alt = (!defined($altip)) ? 0.5*$diameter-$parallax+34./60*$DEG : 0.;
  710. my $altitude = (!defined($altip)) ? 0. : $altip;
  711. my ($transit1, $rise1, $set1) = Astro_GMSTRiseSet($ra1, $dec1, $lon, $lat, $altitude);
  712. my ($transit2, $rise2, $set2) = Astro_GMSTRiseSet($ra2, $dec2, $lon, $lat, $altitude);
  713. #-- complex angle
  714. if( ($transit1 eq "---") || ($transit2 eq "---") ){
  715. return( ("---","---","---") );
  716. }
  717. #-- unwrap GMST in case we move across 24h -> 0h
  718. $transit2 += 24
  719. if ($transit1 > $transit2 && abs($transit1-$transit2)>18);
  720. $rise2 += 24
  721. if ($rise1 > $rise2 && abs($rise1-$rise2)>18);
  722. $set2 += 24
  723. if ($set1 > $set2 && abs($set1-$set2)>18);
  724. my $T0 = Astro_GMST($jd0UT);
  725. # my $T02 = T0-zone*1.002738; // Greenwich sidereal time at 0h time zone (zone: hours)
  726. #-- Greenwich sidereal time for 0h at selected longitude
  727. my $T02 = $T0-$lon*$RAD/15*1.002738;
  728. $T02 +=24 if ($T02 < 0);
  729. if ($transit1 < $T02) {
  730. $transit1 += 24;
  731. $transit2 += 24;
  732. }
  733. if ($rise1 < $T02) {
  734. $rise1 += 24;
  735. $rise2 += 24;
  736. }
  737. if ($set1 < $T02) {
  738. $set1 += 24;
  739. $set2 += 24;
  740. }
  741. #-- Refraction and Parallax correction
  742. my $decMean = 0.5*($dec1+$dec2);
  743. my $psi = acos(sin($lat)/cos($decMean));
  744. my $y = asin(sin($alt)/sin($psi));
  745. my $dt = 240*$RAD*$y/cos($decMean)/3600; # time correction due to refraction, parallax
  746. my $transit = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $transit1, $transit2, $timeinterval) );
  747. my $rise = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $rise1, $rise2, $timeinterval) - $dt );
  748. my $set = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $set1, $set2, $timeinterval) + $dt );
  749. return( ($transit,$rise,$set) );
  750. }
  751. ########################################################################################################
  752. #
  753. # Astro_SunRise - Find (local) time of sunrise and sunset, and twilights
  754. # JD is the Julian Date of 0h local time (midnight)
  755. # Accurate to about 1-2 minutes
  756. # recursive: 1 - calculate rise/set in UTC in a second run
  757. # recursive: 0 - find rise/set on the current local day.
  758. # This is set when doing the first call to this function
  759. #
  760. ########################################################################################################
  761. sub Astro_SunRise($$$$$$){
  762. my ($JD, $deltaT, $lon, $lat, $zone, $recursive) = @_;
  763. my $jd0UT = floor($JD-0.5)+0.5; # JD at 0 hours UT
  764. #-- calculations for noon
  765. my $sunCoor1 = Astro_SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef);
  766. #-- calculations for next day's UTC midnight
  767. my $sunCoor2 = Astro_SunPosition($jd0UT+1.+$deltaT/24./3600.,undef,undef);
  768. #-- rise/set time in UTC
  769. my ($transit,$rise,$set) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax},
  770. $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1,undef);
  771. if( $transit eq "---" ){
  772. Log 1,"[Astro_SunRise] no solution possible - maybe the sun never sets ?";
  773. return( ($transit,$rise,$set) );
  774. }
  775. my ($transittemp,$risetemp,$settemp);
  776. #-- check and adjust to have rise/set time on local calendar day
  777. if ( $recursive==0 ) {
  778. if ($zone>0) {
  779. #rise time was yesterday local time -> calculate rise time for next UTC day
  780. if ($rise >=24-$zone || $transit>=24-$zone || $set>=24-$zone) {
  781. ($transittemp,$risetemp,$settemp) = Astro_SunRise($JD+1, $deltaT, $lon, $lat, $zone, 1);
  782. $transit = $transittemp
  783. if ($transit>=24-$zone);
  784. $rise = $risetemp
  785. if ($rise>=24-$zone);
  786. $set = $settemp
  787. if ($set>=24-$zone);
  788. }
  789. }elsif ($zone<0) {
  790. #rise time was yesterday local time -> calculate rise time for previous UTC day
  791. if ($rise<-$zone || $transit<-zone || $set<-zone) {
  792. ($transittemp,$risetemp,$settemp) = Astro_SunRise($JD-1, $deltaT, $lon, $lat, $zone, 1);
  793. $rise = $risetemp
  794. if ($rise<-$zone);
  795. $transit = $transittemp
  796. if ($transit<-$zone);
  797. $set = $settemp
  798. if ($set <-$zone);
  799. }
  800. }
  801. $transit = Astro_mod($transit+$zone, 24.);
  802. $rise = Astro_mod($rise +$zone, 24.);
  803. $set = Astro_mod($set +$zone, 24.);
  804. #-- Twilight calculation
  805. #-- civil twilight time in UTC.
  806. my $CivilTwilightMorning;
  807. my $CivilTwilightEvening;
  808. ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax},
  809. $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -6.*$DEG);
  810. if( $transittemp eq "---" ){
  811. Log 3,"[Astro_SunRise] no solution possible for civil twilight - maybe the sun never sets below -6 degrees?";
  812. $CivilTwilightMorning = "---";
  813. $CivilTwilightEvening = "---";
  814. }else{
  815. $CivilTwilightMorning = Astro_mod($risetemp +$zone, 24.);
  816. $CivilTwilightEvening = Astro_mod($settemp +$zone, 24.);
  817. }
  818. #-- nautical twilight time in UTC.
  819. my $NauticTwilightMorning;
  820. my $NauticTwilightEvening;
  821. ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax},
  822. $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -12.*$DEG);
  823. if( $transittemp eq "---" ){
  824. Log 3,"[Astro_SunRise] no solution possible for nautical twilight - maybe the sun never sets below -12 degrees?";
  825. $NauticTwilightMorning = "---";
  826. $NauticTwilightEvening = "---";
  827. }else{
  828. $NauticTwilightMorning = Astro_mod($risetemp +$zone, 24.);
  829. $NauticTwilightEvening = Astro_mod($settemp +$zone, 24.);
  830. }
  831. #-- astronomical twilight time in UTC.
  832. my $AstroTwilightMorning;
  833. my $AstroTwilightEvening;
  834. ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax},
  835. $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -18.*$DEG);
  836. if( $transittemp eq "---" ){
  837. Log 3,"[Astro_SunRise] no solution possible for astronomical twilight - maybe the sun never sets below -18 degrees?";
  838. $AstroTwilightMorning = "---";
  839. $AstroTwilightEvening = "---";
  840. }else{
  841. $AstroTwilightMorning = Astro_mod($risetemp +$zone, 24.);
  842. $AstroTwilightEvening = Astro_mod($settemp +$zone, 24.);
  843. }
  844. #-- custom twilight time in UTC
  845. my $CustomTwilightMorning;
  846. my $CustomTwilightEvening;
  847. ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax},
  848. $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, $Astro{ObsHor}*$DEG);
  849. if( $transittemp eq "---" ){
  850. Log 3,"[Astro_SunRise] no solution possible for custom twilight - maybe the sun never sets below ".$Astro{ObsHor}." degrees?";
  851. $CustomTwilightMorning = "---";
  852. $CustomTwilightEvening = "---";
  853. }else{
  854. $CustomTwilightMorning = Astro_mod($risetemp +$zone, 24.);
  855. $CustomTwilightEvening = Astro_mod($settemp +$zone, 24.);
  856. }
  857. return( ($transit,$rise,$set,$CivilTwilightMorning,$CivilTwilightEvening,
  858. $NauticTwilightMorning,$NauticTwilightEvening,$AstroTwilightMorning,$AstroTwilightEvening,$CustomTwilightMorning,$CustomTwilightEvening) );
  859. }else{
  860. return( ($transit,$rise,$set) );
  861. }
  862. }
  863. ########################################################################################################
  864. #
  865. # Astro_MoonRise - Find local time of moonrise and moonset
  866. # JD is the Julian Date of 0h local time (midnight)
  867. # Accurate to about 5 minutes or better
  868. # recursive: 1 - calculate rise/set in UTC
  869. # recursive: 0 - find rise/set on the current local day (set could also be first)
  870. # returns '' for moonrise/set does not occur on selected day
  871. #
  872. ########################################################################################################
  873. sub Astro_MoonRise($$$$$$$){
  874. my ($JD, $deltaT, $lon, $lat, $radius, $zone, $recursive) = @_;
  875. my $timeinterval = 0.5;
  876. my $jd0UT = floor($JD-0.5)+0.5; # JD at 0 hours UT
  877. #-- calculations for noon
  878. my $sunCoor1 = Astro_SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef);
  879. my $moonCoor1 = Astro_MoonPosition($sunCoor1->{lon}, $sunCoor1->{anomalyMean}, $jd0UT+ $deltaT/24./3600.,undef,undef,undef,undef);
  880. #-- calculations for next day's midnight
  881. my $sunCoor2 = Astro_SunPosition($jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef);
  882. my $moonCoor2 = Astro_MoonPosition($sunCoor2->{lon}, $sunCoor2->{anomalyMean}, $jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef,undef,undef);
  883. # rise/set time in UTC, time zone corrected later.
  884. # Taking into account refraction, semi-diameter and parallax
  885. my ($transit,$rise,$set) = Astro_RiseSet($jd0UT, $moonCoor1->{diameter}, $moonCoor1->{parallax},
  886. $moonCoor1->{ra}, $moonCoor1->{dec}, $moonCoor2->{ra}, $moonCoor2->{dec}, $lon, $lat, $timeinterval,undef);
  887. my ($transittemp,$risetemp,$settemp);
  888. my ($transitprev,$riseprev,$setprev);
  889. # check and adjust to have rise/set time on local calendar day
  890. if ( $recursive==0 ) {
  891. if ($zone>0) {
  892. # recursive call to MoonRise returns events in UTC
  893. ($transitprev,$riseprev,$setprev) = Astro_MoonRise($JD-1., $deltaT, $lon, $lat, $radius, $zone, 1);
  894. if ($transit >= 24.-$zone || $transit < -$zone) { # transit time is tomorrow local time
  895. if ($transitprev < 24.-$zone){
  896. $transit = ""; # there is no moontransit today
  897. }else{
  898. $transit = $transitprev;
  899. }
  900. }
  901. if ($rise >= 24.-$zone || $rise < -$zone) { # rise time is tomorrow local time
  902. if ($riseprev < 24.-$zone){
  903. $rise = ""; # there is no moontransit today
  904. }else{
  905. $rise = $riseprev;
  906. }
  907. }
  908. if ($set >= 24.-$zone || $set < -$zone) { # set time is tomorrow local time
  909. if ($setprev < 24.-$zone){
  910. $set = ""; # there is no moontransit today
  911. }else{
  912. $set = $setprev;
  913. }
  914. }
  915. }elsif ($zone<0) { # rise/set time was tomorrow local time -> calculate rise time for previous UTC day
  916. if ($rise<-$zone || $set<-$zone || $transit<-$zone) {
  917. ($transittemp,$risetemp,$settemp) = Astro_MoonRise($JD+1., $deltaT, $lon, $lat, $radius, $zone, 1);
  918. if ($rise < -$zone) {
  919. if ($risetemp > -$zone){
  920. $rise = ''; # there is no moonrise today
  921. }else{
  922. $rise = $risetemp;
  923. }
  924. }
  925. if ($transit < -zone){
  926. if ($transittemp > -zone){
  927. $transit = ''; # there is no moonset today
  928. }else{
  929. $transit = $transittemp;
  930. }
  931. }
  932. if ($set < -zone){
  933. if ($settemp > -zone){
  934. $set = ''; # there is no moonset today
  935. }else{
  936. $set = $settemp;
  937. }
  938. }
  939. }
  940. }
  941. #-- correct for time zone, if time is valid
  942. $transit = Astro_mod($transit +$zone, 24.)
  943. if( $transit ne "");
  944. $rise = Astro_mod($rise +$zone, 24.)
  945. if ($rise ne "");
  946. $set = Astro_mod($set +$zone, 24.)
  947. if ($set ne "");
  948. }
  949. return( ($transit,$rise,$set) );
  950. }
  951. ########################################################################################################
  952. #
  953. # Astro_Compute - sequential calculation of properties
  954. #
  955. ########################################################################################################
  956. sub Astro_Compute($){
  957. my ($hash) = @_;
  958. my $name = $hash->{NAME};
  959. #-- readjust language
  960. my $lang = AttrVal("global","language","EN");
  961. if( $lang eq "DE"){
  962. $astro_tt = \%astro_transtable_DE;
  963. }else{
  964. $astro_tt = \%astro_transtable_EN;
  965. }
  966. return undef if( !$init_done );
  967. #-- geodetic latitude and longitude of observer on WGS84
  968. if( defined($attr{$name}{"latitude"}) ){
  969. $Astro{ObsLat} = $attr{$name}{"latitude"};
  970. }elsif( defined($attr{"global"}{"latitude"}) ){
  971. $Astro{ObsLat} = $attr{"global"}{"latitude"};
  972. }else{
  973. $Astro{ObsLat} = 50.0;
  974. Log3 $name,3,"[Astro] No latitude attribute set in global device, using 50.0°";
  975. }
  976. if( defined($attr{$name}{"longitude"}) ){
  977. $Astro{ObsLon} = $attr{$name}{"longitude"};
  978. }elsif( defined($attr{"global"}{"longitude"}) ){
  979. $Astro{ObsLon} = $attr{"global"}{"longitude"};
  980. }else{
  981. $Astro{ObsLon} = 10.0;
  982. Log3 $name,3,"[Astro] No longitude attribute set in global device, using 10.0°";
  983. }
  984. #-- altitude of observer in meters above WGS84 ellipsoid
  985. if( defined($attr{$name}{"altitude"}) ){
  986. $Astro{ObsAlt} = $attr{$name}{"altitude"};
  987. }elsif( defined($attr{"global"}{"altitude"}) ){
  988. $Astro{ObsAlt} = $attr{"global"}{"altitude"};
  989. }else{
  990. $Astro{ObsAlt} = 0.0;
  991. Log3 $name,3,"[Astro] No altitude attribute set in global device, using 0.0 m above sea level";
  992. }
  993. #-- custom horizon of observer in degrees
  994. if( defined($attr{$name}{"horizon"}) ){
  995. $Astro{ObsHor} = $attr{$name}{"horizon"};
  996. }else{
  997. $Astro{ObsHor} = 0.0;
  998. Log3 $name,5,"[Astro] No horizon attribute defined, using 0.0°";
  999. }
  1000. #-- internal variables converted to Radians and km
  1001. my $lat = $Astro{ObsLat}*$DEG;
  1002. my $lon = $Astro{ObsLon}*$DEG;
  1003. my $height = $Astro{ObsAlt} * 0.001;
  1004. #if (eval(form.Year.value)<=1900 || eval(form.Year.value)>=2100 ) {
  1005. # alert("Dies Script erlaubt nur Berechnungen"+
  1006. # return;
  1007. #}
  1008. my $JD0 = Astro_CalcJD( $Date{day}, $Date{month}, $Date{year} );
  1009. my $JD = $JD0 + ( $Date{hour} - $Date{zonedelta} + $Date{min}/60. + $Date{sec}/3600.)/24;
  1010. my $TDT = $JD + $deltaT/86400.0;
  1011. $Astro{ObsJD} = Astro_round($JD,2);
  1012. my $gmst = Astro_GMST($JD);
  1013. $Astro{ObsGMST} = Astro_HHMMSS($gmst);
  1014. my $lmst = Astro_GMST2LMST($gmst, $lon);
  1015. $Astro{ObsLMST} = Astro_HHMMSS($lmst);
  1016. #-- geocentric cartesian coordinates of observer
  1017. my ($x,$y,$z,$radius) = Astro_Observer2EquCart($lon, $lat, $height, $gmst);
  1018. #-- calculate data for the sun at given time
  1019. my $sunCoor = Astro_SunPosition($TDT, $lat, $lmst*15.*$DEG);
  1020. $Astro{SunLon} = Astro_round($sunCoor->{lon}*$RAD,1);
  1021. #$Astro{SunLat} = $sunCoor->{lat}*$RAD;
  1022. $Astro{SunRa} = Astro_round($sunCoor->{ra} *$RAD/15,1);
  1023. $Astro{SunDec} = Astro_round($sunCoor->{dec}*$RAD,1);
  1024. $Astro{SunAz} = Astro_round($sunCoor->{az} *$RAD,1);
  1025. $Astro{SunAlt} = Astro_round($sunCoor->{alt}*$RAD + Astro_Refraction($sunCoor->{alt}),1); # including refraction WARNUNG => *RAD ???
  1026. $Astro{SunSign} = $astro_tt->{$sunCoor->{sig}};
  1027. $Astro{SunDiameter}=Astro_round($sunCoor->{diameter}*$RAD*60,1); #angular diameter in arc seconds
  1028. $Astro{SunDistance}=Astro_round($sunCoor->{distance},0);
  1029. #-- calculate distance from the observer (on the surface of earth) to the center of the sun
  1030. my ($xs,$ys,$zs) = Astro_EquPolar2Cart($sunCoor->{ra}, $sunCoor->{dec}, $sunCoor->{distance});
  1031. $Astro{SunDistanceObserver} = Astro_round(sqrt( ($xs-$x)**2 + ($ys-$y)**2 + ($zs-$z)**2 ),0);
  1032. my ($suntransit,$sunrise,$sunset,$CivilTwilightMorning,$CivilTwilightEvening,
  1033. $NauticTwilightMorning,$NauticTwilightEvening,$AstroTwilightMorning,$AstroTwilightEvening,$CustomTwilightMorning,$CustomTwilightEvening) =
  1034. Astro_SunRise($JD0, $deltaT, $lon, $lat, $Date{zonedelta}, 0);
  1035. $Astro{SunTransit} = Astro_HHMM($suntransit);
  1036. $Astro{SunRise} = Astro_HHMM($sunrise);
  1037. $Astro{SunSet} = Astro_HHMM($sunset);
  1038. $Astro{CivilTwilightMorning} = Astro_HHMM($CivilTwilightMorning);
  1039. $Astro{CivilTwilightEvening} = Astro_HHMM($CivilTwilightEvening);
  1040. $Astro{NauticTwilightMorning} = Astro_HHMM($NauticTwilightMorning);
  1041. $Astro{NauticTwilightEvening} = Astro_HHMM($NauticTwilightEvening);
  1042. $Astro{AstroTwilightMorning} = Astro_HHMM($AstroTwilightMorning);
  1043. $Astro{AstroTwilightEvening} = Astro_HHMM($AstroTwilightEvening);
  1044. $Astro{CustomTwilightMorning} = Astro_HHMM($CustomTwilightMorning);
  1045. $Astro{CustomTwilightEvening} = Astro_HHMM($CustomTwilightEvening);
  1046. #-- calculate data for the moon at given time
  1047. my $moonCoor = Astro_MoonPosition($sunCoor->{lon}, $sunCoor->{anomalyMean}, $TDT, $lon, $lat, $radius, $lmst*15.*$DEG);
  1048. $Astro{MoonLon} = Astro_round($moonCoor->{lon}*$RAD,1);
  1049. $Astro{MoonLat} = Astro_round($moonCoor->{lat}*$RAD,1);
  1050. $Astro{MoonRa} = Astro_round($moonCoor->{ra} *$RAD/15.,1);
  1051. $Astro{MoonDec} = Astro_round($moonCoor->{dec}*$RAD,1);
  1052. $Astro{MoonAz} = Astro_round($moonCoor->{az} *$RAD,1);
  1053. $Astro{MoonAlt} = Astro_round($moonCoor->{alt}*$RAD + Astro_Refraction($moonCoor->{alt}),1); # including refraction WARNUNG => *RAD ???
  1054. $Astro{MoonSign} = $astro_tt->{$moonCoor->{sig}};
  1055. $Astro{MoonDistance} = Astro_round($moonCoor->{distance},0);
  1056. $Astro{MoonDiameter} = Astro_round($moonCoor->{diameter}*$RAD*60.,1); # angular diameter in arc seconds
  1057. $Astro{MoonAge} = Astro_round($moonCoor->{age}*$RAD,1);
  1058. $Astro{MoonPhaseN} = Astro_round($moonCoor->{phasen},2);
  1059. $Astro{MoonPhaseI} = $moonCoor->{phasei};
  1060. $Astro{MoonPhaseS} = $astro_tt->{$moonCoor->{phases}};
  1061. #-- calculate distance from the observer (on the surface of earth) to the center of the moon
  1062. my ($xm,$ym,$zm) = Astro_EquPolar2Cart($moonCoor->{ra}, $moonCoor->{dec}, $moonCoor->{distance});
  1063. #Log 1," distance=".$moonCoor->{distance}." test=".sqrt( ($xm)**2 + ($ym)**2 + ($zm)**2 )." $xm $ym $zm";
  1064. #Log 1," distance=".$radius." test=".sqrt( ($x)**2 + ($y)**2 + ($z)**2 )." $x $y $z";
  1065. $Astro{MoonDistanceObserver} = Astro_round(sqrt( ($xm-$x)**2 + ($ym-$y)**2 + ($zm-$z)**2 ),0);
  1066. my ($moontransit,$moonrise,$moonset) = Astro_MoonRise($JD0, $deltaT, $lon, $lat, $radius, $Date{zonedelta}, 0);
  1067. $Astro{MoonTransit} = Astro_HHMM($moontransit);
  1068. $Astro{MoonRise} = Astro_HHMM($moonrise);
  1069. $Astro{MoonSet} = Astro_HHMM($moonset);
  1070. #-- fix date
  1071. $Astro{ObsDate}= sprintf("%02d.%02d.%04d",$Date{day},$Date{month},$Date{year});
  1072. $Astro{ObsTime}= sprintf("%02d:%02d:%02d",$Date{hour},$Date{min},$Date{sec});
  1073. $Astro{ObsTimezone}= $Date{zonedelta};
  1074. $Astro{ObsIsDST}= $Date{isdst};
  1075. #-- check season
  1076. my $doj = $Date{dayofyear};
  1077. $Astro{ObsDayofyear} = $doj;
  1078. for( my $i=0;$i<4;$i++){
  1079. my $key = $seasons[$i];
  1080. if( (($seasonn{$key}[0] < $seasonn{$key}[1]) && ($seasonn{$key}[0] <= $doj) && ($seasonn{$key}[1] >= $doj))
  1081. || (($seasonn{$key}[0] > $seasonn{$key}[1]) && (($seasonn{$key}[0] <= $doj) || ($seasonn{$key}[1] >= $doj))) ){
  1082. $Astro{ObsSeason} = $astro_tt->{$key};
  1083. $Astro{ObsSeasonN} = $i;
  1084. last;
  1085. }
  1086. }
  1087. return( undef );
  1088. };
  1089. ########################################################################################
  1090. #
  1091. # Astro_moonwidget - SVG picture of the moon
  1092. #
  1093. # Parameter hash = hash of the bus master a = argument array
  1094. #
  1095. ########################################################################################
  1096. sub Astro_moonwidget($){
  1097. my ($arg) = @_;
  1098. my $name = $FW_webArgs{name};
  1099. $name =~ s/'//g;
  1100. my $hash = $defs{$name};
  1101. my $mooncolor = 'rgb(255,220,100)';
  1102. my $moonshadow = 'rgb(70,70,100)';
  1103. $mooncolor = $FW_webArgs{mooncolor}
  1104. if ($FW_webArgs{mooncolor} );
  1105. $moonshadow = $FW_webArgs{moonshadow}
  1106. if ($FW_webArgs{moonshadow} );
  1107. my @size = split('x', ($FW_webArgs{size} ? $FW_webArgs{size} : '400x400'));
  1108. $FW_RETTYPE = "image/svg+xml";
  1109. $FW_RET="";
  1110. FW_pO '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" width="'.$size[0].'px" height="'.$size[1].'px">';
  1111. my $ma = Astro_Get($hash,("","text","MoonAge"));
  1112. my $mb = Astro_Get($hash,("","text","MoonPhaseS"));
  1113. my ($radius,$axis,$dir,$start,$middle);
  1114. $radius = 250;
  1115. $axis = sin(($ma+90)*$DEG)*$radius;
  1116. $axis = -$axis
  1117. if ($axis < 0);
  1118. if( (0.0 <= $ma && $ma <= 90) || (270.0 < $ma && $ma <= 360.0) ){
  1119. $dir = 1;
  1120. }else{
  1121. $dir = 0;
  1122. }
  1123. if( 0.0 < $ma && $ma <= 180 ){
  1124. $start = $radius;
  1125. $middle = -$radius;
  1126. }else{
  1127. $start = -$radius;
  1128. $middle = $radius;
  1129. }
  1130. FW_pO '<g transform="translate(400,400) scale(-1,1)">';
  1131. FW_pO '<circle cx="0" cy="0" r="250" fill="'.$moonshadow.'"/>';
  1132. FW_pO '<path d="M 0 '.$start.' A '.$axis.' '.$radius.' 0 0 '.$dir.' 0 '.$middle.' A '.$radius.' '.$radius.' 0 0 0 0 '.$start.' Z" fill="'.$mooncolor.'"/>';
  1133. FW_pO '</g>';
  1134. #FW_pO '<text x="100" y="710" style="font-family:Helvetica;font-size:60px;font-weight:bold" fill="black">'.$mb.'</text>';
  1135. FW_pO '</svg>';
  1136. return ($FW_RETTYPE, $FW_RET);
  1137. }
  1138. ########################################################################################
  1139. #
  1140. # Astro_Update - Update readings
  1141. #
  1142. # Parameter hash = hash of the bus master a = argument array
  1143. #
  1144. ########################################################################################
  1145. sub Astro_Update($@) {
  1146. my ($hash) = @_;
  1147. my $name = $hash->{NAME};
  1148. RemoveInternalTimer($hash);
  1149. my $interval = ( defined($hash->{INTERVAL})) ? $hash->{INTERVAL} : 3600;
  1150. InternalTimer(gettimeofday()+ $interval, "Astro_Update", $hash,1)
  1151. if( $interval > 0 );
  1152. #-- Current time will be used
  1153. my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time);
  1154. $year += 1900;
  1155. $month += 1;
  1156. $Date{year} = $year;
  1157. $Date{month}= $month;
  1158. $Date{day} = $day;
  1159. $Date{hour} = $hour;
  1160. $Date{min} = $min;
  1161. $Date{sec} = $sec;
  1162. $Date{isdst}= $isdst;
  1163. #-- broken on windows
  1164. #$Date{zonedelta} = (strftime "%z", localtime)/100;
  1165. $Date{zonedelta} = Astro_tzoffset(time)/100;
  1166. #-- half broken in windows
  1167. $Date{dayofyear} = 1*strftime("%j", localtime);
  1168. Astro_Compute($hash);
  1169. readingsBeginUpdate($hash);
  1170. foreach my $key (keys %Astro){
  1171. readingsBulkUpdateIfChanged($hash,$key,$Astro{$key});
  1172. }
  1173. readingsEndUpdate($hash,1);
  1174. readingsSingleUpdate($hash,"state","Updated",1);
  1175. }
  1176. ########################################################################################
  1177. #
  1178. # Astro_Get - Implements GetFn function
  1179. #
  1180. # Parameter hash = hash of the bus master a = argument array
  1181. #
  1182. ########################################################################################
  1183. sub Astro_Get($@) {
  1184. my ($hash, @a) = @_;
  1185. my $name = $hash->{NAME};
  1186. my $wantsreading = 0;
  1187. #-- second parameter may be a reading
  1188. if( (int(@a)>2) && exists($Astro{$a[2]})) {
  1189. $wantsreading = 1;
  1190. #Log 1,"=================> WANT as ".$a[1]." READING ".$a[2]." GET READING ".$Astro{$a[2]};
  1191. }
  1192. if( int(@a) > (2+$wantsreading) ) {
  1193. my $str = (int(@a) == (4+$wantsreading)) ? $a[2+$wantsreading]." ".$a[3+$wantsreading] : $a[2+$wantsreading];
  1194. if( $str =~ /(\d{4})-(\d{2})-(\d{2})(\D*(\d{2}):(\d{2})(:(\d{2}))?)?/){
  1195. $Date{year} = $1;
  1196. $Date{month}= $2;
  1197. $Date{day} = $3;
  1198. $Date{hour} = (defined($5)) ? $5 : 12;
  1199. $Date{min} = (defined($6)) ? $6 : 0;
  1200. $Date{sec} = (defined($8)) ? $8 : 0;
  1201. my $fTot = timelocal($Date{sec},$Date{min},$Date{hour},$Date{day},$Date{month}-1,$Date{year});
  1202. #-- broken on windows
  1203. #$Date{zonedelta} = (strftime "%z", localtime($fTot))/100;
  1204. $Date{zonedelta} = Astro_tzoffset($fTot)/100;
  1205. $Date{isdst} = (localtime($fTot))[8];
  1206. #-- half broken in windows
  1207. $Date{dayofyear} = 1*strftime("%j", localtime($fTot));
  1208. }else{
  1209. return "[Astro_Get] $name has improper time specification $str, use YYYY-MM-DD HH:MM:SS";
  1210. }
  1211. }else{
  1212. #-- Current time will be used
  1213. my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time);
  1214. $year += 1900;
  1215. $month += 1;
  1216. $Date{year} = $year;
  1217. $Date{month}= $month;
  1218. $Date{day} = $day;
  1219. $Date{hour} = $hour;
  1220. $Date{min} = $min;
  1221. $Date{sec} = $sec;
  1222. $Date{isdst}= $isdst;
  1223. #-- broken on windows
  1224. #$Date{zonedelta} = (strftime "%z", localtime)/100;
  1225. $Date{zonedelta} = Astro_tzoffset(time)/100;
  1226. #-- half broken in windows
  1227. $Date{dayofyear} = 1*strftime("%j", localtime);
  1228. }
  1229. if( $a[1] eq "version") {
  1230. return $astroversion;
  1231. }elsif( $a[1] eq "json") {
  1232. Astro_Compute($hash);
  1233. if( $wantsreading==1 ){
  1234. return toJSON($Astro{$a[2]});
  1235. }else{
  1236. return toJSON(\%Astro);
  1237. }
  1238. }elsif( $a[1] eq "text") {
  1239. Astro_Compute($hash);
  1240. if( $wantsreading==1 ){
  1241. return $Astro{$a[2]};
  1242. }else{
  1243. my $ret=sprintf("%s %s %s",$astro_tt->{"date"},$Astro{ObsDate},$Astro{ObsTime});
  1244. $ret .= (($Astro{ObsIsDST}==1) ? " (".$astro_tt->{"dst"}.")\n" : "\n" );
  1245. $ret .= sprintf("%s %.2f %s, %d %s\n",$astro_tt->{"jdate"},$Astro{ObsJD},$astro_tt->{"days"},$Astro{ObsDayofyear},$astro_tt->{"dayofyear"});
  1246. $ret .= sprintf("%s %s, %s %2d\n",$astro_tt->{"season"},$Astro{ObsSeason},$astro_tt->{"timezone"},$Astro{ObsTimezone});
  1247. $ret .= sprintf("%s %.5f° %s, %.5f° %s, %.0fm %s\n",$astro_tt->{"coord"},$Astro{ObsLon},$astro_tt->{"longitude"},
  1248. $Astro{ObsLat},$astro_tt->{"latitude"},$Astro{ObsAlt},$astro_tt->{"altitude"});
  1249. $ret .= sprintf("%s %s \n\n",$astro_tt->{"lmst"},$Astro{ObsLMST});
  1250. $ret .= "\n".$astro_tt->{"sun"}."\n";
  1251. $ret .= sprintf("%s %s %s %s %s %s\n",$astro_tt->{"rise"},$Astro{SunRise},$astro_tt->{"set"},$Astro{SunSet},$astro_tt->{"transit"},$Astro{SunTransit});
  1252. $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightcivil"},$Astro{CivilTwilightMorning},$Astro{CivilTwilightEvening});
  1253. $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightnautic"},$Astro{NauticTwilightMorning},$Astro{NauticTwilightEvening});
  1254. $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightastro"},$Astro{AstroTwilightMorning},$Astro{AstroTwilightEvening});
  1255. $ret .= sprintf("%s: %.0fkm %s (%.0fkm %s)\n",$astro_tt->{"distance"},$Astro{SunDistance},$astro_tt->{"toce"},$Astro{SunDistanceObserver},$astro_tt->{"toobs"});
  1256. $ret .= sprintf("%s: %s %2.1f°, %s %2.2fh, %s %2.1f°; %s %2.1f°, %s %2.1f°\n",
  1257. $astro_tt->{"position"},$astro_tt->{"lonecl"},$Astro{SunLon},$astro_tt->{"ra"},
  1258. $Astro{SunRa},$astro_tt->{"dec"},$Astro{SunDec},$astro_tt->{"az"},$Astro{SunAz},$astro_tt->{"alt"},$Astro{SunAlt});
  1259. $ret .= sprintf("%s %2.1f', %s %s\n\n",$astro_tt->{"diameter"},$Astro{SunDiameter},$astro_tt->{"sign"},$Astro{SunSign});
  1260. $ret .= "\n".$astro_tt->{"moon"}."\n";
  1261. $ret .= sprintf("%s %s %s %s %s %s\n",$astro_tt->{"rise"},$Astro{MoonRise},$astro_tt->{"set"},$Astro{MoonSet},$astro_tt->{"transit"},$Astro{MoonTransit});
  1262. $ret .= sprintf("%s: %.0fkm %s (%.0fkm %s)\n",$astro_tt->{"distance"},$Astro{MoonDistance},$astro_tt->{"toce"},$Astro{MoonDistanceObserver},$astro_tt->{"toobs"});
  1263. $ret .= sprintf("%s: %s %2.1f°, %s %2.1f°; %s %2.2fh, %s %2.1f°; %s %2.1f°, %s %2.1f°\n",
  1264. $astro_tt->{"position"},$astro_tt->{"lonecl"},$Astro{MoonLon},$astro_tt->{"latecl"},$Astro{MoonLat},$astro_tt->{"ra"},
  1265. $Astro{MoonRa},$astro_tt->{"dec"},$Astro{MoonDec},$astro_tt->{"az"},$Astro{MoonAz},$astro_tt->{"alt"},$Astro{MoonAlt});
  1266. $ret .= sprintf("%s %2.1f', %s %2.1f°, %s %1.2f = %s, %s %s\n",$astro_tt->{"diameter"},
  1267. $Astro{MoonDiameter},$astro_tt->{"age"},$Astro{MoonAge},$astro_tt->{"phase"},$Astro{MoonPhaseN},$Astro{MoonPhaseS},$astro_tt->{"sign"},$Astro{MoonSign});
  1268. return $ret;
  1269. }
  1270. }else {
  1271. return "[Astro_Get] $name with unknown argument $a[1], choose one of ".
  1272. join(" ", sort keys %gets);
  1273. }
  1274. }
  1275. 1;
  1276. =pod
  1277. =item helper
  1278. =item summary collection of various routines for astronomical data
  1279. =item summary_DE Sammlung verschiedener Routinen für astronomische Daten
  1280. =begin html
  1281. <a name="Astro"></a>
  1282. <h3>Astro</h3>
  1283. <ul>
  1284. <p> FHEM module with a collection of various routines for astronomical data</p>
  1285. <a name="Astrodefine"></a>
  1286. <h4>Define</h4>
  1287. <p>
  1288. <code>define &lt;name&gt; Astro</code>
  1289. <br />Defines the Astro device (only one is needed per FHEM installation). </p>
  1290. <p>
  1291. Readings with prefix <i>Sun</i> refer to the sun, with prefix <i>Moon</i> refer to the moon.
  1292. The suffixes for these readings are
  1293. <ul>
  1294. <li><i>Age</i> = angle (in degrees) of body along its track</li>
  1295. <li><i>Az,Alt</i> = azimuth and altitude angle (in degrees) of body above horizon</li>
  1296. <li><i>Dec,Ra</i> = declination (in degrees) and right ascension (in HH:MM) of body position</li>
  1297. <li><i>Lat,Lon</i> = latitude and longituds (in degrees) of body position</li>
  1298. <li><i>Diameter</i> = virtual diameter (in arc minutes) of body</li>
  1299. <li><i>Distance,DistanceObserver</i> = distance (in km) of body to center of earth or to observer</li>
  1300. <li><i>PhaseN,PhaseS</i> = Numerical and string value for phase of body</li>
  1301. <li><i>Sign</i> = Circadian sign for body along its track</li>
  1302. <li><i>Rise,Transit,Set</i> = times (in HH:MM) for rise and set as well as for highest position of body</li>
  1303. </ul>
  1304. <p>
  1305. Readings with prefix <i>Obs</i> refer to the observer.
  1306. In addition to some of the suffixes gives above, the following may occur
  1307. <ul>
  1308. <li><i>Date,Dayofyear</i> = date</li>
  1309. <li><i>JD</i> = Julian date</li>
  1310. <li><i>Season,SeasonN</i> = String and numerical (0..3) value of season</li>
  1311. <li><i>Time,Timezone</i> obvious meaning</li>
  1312. <li><i>IsDST</i> = 1 if running on daylight savings time, 0 otherwise</li>
  1313. <li><i>GMST,LMST</i> = Greenwich and Local Mean Sidereal Time (in HH:MM)</li>
  1314. </ul>
  1315. <p>
  1316. An SVG image of the current moon phase may be obtained under the link
  1317. <code>&lt;ip address of fhem&gt;/fhem/Astro_moonwidget?name='&lt;device name&gt;'</code>
  1318. Optional web parameters are <code>[&amp;size='&lt;width&gt;x&lt;height&gt;'][&amp;mooncolor=&lt;color&gt;][&amp;moonshadow=&lt;color&gt;]</code>
  1319. <p>
  1320. Notes: <ul>
  1321. <li>Calculations are only valid between the years 1900 and 2100</li>
  1322. <li>Attention: Timezone is taken from the local Perl settings, NOT automatically defined for a location</li>
  1323. <li>This module uses the global attribute <code>language</code> to determine its output data<br/>
  1324. (default: EN=english). For German output set <code>attr global language DE</code>.</li>
  1325. <li>The time zone is determined automatically from the local settings of the <br/>
  1326. operating system. If geocordinates from a different time zone are used, the results are<br/>
  1327. not corrected automatically.</li>
  1328. <li>Some definitions determining the observer position are used<br/>
  1329. from the global device, i.e.<br/>
  1330. <code>attr global longitude &lt;value&gt;</code><br/>
  1331. <code>attr global latitude &lt;value&gt;</code><br/>
  1332. <code>attr global altitude &lt;value&gt;</code> (in m above sea level)<br/>
  1333. These definitions are only used when there are no corresponding local attribute settings.
  1334. </li>
  1335. <li>
  1336. It is not necessary to define an Astro device to use the data provided by this module<br/>
  1337. To use its data in any other module, you just need to put <code>require "95_Astro.pm";</code> <br/>
  1338. at the start of your own code, and then may call, for example, the function<br/>
  1339. <code>Astro_Get( SOME_HASH_REFERENCE,"dummy","text", "SunRise","2019-12-24");</code><br/>
  1340. to acquire the sunrise on Christmas Eve 2019</li>
  1341. </ul>
  1342. <a name="Astroget"></a>
  1343. <h4>Get</h4>
  1344. Attention: Get-calls are NOT written into the readings of the device ! Readings change only through periodic updates !<br/>
  1345. <ul>
  1346. <li><a name="astro_json"></a>
  1347. <code>get &lt;name&gt; json [&lt;reading&gt;]</code><br/>
  1348. <code>get &lt;name&gt; json [&lt;reading&gt;] YYYY-MM-DD</code><br/>
  1349. <code>get &lt;name&gt; json [&lt;reading&gt;] YYYY-MM-DD HH:MM:[SS]</code>
  1350. <br />returns the complete set or an individual reading of astronomical data either for the current time, or for a day and time given in the argument.</li>
  1351. <li><a name="astro_text"></a>
  1352. <code>get &lt;name&gt; text [&lt;reading&gt;]</code><br/>
  1353. <code>get &lt;name&gt; text [&lt;reading&gt;] YYYY-MM-DD</code><br/>
  1354. <code>get &lt;name&gt; text [&lt;reading&gt;] YYYY-MM-DD HH:MM:[SS]</code>
  1355. <br />returns the complete set or an individual reading of astronomical data either for the current time, or for a day and time given in the argument.</li>
  1356. <li><a name="astro_version"></a>
  1357. <code>get &lt;name&gt; version</code>
  1358. <br />Display the version of the module</li>
  1359. </ul>
  1360. <a name="Astroattr"></a>
  1361. <h4>Attributes</h4>
  1362. <ul>
  1363. <li><a name="astro_interval">
  1364. <code>&lt;interval&gt;</code>
  1365. <br />Update interval in seconds. The default is 3600 seconds, a value of 0 disables the automatic update. </li>
  1366. <li>Some definitions determining the observer position:<br/>
  1367. <code>attr &lt;name&gt; longitude &lt;value&gt;</code><br/>
  1368. <code>attr &lt;name&gt; latitude &lt;value&gt;</code><br/>
  1369. <code>attr &lt;name&gt; altitude &lt;value&gt;</code> (in m above sea level)<br/>
  1370. <code>attr &lt;name&gt; horizon &lt;value&gt;</code> custom horizon angle in degrees, default 0<br/>
  1371. These definitions take precedence over global attribute settings.
  1372. </li>
  1373. <li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
  1374. href="#event-on-update-reading">event-on-update-reading</a>, <a
  1375. href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
  1376. >room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
  1377. <a href="#webCmd">webCmd</a></li>
  1378. </ul>
  1379. </ul>
  1380. =end html
  1381. =begin html_DE
  1382. <a name="Astro"></a>
  1383. <h3>Astro</h3>
  1384. <ul>
  1385. <a href="https://wiki.fhem.de/wiki/Modul_Astro">Deutsche Dokumentation im Wiki</a> vorhanden, die englische Version gibt es hier: <a href="/fhem/docs/commandref.html#Astro">Astro</a>
  1386. </ul>
  1387. =end html_DE
  1388. =cut