diff --git a/demo/java/src/main/java/io/github/cosinekitty/astronomy/demo/Main.java b/demo/java/src/main/java/io/github/cosinekitty/astronomy/demo/Main.java index 0da0fe59..83752f28 100644 --- a/demo/java/src/main/java/io/github/cosinekitty/astronomy/demo/Main.java +++ b/demo/java/src/main/java/io/github/cosinekitty/astronomy/demo/Main.java @@ -23,10 +23,17 @@ public class Main { " current date and time.", "", " seasons year", - " Given an integer year number, display the solstices and equinoxes for that year.", + " Given an integer year number, displays the solstices and equinoxes for that year.", + " The year must be in the range 0000..9999.", "" ); + private static class DemoException extends Exception { + public DemoException(String message) { + super(message); + } + } + public static void main(String[] args) { int rc = 1; if (args.length == 0) { @@ -51,7 +58,10 @@ public class Main { System.out.printf("ERROR: Unknown command '%s'.%n", verb); } } catch (DateTimeParseException e) { - System.out.println("FATAL: Invalid date/time syntax."); + System.out.println("ERROR: Invalid date/time format on command line."); + rc = 1; + } catch (DemoException e) { + System.out.printf("ERROR: %s%n", e.getMessage()); rc = 1; } } @@ -67,44 +77,38 @@ public class Main { return Time.fromMillisecondsSince1970(millis); } - private static double parseNumber(String name, String text, double minValue, double maxValue) { - double value = Double.NaN; + private static double parseNumber(String name, String text, double minValue, double maxValue) throws DemoException { try { - value = Double.parseDouble(text); - if (value < minValue || value > maxValue) { - System.out.printf("ERROR: Value is out of range for %s.%n", name); - System.exit(1); + double value = Double.parseDouble(text); + if (!Double.isFinite(value) || value < minValue || value > maxValue) { + throw new DemoException(String.format("Value is out of range for %s.", name)); } + return value; } catch (NumberFormatException e) { - System.out.printf("ERROR: Invalid numeric format '%s' for %s.%n", text, name); - System.exit(1); + throw new DemoException(String.format("Invalid numeric format '%s' for %s.%n", text, name)); } - return value; } - private static int parseYear(String text) { - int year = -1; + private static int parseYear(String text) throws DemoException { try { - year = Integer.parseInt(text); + int year = Integer.parseInt(text); if (year < 0 || year > 9999) { - System.out.println("ERROR: Year must be in the range 0000..9999."); - System.exit(1); + throw new DemoException("Year must be in the range 0000..9999."); } + return year; } catch (NumberFormatException e) { - System.out.printf("ERROR: Invalid numeric format '%s' for year.%n", text); - System.exit(1); + throw new DemoException(String.format("Invalid numeric format '%s' for year.", text)); } - return year; } - private static Observer parseObserver(String[] args, int index) { + private static Observer parseObserver(String[] args, int index) throws DemoException { double latitude = parseNumber("latitude", args[index], -90.0, +90.0); double longitude = parseNumber("longitude", args[index+1], -180.0, +180.0); return new Observer(latitude, longitude, 0.0); } private static interface DemoRunner { - public int run(String[] args); + public int run(String[] args) throws DemoException; } private static class Demo { @@ -122,8 +126,21 @@ public class Main { } private static Demo[] demoList = new Demo[] { - new Demo("moonphase", 1, 2, args -> MoonPhase.run(parseTime(args, 1))), - new Demo("positions", 3, 4, args -> Positions.run(parseObserver(args, 1), parseTime(args, 3))), - new Demo("seasons", 2, 2, args -> Seasons.run(parseYear(args[1]))) + new Demo("moonphase", 1, 2, args -> + MoonPhase.run( + parseTime(args, 1) + ) + ), + new Demo("positions", 3, 4, args -> + Positions.run( + parseObserver(args, 1), + parseTime(args, 3) + ) + ), + new Demo("seasons", 2, 2, args -> + Seasons.run( + parseYear(args[1]) + ) + ) }; } diff --git a/demo/kotlin/correct/positions.txt b/demo/kotlin/correct/positions.txt new file mode 100644 index 00000000..1197cc3e --- /dev/null +++ b/demo/kotlin/correct/positions.txt @@ -0,0 +1,13 @@ +UTC date = 2018-11-30T17:55:07.234Z + +BODY RA DEC AZ ALT +Sun 16.43 -21.68 180.91 22.72 +Moon 11.30 7.81 266.73 14.07 +Mercury 15.92 -18.41 189.05 25.50 +Venus 13.81 -9.79 224.05 23.85 +Mars 22.74 -9.25 93.82 -8.52 +Jupiter 16.19 -20.43 184.62 23.84 +Saturn 18.54 -22.70 150.51 16.25 +Uranus 1.80 10.57 43.72 -22.60 +Neptune 23.01 -7.41 89.63 -10.05 +Pluto 19.40 -22.09 139.25 11.68 diff --git a/demo/kotlin/demotest b/demo/kotlin/demotest index 7a185238..a8e24dcd 100755 --- a/demo/kotlin/demotest +++ b/demo/kotlin/demotest @@ -18,7 +18,7 @@ mkdir -p test ./gradlew jar || Fail "Error building Kotlin demo application." TestDemo moonphase 2019-06-15T09:15:32.987Z -#TestDemo positions +45.6 -90.7 2018-11-30T17:55:07.234Z +TestDemo positions +45.6 -90.7 2018-11-30T17:55:07.234Z TestDemo seasons 2019 echo "Kotlin demos: PASS" diff --git a/demo/kotlin/demotest.bat b/demo/kotlin/demotest.bat index b17f63d4..08e035b2 100644 --- a/demo/kotlin/demotest.bat +++ b/demo/kotlin/demotest.bat @@ -13,6 +13,7 @@ call gradlew.bat jar || exit /b 1 REM ---------------------------------------------------------------------------------- call :TestDemo moonphase 2019-06-15T09:15:32.987Z || exit /b 1 +call :TestDemo positions +45.6 -90.7 2018-11-30T17:55:07.234Z || exit /b 1 call :TestDemo seasons 2019 || exit /b 1 echo. diff --git a/demo/kotlin/src/main/kotlin/Main.kt b/demo/kotlin/src/main/kotlin/Main.kt index cbff8e68..90f04cda 100644 --- a/demo/kotlin/src/main/kotlin/Main.kt +++ b/demo/kotlin/src/main/kotlin/Main.kt @@ -11,8 +11,16 @@ Command line arguments: time if none is given on the command line. Also finds the dates and times of the subsequent 10 quarter phases. + positions latitude longitude [yyyy-mm-ddThh:mm:ssZ] + Displays the equatorial and horizontal coordinates of + the Sun, Moon, and planets, as seen from a given + geographic location. Uses the date and time specified on + the command line, if present. Otherwise, uses the computer's + current date and time. + seasons year - Given an integer year number, display the solstices and equinoxes for that year. + Given an integer year number, displays the solstices and equinoxes for that year. + The year must be in the range 0000..9999. """ @@ -32,6 +40,8 @@ fun main(args: Array) { System.exit(runDemo(args)) } +class DemoException(message:String): Exception(message) + internal fun runDemo(args: Array): Int { if (args.size > 0) { val verb = args[0]; @@ -43,9 +53,12 @@ internal fun runDemo(args: Array): Int { } try { return demo.func(args) - } catch (e: DateTimeParseException) { + } catch (_: DateTimeParseException) { println("ERROR: Invalid date/time format on command line.") return 1 + } catch (e: DemoException) { + println("ERROR: ${e.message}") + return 1 } } } @@ -63,7 +76,51 @@ internal fun parseTime(args: Array, index: Int): Time { return Time.fromMillisecondsSince1970(millis) } +internal fun parseNumber(name: String, text: String, minValue: Double, maxValue: Double): Double { + try { + val value = text.toDouble() + if (!value.isFinite() || value < minValue || value > maxValue) { + throw DemoException("Value for $name is out of range.") + } + return value + } catch (_: NumberFormatException) { + throw DemoException("Invalid numeric format '$text' for $name.") + } +} + +internal fun parseYear(text: String): Int { + try { + val year = text.toInt() + if (year < 0 || year > 9999) { + throw DemoException("Year must be in the range 0000..9999.") + } + return year + } catch (_: NumberFormatException) { + throw DemoException("Invalid numeric format '$text' for year.") + } +} + +internal fun parseObserver(args: Array, index: Int): Observer { + val latitude = parseNumber("latitude", args[index], -90.0, +90.0) + val longitude = parseNumber("longitude", args[index+1], -180.0, +180.0) + return Observer(latitude, longitude, 0.0) +} + internal val demoList = arrayOf( - Demo("moonphase", 1, 2) { args -> `Moon Phase demo`(parseTime(args, 1)) }, - Demo("seasons", 2, 2) { args -> `Seasons demo`(args[1].toInt()) } + Demo("moonphase", 1, 2) { args -> + `Moon Phase demo`( + parseTime(args, 1) + ) + }, + Demo("positions", 3, 4) { args -> + `Celestial body positions demo`( + parseObserver(args, 1), + parseTime(args, 3) + ) + }, + Demo("seasons", 2, 2) { args -> + `Seasons demo`( + parseYear(args[1]) + ) + } ) diff --git a/demo/kotlin/src/main/kotlin/Positions.kt b/demo/kotlin/src/main/kotlin/Positions.kt new file mode 100644 index 00000000..bf4ab2fa --- /dev/null +++ b/demo/kotlin/src/main/kotlin/Positions.kt @@ -0,0 +1,28 @@ +import io.github.cosinekitty.astronomy.* + +private val bodyList = arrayOf( + Body.Sun, Body.Moon, Body.Mercury, Body.Venus, Body.Mars, + Body.Jupiter, Body.Saturn, Body.Uranus, Body.Neptune, Body.Pluto +) + +/** + * Print a table of solar system body positions in equatorial and horizontal coordinates. + * + * @param observer + * The geographic location for which to calculate apparent celestial body positions. + * + * @param time + * The date and time for which to calculate positions. + */ +internal fun `Celestial body positions demo`(observer: Observer, time: Time): Int { + println("UTC date = $time") + println() + println("BODY RA DEC AZ ALT") + for (body in bodyList) { + val equ_2000: Equatorial = equator(body, time, observer, EquatorEpoch.J2000, Aberration.Corrected) + val equ_ofdate: Equatorial = equator(body, time, observer, EquatorEpoch.OfDate, Aberration.Corrected) + val hor: Topocentric = horizon(time, observer, equ_ofdate.ra, equ_ofdate.dec, Refraction.Normal) + println("%-8s %8.2f %8.2f %8.2f %8.2f".format(body, equ_2000.ra, equ_2000.dec, hor.azimuth, hor.altitude)) + } + return 0 +}