Hi there,
I was looking for an offline library file for astronomical calculations - and I was lucky. This cool script carries all I need (and more). BUT due to my lack of experience I am not able to make it work. I tried to import it - some functions work, but everything where the coordinates are needed fails.(e.g. A.Get.sunEvents)
So any help or hint is appreciated - thank you in advance.
Christian
I definitely ran into problems when using importModule to load the Astro code. Here is what I initially tried:
- Use the āCopy raw fileā button on the
Astro.js
fileās GitHub page to copy the code. - Start a new Scriptable document, set its name to
astro
and paste in the code. - Create another new Scriptable document with this code:
const A = importModule('astro');
const llh = [47, -87, 100];
console.log(A.Get.sunEvents(new Date, ...llh));
The A.Get.sunEvents call throws an error: Invalid EqCoord object: (NaN, NaN)
I tried using Astro with Node on a computer, and it seemed to work fine (files next to each other, using require instead of importModule). The output shows an object with lots of properties for Sun events for the day in question, the values are Date objects (except a couple of HH:MM:SS
strings for durations).
Using iCloud Drive on the computer, I confirmed that the astro.js file that I used on the computer and phone were identical (except for the comment header that Scriptable adds).
The Cause
After copy-and-pasting bits of code from the A.JulianDay constructor (it is the first thing used by A.Get.sunEvents), I noticed that the properties of the created A.JulianDay objects had the right āshapeā, but the wrong values (even one of the wrong values had a āshapeā that indicated most of the code was running as expected, just with bad values).
It turns out that the Date object in the context of the loaded Astro code is not the same as Date object in the āmainā program. This kind of thing happens in web browsers where different JavaScript realms are used in certain situations. I am not sure why importModule does it though, it doesnāt happen in Node with plain require or import.
This difference causes the instanceof
check in the A.JulianDay constructor to bypass converting the Date to a number. Then chaos ensues when math operations are applied to a Date object (causing many NaNs and some string conversions).
Workaround: Export the import-realm Date
If you donāt need to use importModule with other libraries that also check for Date instances, then you can patch-over the problem like this:
- In your copy of Astro, add this line just after the main A object is initialized (
var A = {}
)
A.Date = Date // export Date for Scriptable importModule users
- In your code, bind this exported Date so that your code uses it instead of the āmain-realmā Date:
const A = importModule('astro');
const Date = A.Date; // use Date from the imported code's realm
// the rest of your code
You could technically do this with multiple imported, instanceof
-Date-checking libraries, but it might get painful having to convert Date objects before switching between using each library.
Workaround: Paste Astro into Your Code
If you only have one program that uses Astro, then you can paste it into your program (with a bit of wrapping to let your code be at the top of the file for easy access).
function main(A) {
const llh = [47, -87, 100];
console.log(A.Get.sunEvents(new Date, ...llh));
}
// "import" Astro and call main
(function(){let exports={};const module={exports};
// paste the Astro code here; it can be run through a minifier to reduce the
// size somewhat
;main(module.exports)})();
Workaround: Eval-based Import
If you do not want a huge blob in your file, or you have multiple programs and do not want to duplicate the Astro code in each one, then you can do a manual import with eval:
function main(A){
const llh = [47, -87, 100];
console.log(A.Get.sunEvents(new Date, ...llh));
}
async function evalFile(fn) {
const code = await (async () => {
const fm = FileManager.local();
fn = [...module.filename.split('/').slice(0, -1), fn].join('/');
if (!fm.fileExists(fn)) fn += '.js';
await fm.downloadFileFromiCloud(fn);
return fm.readString(fn);
})();
{ // use a block to hide module binding so we can still use the global above
let exports = {};
const module = { exports };
eval(code);
return module.exports;
}
}
await main(await evalFile('astro origish'))
Using eval is not great in general, but it is probably okay here: the code is coming from a static file on your device, and this is not really much of a security vulnerability over importing it in some other way.
Neither the paste, nor the eval workarounds require any modification to the Astro code.
Chris - what an amazing thorough answer-
thank you very much!
I was doing the same steps, encountering the same NaN problems and reading about the same workarounds 2 (evaluating) and 3 (paste into own script.)
But I would never ever have managed to dig deep enough to understand the problem as you did - and therefor the proposed A.Date version is a perfect fit for me.
Thank you again!
I tried it out, and it works with some minor adaptations:
I had to assign A.Date like this in the Astro script:
A.Date = new Date()
Using the import I had to assign it to a variable like this:
const B = importModule(ālib_Astro.jsā);
const Date = B.A.Date;
lon = 34.05361
lat = -118.24550
var events = B.A.Get.sunEvents(Date,lat,lon)
log(events)
Or did I miss something ?