ascii-side-of-the-moon 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,7 +71,23 @@ Inside of repository for `ascii-side-of-the-moon`.
71
71
 
72
72
  Render a single date:
73
73
  ```sh
74
+ # Show moon for current date
75
+ pnpm run render:demo
76
+
77
+ # Show moon for a specific date
74
78
  pnpm run render:demo 2025-01-01
79
+
80
+ # Include a specific UTC time
81
+ pnpm run render:demo 2025-01-01 21:30
82
+
83
+ # Include observer location (enables parallactic rotation and horizon display)
84
+ pnpm run render:demo 2025-01-01 21:30 --lat 40.7128 --lon -74.0060
85
+
86
+ # With elevation (meters)
87
+ pnpm run render:demo 2025-01-01 21:30 --lat 37.7749 --lon -122.4194 --elevation 25
88
+
89
+ # Just observer location (uses current date/time)
90
+ pnpm run render:demo --lat 40.7128 --lon -74.0060
75
91
  ```
76
92
 
77
93
  Render an animation:
package/dist/cli.cjs CHANGED
@@ -46,6 +46,22 @@ function addDays(d, days) {
46
46
  function normalizeDegrees(angle) {
47
47
  return (angle % 360 + 360) % 360;
48
48
  }
49
+ function calculateBrightLimbAngle(date) {
50
+ const sunVec = Astronomy.GeoVector(Astronomy.Body.Sun, date, true);
51
+ const moonVec = Astronomy.GeoVector(Astronomy.Body.Moon, date, true);
52
+ const sunEq = Astronomy.EquatorFromVector(sunVec);
53
+ const moonEq = Astronomy.EquatorFromVector(moonVec);
54
+ const dRA_hours = sunEq.ra - moonEq.ra;
55
+ const dRA_rad = dRA_hours * 15 * Astronomy.DEG2RAD;
56
+ const moonDecRad = moonEq.dec * Astronomy.DEG2RAD;
57
+ const sunDecRad = sunEq.dec * Astronomy.DEG2RAD;
58
+ const sinDRA = Math.sin(dRA_rad);
59
+ const cosDRA = Math.cos(dRA_rad);
60
+ const numerator = sinDRA;
61
+ const denominator = Math.cos(moonDecRad) * Math.tan(sunDecRad) - Math.sin(moonDecRad) * cosDRA;
62
+ const positionAngle = Math.atan2(numerator, denominator) * Astronomy.RAD2DEG;
63
+ return normalizeDegrees(positionAngle);
64
+ }
49
65
  function normalizeHourAngle(hours) {
50
66
  let h = (hours % 24 + 24) % 24;
51
67
  if (h > 12) h -= 24;
@@ -85,7 +101,8 @@ function getMoonState(date, observerLocation) {
85
101
  phase: {
86
102
  phaseAngleDeg: illum.phase_angle,
87
103
  illuminatedFraction: illum.phase_fraction,
88
- isWaxing: isWaxingAt(date)
104
+ isWaxing: isWaxingAt(date),
105
+ brightLimbAngle: calculateBrightLimbAngle(date)
89
106
  },
90
107
  size: {
91
108
  distanceKm: lib.dist_km,
@@ -444,7 +461,7 @@ var ascii_default = {
444
461
  // src/render/renderer.ts
445
462
  var FRAME_W = 60;
446
463
  var FRAME_H = 29;
447
- var BASE_ANGLE_OFFSET = 90;
464
+ var TEXTURE_ORIENTATION_OFFSET = -45;
448
465
  function asciiMoonDim(asciiArt) {
449
466
  const lines = asciiArt.split("\n");
450
467
  let minX = Infinity;
@@ -484,15 +501,22 @@ function clamp(v, lo, hi) {
484
501
  function rad(d) {
485
502
  return d * Math.PI / 180;
486
503
  }
487
- function phaseSunVector(phaseAngleDeg, isWaxing, elatDeg = 0) {
504
+ function phaseSunVector(phaseAngleDeg, brightLimbAngleDeg, isWaxing, elatDeg = 0) {
488
505
  const a = rad(phaseAngleDeg);
489
506
  const elat = rad(elatDeg);
490
- const sign = isWaxing ? 1 : -1;
491
- const sx = sign * Math.sin(a);
492
- let sy = 0;
507
+ let sunAngle;
508
+ if (brightLimbAngleDeg !== void 0) {
509
+ sunAngle = rad(brightLimbAngleDeg);
510
+ } else {
511
+ sunAngle = isWaxing ? rad(270) : rad(90);
512
+ }
513
+ const sunDirX = -Math.sin(sunAngle);
514
+ const sunDirY = -Math.cos(sunAngle);
515
+ const sx = sunDirX * Math.sin(a);
516
+ let sy = sunDirY * Math.sin(a);
493
517
  let sz = Math.cos(a);
494
- const sy1 = -sz * Math.sin(elat);
495
- const sz1 = sz * Math.cos(elat);
518
+ const sy1 = sy * Math.cos(elat) - sz * Math.sin(elat);
519
+ const sz1 = sy * Math.sin(elat) + sz * Math.cos(elat);
496
520
  sy = sy1;
497
521
  sz = sz1;
498
522
  const mag = Math.sqrt(sx * sx + sy * sy + sz * sz);
@@ -578,6 +602,7 @@ function renderMoon(state, _options = {}) {
578
602
  const dim = asciiMoonDim(nearestMoon.ascii);
579
603
  const { sx, sy, sz } = phaseSunVector(
580
604
  state.phase.phaseAngleDeg,
605
+ state.phase.brightLimbAngle,
581
606
  state.phase.isWaxing,
582
607
  state.libration.elat
583
608
  );
@@ -643,18 +668,19 @@ function renderMoon(state, _options = {}) {
643
668
  }
644
669
  out.push(row);
645
670
  }
646
- const composed = out.join("\n");
647
- let finalArt = composed;
671
+ let composed = out.join("\n");
648
672
  if (state.position?.parallacticAngle !== void 0) {
649
- const rotationCenter = dim;
650
- finalArt = rotateCharacters(
651
- composed,
652
- state.position.parallacticAngle + BASE_ANGLE_OFFSET,
653
- rotationCenter.centerX,
654
- rotationCenter.centerY
655
- );
673
+ const totalRotation = -state.position.parallacticAngle + TEXTURE_ORIENTATION_OFFSET;
674
+ if (Math.abs(totalRotation) > 0.1) {
675
+ composed = rotateCharacters(
676
+ composed,
677
+ totalRotation,
678
+ dim.centerX,
679
+ dim.centerY
680
+ );
681
+ }
656
682
  }
657
- return showHorizon ? overlayHorizon(finalArt, state, dim) : finalArt;
683
+ return showHorizon ? overlayHorizon(composed, state, dim) : composed;
658
684
  }
659
685
  function overlayHorizon(art, state, moonDim) {
660
686
  const pos = state.position;
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var T=require('astronomy-engine');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var T__namespace=/*#__PURE__*/_interopNamespace(T);var W=T__namespace.default??T__namespace;function P($,g){let n=new Date($.getTime());return n.setDate(n.getDate()+g),n}function p($){return ($%360+360)%360}function X($){let g=($%24+24)%24;return g>12&&(g-=24),g}function N($){let g=W.Illumination(W.Body.Moon,$).phase_fraction;return W.Illumination(W.Body.Moon,P($,1)).phase_fraction>g}function D($,g,n){let _=W.SiderealTime($),t=g.longitude/15,B=X(t+_-n.ra)*15*W.DEG2RAD,a=g.latitude*W.DEG2RAD,R=n.dec*W.DEG2RAD,M=Math.sin(B),o=Math.tan(a)*Math.cos(R)-Math.sin(R)*Math.cos(B);return Math.atan2(M,o)*W.RAD2DEG}function Y($,g){let n=new W.Observer(g.latitude,g.longitude,g.elevationMeters??0),_=W.Equator(W.Body.Moon,$,n,true,true),t=W.Horizon($,n,_.ra,_.dec,"normal");return {azimuth:p(t.azimuth),altitude:t.altitude,parallacticAngle:D($,g,_)}}function H($,g){let n=W.Illumination(W.Body.Moon,$),_=W.Libration($);return {date:$,phase:{phaseAngleDeg:n.phase_angle,illuminatedFraction:n.phase_fraction,isWaxing:N($)},size:{distanceKm:_.dist_km,angularDiameterDeg:_.diam_deg},libration:{elon:_.elon,elat:_.elat},position:g?Y($,g):void 0}}function v($){let{phaseAngleDeg:g,illuminatedFraction:n}=$.phase,_=$.phase.isWaxing??g>90,t=(g%360+360)%360,i=t>180?360-t:t,B=10,a=10,R=8,M=.98,o=.02,r=.02;return i<=B||n>=M?"Full Moon":i>=180-a||n<=o?"New Moon":Math.abs(i-90)<=R||Math.abs(n-.5)<=r?_?"First Quarter":"Last Quarter":i<90?_?"Waxing Gibbous":"Waning Gibbous":_?"Waxing Crescent":"Waning Crescent"}var K={moons:[{index:0,distance_km:405493.5,libration_elat:-0.888,libration_elon:-0.412,ascii:`
1
+ 'use strict';var T=require('astronomy-engine');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var T__namespace=/*#__PURE__*/_interopNamespace(T);var s=T__namespace.default??T__namespace;function p($,g){let n=new Date($.getTime());return n.setDate(n.getDate()+g),n}function z($){return ($%360+360)%360}function P($){let g=s.GeoVector(s.Body.Sun,$,true),n=s.GeoVector(s.Body.Moon,$,true),t=s.EquatorFromVector(g),_=s.EquatorFromVector(n),B=(t.ra-_.ra)*15*s.DEG2RAD,M=_.dec*s.DEG2RAD,a=t.dec*s.DEG2RAD,o=Math.sin(B),i=Math.cos(B),e=o,m=Math.cos(M)*Math.tan(a)-Math.sin(M)*i,A=Math.atan2(e,m)*s.RAD2DEG;return z(A)}function D($){let g=($%24+24)%24;return g>12&&(g-=24),g}function X($){let g=s.Illumination(s.Body.Moon,$).phase_fraction;return s.Illumination(s.Body.Moon,p($,1)).phase_fraction>g}function N($,g,n){let t=s.SiderealTime($),_=g.longitude/15,B=D(_+t-n.ra)*15*s.DEG2RAD,M=g.latitude*s.DEG2RAD,a=n.dec*s.DEG2RAD,o=Math.sin(B),i=Math.tan(M)*Math.cos(a)-Math.sin(a)*Math.cos(B);return Math.atan2(o,i)*s.RAD2DEG}function Y($,g){let n=new s.Observer(g.latitude,g.longitude,g.elevationMeters??0),t=s.Equator(s.Body.Moon,$,n,true,true),_=s.Horizon($,n,t.ra,t.dec,"normal");return {azimuth:z(_.azimuth),altitude:_.altitude,parallacticAngle:N($,g,t)}}function G($,g){let n=s.Illumination(s.Body.Moon,$),t=s.Libration($);return {date:$,phase:{phaseAngleDeg:n.phase_angle,illuminatedFraction:n.phase_fraction,isWaxing:X($),brightLimbAngle:P($)},size:{distanceKm:t.dist_km,angularDiameterDeg:t.diam_deg},libration:{elon:t.elon,elat:t.elat},position:g?Y($,g):void 0}}function H($){let{phaseAngleDeg:g,illuminatedFraction:n}=$.phase,t=$.phase.isWaxing??g>90,_=(g%360+360)%360,R=_>180?360-_:_,B=10,M=10,a=8,o=.98,i=.02,e=.02;return R<=B||n>=o?"Full Moon":R>=180-M||n<=i?"New Moon":Math.abs(R-90)<=a||Math.abs(n-.5)<=e?t?"First Quarter":"Last Quarter":R<90?t?"Waxing Gibbous":"Waning Gibbous":t?"Waxing Crescent":"Waning Crescent"}var K={moons:[{index:0,distance_km:405493.5,libration_elat:-0.888,libration_elon:-0.412,ascii:`
2
2
 
3
3
  ._ . - .
4
4
  ,_++> a, +. \`-.
@@ -894,12 +894,12 @@
894
894
  'FFF'"Z"@MGR^^'\`
895
895
 
896
896
 
897
- `}]};var E=60,w=29,J=90;function V($){let g=$.split(`
898
- `),n=1/0,_=-1/0,t=1/0,i=-1/0;if(g.forEach((R,M)=>{let o=R.search(/\S/);if(o!==-1){let r=R.search(/\s+$/);n=Math.min(n,o),_=Math.max(_,r===-1?R.length-1:r-1),t===1/0&&(t=M),i=M;}}),n===1/0)return {width:E,height:w,centerX:Math.floor(E/2),centerY:Math.floor(w/2)};let B=_-n+1,a=i-t+1;return {width:B,height:a,centerX:n+Math.floor(B/2),centerY:t+Math.floor(a/2)}}function j($,g,n){return Math.min(n,Math.max(g,$))}function z($){return $*Math.PI/180}function Q($,g,n=0){let _=z($),t=z(n),B=(g?1:-1)*Math.sin(_),a=0,R=Math.cos(_),M=-R*Math.sin(t),o=R*Math.cos(t);a=M,R=o;let r=Math.sqrt(B*B+a*a+R*R);return {sx:B/r,sy:a/r,sz:R/r}}function I($,g,n,_,t){let i=1/E,B=1/w,a=[-0.5,-0.25,0,.25,.5],R=0,M=0;for(let o of a)for(let r of a){let A=$+o*i,u=g+r*B,c=A*A+u*u;if(c>1)continue;let d=Math.sqrt(Math.max(0,1-c)),b=A*n+u*_+d*t;R+=Math.max(0,b),M++;}return M===0?0:R/M}function S($){let _=0,t=1/0;for(let i=0;i<K.moons.length;i++){let B=K.moons[i],a=Math.abs(B.distance_km-$.size.distanceKm)*1,R=Math.abs(B.libration_elat-$.libration.elat)*1e4,M=Math.abs(B.libration_elon-$.libration.elon)*1e4,o=a+R+M;o<t&&(t=o,_=i);}return K.moons[_]}var k=22/10;function C($,g,n,_){let t=(g%360+360)%360;if(Math.abs(t)<.1)return $;let i=$.split(`
899
- `),B=i.length,a=i[0]?.length??0,R=n??(a-1)/2,M=_??(B-1)/2,o=t*Math.PI/180,r=Math.cos(o),A=Math.sin(o),u=Array.from({length:B},()=>Array(a).fill(" "));for(let c=0;c<B;c++)for(let d=0;d<a;d++){let b=d-R,f=(c-M)*k,x=b*r-f*A,e=b*A+f*r,s=Math.round(x+R),m=Math.round(e/k+M);if(s>=0&&s<a&&m>=0&&m<B){let y=i[m]?.[s]??" ";u[c][d]=y;}}return u.map(c=>c.join("")).join(`
900
- `)}function q($,g={}){let _=(g??{}).showHorizon!==false,t=S($),i=t.ascii.split(`
901
- `),B=V(t.ascii),{sx:a,sy:R,sz:M}=Q($.phase.phaseAngleDeg,$.phase.isWaxing,$.libration.elat),o=Array.from({length:w},()=>Array(E).fill(0)),r=Array.from({length:w},()=>Array(E).fill(-1)),A=Array.from({length:w},()=>Array(E).fill(false)),u=0,c=0;for(let e=0;e<w;e++)for(let s=0;s<E;s++){let m=s-B.centerX,y=e-B.centerY,l=m/(B.width/2),F=y/(B.height/2),Z=l*l+F*F;if(Z>1){o[e][s]=0,r[e][s]=-1;continue}A[e][s]=true,u++;let h=I(l,F,a,R,M);o[e][s]=h,h>0&&c++;let L=Math.sqrt(Math.max(0,1-Z));r[e][s]=l*a+F*R+L*M;}let d=Array.from({length:w},()=>Array(E).fill(false));if(c>0)for(let e=0;e<w;e++)for(let s=0;s<E;s++)d[e][s]=o[e][s]>0;else {let e=j($.phase.illuminatedFraction??0,0,1),s=Math.max(1,Math.round(e*u)),m=[];for(let y=0;y<w;y++)for(let l=0;l<E;l++)A[y][l]&&m.push({ix:l,iy:y,v:r[y][l]});m.sort((y,l)=>l.v-y.v);for(let y=0;y<Math.min(s,m.length);y++){let{ix:l,iy:F}=m[y];d[F][l]=true;}}let b=[];for(let e=0;e<w;e++){let s="",m=i[e]??"";for(let y=0;y<E;y++)d[e][y]?s+=m[y]??" ":s+=" ";b.push(s);}let f=b.join(`
902
- `),x=f;if($.position?.parallacticAngle!==void 0){let e=B;x=C(f,$.position.parallacticAngle+J,e.centerX,e.centerY);}return _?U(x,$,B):x}function U($,g,n){let _=g.position;if(!_||g.size.angularDiameterDeg<=0)return $;let t=_.altitude??0,i=g.size.angularDiameterDeg/2,B=t+i;if(t-i>=0)return $;let R=$.split(`
903
- `),M=g.size.angularDiameterDeg/Math.max(1,n.height),o=n.centerY+t/M;o=Math.round(j(o,0,w-1));let r;return B<=0?r=`${Math.abs(t).toFixed(1).replace(/\.0$/,"")}-deg-below-horizon`:r="horizon",R[o]=O(r),R.join(`
904
- `)}function O($){let n=`--${$.trim().replace(/\s+/g,"-")}--`;if(n.length>E&&(n=n.slice(0,E)),n.length===E)return n;let _=E-n.length,t=Math.floor(_/2),i=_-t;return "-".repeat(t)+n+"-".repeat(i)}exports.getMoonPhase=v;exports.getMoonState=H;exports.renderMoon=q;//# sourceMappingURL=index.cjs.map
897
+ `}]};var c=60,d=29,J=-45;function V($){let g=$.split(`
898
+ `),n=1/0,t=-1/0,_=1/0,R=-1/0;if(g.forEach((a,o)=>{let i=a.search(/\S/);if(i!==-1){let e=a.search(/\s+$/);n=Math.min(n,i),t=Math.max(t,e===-1?a.length-1:e-1),_===1/0&&(_=o),R=o;}}),n===1/0)return {width:c,height:d,centerX:Math.floor(c/2),centerY:Math.floor(d/2)};let B=t-n+1,M=R-_+1;return {width:B,height:M,centerX:n+Math.floor(B/2),centerY:_+Math.floor(M/2)}}function L($,g,n){return Math.min(n,Math.max(g,$))}function x($){return $*Math.PI/180}function Q($,g,n,t=0){let _=x($),R=x(t),B;g!==void 0?B=x(g):B=x(n?270:90);let M=-Math.sin(B),a=-Math.cos(B),o=M*Math.sin(_),i=a*Math.sin(_),e=Math.cos(_),m=i*Math.cos(R)-e*Math.sin(R),A=i*Math.sin(R)+e*Math.cos(R);i=m,e=A;let W=Math.sqrt(o*o+i*i+e*e);return {sx:o/W,sy:i/W,sz:e/W}}function I($,g,n,t,_){let R=1/c,B=1/d,M=[-0.5,-0.25,0,.25,.5],a=0,o=0;for(let i of M)for(let e of M){let m=$+i*R,A=g+e*B,W=m*m+A*A;if(W>1)continue;let w=Math.sqrt(Math.max(0,1-W)),b=m*n+A*t+w*_;a+=Math.max(0,b),o++;}return o===0?0:a/o}function S($){let t=0,_=1/0;for(let R=0;R<K.moons.length;R++){let B=K.moons[R],M=Math.abs(B.distance_km-$.size.distanceKm)*1,a=Math.abs(B.libration_elat-$.libration.elat)*1e4,o=Math.abs(B.libration_elon-$.libration.elon)*1e4,i=M+a+o;i<_&&(_=i,t=R);}return K.moons[t]}var k=22/10;function q($,g,n,t){let _=(g%360+360)%360;if(Math.abs(_)<.1)return $;let R=$.split(`
899
+ `),B=R.length,M=R[0]?.length??0,a=n??(M-1)/2,o=t??(B-1)/2,i=_*Math.PI/180,e=Math.cos(i),m=Math.sin(i),A=Array.from({length:B},()=>Array(M).fill(" "));for(let W=0;W<B;W++)for(let w=0;w<M;w++){let b=w-a,F=(W-o)*k,r=b*e-F*m,E=b*m+F*e,u=Math.round(r+a),y=Math.round(E/k+o);if(u>=0&&u<M&&y>=0&&y<B){let l=R[y]?.[u]??" ";A[W][w]=l;}}return A.map(W=>W.join("")).join(`
900
+ `)}function C($,g={}){let t=(g??{}).showHorizon!==false,_=S($),R=_.ascii.split(`
901
+ `),B=V(_.ascii),{sx:M,sy:a,sz:o}=Q($.phase.phaseAngleDeg,$.phase.brightLimbAngle,$.phase.isWaxing,$.libration.elat),i=Array.from({length:d},()=>Array(c).fill(0)),e=Array.from({length:d},()=>Array(c).fill(-1)),m=Array.from({length:d},()=>Array(c).fill(false)),A=0,W=0;for(let r=0;r<d;r++)for(let E=0;E<c;E++){let u=E-B.centerX,y=r-B.centerY,l=u/(B.width/2),f=y/(B.height/2),h=l*l+f*f;if(h>1){i[r][E]=0,e[r][E]=-1;continue}m[r][E]=true,A++;let Z=I(l,f,M,a,o);i[r][E]=Z,Z>0&&W++;let j=Math.sqrt(Math.max(0,1-h));e[r][E]=l*M+f*a+j*o;}let w=Array.from({length:d},()=>Array(c).fill(false));if(W>0)for(let r=0;r<d;r++)for(let E=0;E<c;E++)w[r][E]=i[r][E]>0;else {let r=L($.phase.illuminatedFraction??0,0,1),E=Math.max(1,Math.round(r*A)),u=[];for(let y=0;y<d;y++)for(let l=0;l<c;l++)m[y][l]&&u.push({ix:l,iy:y,v:e[y][l]});u.sort((y,l)=>l.v-y.v);for(let y=0;y<Math.min(E,u.length);y++){let{ix:l,iy:f}=u[y];w[f][l]=true;}}let b=[];for(let r=0;r<d;r++){let E="",u=R[r]??"";for(let y=0;y<c;y++)w[r][y]?E+=u[y]??" ":E+=" ";b.push(E);}let F=b.join(`
902
+ `);if($.position?.parallacticAngle!==void 0){let r=-$.position.parallacticAngle+J;Math.abs(r)>.1&&(F=q(F,r,B.centerX,B.centerY));}return t?U(F,$,B):F}function U($,g,n){let t=g.position;if(!t||g.size.angularDiameterDeg<=0)return $;let _=t.altitude??0,R=g.size.angularDiameterDeg/2,B=_+R;if(_-R>=0)return $;let a=$.split(`
903
+ `),o=g.size.angularDiameterDeg/Math.max(1,n.height),i=n.centerY+_/o;i=Math.round(L(i,0,d-1));let e;return B<=0?e=`${Math.abs(_).toFixed(1).replace(/\.0$/,"")}-deg-below-horizon`:e="horizon",a[i]=O(e),a.join(`
904
+ `)}function O($){let n=`--${$.trim().replace(/\s+/g,"-")}--`;if(n.length>c&&(n=n.slice(0,c)),n.length===c)return n;let t=c-n.length,_=Math.floor(t/2),R=t-_;return "-".repeat(_)+n+"-".repeat(R)}exports.getMoonPhase=H;exports.getMoonState=G;exports.renderMoon=C;//# sourceMappingURL=index.cjs.map
905
905
  //# sourceMappingURL=index.cjs.map