ascii-side-of-the-moon 1.0.9 → 1.0.11
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 +23 -2
- package/dist/cli.cjs +49 -14
- package/dist/index.cjs +9 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.mjs +9 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,12 +24,23 @@ npx ascii-side-of-the-moon "2025-09-19 21:30"
|
|
|
24
24
|
# (Latitude and longitude are optional, but both must be supplied together;
|
|
25
25
|
# elevation is ignored unless lat/lon are provided.)
|
|
26
26
|
npx ascii-side-of-the-moon 2025-09-19T21:30 --lat 37.7749 --lon -122.4194 --elevation 25
|
|
27
|
+
|
|
28
|
+
# Choose a reference frame for the moon orientation
|
|
29
|
+
npx ascii-side-of-the-moon 2025-09-19 --frame=celestial_up # Celestial north up (default without lat/lon)
|
|
30
|
+
npx ascii-side-of-the-moon 2025-09-19 --frame=celestial_down # Celestial south up (inverted)
|
|
31
|
+
npx ascii-side-of-the-moon 2025-09-19 --frame=observer --lat 40.7 --lon -74 # Observer view (zenith up)
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
The CLI will display the ASCII moon art along with information about the moon's phase, illumination percentage, distance, and angular diameter.
|
|
30
35
|
When an observer location is supplied, the renderer also knows the altitude/azimuth
|
|
31
36
|
and can draw the horizon line to show whether the moon is above or below your local horizon.
|
|
32
37
|
|
|
38
|
+
### Frame Options
|
|
39
|
+
|
|
40
|
+
- `celestial_up` - Standard geocentric orientation with celestial north up (default when no lat/lon provided)
|
|
41
|
+
- `celestial_down` - Inverted orientation with celestial south up
|
|
42
|
+
- `observer` - Observer-relative orientation with zenith up, uses parallactic angle (default when lat/lon provided)
|
|
43
|
+
|
|
33
44
|
## Example
|
|
34
45
|
|
|
35
46
|
```js
|
|
@@ -60,8 +71,13 @@ this enables horizon-aware rendering and correct rotation for your sky.
|
|
|
60
71
|
|
|
61
72
|
### `renderMoon(moonState: MoonState, options?: RenderOptions): string`
|
|
62
73
|
Renders the moon as ASCII art. Returns a 29×60 character string.
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
|
|
75
|
+
`RenderOptions` includes:
|
|
76
|
+
- `showHorizon` (default `true` for observer frame): set to `false` to suppress the horizon overlay
|
|
77
|
+
- `frame`: Reference frame for the moon orientation:
|
|
78
|
+
- `"celestial_up"` - Celestial north up (default when no position data)
|
|
79
|
+
- `"celestial_down"` - Celestial south up (180° rotation)
|
|
80
|
+
- `"observer"` - Observer's zenith up, uses parallactic angle (default when position data available)
|
|
65
81
|
|
|
66
82
|
### `getMoonPhase(moonState: MoonState): string`
|
|
67
83
|
Returns the English name of the moon phase (e.g., "New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", "Full Moon", "Waning Gibbous", "Last Quarter", "Waning Crescent").
|
|
@@ -88,6 +104,11 @@ pnpm run render:demo 2025-01-01 21:30 --lat 37.7749 --lon -122.4194 --elevation
|
|
|
88
104
|
|
|
89
105
|
# Just observer location (uses current date/time)
|
|
90
106
|
pnpm run render:demo --lat 40.7128 --lon -74.0060
|
|
107
|
+
|
|
108
|
+
# Choose a reference frame
|
|
109
|
+
pnpm run render:demo 2025-01-01 --frame=celestial_up # Celestial north up
|
|
110
|
+
pnpm run render:demo 2025-01-01 --frame=celestial_down # Celestial south up (inverted)
|
|
111
|
+
pnpm run render:demo 2025-01-01 --frame=observer --lat 40.7 --lon -74 # Observer view
|
|
91
112
|
```
|
|
92
113
|
|
|
93
114
|
Render an animation:
|
package/dist/cli.cjs
CHANGED
|
@@ -594,12 +594,28 @@ function rotateCharacters(ascii, angleDeg, centerX, centerY) {
|
|
|
594
594
|
}
|
|
595
595
|
return output.map((row) => row.join("")).join("\n");
|
|
596
596
|
}
|
|
597
|
+
function resolveFrame(state, options) {
|
|
598
|
+
if (options.frame !== void 0) {
|
|
599
|
+
return options.frame;
|
|
600
|
+
}
|
|
601
|
+
return state.position !== void 0 ? "observer" : "celestial_up";
|
|
602
|
+
}
|
|
597
603
|
function renderMoon(state, _options = {}) {
|
|
598
604
|
const options = _options ?? {};
|
|
599
|
-
const
|
|
605
|
+
const frame = resolveFrame(state, options);
|
|
606
|
+
const showHorizon = options.showHorizon ?? frame === "observer";
|
|
600
607
|
const nearestMoon = findNearestMoonState(state);
|
|
601
|
-
const asciiLines = nearestMoon.ascii.split("\n");
|
|
602
608
|
const dim = asciiMoonDim(nearestMoon.ascii);
|
|
609
|
+
let textureAscii = nearestMoon.ascii;
|
|
610
|
+
if (Math.abs(TEXTURE_ORIENTATION_OFFSET) > 0.1) {
|
|
611
|
+
textureAscii = rotateCharacters(
|
|
612
|
+
textureAscii,
|
|
613
|
+
TEXTURE_ORIENTATION_OFFSET,
|
|
614
|
+
dim.centerX,
|
|
615
|
+
dim.centerY
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const asciiLines = textureAscii.split("\n");
|
|
603
619
|
const { sx, sy, sz } = phaseSunVector(
|
|
604
620
|
state.phase.phaseAngleDeg,
|
|
605
621
|
state.phase.brightLimbAngle,
|
|
@@ -669,16 +685,19 @@ function renderMoon(state, _options = {}) {
|
|
|
669
685
|
out.push(row);
|
|
670
686
|
}
|
|
671
687
|
let composed = out.join("\n");
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
688
|
+
let rotation = 0;
|
|
689
|
+
if (frame === "celestial_down") {
|
|
690
|
+
rotation = 180;
|
|
691
|
+
} else if (frame === "observer" && state.position?.parallacticAngle !== void 0) {
|
|
692
|
+
rotation = -state.position.parallacticAngle;
|
|
693
|
+
}
|
|
694
|
+
if (Math.abs(rotation) > 0.1) {
|
|
695
|
+
composed = rotateCharacters(
|
|
696
|
+
composed,
|
|
697
|
+
rotation,
|
|
698
|
+
dim.centerX,
|
|
699
|
+
dim.centerY
|
|
700
|
+
);
|
|
682
701
|
}
|
|
683
702
|
return showHorizon ? overlayHorizon(composed, state, dim) : composed;
|
|
684
703
|
}
|
|
@@ -781,6 +800,13 @@ function combineDateAndTime(datePart, timePart) {
|
|
|
781
800
|
}
|
|
782
801
|
return `${datePart}T${timePart}`;
|
|
783
802
|
}
|
|
803
|
+
var VALID_FRAMES = ["celestial_up", "celestial_down", "observer"];
|
|
804
|
+
function parseFrameOrThrow(value) {
|
|
805
|
+
if (!VALID_FRAMES.includes(value)) {
|
|
806
|
+
throw new Error(`Invalid frame value: ${value}. Must be one of: ${VALID_FRAMES.join(", ")}.`);
|
|
807
|
+
}
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
784
810
|
function parseCliArgs(args, options = {}) {
|
|
785
811
|
const nowProvider = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
786
812
|
let datePart;
|
|
@@ -788,6 +814,7 @@ function parseCliArgs(args, options = {}) {
|
|
|
788
814
|
let latitude;
|
|
789
815
|
let longitude;
|
|
790
816
|
let elevation;
|
|
817
|
+
let frame;
|
|
791
818
|
for (let i = 0; i < args.length; i++) {
|
|
792
819
|
const arg = args[i];
|
|
793
820
|
if (arg.startsWith("--")) {
|
|
@@ -817,6 +844,12 @@ function parseCliArgs(args, options = {}) {
|
|
|
817
844
|
i = nextIndex;
|
|
818
845
|
break;
|
|
819
846
|
}
|
|
847
|
+
case "frame": {
|
|
848
|
+
const { value, nextIndex } = readFlagValue(args, i, inline);
|
|
849
|
+
frame = parseFrameOrThrow(value);
|
|
850
|
+
i = nextIndex;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
820
853
|
default:
|
|
821
854
|
throw new Error(`Unknown flag: ${flag}`);
|
|
822
855
|
}
|
|
@@ -844,9 +877,11 @@ function parseCliArgs(args, options = {}) {
|
|
|
844
877
|
longitude,
|
|
845
878
|
elevationMeters: elevation
|
|
846
879
|
} : void 0;
|
|
880
|
+
const resolvedFrame = frame ?? (observer ? "observer" : "celestial_up");
|
|
847
881
|
return {
|
|
848
882
|
date,
|
|
849
|
-
observer
|
|
883
|
+
observer,
|
|
884
|
+
frame: resolvedFrame
|
|
850
885
|
};
|
|
851
886
|
}
|
|
852
887
|
|
|
@@ -855,7 +890,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
855
890
|
try {
|
|
856
891
|
const args = parseCliArgs(argv);
|
|
857
892
|
const moonState = getMoonState(args.date, args.observer);
|
|
858
|
-
const asciiMoon = renderMoon(moonState);
|
|
893
|
+
const asciiMoon = renderMoon(moonState, { frame: args.frame });
|
|
859
894
|
console.log(asciiMoon);
|
|
860
895
|
} catch (error) {
|
|
861
896
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var z=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 z__namespace=/*#__PURE__*/_interopNamespace(z);var o=z__namespace.default??z__namespace;function Y($,g){let n=new Date($.getTime());return n.setDate(n.getDate()+g),n}function j($){return ($%360+360)%360}function G($){let g=o.GeoVector(o.Body.Sun,$,true),n=o.GeoVector(o.Body.Moon,$,true),_=o.EquatorFromVector(g),t=o.EquatorFromVector(n),B=(_.ra-t.ra)*15*o.DEG2RAD,e=t.dec*o.DEG2RAD,s=_.dec*o.DEG2RAD,a=Math.sin(B),i=Math.cos(B),r=a,l=Math.cos(e)*Math.tan(s)-Math.sin(e)*i,m=Math.atan2(r,l)*o.RAD2DEG;return j(m)}function H($){let g=($%24+24)%24;return g>12&&(g-=24),g}function v($){let g=o.Illumination(o.Body.Moon,$).phase_fraction;return o.Illumination(o.Body.Moon,Y($,1)).phase_fraction>g}function J($,g,n){let _=o.SiderealTime($),t=g.longitude/15,B=H(t+_-n.ra)*15*o.DEG2RAD,e=g.latitude*o.DEG2RAD,s=n.dec*o.DEG2RAD,a=Math.sin(B),i=Math.tan(e)*Math.cos(s)-Math.sin(s)*Math.cos(B);return Math.atan2(a,i)*o.RAD2DEG}function V($,g){let n=new o.Observer(g.latitude,g.longitude,g.elevationMeters??0),_=o.Equator(o.Body.Moon,$,n,true,true),t=o.Horizon($,n,_.ra,_.dec,"normal");return {azimuth:j(t.azimuth),altitude:t.altitude,parallacticAngle:J($,g,_)}}function Q($,g){let n=o.Illumination(o.Body.Moon,$),_=o.Libration($);return {date:$,phase:{phaseAngleDeg:n.phase_angle,illuminatedFraction:n.phase_fraction,isWaxing:v($),brightLimbAngle:G($)},size:{distanceKm:_.dist_km,angularDiameterDeg:_.diam_deg},libration:{elon:_.elon,elat:_.elat},position:g?V($,g):void 0}}function I($){let{phaseAngleDeg:g,illuminatedFraction:n}=$.phase,_=$.phase.isWaxing??g>90,t=(g%360+360)%360,R=t>180?360-t:t,B=10,e=10,s=8,a=.98,i=.02,r=.02;return R<=B||n>=a?"Full Moon":R>=180-e||n<=i?"New Moon":Math.abs(R-90)<=s||Math.abs(n-.5)<=r?_?"First Quarter":"Last Quarter":R<90?_?"Waxing Gibbous":"Waning Gibbous":_?"Waxing Crescent":"Waning Crescent"}var Z={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 c=60,
|
|
898
|
-
`),n=1/0,
|
|
899
|
-
`),B=R.length,
|
|
900
|
-
`)}function
|
|
901
|
-
`),
|
|
902
|
-
`);
|
|
903
|
-
`),
|
|
904
|
-
`)}function
|
|
897
|
+
`}]};var c=60,A=29,p=-45;function q($){let g=$.split(`
|
|
898
|
+
`),n=1/0,_=-1/0,t=1/0,R=-1/0;if(g.forEach((s,a)=>{let i=s.search(/\S/);if(i!==-1){let r=s.search(/\s+$/);n=Math.min(n,i),_=Math.max(_,r===-1?s.length-1:r-1),t===1/0&&(t=a),R=a;}}),n===1/0)return {width:c,height:A,centerX:Math.floor(c/2),centerY:Math.floor(A/2)};let B=_-n+1,e=R-t+1;return {width:B,height:e,centerX:n+Math.floor(B/2),centerY:t+Math.floor(e/2)}}function X($,g,n){return Math.min(n,Math.max(g,$))}function T($){return $*Math.PI/180}function C($,g,n,_=0){let t=T($),R=T(_),B;g!==void 0?B=T(g):B=T(n?270:90);let e=-Math.sin(B),s=-Math.cos(B),a=e*Math.sin(t),i=s*Math.sin(t),r=Math.cos(t),l=i*Math.cos(R)-r*Math.sin(R),m=i*Math.sin(R)+r*Math.cos(R);i=l,r=m;let W=Math.sqrt(a*a+i*i+r*r);return {sx:a/W,sy:i/W,sz:r/W}}function U($,g,n,_,t){let R=1/c,B=1/A,e=[-0.5,-0.25,0,.25,.5],s=0,a=0;for(let i of e)for(let r of e){let l=$+i*R,m=g+r*B,W=l*l+m*m;if(W>1)continue;let d=Math.sqrt(Math.max(0,1-W)),f=l*n+m*_+d*t;s+=Math.max(0,f),a++;}return a===0?0:s/a}function O($){let _=0,t=1/0;for(let R=0;R<Z.moons.length;R++){let B=Z.moons[R],e=Math.abs(B.distance_km-$.size.distanceKm)*1,s=Math.abs(B.libration_elat-$.libration.elat)*1e4,a=Math.abs(B.libration_elon-$.libration.elon)*1e4,i=e+s+a;i<t&&(t=i,_=R);}return Z.moons[_]}var P=22/10;function D($,g,n,_){let t=(g%360+360)%360;if(Math.abs(t)<.1)return $;let R=$.split(`
|
|
899
|
+
`),B=R.length,e=R[0]?.length??0,s=n??(e-1)/2,a=_??(B-1)/2,i=t*Math.PI/180,r=Math.cos(i),l=Math.sin(i),m=Array.from({length:B},()=>Array(e).fill(" "));for(let W=0;W<B;W++)for(let d=0;d<e;d++){let f=d-s,F=(W-a)*P,h=f*r-F*l,x=f*l+F*r,w=Math.round(h+s),M=Math.round(x/P+a);if(w>=0&&w<e&&M>=0&&M<B){let y=R[M]?.[w]??" ";m[W][d]=y;}}return m.map(W=>W.join("")).join(`
|
|
900
|
+
`)}function $$($,g){return g.frame!==void 0?g.frame:$.position!==void 0?"observer":"celestial_up"}function n$($,g={}){let n=g??{},_=$$($,n),t=n.showHorizon??_==="observer",R=O($),B=q(R.ascii),e=R.ascii;Math.abs(p)>.1&&(e=D(e,p,B.centerX,B.centerY));let s=e.split(`
|
|
901
|
+
`),{sx:a,sy:i,sz:r}=C($.phase.phaseAngleDeg,$.phase.brightLimbAngle,$.phase.isWaxing,$.libration.elat),l=Array.from({length:A},()=>Array(c).fill(0)),m=Array.from({length:A},()=>Array(c).fill(-1)),W=Array.from({length:A},()=>Array(c).fill(false)),d=0,f=0;for(let M=0;M<A;M++)for(let y=0;y<c;y++){let b=y-B.centerX,E=M-B.centerY,u=b/(B.width/2),K=E/(B.height/2),k=u*u+K*K;if(k>1){l[M][y]=0,m[M][y]=-1;continue}W[M][y]=true,d++;let L=U(u,K,a,i,r);l[M][y]=L,L>0&&f++;let N=Math.sqrt(Math.max(0,1-k));m[M][y]=u*a+K*i+N*r;}let F=Array.from({length:A},()=>Array(c).fill(false));if(f>0)for(let M=0;M<A;M++)for(let y=0;y<c;y++)F[M][y]=l[M][y]>0;else {let M=X($.phase.illuminatedFraction??0,0,1),y=Math.max(1,Math.round(M*d)),b=[];for(let E=0;E<A;E++)for(let u=0;u<c;u++)W[E][u]&&b.push({ix:u,iy:E,v:m[E][u]});b.sort((E,u)=>u.v-E.v);for(let E=0;E<Math.min(y,b.length);E++){let{ix:u,iy:K}=b[E];F[K][u]=true;}}let h=[];for(let M=0;M<A;M++){let y="",b=s[M]??"";for(let E=0;E<c;E++)F[M][E]?y+=b[E]??" ":y+=" ";h.push(y);}let x=h.join(`
|
|
902
|
+
`),w=0;return _==="celestial_down"?w=180:_==="observer"&&$.position?.parallacticAngle!==void 0&&(w=-$.position.parallacticAngle),Math.abs(w)>.1&&(x=D(x,w,B.centerX,B.centerY)),t?g$(x,$,B):x}function g$($,g,n){let _=g.position;if(!_||g.size.angularDiameterDeg<=0)return $;let t=_.altitude??0,R=g.size.angularDiameterDeg/2,B=t+R;if(t-R>=0)return $;let s=$.split(`
|
|
903
|
+
`),a=g.size.angularDiameterDeg/Math.max(1,n.height),i=n.centerY+t/a;i=Math.round(X(i,0,A-1));let r;return B<=0?r=`${Math.abs(t).toFixed(1).replace(/\.0$/,"")}-deg-below-horizon`:r="horizon",s[i]=_$(r),s.join(`
|
|
904
|
+
`)}function _$($){let n=`--${$.trim().replace(/\s+/g,"-")}--`;if(n.length>c&&(n=n.slice(0,c)),n.length===c)return n;let _=c-n.length,t=Math.floor(_/2),R=_-t;return "-".repeat(t)+n+"-".repeat(R)}exports.getMoonPhase=I;exports.getMoonState=Q;exports.renderMoon=n$;//# sourceMappingURL=index.cjs.map
|
|
905
905
|
//# sourceMappingURL=index.cjs.map
|