ascii-side-of-the-moon 1.0.10 → 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 +39 -13
- 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,9 +594,16 @@ 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
608
|
const dim = asciiMoonDim(nearestMoon.ascii);
|
|
602
609
|
let textureAscii = nearestMoon.ascii;
|
|
@@ -678,16 +685,19 @@ function renderMoon(state, _options = {}) {
|
|
|
678
685
|
out.push(row);
|
|
679
686
|
}
|
|
680
687
|
let composed = out.join("\n");
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
+
);
|
|
691
701
|
}
|
|
692
702
|
return showHorizon ? overlayHorizon(composed, state, dim) : composed;
|
|
693
703
|
}
|
|
@@ -790,6 +800,13 @@ function combineDateAndTime(datePart, timePart) {
|
|
|
790
800
|
}
|
|
791
801
|
return `${datePart}T${timePart}`;
|
|
792
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
|
+
}
|
|
793
810
|
function parseCliArgs(args, options = {}) {
|
|
794
811
|
const nowProvider = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
795
812
|
let datePart;
|
|
@@ -797,6 +814,7 @@ function parseCliArgs(args, options = {}) {
|
|
|
797
814
|
let latitude;
|
|
798
815
|
let longitude;
|
|
799
816
|
let elevation;
|
|
817
|
+
let frame;
|
|
800
818
|
for (let i = 0; i < args.length; i++) {
|
|
801
819
|
const arg = args[i];
|
|
802
820
|
if (arg.startsWith("--")) {
|
|
@@ -826,6 +844,12 @@ function parseCliArgs(args, options = {}) {
|
|
|
826
844
|
i = nextIndex;
|
|
827
845
|
break;
|
|
828
846
|
}
|
|
847
|
+
case "frame": {
|
|
848
|
+
const { value, nextIndex } = readFlagValue(args, i, inline);
|
|
849
|
+
frame = parseFrameOrThrow(value);
|
|
850
|
+
i = nextIndex;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
829
853
|
default:
|
|
830
854
|
throw new Error(`Unknown flag: ${flag}`);
|
|
831
855
|
}
|
|
@@ -853,9 +877,11 @@ function parseCliArgs(args, options = {}) {
|
|
|
853
877
|
longitude,
|
|
854
878
|
elevationMeters: elevation
|
|
855
879
|
} : void 0;
|
|
880
|
+
const resolvedFrame = frame ?? (observer ? "observer" : "celestial_up");
|
|
856
881
|
return {
|
|
857
882
|
date,
|
|
858
|
-
observer
|
|
883
|
+
observer,
|
|
884
|
+
frame: resolvedFrame
|
|
859
885
|
};
|
|
860
886
|
}
|
|
861
887
|
|
|
@@ -864,7 +890,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
864
890
|
try {
|
|
865
891
|
const args = parseCliArgs(argv);
|
|
866
892
|
const moonState = getMoonState(args.date, args.observer);
|
|
867
|
-
const asciiMoon = renderMoon(moonState);
|
|
893
|
+
const asciiMoon = renderMoon(moonState, { frame: args.frame });
|
|
868
894
|
console.log(asciiMoon);
|
|
869
895
|
} catch (error) {
|
|
870
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
|
-
`),R
|
|
900
|
-
`)}function
|
|
901
|
-
`),{sx:a,sy:
|
|
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
|