From b60a3689910731d9bdb8a431a3dcc9e99f961b35 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Thu, 22 May 2014 18:46:58 -0700 Subject: altoslib: Move CSV/KML output code to altoslib It's sharable, so share it Signed-off-by: Keith Packard --- altoslib/Makefile.am | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'altoslib/Makefile.am') diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index 2ee4d89f..67cc38ff 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -31,6 +31,7 @@ altoslib_JAVA = \ AltosConfigValues.java \ AltosConvert.java \ AltosCRCException.java \ + AltosCSV.java \ AltosDebug.java \ AltosEeprom.java \ AltosEepromChunk.java \ @@ -62,6 +63,7 @@ altoslib_JAVA = \ AltosIdleMonitorListener.java \ AltosIgnite.java \ AltosIMU.java \ + AltosKML.java \ AltosLine.java \ AltosLink.java \ AltosListenerState.java \ @@ -114,7 +116,8 @@ altoslib_JAVA = \ AltosSpeed.java \ AltosTemperature.java \ AltosAccel.java \ - AltosPyro.java + AltosPyro.java \ + AltosWriter.java JAR=altoslib_$(ALTOSLIB_VERSION).jar -- cgit v1.2.3 From 0a6c76fc0525d6588a1d88127f0085f13a02f1af Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Sun, 25 May 2014 20:55:11 -0700 Subject: altosui/altosuilib/altoslib: Move more stuff out of autosui. Reduce site map memory Prepare to share with TeleGPS application. This also has the changes to the site map tile which cache only a few images and regenerate the flight path on the fly, saving piles of memory Signed-off-by: Keith Packard --- altoslib/AltosFlightStats.java | 188 ++++++++++++++ altoslib/Makefile.am | 3 +- altosui/AltosDisplayThread.java | 259 ------------------- altosui/AltosFlightDisplay.java | 28 --- altosui/AltosFlightStats.java | 189 -------------- altosui/AltosFreqList.java | 86 ------- altosui/AltosSiteMap.java | 476 ----------------------------------- altosui/AltosSiteMapCache.java | 119 --------- altosui/AltosSiteMapPreload.java | 467 ---------------------------------- altosui/AltosSiteMapTile.java | 122 --------- altosui/AltosVoice.java | 95 ------- altosui/GrabNDrag.java | 48 ---- altosui/Makefile.am | 12 +- altosuilib/AltosDisplayThread.java | 259 +++++++++++++++++++ altosuilib/AltosFlightDisplay.java | 28 +++ altosuilib/AltosFreqList.java | 85 +++++++ altosuilib/AltosSiteMap.java | 484 ++++++++++++++++++++++++++++++++++++ altosuilib/AltosSiteMapCache.java | 194 +++++++++++++++ altosuilib/AltosSiteMapPreload.java | 467 ++++++++++++++++++++++++++++++++++ altosuilib/AltosSiteMapTile.java | 142 +++++++++++ altosuilib/AltosVoice.java | 94 +++++++ altosuilib/GrabNDrag.java | 48 ++++ altosuilib/Makefile.am | 13 +- 23 files changed, 2003 insertions(+), 1903 deletions(-) create mode 100644 altoslib/AltosFlightStats.java delete mode 100644 altosui/AltosDisplayThread.java delete mode 100644 altosui/AltosFlightDisplay.java delete mode 100644 altosui/AltosFlightStats.java delete mode 100644 altosui/AltosFreqList.java delete mode 100644 altosui/AltosSiteMap.java delete mode 100644 altosui/AltosSiteMapCache.java delete mode 100644 altosui/AltosSiteMapPreload.java delete mode 100644 altosui/AltosSiteMapTile.java delete mode 100644 altosui/AltosVoice.java delete mode 100644 altosui/GrabNDrag.java create mode 100644 altosuilib/AltosDisplayThread.java create mode 100644 altosuilib/AltosFlightDisplay.java create mode 100644 altosuilib/AltosFreqList.java create mode 100644 altosuilib/AltosSiteMap.java create mode 100644 altosuilib/AltosSiteMapCache.java create mode 100644 altosuilib/AltosSiteMapPreload.java create mode 100644 altosuilib/AltosSiteMapTile.java create mode 100644 altosuilib/AltosVoice.java create mode 100644 altosuilib/GrabNDrag.java (limited to 'altoslib/Makefile.am') diff --git a/altoslib/AltosFlightStats.java b/altoslib/AltosFlightStats.java new file mode 100644 index 00000000..87e04293 --- /dev/null +++ b/altoslib/AltosFlightStats.java @@ -0,0 +1,188 @@ +/* + * Copyright © 2011 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +import java.io.*; + +public class AltosFlightStats { + public double max_height; + public double max_gps_height; + public double max_speed; + public double max_acceleration; + public double[] state_speed = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_accel = new double[AltosLib.ao_flight_invalid + 1]; + public int[] state_count = new int[AltosLib.ao_flight_invalid + 1]; + public double[] state_start = new double[AltosLib.ao_flight_invalid + 1]; + public double[] state_end = new double[AltosLib.ao_flight_invalid + 1]; + public int serial; + public int flight; + public int year, month, day; + public int hour, minute, second; + public double lat, lon; + public double pad_lat, pad_lon; + public boolean has_gps; + public boolean has_other_adc; + public boolean has_rssi; + public boolean has_imu; + public boolean has_mag; + public boolean has_orient; + public int num_ignitor; + + double landed_time(AltosStateIterable states) { + AltosState state = null; + + for (AltosState s : states) { + state = s; + if (state.state == AltosLib.ao_flight_landed) + break; + } + + if (state == null) + return 0; + + double landed_height = state.height(); + + state = null; + + boolean above = true; + + double landed_time = -1000; + + for (AltosState s : states) { + state = s; + + if (state.height() > landed_height + 10) { + above = true; + } else { + if (above && state.height() < landed_height + 2) { + above = false; + landed_time = state.time; + } + } + } + if (landed_time == -1000) + landed_time = state.time; + return landed_time; + } + + double boost_time(AltosStateIterable states) { + double boost_time = AltosLib.MISSING; + AltosState state = null; + + for (AltosState s : states) { + state = s; + if (state.acceleration() < 1) + boost_time = state.time; + if (state.state >= AltosLib.ao_flight_boost && state.state <= AltosLib.ao_flight_landed) + break; + } + if (state == null) + return 0; + + if (boost_time == AltosLib.MISSING) + boost_time = state.time; + return boost_time; + } + + + public AltosFlightStats(AltosStateIterable states) throws InterruptedException, IOException { + double boost_time = boost_time(states); + double end_time = 0; + double landed_time = landed_time(states); + + year = month = day = AltosLib.MISSING; + hour = minute = second = AltosLib.MISSING; + serial = flight = AltosLib.MISSING; + lat = lon = AltosLib.MISSING; + has_gps = false; + has_other_adc = false; + has_rssi = false; + has_imu = false; + has_mag = false; + has_orient = false; + for (AltosState state : states) { + if (serial == AltosLib.MISSING && state.serial != AltosLib.MISSING) + serial = state.serial; + if (flight == AltosLib.MISSING && state.flight != AltosLib.MISSING) + flight = state.flight; + if (state.battery_voltage != AltosLib.MISSING) + has_other_adc = true; + if (state.rssi != AltosLib.MISSING) + has_rssi = true; + end_time = state.time; + + int state_id = state.state; + if (state.time >= boost_time && state_id < AltosLib.ao_flight_boost) + state_id = AltosLib.ao_flight_boost; + if (state.time >= landed_time && state_id < AltosLib.ao_flight_landed) + state_id = AltosLib.ao_flight_landed; + if (state.gps != null && state.gps.locked) { + year = state.gps.year; + month = state.gps.month; + day = state.gps.day; + hour = state.gps.hour; + minute = state.gps.minute; + second = state.gps.second; + } + if (0 <= state_id && state_id < AltosLib.ao_flight_invalid) { + double acceleration = state.acceleration(); + double speed = state.speed(); + if (acceleration != AltosLib.MISSING && speed != AltosLib.MISSING) { + state_accel[state_id] += acceleration; + state_speed[state_id] += speed; + state_count[state_id]++; + } + if (state_start[state_id] == 0.0) + state_start[state_id] = state.time; + if (state_end[state_id] < state.time) + state_end[state_id] = state.time; + max_height = state.max_height(); + max_speed = state.max_speed(); + max_acceleration = state.max_acceleration(); + max_gps_height = state.max_gps_height(); + } + if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) { + if (state_id <= AltosLib.ao_flight_pad) { + pad_lat = state.gps.lat; + pad_lon = state.gps.lon; + } + lat = state.gps.lat; + lon = state.gps.lon; + has_gps = true; + } + if (state.imu != null) + has_imu = true; + if (state.mag != null) + has_mag = true; + if (state.orient() != AltosLib.MISSING) + has_orient = true; + if (state.ignitor_voltage != null && state.ignitor_voltage.length > num_ignitor) + num_ignitor = state.ignitor_voltage.length; + } + for (int s = AltosLib.ao_flight_startup; s <= AltosLib.ao_flight_landed; s++) { + if (state_count[s] > 0) { + state_speed[s] /= state_count[s]; + state_accel[s] /= state_count[s]; + } + if (state_start[s] == 0) + state_start[s] = end_time; + if (state_end[s] == 0) + state_end[s] = end_time; + } + } +} diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index 67cc38ff..bff09704 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -1,4 +1,4 @@ -AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -source 6 +AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6 JAVAROOT=bin @@ -51,6 +51,7 @@ altoslib_JAVA = \ AltosFlash.java \ AltosFlashListener.java \ AltosFlightReader.java \ + AltosFlightStats.java \ AltosFrequency.java \ AltosGPS.java \ AltosGPSSat.java \ diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java deleted file mode 100644 index ab85607d..00000000 --- a/altosui/AltosDisplayThread.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.awt.*; -import javax.swing.*; -import java.io.*; -import java.text.*; -import org.altusmetrum.altoslib_3.*; - -public class AltosDisplayThread extends Thread { - - Frame parent; - IdleThread idle_thread; - AltosVoice voice; - AltosFlightReader reader; - AltosState old_state, state; - AltosListenerState listener_state; - AltosFlightDisplay display; - - synchronized void show_safely() { - final AltosState my_state = state; - final AltosListenerState my_listener_state = listener_state; - Runnable r = new Runnable() { - public void run() { - try { - display.show(my_state, my_listener_state); - } catch (Exception ex) { - } - } - }; - SwingUtilities.invokeLater(r); - } - - void reading_error_internal() { - JOptionPane.showMessageDialog(parent, - String.format("Error reading from \"%s\"", reader.name), - "Telemetry Read Error", - JOptionPane.ERROR_MESSAGE); - } - - void reading_error_safely() { - Runnable r = new Runnable() { - public void run() { - try { - reading_error_internal(); - } catch (Exception ex) { - } - } - }; - SwingUtilities.invokeLater(r); - } - - class IdleThread extends Thread { - - boolean started; - int reported_landing; - int report_interval; - long report_time; - - public synchronized void report(boolean last) { - if (state == null) - return; - - /* reset the landing count once we hear about a new flight */ - if (state.state < Altos.ao_flight_drogue) - reported_landing = 0; - - /* Shut up once the rocket is on the ground */ - if (reported_landing > 2) { - return; - } - - /* If the rocket isn't on the pad, then report height */ - if (Altos.ao_flight_drogue <= state.state && - state.state < Altos.ao_flight_landed && - state.from_pad != null && - state.range >= 0) - { - voice.speak("Height %s, bearing %s %d, elevation %d, range %s.\n", - AltosConvert.height.say(state.height()), - state.from_pad.bearing_words( - AltosGreatCircle.BEARING_VOICE), - (int) (state.from_pad.bearing + 0.5), - (int) (state.elevation + 0.5), - AltosConvert.distance.say(state.range)); - } else if (state.state > Altos.ao_flight_pad) { - voice.speak(AltosConvert.height.say_units(state.height())); - } else { - reported_landing = 0; - } - - /* If the rocket is coming down, check to see if it has landed; - * either we've got a landed report or we haven't heard from it in - * a long time - */ - if (state.state >= Altos.ao_flight_drogue && - (last || - System.currentTimeMillis() - state.received_time >= 15000 || - state.state == Altos.ao_flight_landed)) - { - if (Math.abs(state.speed()) < 20 && state.height() < 100) - voice.speak("rocket landed safely"); - else - voice.speak("rocket may have crashed"); - if (state.from_pad != null) - voice.speak("Bearing %d degrees, range %s.", - (int) (state.from_pad.bearing + 0.5), - AltosConvert.distance.say_units(state.from_pad.distance)); - ++reported_landing; - if (state.state != Altos.ao_flight_landed) { - state.state = Altos.ao_flight_landed; - show_safely(); - } - } - } - - long now () { - return System.currentTimeMillis(); - } - - void set_report_time() { - report_time = now() + report_interval; - } - - public void run () { - try { - for (;;) { - if (reader.has_monitor_battery()) { - listener_state.battery = reader.monitor_battery(); - show_safely(); - } - set_report_time(); - for (;;) { - voice.drain(); - synchronized (this) { - long sleep_time = report_time - now(); - if (sleep_time <= 0) - break; - wait(sleep_time); - } - } - - report(false); - } - } catch (InterruptedException ie) { - try { - voice.drain(); - } catch (InterruptedException iie) { } - } - } - - public synchronized void notice(boolean spoken) { - if (old_state != null && old_state.state != state.state) { - report_time = now(); - this.notify(); - } else if (spoken) - set_report_time(); - } - - public IdleThread() { - reported_landing = 0; - report_interval = 10000; - } - } - - synchronized boolean tell() { - boolean ret = false; - if (old_state == null || old_state.state != state.state) { - voice.speak(state.state_name()); - if ((old_state == null || old_state.state <= Altos.ao_flight_boost) && - state.state > Altos.ao_flight_boost) { - voice.speak("max speed: %s.", - AltosConvert.speed.say_units(state.max_speed() + 0.5)); - ret = true; - } else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) && - state.state >= Altos.ao_flight_drogue) { - voice.speak("max height: %s.", - AltosConvert.height.say_units(state.max_height() + 0.5)); - ret = true; - } - } - if (old_state == null || old_state.gps_ready != state.gps_ready) { - if (state.gps_ready) { - voice.speak("GPS ready"); - ret = true; - } - else if (old_state != null) { - voice.speak("GPS lost"); - ret = true; - } - } - old_state = state; - return ret; - } - - public void run() { - boolean interrupted = false; - boolean told; - - idle_thread = new IdleThread(); - idle_thread.start(); - - try { - for (;;) { - try { - state = reader.read(); - if (state == null) - break; - reader.update(state); - show_safely(); - told = tell(); - idle_thread.notice(told); - } catch (ParseException pp) { - System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage()); - } catch (AltosCRCException ce) { - ++listener_state.crc_errors; - show_safely(); - } - } - } catch (InterruptedException ee) { - interrupted = true; - } catch (IOException ie) { - reading_error_safely(); - } finally { - if (!interrupted) - idle_thread.report(true); - reader.close(interrupted); - idle_thread.interrupt(); - try { - idle_thread.join(); - } catch (InterruptedException ie) {} - } - } - - public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) { - listener_state = new AltosListenerState(); - parent = in_parent; - voice = in_voice; - display = in_display; - reader = in_reader; - display.reset(); - } -} diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java deleted file mode 100644 index c1264259..00000000 --- a/altosui/AltosFlightDisplay.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import org.altusmetrum.altoslib_3.*; - -public interface AltosFlightDisplay { - void reset(); - - void show(AltosState state, AltosListenerState listener_state); - - void set_font(); -} diff --git a/altosui/AltosFlightStats.java b/altosui/AltosFlightStats.java deleted file mode 100644 index d02a518d..00000000 --- a/altosui/AltosFlightStats.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright © 2011 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.io.*; -import org.altusmetrum.altoslib_3.*; - -public class AltosFlightStats { - double max_height; - double max_gps_height; - double max_speed; - double max_acceleration; - double[] state_speed = new double[Altos.ao_flight_invalid + 1]; - double[] state_accel = new double[Altos.ao_flight_invalid + 1]; - int[] state_count = new int[Altos.ao_flight_invalid + 1]; - double[] state_start = new double[Altos.ao_flight_invalid + 1]; - double[] state_end = new double[Altos.ao_flight_invalid + 1]; - int serial; - int flight; - int year, month, day; - int hour, minute, second; - double lat, lon; - double pad_lat, pad_lon; - boolean has_gps; - boolean has_other_adc; - boolean has_rssi; - boolean has_imu; - boolean has_mag; - boolean has_orient; - int num_ignitor; - - double landed_time(AltosStateIterable states) { - AltosState state = null; - - for (AltosState s : states) { - state = s; - if (state.state == Altos.ao_flight_landed) - break; - } - - if (state == null) - return 0; - - double landed_height = state.height(); - - state = null; - - boolean above = true; - - double landed_time = -1000; - - for (AltosState s : states) { - state = s; - - if (state.height() > landed_height + 10) { - above = true; - } else { - if (above && state.height() < landed_height + 2) { - above = false; - landed_time = state.time; - } - } - } - if (landed_time == -1000) - landed_time = state.time; - return landed_time; - } - - double boost_time(AltosStateIterable states) { - double boost_time = AltosLib.MISSING; - AltosState state = null; - - for (AltosState s : states) { - state = s; - if (state.acceleration() < 1) - boost_time = state.time; - if (state.state >= AltosLib.ao_flight_boost && state.state <= AltosLib.ao_flight_landed) - break; - } - if (state == null) - return 0; - - if (boost_time == AltosLib.MISSING) - boost_time = state.time; - return boost_time; - } - - - public AltosFlightStats(AltosStateIterable states) throws InterruptedException, IOException { - double boost_time = boost_time(states); - double end_time = 0; - double landed_time = landed_time(states); - - year = month = day = AltosLib.MISSING; - hour = minute = second = AltosLib.MISSING; - serial = flight = AltosLib.MISSING; - lat = lon = AltosLib.MISSING; - has_gps = false; - has_other_adc = false; - has_rssi = false; - has_imu = false; - has_mag = false; - has_orient = false; - for (AltosState state : states) { - if (serial == AltosLib.MISSING && state.serial != AltosLib.MISSING) - serial = state.serial; - if (flight == AltosLib.MISSING && state.flight != AltosLib.MISSING) - flight = state.flight; - if (state.battery_voltage != AltosLib.MISSING) - has_other_adc = true; - if (state.rssi != AltosLib.MISSING) - has_rssi = true; - end_time = state.time; - - int state_id = state.state; - if (state.time >= boost_time && state_id < Altos.ao_flight_boost) - state_id = Altos.ao_flight_boost; - if (state.time >= landed_time && state_id < Altos.ao_flight_landed) - state_id = Altos.ao_flight_landed; - if (state.gps != null && state.gps.locked) { - year = state.gps.year; - month = state.gps.month; - day = state.gps.day; - hour = state.gps.hour; - minute = state.gps.minute; - second = state.gps.second; - } - if (0 <= state_id && state_id < Altos.ao_flight_invalid) { - double acceleration = state.acceleration(); - double speed = state.speed(); - if (acceleration != AltosLib.MISSING && speed != AltosLib.MISSING) { - state_accel[state_id] += acceleration; - state_speed[state_id] += speed; - state_count[state_id]++; - } - if (state_start[state_id] == 0.0) - state_start[state_id] = state.time; - if (state_end[state_id] < state.time) - state_end[state_id] = state.time; - max_height = state.max_height(); - max_speed = state.max_speed(); - max_acceleration = state.max_acceleration(); - max_gps_height = state.max_gps_height(); - } - if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) { - if (state_id <= Altos.ao_flight_pad) { - pad_lat = state.gps.lat; - pad_lon = state.gps.lon; - } - lat = state.gps.lat; - lon = state.gps.lon; - has_gps = true; - } - if (state.imu != null) - has_imu = true; - if (state.mag != null) - has_mag = true; - if (state.orient() != AltosLib.MISSING) - has_orient = true; - if (state.ignitor_voltage != null && state.ignitor_voltage.length > num_ignitor) - num_ignitor = state.ignitor_voltage.length; - } - for (int s = Altos.ao_flight_startup; s <= Altos.ao_flight_landed; s++) { - if (state_count[s] > 0) { - state_speed[s] /= state_count[s]; - state_accel[s] /= state_count[s]; - } - if (state_start[s] == 0) - state_start[s] = end_time; - if (state_end[s] == 0) - state_end[s] = end_time; - } - } -} diff --git a/altosui/AltosFreqList.java b/altosui/AltosFreqList.java deleted file mode 100644 index 525e5ce5..00000000 --- a/altosui/AltosFreqList.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright © 2011 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import javax.swing.*; -import org.altusmetrum.altoslib_3.*; -import org.altusmetrum.altosuilib_1.*; - -public class AltosFreqList extends JComboBox { - - String product; - int serial; - int calibrate; - - public void set_frequency(double new_frequency) { - int i; - - if (new_frequency < 0) { - setVisible(false); - return; - } - - for (i = 0; i < getItemCount(); i++) { - AltosFrequency f = (AltosFrequency) getItemAt(i); - - if (f.close(new_frequency)) { - setSelectedIndex(i); - return; - } - } - for (i = 0; i < getItemCount(); i++) { - AltosFrequency f = (AltosFrequency) getItemAt(i); - - if (new_frequency < f.frequency) - break; - } - String description = String.format("%s serial %d", product, serial); - AltosFrequency frequency = new AltosFrequency(new_frequency, description); - AltosUIPreferences.add_common_frequency(frequency); - insertItemAt(frequency, i); - setMaximumRowCount(getItemCount()); - } - - public void set_product(String new_product) { - product = new_product; - } - - public void set_serial(int new_serial) { - serial = new_serial; - } - - public double frequency() { - AltosFrequency f = (AltosFrequency) getSelectedItem(); - if (f != null) - return f.frequency; - return 434.550; - } - - public AltosFreqList () { - super(AltosUIPreferences.common_frequencies()); - setMaximumRowCount(getItemCount()); - setEditable(false); - product = "Unknown"; - serial = 0; - } - - public AltosFreqList(double in_frequency) { - this(); - set_frequency(in_frequency); - } -} diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java deleted file mode 100644 index 643417b5..00000000 --- a/altosui/AltosSiteMap.java +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright © 2010 Anthony Towns - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.awt.*; -import javax.swing.*; -import java.io.*; -import java.lang.Math; -import java.awt.geom.Point2D; -import java.util.concurrent.*; -import org.altusmetrum.altoslib_3.*; -import org.altusmetrum.altosuilib_1.*; - -public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { - // preferred vertical step in a tile in naut. miles - // will actually choose a step size between x and 2x, where this - // is 1.5x - static final double tile_size_nmi = 0.75; - - static final int px_size = 512; - - static final int MAX_TILE_DELTA = 100; - - private static Point2D.Double translatePoint(Point2D.Double p, - Point2D.Double d) - { - return new Point2D.Double(p.x + d.x, p.y + d.y); - } - - static class LatLng { - public double lat, lng; - public LatLng(double lat, double lng) { - this.lat = lat; - this.lng = lng; - } - } - - // based on google js - // http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js - // search for fromLatLngToPoint and fromPointToLatLng - /* - private static Point2D.Double pt(LatLng latlng, int zoom) { - double scale_x = 256/360.0 * Math.pow(2, zoom); - double scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); - return pt(latlng, scale_x, scale_y); - } - */ - - private static Point2D.Double pt(LatLng latlng, - double scale_x, double scale_y) - { - Point2D.Double res = new Point2D.Double(); - double e; - - res.x = latlng.lng * scale_x; - - e = Math.sin(Math.toRadians(latlng.lat)); - e = Math.max(e,-(1-1.0E-15)); - e = Math.min(e, 1-1.0E-15 ); - - res.y = 0.5*Math.log((1+e)/(1-e))*-scale_y; - return res; - } - - static private LatLng latlng(Point2D.Double pt, - double scale_x, double scale_y) - { - double lat, lng; - double rads; - - lng = pt.x/scale_x; - rads = 2 * Math.atan(Math.exp(-pt.y/scale_y)); - lat = Math.toDegrees(rads - Math.PI/2); - - return new LatLng(lat,lng); - } - - int zoom; - double scale_x, scale_y; - - int radius; /* half width/height of tiles to load */ - - private Point2D.Double pt(double lat, double lng) { - return pt(new LatLng(lat, lng), scale_x, scale_y); - } - - private LatLng latlng(double x, double y) { - return latlng(new Point2D.Double(x,y), scale_x, scale_y); - } - /* - private LatLng latlng(Point2D.Double pt) { - return latlng(pt, scale_x, scale_y); - } - */ - - ConcurrentHashMap mapTiles = new ConcurrentHashMap(); - Point2D.Double centre; - - private Point2D.Double tileCoordOffset(Point p) { - return new Point2D.Double(centre.x - p.x*px_size, - centre.y - p.y * px_size); - } - - private Point tileOffset(Point2D.Double p) { - return new Point((int)Math.floor((centre.x+p.x)/px_size), - (int)Math.floor((centre.y+p.y)/px_size)); - } - - private Point2D.Double getBaseLocation(double lat, double lng) { - Point2D.Double locn, north_step; - - zoom = 2; - // stupid loop structure to please Java's control flow analysis - do { - zoom++; - scale_x = 256/360.0 * Math.pow(2, zoom); - scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); - locn = pt(lat, lng); - north_step = pt(lat+tile_size_nmi*4/3/60.0, lng); - if (locn.y - north_step.y > px_size) - break; - } while (zoom < 22); - locn.x = -px_size * Math.floor(locn.x/px_size); - locn.y = -px_size * Math.floor(locn.y/px_size); - return locn; - } - - public void reset() { - // nothing - } - - public void set_font() { - // nothing - } - - private void loadMap(final AltosSiteMapTile tile, - File pngfile, String pngurl) - { - final ImageIcon res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); - if (res != null) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - tile.loadMap(res); - } - }); - } else { - System.out.printf("# Failed to fetch file %s\n", pngfile); - System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl); - } - } - - File pngfile; - String pngurl; - - public int prefetchMap(int x, int y) { - LatLng map_latlng = latlng( - -centre.x + x*px_size + px_size/2, - -centre.y + y*px_size + px_size/2); - pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); - pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); - if (pngfile.exists()) { - return 1; - } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { - return 0; - } else { - return -1; - } - } - - public static void prefetchMaps(double lat, double lng) { - int w = AltosSiteMapPreload.width; - int h = AltosSiteMapPreload.height; - AltosSiteMap asm = new AltosSiteMap(true); - asm.centre = asm.getBaseLocation(lat, lng); - - //Point2D.Double p = new Point2D.Double(); - //Point2D.Double p2; - int dx = -w/2, dy = -h/2; - for (int y = dy; y < h+dy; y++) { - for (int x = dx; x < w+dx; x++) { - int r = asm.prefetchMap(x, y); - switch (r) { - case 1: - System.out.printf("Already have %s\n", asm.pngfile); - break; - case 0: - System.out.printf("Fetched map %s\n", asm.pngfile); - break; - case -1: - System.out.printf("# Failed to fetch file %s\n", asm.pngfile); - System.out.printf(" wget -O '%s' ''\n", asm.pngfile, asm.pngurl); - break; - } - } - } - } - - public String initMap(Point offset) { - AltosSiteMapTile tile = mapTiles.get(offset); - Point2D.Double coord = tileCoordOffset(offset); - - LatLng map_latlng = latlng(px_size/2-coord.x, px_size/2-coord.y); - - File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); - String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); - loadMap(tile, pngfile, pngurl); - return pngfile.toString(); - } - - public void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) { - Thread thread = new Thread() { - public void run() { - initMap(offset); - finishTileLater(tile, offset); - } - }; - thread.start(); - } - - public void setBaseLocation(double lat, double lng) { - for (Point k : mapTiles.keySet()) { - AltosSiteMapTile tile = mapTiles.get(k); - tile.clearMap(); - } - - centre = getBaseLocation(lat, lng); - scrollRocketToVisible(pt(lat,lng)); - } - - private void initMaps(double lat, double lng) { - setBaseLocation(lat, lng); - - Thread thread = new Thread() { - public void run() { - for (Point k : mapTiles.keySet()) - initMap(k); - } - }; - thread.start(); - } - - private static File MapFile(double lat, double lng, int zoom) { - char chlat = lat < 0 ? 'S' : 'N'; - char chlng = lng < 0 ? 'W' : 'E'; - if (lat < 0) lat = -lat; - if (lng < 0) lng = -lng; - return new File(AltosUIPreferences.mapdir(), - String.format("map-%c%.6f,%c%.6f-%d.png", - chlat, lat, chlng, lng, zoom)); - } - - private static String MapURL(double lat, double lng, int zoom) { - return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size); - } - - boolean initialised = false; - Point2D.Double last_pt = null; - int last_state = -1; - - public void show(double lat, double lon) { - System.out.printf ("show %g %g\n", lat, lon); - return; -// initMaps(lat, lon); -// scrollRocketToVisible(pt(lat, lon)); - } - public void show(final AltosState state, final AltosListenerState listener_state) { - // if insufficient gps data, nothing to update - AltosGPS gps = state.gps; - - if (gps == null) - return; - - if (!gps.locked && gps.nsat < 4) - return; - - if (!initialised) { - if (state.pad_lat != AltosLib.MISSING && state.pad_lon != AltosLib.MISSING) { - initMaps(state.pad_lat, state.pad_lon); - initialised = true; - } else if (gps.lat != AltosLib.MISSING && gps.lon != AltosLib.MISSING) { - initMaps(gps.lat, gps.lon); - initialised = true; - } else { - return; - } - } - - final Point2D.Double pt = pt(gps.lat, gps.lon); - if (last_pt == pt && last_state == state.state) - return; - - if (last_pt == null) { - last_pt = pt; - } - boolean in_any = false; - for (Point offset : mapTiles.keySet()) { - AltosSiteMapTile tile = mapTiles.get(offset); - Point2D.Double ref, lref; - ref = translatePoint(pt, tileCoordOffset(offset)); - lref = translatePoint(last_pt, tileCoordOffset(offset)); - tile.show(state, listener_state, lref, ref); - if (0 <= ref.x && ref.x < px_size) - if (0 <= ref.y && ref.y < px_size) - in_any = true; - } - - Point offset = tileOffset(pt); - if (!in_any) { - Point2D.Double ref, lref; - ref = translatePoint(pt, tileCoordOffset(offset)); - lref = translatePoint(last_pt, tileCoordOffset(offset)); - - AltosSiteMapTile tile = createTile(offset); - tile.show(state, listener_state, lref, ref); - initAndFinishMapAsync(tile, offset); - } - - scrollRocketToVisible(pt); - - if (offset != tileOffset(last_pt)) { - ensureTilesAround(offset); - } - - last_pt = pt; - last_state = state.state; - } - - public void centre(Point2D.Double pt) { - Rectangle r = comp.getVisibleRect(); - Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft)); - int dx = (int)copt.x - r.width/2 - r.x; - int dy = (int)copt.y - r.height/2 - r.y; - r.x += dx; - r.y += dy; - comp.scrollRectToVisible(r); - } - - public void centre(AltosState state) { - if (!state.gps.locked && state.gps.nsat < 4) - return; - centre(pt(state.gps.lat, state.gps.lon)); - } - - public void draw_circle(double lat, double lon) { - final Point2D.Double pt = pt(lat, lon); - - for (Point offset : mapTiles.keySet()) { - AltosSiteMapTile tile = mapTiles.get(offset); - Point2D.Double ref = translatePoint(pt, tileCoordOffset(offset)); - tile.draw_circle(ref); - } - } - - private AltosSiteMapTile createTile(Point offset) { - AltosSiteMapTile tile = new AltosSiteMapTile(px_size); - mapTiles.put(offset, tile); - return tile; - } - private void finishTileLater(final AltosSiteMapTile tile, - final Point offset) - { - SwingUtilities.invokeLater( new Runnable() { - public void run() { - addTileAt(tile, offset); - } - } ); - } - - private void ensureTilesAround(Point base_offset) { - for (int x = -radius; x <= radius; x++) { - for (int y = -radius; y <= radius; y++) { - Point offset = new Point(base_offset.x + x, base_offset.y + y); - if (mapTiles.containsKey(offset)) - continue; - AltosSiteMapTile tile = createTile(offset); - initAndFinishMapAsync(tile, offset); - } - } - } - - private Point topleft = new Point(0,0); - private void scrollRocketToVisible(Point2D.Double pt) { - Rectangle r = comp.getVisibleRect(); - Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft)); - int dx = (int)copt.x - r.width/2 - r.x; - int dy = (int)copt.y - r.height/2 - r.y; - if (Math.abs(dx) > r.width/4 || Math.abs(dy) > r.height/4) { - r.x += dx; - r.y += dy; - comp.scrollRectToVisible(r); - } - } - - private void addTileAt(AltosSiteMapTile tile, Point offset) { - if (Math.abs(offset.x) >= MAX_TILE_DELTA || - Math.abs(offset.y) >= MAX_TILE_DELTA) - { - System.out.printf("Rocket too far away from pad (tile %d,%d)\n", - offset.x, offset.y); - return; - } - - boolean review = false; - Rectangle r = comp.getVisibleRect(); - if (offset.x < topleft.x) { - r.x += (topleft.x - offset.x) * px_size; - topleft.x = offset.x; - review = true; - } - if (offset.y < topleft.y) { - r.y += (topleft.y - offset.y) * px_size; - topleft.y = offset.y; - review = true; - } - GridBagConstraints c = new GridBagConstraints(); - c.anchor = GridBagConstraints.CENTER; - c.fill = GridBagConstraints.BOTH; - // put some space between the map tiles, debugging only - // c.insets = new Insets(5, 5, 5, 5); - - c.gridx = offset.x + MAX_TILE_DELTA; - c.gridy = offset.y + MAX_TILE_DELTA; - layout.setConstraints(tile, c); - - comp.add(tile); - if (review) { - comp.scrollRectToVisible(r); - } - } - - private AltosSiteMap(boolean knowWhatYouAreDoing) { - if (!knowWhatYouAreDoing) { - throw new RuntimeException("Arggh."); - } - } - - JComponent comp = new JComponent() { }; - private GridBagLayout layout = new GridBagLayout(); - - public AltosSiteMap(int in_radius) { - radius = in_radius; - - GrabNDrag scroller = new GrabNDrag(comp); - - comp.setLayout(layout); - - for (int x = -radius; x <= radius; x++) { - for (int y = -radius; y <= radius; y++) { - Point offset = new Point(x, y); - AltosSiteMapTile t = createTile(offset); - addTileAt(t, offset); - } - } - setViewportView(comp); - setPreferredSize(new Dimension(500,500)); - } - - public AltosSiteMap() { - this(1); - } -} diff --git a/altosui/AltosSiteMapCache.java b/altosui/AltosSiteMapCache.java deleted file mode 100644 index 03dc3cf5..00000000 --- a/altosui/AltosSiteMapCache.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2010 Anthony Towns - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import javax.swing.*; -import javax.imageio.ImageIO; -import java.awt.image.*; -import java.io.*; -import java.net.URL; -import java.net.URLConnection; - -public class AltosSiteMapCache extends JLabel { - static final long google_maps_ratelimit_ms = 1200; - // Google limits static map queries to 50 per minute per IP, so - // each query should take at least 1.2 seconds. - - public static boolean fetchMap(File file, String url) { - URL u; - long startTime = System.nanoTime(); - - try { - u = new URL(url); - } catch (java.net.MalformedURLException e) { - return false; - } - - byte[] data; - try { - URLConnection uc = u.openConnection(); - int contentLength = uc.getContentLength(); - InputStream in = new BufferedInputStream(uc.getInputStream()); - int bytesRead = 0; - int offset = 0; - data = new byte[contentLength]; - while (offset < contentLength) { - bytesRead = in.read(data, offset, data.length - offset); - if (bytesRead == -1) - break; - offset += bytesRead; - } - in.close(); - - if (offset != contentLength) { - return false; - } - } catch (IOException e) { - return false; - } - - try { - FileOutputStream out = new FileOutputStream(file); - out.write(data); - out.flush(); - out.close(); - } catch (FileNotFoundException e) { - return false; - } catch (IOException e) { - if (file.exists()) { - file.delete(); - } - return false; - } - - long duration_ms = (System.nanoTime() - startTime) / 1000000; - if (duration_ms < google_maps_ratelimit_ms) { - try { - Thread.sleep(google_maps_ratelimit_ms - duration_ms); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - return true; - } - - public static ImageIcon fetchAndLoadMap(File pngfile, String url) { - if (!pngfile.exists()) { - if (!fetchMap(pngfile, url)) { - return null; - } - } - return loadMap(pngfile, url); - } - - public static ImageIcon loadMap(File pngfile, String url) { - if (!pngfile.exists()) { - return null; - } - - try { - BufferedImage img; - - img = ImageIO.read(pngfile); - if (img == null) { - System.out.printf("# Can't read pngfile %s\n", pngfile); - return null; - } - return new ImageIcon(img); - } catch (IOException e) { - System.out.printf("# IO error trying to load %s\n", pngfile); - return null; - } - } -} diff --git a/altosui/AltosSiteMapPreload.java b/altosui/AltosSiteMapPreload.java deleted file mode 100644 index 8380b967..00000000 --- a/altosui/AltosSiteMapPreload.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright © 2011 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; -import java.io.*; -import java.util.*; -import java.text.*; -import java.lang.Math; -import java.net.URL; -import java.net.URLConnection; -import org.altusmetrum.altosuilib_1.*; - -class AltosMapPos extends Box { - AltosUI owner; - JLabel label; - JComboBox hemi; - JTextField deg; - JLabel deg_label; - JTextField min; - JLabel min_label; - - public void set_value(double new_value) { - double d, m; - int h; - - h = 0; - if (new_value < 0) { - h = 1; - new_value = -new_value; - } - d = Math.floor(new_value); - deg.setText(String.format("%3.0f", d)); - m = (new_value - d) * 60.0; - min.setText(String.format("%7.4f", m)); - hemi.setSelectedIndex(h); - } - - public double get_value() throws NumberFormatException { - int h = hemi.getSelectedIndex(); - String d_t = deg.getText(); - String m_t = min.getText(); - double d, m, v; - try { - d = Double.parseDouble(d_t); - } catch (NumberFormatException ne) { - JOptionPane.showMessageDialog(owner, - String.format("Invalid degrees \"%s\"", - d_t), - "Invalid number", - JOptionPane.ERROR_MESSAGE); - throw ne; - } - try { - if (m_t.equals("")) - m = 0; - else - m = Double.parseDouble(m_t); - } catch (NumberFormatException ne) { - JOptionPane.showMessageDialog(owner, - String.format("Invalid minutes \"%s\"", - m_t), - "Invalid number", - JOptionPane.ERROR_MESSAGE); - throw ne; - } - v = d + m/60.0; - if (h == 1) - v = -v; - return v; - } - - public AltosMapPos(AltosUI in_owner, - String label_value, - String[] hemi_names, - double default_value) { - super(BoxLayout.X_AXIS); - owner = in_owner; - label = new JLabel(label_value); - hemi = new JComboBox(hemi_names); - hemi.setEditable(false); - deg = new JTextField(5); - deg.setMinimumSize(deg.getPreferredSize()); - deg.setHorizontalAlignment(JTextField.RIGHT); - deg_label = new JLabel("°"); - min = new JTextField(9); - min.setMinimumSize(min.getPreferredSize()); - min_label = new JLabel("'"); - set_value(default_value); - add(label); - add(Box.createRigidArea(new Dimension(5, 0))); - add(hemi); - add(Box.createRigidArea(new Dimension(5, 0))); - add(deg); - add(Box.createRigidArea(new Dimension(5, 0))); - add(deg_label); - add(Box.createRigidArea(new Dimension(5, 0))); - add(min); - add(Box.createRigidArea(new Dimension(5, 0))); - add(min_label); - } -} - -class AltosSite { - String name; - double latitude; - double longitude; - - public String toString() { - return name; - } - - public AltosSite(String in_name, double in_latitude, double in_longitude) { - name = in_name; - latitude = in_latitude; - longitude = in_longitude; - } - - public AltosSite(String line) throws ParseException { - String[] elements = line.split(":"); - - if (elements.length < 3) - throw new ParseException(String.format("Invalid site line %s", line), 0); - - name = elements[0]; - - try { - latitude = Double.parseDouble(elements[1]); - longitude = Double.parseDouble(elements[2]); - } catch (NumberFormatException ne) { - throw new ParseException(String.format("Invalid site line %s", line), 0); - } - } -} - -class AltosSites extends Thread { - AltosSiteMapPreload preload; - URL url; - LinkedList sites; - - void notify_complete() { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - preload.set_sites(); - } - }); - } - - void add(AltosSite site) { - sites.add(site); - } - - void add(String line) { - try { - add(new AltosSite(line)); - } catch (ParseException pe) { - } - } - - public void run() { - try { - URLConnection uc = url.openConnection(); - //int length = uc.getContentLength(); - - InputStreamReader in_stream = new InputStreamReader(uc.getInputStream(), Altos.unicode_set); - BufferedReader in = new BufferedReader(in_stream); - - for (;;) { - String line = in.readLine(); - if (line == null) - break; - add(line); - } - } catch (IOException e) { - } finally { - notify_complete(); - } - } - - public AltosSites(AltosSiteMapPreload in_preload) { - sites = new LinkedList(); - preload = in_preload; - try { - url = new URL(Altos.launch_sites_url); - } catch (java.net.MalformedURLException e) { - notify_complete(); - } - start(); - } -} - -public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener, ItemListener { - AltosUI owner; - AltosSiteMap map; - - AltosMapPos lat; - AltosMapPos lon; - - final static int radius = 5; - final static int width = (radius * 2 + 1); - final static int height = (radius * 2 + 1); - - JProgressBar pbar; - - AltosSites sites; - JLabel site_list_label; - JComboBox site_list; - - JToggleButton load_button; - boolean loading; - JButton close_button; - - static final String[] lat_hemi_names = { "N", "S" }; - static final String[] lon_hemi_names = { "E", "W" }; - - class updatePbar implements Runnable { - int n; - String s; - - public updatePbar(int x, int y, String in_s) { - n = (x + radius) + (y + radius) * width + 1; - s = in_s; - } - - public void run() { - pbar.setValue(n); - pbar.setString(s); - if (n < width * height) { - pbar.setValue(n); - pbar.setString(s); - } else { - pbar.setValue(0); - pbar.setString(""); - load_button.setSelected(false); - loading = false; - } - } - } - - class bgLoad extends Thread { - - AltosSiteMap map; - - public bgLoad(AltosSiteMap in_map) { - map = in_map; - } - - public void run() { - for (int y = -map.radius; y <= map.radius; y++) { - for (int x = -map.radius; x <= map.radius; x++) { - String pngfile; - pngfile = map.initMap(new Point(x,y)); - SwingUtilities.invokeLater(new updatePbar(x, y, pngfile)); - } - } - } - } - - public void set_sites() { - int i = 1; - for (AltosSite site : sites.sites) { - site_list.insertItemAt(site, i); - i++; - } - } - - public void itemStateChanged(ItemEvent e) { - int state = e.getStateChange(); - - if (state == ItemEvent.SELECTED) { - Object o = e.getItem(); - if (o instanceof AltosSite) { - AltosSite site = (AltosSite) o; - lat.set_value(site.latitude); - lon.set_value(site.longitude); - } - } - } - - public void actionPerformed(ActionEvent e) { - String cmd = e.getActionCommand(); - - if (cmd.equals("close")) - setVisible(false); - - if (cmd.equals("load")) { - if (!loading) { - try { - final double latitude = lat.get_value(); - final double longitude = lon.get_value(); - map.setBaseLocation(latitude,longitude); - map.draw_circle(latitude,longitude); - loading = true; - bgLoad thread = new bgLoad(map); - thread.start(); - } catch (NumberFormatException ne) { - load_button.setSelected(false); - } - } - } - } - - public AltosSiteMapPreload(AltosUI in_owner) { - owner = in_owner; - - Container pane = getContentPane(); - GridBagConstraints c = new GridBagConstraints(); - Insets i = new Insets(4,4,4,4); - - pane.setLayout(new GridBagLayout()); - - map = new AltosSiteMap(radius); - - c.fill = GridBagConstraints.BOTH; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 1; - - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 2; - c.anchor = GridBagConstraints.CENTER; - - pane.add(map, c); - - pbar = new JProgressBar(); - pbar.setMinimum(0); - pbar.setMaximum(width * height); - pbar.setValue(0); - pbar.setString(""); - pbar.setStringPainted(true); - - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 0; - - c.gridx = 0; - c.gridy = 1; - c.gridwidth = 2; - - pane.add(pbar, c); - - site_list_label = new JLabel ("Known Launch Sites:"); - - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 0; - - c.gridx = 0; - c.gridy = 2; - c.gridwidth = 1; - - pane.add(site_list_label, c); - - site_list = new JComboBox(new String[] { "Site List" }); - site_list.addItemListener(this); - - sites = new AltosSites(this); - - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 0; - - c.gridx = 1; - c.gridy = 2; - c.gridwidth = 1; - - pane.add(site_list, c); - - lat = new AltosMapPos(owner, - "Latitude:", - lat_hemi_names, - 37.167833333); - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 0; - c.weighty = 0; - - c.gridx = 0; - c.gridy = 3; - c.gridwidth = 1; - c.anchor = GridBagConstraints.CENTER; - - pane.add(lat, c); - - lon = new AltosMapPos(owner, - "Longitude:", - lon_hemi_names, - -97.73975); - - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 0; - c.weighty = 0; - - c.gridx = 1; - c.gridy = 3; - c.gridwidth = 1; - c.anchor = GridBagConstraints.CENTER; - - pane.add(lon, c); - - load_button = new JToggleButton("Load Map"); - load_button.addActionListener(this); - load_button.setActionCommand("load"); - - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 0; - - c.gridx = 0; - c.gridy = 4; - c.gridwidth = 1; - c.anchor = GridBagConstraints.CENTER; - - pane.add(load_button, c); - - close_button = new JButton("Close"); - close_button.addActionListener(this); - close_button.setActionCommand("close"); - - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.CENTER; - c.insets = i; - c.weightx = 1; - c.weighty = 0; - - c.gridx = 1; - c.gridy = 4; - c.gridwidth = 1; - c.anchor = GridBagConstraints.CENTER; - - pane.add(close_button, c); - - pack(); - setLocationRelativeTo(owner); - setVisible(true); - } -} diff --git a/altosui/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java deleted file mode 100644 index 7d5b65e1..00000000 --- a/altosui/AltosSiteMapTile.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright © 2010 Anthony Towns - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.awt.*; -import java.awt.image.*; -import javax.swing.*; -import java.awt.geom.Point2D; -import java.awt.geom.Line2D; -import org.altusmetrum.altoslib_3.*; - -public class AltosSiteMapTile extends JLayeredPane { - JLabel mapLabel; - JLabel draw; - Graphics2D g2d; - int px_size; - - public void loadMap(ImageIcon icn) { - mapLabel.setIcon(icn); - } - - public void clearMap() { - fillLabel(mapLabel, Color.GRAY, px_size); - g2d = fillLabel(draw, new Color(127,127,127,0), px_size); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - } - - static Color stateColors[] = { - Color.WHITE, // startup - Color.WHITE, // idle - Color.WHITE, // pad - Color.RED, // boost - Color.PINK, // fast - Color.YELLOW, // coast - Color.CYAN, // drogue - Color.BLUE, // main - Color.BLACK // landed - }; - - private boolean drawn_landed_circle = false; - private boolean drawn_boost_circle = false; - public synchronized void show(AltosState state, AltosListenerState listener_state, - Point2D.Double last_pt, Point2D.Double pt) - { - if (0 <= state.state && state.state < stateColors.length) { - g2d.setColor(stateColors[state.state]); - } - g2d.draw(new Line2D.Double(last_pt, pt)); - - if (state.state == 3 && !drawn_boost_circle) { - drawn_boost_circle = true; - g2d.setColor(Color.RED); - g2d.drawOval((int)last_pt.x-5, (int)last_pt.y-5, 10, 10); - g2d.drawOval((int)last_pt.x-20, (int)last_pt.y-20, 40, 40); - g2d.drawOval((int)last_pt.x-35, (int)last_pt.y-35, 70, 70); - } - if (state.state == 8 && !drawn_landed_circle) { - drawn_landed_circle = true; - g2d.setColor(Color.BLACK); - g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); - g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); - g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); - } - - repaint(); - } - - public void draw_circle(Point2D.Double pt) { - g2d.setColor(Color.RED); - g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); - g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); - g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); - } - - public static Graphics2D fillLabel(JLabel l, Color c, int px_size) { - BufferedImage img = new BufferedImage(px_size, px_size, - BufferedImage.TYPE_INT_ARGB); - Graphics2D g = img.createGraphics(); - g.setColor(c); - g.fillRect(0, 0, px_size, px_size); - l.setIcon(new ImageIcon(img)); - return g; - } - - public AltosSiteMapTile(int in_px_size) { - px_size = in_px_size; - setPreferredSize(new Dimension(px_size, px_size)); - - mapLabel = new JLabel(); - fillLabel(mapLabel, Color.GRAY, px_size); - mapLabel.setOpaque(true); - mapLabel.setBounds(0, 0, px_size, px_size); - add(mapLabel, new Integer(0)); - - draw = new JLabel(); - g2d = fillLabel(draw, new Color(127, 127, 127, 0), px_size); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - draw.setBounds(0, 0, px_size, px_size); - draw.setOpaque(false); - - add(draw, new Integer(1)); - } -} diff --git a/altosui/AltosVoice.java b/altosui/AltosVoice.java deleted file mode 100644 index 2ed6a8c2..00000000 --- a/altosui/AltosVoice.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import com.sun.speech.freetts.Voice; -import com.sun.speech.freetts.VoiceManager; -import java.util.concurrent.LinkedBlockingQueue; -import org.altusmetrum.altosuilib_1.*; - -public class AltosVoice implements Runnable { - VoiceManager voice_manager; - Voice voice; - LinkedBlockingQueue phrases; - Thread thread; - boolean busy; - - final static String voice_name = "kevin16"; - - public void run() { - try { - for (;;) { - String s = phrases.take(); - voice.speak(s); - synchronized(this) { - if (phrases.isEmpty()) { - busy = false; - notifyAll(); - } - } - } - } catch (InterruptedException e) { - } - } - - public synchronized void drain() throws InterruptedException { - while (busy) - wait(); - } - - public void speak_always(String s) { - try { - if (voice != null) { - synchronized(this) { - busy = true; - phrases.put(s); - } - } - } catch (InterruptedException e) { - } - } - - public void speak(String s) { - if (AltosUIPreferences.voice()) - speak_always(s); - } - - public void speak(String format, Object... parameters) { - speak(String.format(format, parameters)); - } - - public AltosVoice () { - busy = false; - voice_manager = VoiceManager.getInstance(); - voice = voice_manager.getVoice(voice_name); - if (voice != null) { - voice.allocate(); - phrases = new LinkedBlockingQueue (); - thread = new Thread(this); - thread.start(); - } else { - System.out.printf("Voice manager failed to open %s\n", voice_name); - Voice[] voices = voice_manager.getVoices(); - System.out.printf("Available voices:\n"); - for (int i = 0; i < voices.length; i++) { - System.out.println(" " + voices[i].getName() - + " (" + voices[i].getDomain() + " domain)"); - } - } - } -} diff --git a/altosui/GrabNDrag.java b/altosui/GrabNDrag.java deleted file mode 100644 index 2fd6c4da..00000000 --- a/altosui/GrabNDrag.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright © 2010 Anthony Towns - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - */ - -package altosui; - -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; -import javax.swing.event.MouseInputAdapter; - -class GrabNDrag extends MouseInputAdapter { - private JComponent scroll; - private Point startPt = new Point(); - - public GrabNDrag(JComponent scroll) { - this.scroll = scroll; - scroll.addMouseMotionListener(this); - scroll.addMouseListener(this); - scroll.setAutoscrolls(true); - } - - public void mousePressed(MouseEvent e) { - startPt.setLocation(e.getPoint()); - } - public void mouseDragged(MouseEvent e) { - int xd = e.getX() - startPt.x; - int yd = e.getY() - startPt.y; - - Rectangle r = scroll.getVisibleRect(); - r.x -= xd; - r.y -= yd; - scroll.scrollRectToVisible(r); - } -} diff --git a/altosui/Makefile.am b/altosui/Makefile.am index 76fe9961..0440b4af 100644 --- a/altosui/Makefile.am +++ b/altosui/Makefile.am @@ -1,6 +1,6 @@ JAVAROOT=classes -AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -source 6 +AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6 man_MANS=altosui.1 @@ -17,7 +17,6 @@ altosui_BT = \ AltosBTKnown.java altosui_JAVA = \ - GrabNDrag.java \ AltosAscent.java \ AltosChannelMenu.java \ AltosCompanionInfo.java \ @@ -31,20 +30,16 @@ altosui_JAVA = \ AltosCSVUI.java \ AltosDescent.java \ AltosDeviceUIDialog.java \ - AltosDisplayThread.java \ AltosEepromDelete.java \ AltosEepromManage.java \ AltosEepromMonitorUI.java \ AltosEepromSelect.java \ AltosFlashUI.java \ - AltosFlightDisplay.java \ AltosFlightInfoTableModel.java \ - AltosFlightStats.java \ AltosFlightStatsTable.java \ AltosFlightStatus.java \ AltosFlightStatusUpdate.java \ AltosFlightUI.java \ - AltosFreqList.java \ Altos.java \ AltosIdleMonitorUI.java \ AltosIgniteUI.java \ @@ -61,17 +56,12 @@ altosui_JAVA = \ AltosScanUI.java \ AltosSerial.java \ AltosSerialInUseException.java \ - AltosSiteMap.java \ - AltosSiteMapPreload.java \ - AltosSiteMapCache.java \ - AltosSiteMapTile.java \ AltosUI.java \ AltosGraph.java \ AltosGraphDataPoint.java \ AltosGraphDataSet.java \ AltosGraphUI.java \ AltosDataChooser.java \ - AltosVoice.java \ $(altosui_BT) JFREECHART_CLASS= \ diff --git a/altosuilib/AltosDisplayThread.java b/altosuilib/AltosDisplayThread.java new file mode 100644 index 00000000..e88a891e --- /dev/null +++ b/altosuilib/AltosDisplayThread.java @@ -0,0 +1,259 @@ +/* + * Copyright © 2010 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import java.awt.*; +import javax.swing.*; +import java.io.*; +import java.text.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosDisplayThread extends Thread { + + Frame parent; + IdleThread idle_thread; + AltosVoice voice; + AltosFlightReader reader; + AltosState old_state, state; + AltosListenerState listener_state; + AltosFlightDisplay display; + + synchronized void show_safely() { + final AltosState my_state = state; + final AltosListenerState my_listener_state = listener_state; + Runnable r = new Runnable() { + public void run() { + try { + display.show(my_state, my_listener_state); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + void reading_error_internal() { + JOptionPane.showMessageDialog(parent, + String.format("Error reading from \"%s\"", reader.name), + "Telemetry Read Error", + JOptionPane.ERROR_MESSAGE); + } + + void reading_error_safely() { + Runnable r = new Runnable() { + public void run() { + try { + reading_error_internal(); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + class IdleThread extends Thread { + + boolean started; + int reported_landing; + int report_interval; + long report_time; + + public synchronized void report(boolean last) { + if (state == null) + return; + + /* reset the landing count once we hear about a new flight */ + if (state.state < AltosLib.ao_flight_drogue) + reported_landing = 0; + + /* Shut up once the rocket is on the ground */ + if (reported_landing > 2) { + return; + } + + /* If the rocket isn't on the pad, then report height */ + if (AltosLib.ao_flight_drogue <= state.state && + state.state < AltosLib.ao_flight_landed && + state.from_pad != null && + state.range >= 0) + { + voice.speak("Height %s, bearing %s %d, elevation %d, range %s.\n", + AltosConvert.height.say(state.height()), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_VOICE), + (int) (state.from_pad.bearing + 0.5), + (int) (state.elevation + 0.5), + AltosConvert.distance.say(state.range)); + } else if (state.state > AltosLib.ao_flight_pad) { + voice.speak(AltosConvert.height.say_units(state.height())); + } else { + reported_landing = 0; + } + + /* If the rocket is coming down, check to see if it has landed; + * either we've got a landed report or we haven't heard from it in + * a long time + */ + if (state.state >= AltosLib.ao_flight_drogue && + (last || + System.currentTimeMillis() - state.received_time >= 15000 || + state.state == AltosLib.ao_flight_landed)) + { + if (Math.abs(state.speed()) < 20 && state.height() < 100) + voice.speak("rocket landed safely"); + else + voice.speak("rocket may have crashed"); + if (state.from_pad != null) + voice.speak("Bearing %d degrees, range %s.", + (int) (state.from_pad.bearing + 0.5), + AltosConvert.distance.say_units(state.from_pad.distance)); + ++reported_landing; + if (state.state != AltosLib.ao_flight_landed) { + state.state = AltosLib.ao_flight_landed; + show_safely(); + } + } + } + + long now () { + return System.currentTimeMillis(); + } + + void set_report_time() { + report_time = now() + report_interval; + } + + public void run () { + try { + for (;;) { + if (reader.has_monitor_battery()) { + listener_state.battery = reader.monitor_battery(); + show_safely(); + } + set_report_time(); + for (;;) { + voice.drain(); + synchronized (this) { + long sleep_time = report_time - now(); + if (sleep_time <= 0) + break; + wait(sleep_time); + } + } + + report(false); + } + } catch (InterruptedException ie) { + try { + voice.drain(); + } catch (InterruptedException iie) { } + } + } + + public synchronized void notice(boolean spoken) { + if (old_state != null && old_state.state != state.state) { + report_time = now(); + this.notify(); + } else if (spoken) + set_report_time(); + } + + public IdleThread() { + reported_landing = 0; + report_interval = 10000; + } + } + + synchronized boolean tell() { + boolean ret = false; + if (old_state == null || old_state.state != state.state) { + voice.speak(state.state_name()); + if ((old_state == null || old_state.state <= AltosLib.ao_flight_boost) && + state.state > AltosLib.ao_flight_boost) { + voice.speak("max speed: %s.", + AltosConvert.speed.say_units(state.max_speed() + 0.5)); + ret = true; + } else if ((old_state == null || old_state.state < AltosLib.ao_flight_drogue) && + state.state >= AltosLib.ao_flight_drogue) { + voice.speak("max height: %s.", + AltosConvert.height.say_units(state.max_height() + 0.5)); + ret = true; + } + } + if (old_state == null || old_state.gps_ready != state.gps_ready) { + if (state.gps_ready) { + voice.speak("GPS ready"); + ret = true; + } + else if (old_state != null) { + voice.speak("GPS lost"); + ret = true; + } + } + old_state = state; + return ret; + } + + public void run() { + boolean interrupted = false; + boolean told; + + idle_thread = new IdleThread(); + idle_thread.start(); + + try { + for (;;) { + try { + state = reader.read(); + if (state == null) + break; + reader.update(state); + show_safely(); + told = tell(); + idle_thread.notice(told); + } catch (ParseException pp) { + System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage()); + } catch (AltosCRCException ce) { + ++listener_state.crc_errors; + show_safely(); + } + } + } catch (InterruptedException ee) { + interrupted = true; + } catch (IOException ie) { + reading_error_safely(); + } finally { + if (!interrupted) + idle_thread.report(true); + reader.close(interrupted); + idle_thread.interrupt(); + try { + idle_thread.join(); + } catch (InterruptedException ie) {} + } + } + + public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) { + listener_state = new AltosListenerState(); + parent = in_parent; + voice = in_voice; + display = in_display; + reader = in_reader; + display.reset(); + } +} diff --git a/altosuilib/AltosFlightDisplay.java b/altosuilib/AltosFlightDisplay.java new file mode 100644 index 00000000..5695a015 --- /dev/null +++ b/altosuilib/AltosFlightDisplay.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2010 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import org.altusmetrum.altoslib_4.*; + +public interface AltosFlightDisplay { + void reset(); + + void show(AltosState state, AltosListenerState listener_state); + + void set_font(); +} diff --git a/altosuilib/AltosFreqList.java b/altosuilib/AltosFreqList.java new file mode 100644 index 00000000..e1299aae --- /dev/null +++ b/altosuilib/AltosFreqList.java @@ -0,0 +1,85 @@ +/* + * Copyright © 2011 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import javax.swing.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosFreqList extends JComboBox { + + String product; + int serial; + int calibrate; + + public void set_frequency(double new_frequency) { + int i; + + if (new_frequency < 0) { + setVisible(false); + return; + } + + for (i = 0; i < getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) getItemAt(i); + + if (f.close(new_frequency)) { + setSelectedIndex(i); + return; + } + } + for (i = 0; i < getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) getItemAt(i); + + if (new_frequency < f.frequency) + break; + } + String description = String.format("%s serial %d", product, serial); + AltosFrequency frequency = new AltosFrequency(new_frequency, description); + AltosUIPreferences.add_common_frequency(frequency); + insertItemAt(frequency, i); + setMaximumRowCount(getItemCount()); + } + + public void set_product(String new_product) { + product = new_product; + } + + public void set_serial(int new_serial) { + serial = new_serial; + } + + public double frequency() { + AltosFrequency f = (AltosFrequency) getSelectedItem(); + if (f != null) + return f.frequency; + return 434.550; + } + + public AltosFreqList () { + super(AltosUIPreferences.common_frequencies()); + setMaximumRowCount(getItemCount()); + setEditable(false); + product = "Unknown"; + serial = 0; + } + + public AltosFreqList(double in_frequency) { + this(); + set_frequency(in_frequency); + } +} diff --git a/altosuilib/AltosSiteMap.java b/altosuilib/AltosSiteMap.java new file mode 100644 index 00000000..1cfbc8b5 --- /dev/null +++ b/altosuilib/AltosSiteMap.java @@ -0,0 +1,484 @@ +/* + * Copyright © 2010 Anthony Towns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import java.awt.*; +import javax.swing.*; +import java.io.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_4.*; + +public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { + // preferred vertical step in a tile in naut. miles + // will actually choose a step size between x and 2x, where this + // is 1.5x + static final double tile_size_nmi = 0.75; + + static final int px_size = 512; + + static final int MAX_TILE_DELTA = 100; + + private static Point2D.Double translatePoint(Point2D.Double p, + Point2D.Double d) + { + return new Point2D.Double(p.x + d.x, p.y + d.y); + } + + static class LatLng { + public double lat, lng; + public LatLng(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + } + + // based on google js + // http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js + // search for fromLatLngToPoint and fromPointToLatLng + /* + private static Point2D.Double pt(LatLng latlng, int zoom) { + double scale_x = 256/360.0 * Math.pow(2, zoom); + double scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); + return pt(latlng, scale_x, scale_y); + } + */ + + private static Point2D.Double pt(LatLng latlng, + double scale_x, double scale_y) + { + Point2D.Double res = new Point2D.Double(); + double e; + + res.x = latlng.lng * scale_x; + + e = Math.sin(Math.toRadians(latlng.lat)); + e = Math.max(e,-(1-1.0E-15)); + e = Math.min(e, 1-1.0E-15 ); + + res.y = 0.5*Math.log((1+e)/(1-e))*-scale_y; + return res; + } + + static private LatLng latlng(Point2D.Double pt, + double scale_x, double scale_y) + { + double lat, lng; + double rads; + + lng = pt.x/scale_x; + rads = 2 * Math.atan(Math.exp(-pt.y/scale_y)); + lat = Math.toDegrees(rads - Math.PI/2); + + return new LatLng(lat,lng); + } + + int zoom; + double scale_x, scale_y; + + int radius; /* half width/height of tiles to load */ + + private Point2D.Double pt(double lat, double lng) { + return pt(new LatLng(lat, lng), scale_x, scale_y); + } + + private LatLng latlng(double x, double y) { + return latlng(new Point2D.Double(x,y), scale_x, scale_y); + } + /* + private LatLng latlng(Point2D.Double pt) { + return latlng(pt, scale_x, scale_y); + } + */ + + ConcurrentHashMap mapTiles = new ConcurrentHashMap(); + Point2D.Double centre; + + private Point2D.Double tileCoordOffset(Point p) { + return new Point2D.Double(centre.x - p.x*px_size, + centre.y - p.y * px_size); + } + + private Point tileOffset(Point2D.Double p) { + return new Point((int)Math.floor((centre.x+p.x)/px_size), + (int)Math.floor((centre.y+p.y)/px_size)); + } + + private Point2D.Double getBaseLocation(double lat, double lng) { + Point2D.Double locn, north_step; + + zoom = 2; + // stupid loop structure to please Java's control flow analysis + do { + zoom++; + scale_x = 256/360.0 * Math.pow(2, zoom); + scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); + locn = pt(lat, lng); + north_step = pt(lat+tile_size_nmi*4/3/60.0, lng); + if (locn.y - north_step.y > px_size) + break; + } while (zoom < 22); + locn.x = -px_size * Math.floor(locn.x/px_size); + locn.y = -px_size * Math.floor(locn.y/px_size); + return locn; + } + + public void reset() { + // nothing + } + + public void set_font() { + // nothing + } + + private void loadMap(final AltosSiteMapTile tile, + final File pngfile, String pngurl) + { + boolean loaded = AltosSiteMapCache.fetchMap(pngfile, pngurl); + if (loaded) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + tile.loadMap(pngfile); + } + }); + } else { + System.out.printf("# Failed to fetch file %s\n", pngfile); + System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl); + } + } + + + class AltosSiteMapPrefetch { + int x; + int y; + int result; + File pngfile; + String pngurl; + } + + public AltosSiteMapPrefetch prefetchMap(int x, int y) { + AltosSiteMapPrefetch prefetch = new AltosSiteMapPrefetch(); + LatLng map_latlng = latlng( + -centre.x + x*px_size + px_size/2, + -centre.y + y*px_size + px_size/2); + prefetch.pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + prefetch.pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + if (prefetch.pngfile.exists()) { + prefetch.result = 1; + } else if (AltosSiteMapCache.fetchMap(prefetch.pngfile, prefetch.pngurl)) { + prefetch.result = 0; + } else { + prefetch.result = -1; + } + return prefetch; + } + + public static void prefetchMaps(double lat, double lng) { + int w = AltosSiteMapPreload.width; + int h = AltosSiteMapPreload.height; + AltosSiteMap asm = new AltosSiteMap(true); + asm.centre = asm.getBaseLocation(lat, lng); + + //Point2D.Double p = new Point2D.Double(); + //Point2D.Double p2; + int dx = -w/2, dy = -h/2; + for (int y = dy; y < h+dy; y++) { + for (int x = dx; x < w+dx; x++) { + AltosSiteMapPrefetch prefetch = asm.prefetchMap(x, y); + switch (prefetch.result) { + case 1: + System.out.printf("Already have %s\n", prefetch.pngfile); + break; + case 0: + System.out.printf("Fetched map %s\n", prefetch.pngfile); + break; + case -1: + System.out.printf("# Failed to fetch file %s\n", prefetch.pngfile); + System.out.printf(" wget -O '%s' ''\n", + prefetch.pngfile, prefetch.pngurl); + break; + } + } + } + } + + public String initMap(Point offset) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double coord = tileCoordOffset(offset); + + LatLng map_latlng = latlng(px_size/2-coord.x, px_size/2-coord.y); + + File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + loadMap(tile, pngfile, pngurl); + return pngfile.toString(); + } + + public void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) { + Thread thread = new Thread() { + public void run() { + initMap(offset); + finishTileLater(tile, offset); + } + }; + thread.start(); + } + + public void setBaseLocation(double lat, double lng) { + for (Point k : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(k); + tile.clearMap(); + } + + centre = getBaseLocation(lat, lng); + scrollRocketToVisible(pt(lat,lng)); + } + + private void initMaps(double lat, double lng) { + setBaseLocation(lat, lng); + + Thread thread = new Thread() { + public void run() { + for (Point k : mapTiles.keySet()) + initMap(k); + } + }; + thread.start(); + } + + private static File MapFile(double lat, double lng, int zoom) { + char chlat = lat < 0 ? 'S' : 'N'; + char chlng = lng < 0 ? 'W' : 'E'; + if (lat < 0) lat = -lat; + if (lng < 0) lng = -lng; + return new File(AltosUIPreferences.mapdir(), + String.format("map-%c%.6f,%c%.6f-%d.png", + chlat, lat, chlng, lng, zoom)); + } + + private static String MapURL(double lat, double lng, int zoom) { + return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size); + } + + boolean initialised = false; + Point2D.Double last_pt = null; + int last_state = -1; + + public void show(double lat, double lon) { + System.out.printf ("show %g %g\n", lat, lon); + return; +// initMaps(lat, lon); +// scrollRocketToVisible(pt(lat, lon)); + } + public void show(final AltosState state, final AltosListenerState listener_state) { + // if insufficient gps data, nothing to update + AltosGPS gps = state.gps; + + if (gps == null) + return; + + if (!gps.locked && gps.nsat < 4) + return; + + if (!initialised) { + if (state.pad_lat != AltosLib.MISSING && state.pad_lon != AltosLib.MISSING) { + initMaps(state.pad_lat, state.pad_lon); + initialised = true; + } else if (gps.lat != AltosLib.MISSING && gps.lon != AltosLib.MISSING) { + initMaps(gps.lat, gps.lon); + initialised = true; + } else { + return; + } + } + + final Point2D.Double pt = pt(gps.lat, gps.lon); + if (last_pt == pt && last_state == state.state) + return; + + if (last_pt == null) { + last_pt = pt; + } + boolean in_any = false; + for (Point offset : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double ref, lref; + ref = translatePoint(pt, tileCoordOffset(offset)); + lref = translatePoint(last_pt, tileCoordOffset(offset)); + tile.show(state, listener_state, lref, ref); + if (0 <= ref.x && ref.x < px_size) + if (0 <= ref.y && ref.y < px_size) + in_any = true; + } + + Point offset = tileOffset(pt); + if (!in_any) { + Point2D.Double ref, lref; + ref = translatePoint(pt, tileCoordOffset(offset)); + lref = translatePoint(last_pt, tileCoordOffset(offset)); + + AltosSiteMapTile tile = createTile(offset); + tile.show(state, listener_state, lref, ref); + initAndFinishMapAsync(tile, offset); + } + + scrollRocketToVisible(pt); + + if (offset != tileOffset(last_pt)) { + ensureTilesAround(offset); + } + + last_pt = pt; + last_state = state.state; + } + + public void centre(Point2D.Double pt) { + Rectangle r = comp.getVisibleRect(); + Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft)); + int dx = (int)copt.x - r.width/2 - r.x; + int dy = (int)copt.y - r.height/2 - r.y; + r.x += dx; + r.y += dy; + comp.scrollRectToVisible(r); + } + + public void centre(AltosState state) { + if (!state.gps.locked && state.gps.nsat < 4) + return; + centre(pt(state.gps.lat, state.gps.lon)); + } + + public void draw_circle(double lat, double lon) { + final Point2D.Double pt = pt(lat, lon); + + for (Point offset : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double ref = translatePoint(pt, tileCoordOffset(offset)); + tile.set_boost(ref); + } + } + + private AltosSiteMapTile createTile(Point offset) { + AltosSiteMapTile tile = new AltosSiteMapTile(px_size); + mapTiles.put(offset, tile); + return tile; + } + private void finishTileLater(final AltosSiteMapTile tile, + final Point offset) + { + SwingUtilities.invokeLater( new Runnable() { + public void run() { + addTileAt(tile, offset); + } + } ); + } + + private void ensureTilesAround(Point base_offset) { + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + Point offset = new Point(base_offset.x + x, base_offset.y + y); + if (mapTiles.containsKey(offset)) + continue; + AltosSiteMapTile tile = createTile(offset); + initAndFinishMapAsync(tile, offset); + } + } + } + + private Point topleft = new Point(0,0); + private void scrollRocketToVisible(Point2D.Double pt) { + Rectangle r = comp.getVisibleRect(); + Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft)); + int dx = (int)copt.x - r.width/2 - r.x; + int dy = (int)copt.y - r.height/2 - r.y; + if (Math.abs(dx) > r.width/4 || Math.abs(dy) > r.height/4) { + r.x += dx; + r.y += dy; + comp.scrollRectToVisible(r); + } + } + + private void addTileAt(AltosSiteMapTile tile, Point offset) { + if (Math.abs(offset.x) >= MAX_TILE_DELTA || + Math.abs(offset.y) >= MAX_TILE_DELTA) + { + System.out.printf("Rocket too far away from pad (tile %d,%d)\n", + offset.x, offset.y); + return; + } + + boolean review = false; + Rectangle r = comp.getVisibleRect(); + if (offset.x < topleft.x) { + r.x += (topleft.x - offset.x) * px_size; + topleft.x = offset.x; + review = true; + } + if (offset.y < topleft.y) { + r.y += (topleft.y - offset.y) * px_size; + topleft.y = offset.y; + review = true; + } + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.BOTH; + // put some space between the map tiles, debugging only + // c.insets = new Insets(5, 5, 5, 5); + + c.gridx = offset.x + MAX_TILE_DELTA; + c.gridy = offset.y + MAX_TILE_DELTA; + layout.setConstraints(tile, c); + + comp.add(tile); + if (review) { + comp.scrollRectToVisible(r); + } + } + + private AltosSiteMap(boolean knowWhatYouAreDoing) { + if (!knowWhatYouAreDoing) { + throw new RuntimeException("Arggh."); + } + } + + JComponent comp = new JComponent() { }; + private GridBagLayout layout = new GridBagLayout(); + + public AltosSiteMap(int in_radius) { + radius = in_radius; + + GrabNDrag scroller = new GrabNDrag(comp); + + comp.setLayout(layout); + + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + Point offset = new Point(x, y); + AltosSiteMapTile t = createTile(offset); + addTileAt(t, offset); + } + } + setViewportView(comp); + setPreferredSize(new Dimension(500,500)); + } + + public AltosSiteMap() { + this(1); + } +} diff --git a/altosuilib/AltosSiteMapCache.java b/altosuilib/AltosSiteMapCache.java new file mode 100644 index 00000000..cf93016a --- /dev/null +++ b/altosuilib/AltosSiteMapCache.java @@ -0,0 +1,194 @@ +/* + * Copyright © 2010 Anthony Towns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import javax.swing.*; +import javax.imageio.ImageIO; +import java.awt.image.*; +import java.awt.*; +import java.io.*; +import java.net.URL; +import java.net.URLConnection; + + +class AltosCacheImage { + Component component; + File file; + VolatileImage image; + int width; + int height; + long used; + + public void load_image() throws IOException { + BufferedImage bimg = ImageIO.read(file); + Graphics2D g = image.createGraphics(); + g.drawImage(bimg, 0, 0, null); + bimg.flush(); + } + + public Image validate() { + int returnCode; + + if (image != null) + returnCode = image.validate(component.getGraphicsConfiguration()); + else + returnCode = VolatileImage.IMAGE_INCOMPATIBLE; + if (returnCode == VolatileImage.IMAGE_RESTORED) { + try { + load_image(); + } catch (IOException e) { + return null; + } + } else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) { + image = component.createVolatileImage(width, height); + try { + load_image(); + } catch (IOException e) { + return null; + } + } + return image; + } + + public void flush() { + image.flush(); + } + + public AltosCacheImage(Component component, File file, int w, int h) throws IOException { + this.component = component; + this.file = file; + width = w; + height = h; + image = component.createVolatileImage(w, h); + used = 0; + } +} + +public class AltosSiteMapCache extends JLabel { + static final long google_maps_ratelimit_ms = 1200; + // Google limits static map queries to 50 per minute per IP, so + // each query should take at least 1.2 seconds. + + public static boolean fetchMap(File file, String url) { + if (file.exists()) + return true; + + URL u; + long startTime = System.nanoTime(); + + try { + u = new URL(url); + } catch (java.net.MalformedURLException e) { + return false; + } + + byte[] data; + try { + URLConnection uc = u.openConnection(); + int contentLength = uc.getContentLength(); + InputStream in = new BufferedInputStream(uc.getInputStream()); + int bytesRead = 0; + int offset = 0; + data = new byte[contentLength]; + while (offset < contentLength) { + bytesRead = in.read(data, offset, data.length - offset); + if (bytesRead == -1) + break; + offset += bytesRead; + } + in.close(); + + if (offset != contentLength) { + return false; + } + } catch (IOException e) { + return false; + } + + try { + FileOutputStream out = new FileOutputStream(file); + out.write(data); + out.flush(); + out.close(); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + if (file.exists()) { + file.delete(); + } + return false; + } + + long duration_ms = (System.nanoTime() - startTime) / 1000000; + if (duration_ms < google_maps_ratelimit_ms) { + try { + Thread.sleep(google_maps_ratelimit_ms - duration_ms); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + return true; + } + + static int cache_size = 9; + + static AltosCacheImage[] images; + + static long used; + + public static void set_cache_size(int cache_size) { + AltosSiteMapCache.cache_size = cache_size; + images = null; + } + + public static Image get_image(Component component, File file, int width, int height) { + int oldest = -1; + long age = used; + AltosCacheImage image; + if (images == null) + images = new AltosCacheImage[cache_size]; + for (int i = 0; i < cache_size; i++) { + image = images[i]; + + if (image == null) { + oldest = i; + break; + } + if (image.component == component && file.equals(image.file)) { + image.used = used++; + return image.validate(); + } + if (image.used < age) { + oldest = i; + age = image.used; + } + } + + try { + image = new AltosCacheImage(component, file, width, height); + image.used = used++; + if (images[oldest] != null) + images[oldest].flush(); + images[oldest] = image; + return image.validate(); + } catch (IOException e) { + return null; + } + } +} diff --git a/altosuilib/AltosSiteMapPreload.java b/altosuilib/AltosSiteMapPreload.java new file mode 100644 index 00000000..baa7fc37 --- /dev/null +++ b/altosuilib/AltosSiteMapPreload.java @@ -0,0 +1,467 @@ +/* + * Copyright © 2011 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.lang.Math; +import java.net.URL; +import java.net.URLConnection; +import org.altusmetrum.altoslib_4.*; + +class AltosMapPos extends Box { + AltosUIFrame owner; + JLabel label; + JComboBox hemi; + JTextField deg; + JLabel deg_label; + JTextField min; + JLabel min_label; + + public void set_value(double new_value) { + double d, m; + int h; + + h = 0; + if (new_value < 0) { + h = 1; + new_value = -new_value; + } + d = Math.floor(new_value); + deg.setText(String.format("%3.0f", d)); + m = (new_value - d) * 60.0; + min.setText(String.format("%7.4f", m)); + hemi.setSelectedIndex(h); + } + + public double get_value() throws NumberFormatException { + int h = hemi.getSelectedIndex(); + String d_t = deg.getText(); + String m_t = min.getText(); + double d, m, v; + try { + d = Double.parseDouble(d_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid degrees \"%s\"", + d_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + try { + if (m_t.equals("")) + m = 0; + else + m = Double.parseDouble(m_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid minutes \"%s\"", + m_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + v = d + m/60.0; + if (h == 1) + v = -v; + return v; + } + + public AltosMapPos(AltosUIFrame in_owner, + String label_value, + String[] hemi_names, + double default_value) { + super(BoxLayout.X_AXIS); + owner = in_owner; + label = new JLabel(label_value); + hemi = new JComboBox(hemi_names); + hemi.setEditable(false); + deg = new JTextField(5); + deg.setMinimumSize(deg.getPreferredSize()); + deg.setHorizontalAlignment(JTextField.RIGHT); + deg_label = new JLabel("°"); + min = new JTextField(9); + min.setMinimumSize(min.getPreferredSize()); + min_label = new JLabel("'"); + set_value(default_value); + add(label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(hemi); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg_label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min_label); + } +} + +class AltosSite { + String name; + double latitude; + double longitude; + + public String toString() { + return name; + } + + public AltosSite(String in_name, double in_latitude, double in_longitude) { + name = in_name; + latitude = in_latitude; + longitude = in_longitude; + } + + public AltosSite(String line) throws ParseException { + String[] elements = line.split(":"); + + if (elements.length < 3) + throw new ParseException(String.format("Invalid site line %s", line), 0); + + name = elements[0]; + + try { + latitude = Double.parseDouble(elements[1]); + longitude = Double.parseDouble(elements[2]); + } catch (NumberFormatException ne) { + throw new ParseException(String.format("Invalid site line %s", line), 0); + } + } +} + +class AltosSites extends Thread { + AltosSiteMapPreload preload; + URL url; + LinkedList sites; + + void notify_complete() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + preload.set_sites(); + } + }); + } + + void add(AltosSite site) { + sites.add(site); + } + + void add(String line) { + try { + add(new AltosSite(line)); + } catch (ParseException pe) { + } + } + + public void run() { + try { + URLConnection uc = url.openConnection(); + //int length = uc.getContentLength(); + + InputStreamReader in_stream = new InputStreamReader(uc.getInputStream(), AltosLib.unicode_set); + BufferedReader in = new BufferedReader(in_stream); + + for (;;) { + String line = in.readLine(); + if (line == null) + break; + add(line); + } + } catch (IOException e) { + } finally { + notify_complete(); + } + } + + public AltosSites(AltosSiteMapPreload in_preload) { + sites = new LinkedList(); + preload = in_preload; + try { + url = new URL(AltosLib.launch_sites_url); + } catch (java.net.MalformedURLException e) { + notify_complete(); + } + start(); + } +} + +public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener, ItemListener { + AltosUIFrame owner; + AltosSiteMap map; + + AltosMapPos lat; + AltosMapPos lon; + + final static int radius = 5; + final static int width = (radius * 2 + 1); + final static int height = (radius * 2 + 1); + + JProgressBar pbar; + + AltosSites sites; + JLabel site_list_label; + JComboBox site_list; + + JToggleButton load_button; + boolean loading; + JButton close_button; + + static final String[] lat_hemi_names = { "N", "S" }; + static final String[] lon_hemi_names = { "E", "W" }; + + class updatePbar implements Runnable { + int n; + String s; + + public updatePbar(int x, int y, String in_s) { + n = (x + radius) + (y + radius) * width + 1; + s = in_s; + } + + public void run() { + pbar.setValue(n); + pbar.setString(s); + if (n < width * height) { + pbar.setValue(n); + pbar.setString(s); + } else { + pbar.setValue(0); + pbar.setString(""); + load_button.setSelected(false); + loading = false; + } + } + } + + class bgLoad extends Thread { + + AltosSiteMap map; + + public bgLoad(AltosSiteMap in_map) { + map = in_map; + } + + public void run() { + for (int y = -map.radius; y <= map.radius; y++) { + for (int x = -map.radius; x <= map.radius; x++) { + String pngfile; + pngfile = map.initMap(new Point(x,y)); + SwingUtilities.invokeLater(new updatePbar(x, y, pngfile)); + } + } + } + } + + public void set_sites() { + int i = 1; + for (AltosSite site : sites.sites) { + site_list.insertItemAt(site, i); + i++; + } + } + + public void itemStateChanged(ItemEvent e) { + int state = e.getStateChange(); + + if (state == ItemEvent.SELECTED) { + Object o = e.getItem(); + if (o instanceof AltosSite) { + AltosSite site = (AltosSite) o; + lat.set_value(site.latitude); + lon.set_value(site.longitude); + } + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("close")) + setVisible(false); + + if (cmd.equals("load")) { + if (!loading) { + try { + final double latitude = lat.get_value(); + final double longitude = lon.get_value(); + map.setBaseLocation(latitude,longitude); + map.draw_circle(latitude,longitude); + loading = true; + bgLoad thread = new bgLoad(map); + thread.start(); + } catch (NumberFormatException ne) { + load_button.setSelected(false); + } + } + } + } + + public AltosSiteMapPreload(AltosUIFrame in_owner) { + owner = in_owner; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + pane.setLayout(new GridBagLayout()); + + map = new AltosSiteMap(radius); + + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(map, c); + + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum(width * height); + pbar.setValue(0); + pbar.setString(""); + pbar.setStringPainted(true); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 2; + + pane.add(pbar, c); + + site_list_label = new JLabel ("Known Launch Sites:"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + + pane.add(site_list_label, c); + + site_list = new JComboBox(new AltosSite[] { new AltosSite("Site List", 0, 0) }); + site_list.addItemListener(this); + + sites = new AltosSites(this); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + + pane.add(site_list, c); + + lat = new AltosMapPos(owner, + "Latitude:", + lat_hemi_names, + 37.167833333); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lat, c); + + lon = new AltosMapPos(owner, + "Longitude:", + lon_hemi_names, + -97.73975); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lon, c); + + load_button = new JToggleButton("Load Map"); + load_button.addActionListener(this); + load_button.setActionCommand("load"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(load_button, c); + + close_button = new JButton("Close"); + close_button.addActionListener(this); + close_button.setActionCommand("close"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(close_button, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } +} diff --git a/altosuilib/AltosSiteMapTile.java b/altosuilib/AltosSiteMapTile.java new file mode 100644 index 00000000..1046d6bd --- /dev/null +++ b/altosuilib/AltosSiteMapTile.java @@ -0,0 +1,142 @@ +/* + * Copyright © 2010 Anthony Towns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.image.*; +import javax.swing.*; +import javax.imageio.*; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; +import java.io.*; +import java.util.*; +import org.altusmetrum.altoslib_4.*; + +class AltosPoint { + Point2D.Double pt; + int state; + + AltosPoint(Point2D.Double pt, int state) { + this.pt = pt; + this.state = state; + } +} + +public class AltosSiteMapTile extends JComponent { + int px_size; + File file; + + Point2D.Double boost; + Point2D.Double landed; + + LinkedList points; + + public void loadMap(File pngFile) { + file = pngFile; + repaint(); + } + + public void clearMap() { + boost = null; + landed = null; + points = null; + file = null; + } + + static Color stateColors[] = { + Color.WHITE, // startup + Color.WHITE, // idle + Color.WHITE, // pad + Color.RED, // boost + Color.PINK, // fast + Color.YELLOW, // coast + Color.CYAN, // drogue + Color.BLUE, // main + Color.BLACK // landed + }; + + private void draw_circle(Graphics g, Point2D.Double pt) { + g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); + g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); + g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); + } + + public void set_boost(Point2D.Double boost) { + this.boost = boost; + repaint(); + } + + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + AltosPoint prev = null; + Image img = null; + + if (file != null) + img = AltosSiteMapCache.get_image(this, file, px_size, px_size); + + if (img != null) { + g2d.drawImage(img, 0, 0, null); + } else { + g2d.setColor(Color.GRAY); + g2d.fillRect(0, 0, getWidth(), getHeight()); + } + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + + if (points != null) { + for (AltosPoint point : points) { + if (prev != null) { + if (0 <= point.state && point.state < stateColors.length) + g2d.setColor(stateColors[point.state]); + g2d.draw(new Line2D.Double(prev.pt, point.pt)); + } + prev = point; + } + } + if (boost != null) { + g2d.setColor(Color.RED); + draw_circle(g2d, boost); + } + if (landed != null) { + g2d.setColor(Color.BLACK); + draw_circle(g2d, landed); + } + } + + public synchronized void show(AltosState state, AltosListenerState listener_state, + Point2D.Double last_pt, Point2D.Double pt) + { + if (points == null) + points = new LinkedList(); + + points.add(new AltosPoint(pt, state.state)); + + if (state.state == AltosLib.ao_flight_boost && boost == null) + boost = pt; + if (state.state == AltosLib.ao_flight_landed && landed == null) + landed = pt; + repaint(); + } + + public AltosSiteMapTile(int in_px_size) { + px_size = in_px_size; + setPreferredSize(new Dimension(px_size, px_size)); + } +} diff --git a/altosuilib/AltosVoice.java b/altosuilib/AltosVoice.java new file mode 100644 index 00000000..a3995f68 --- /dev/null +++ b/altosuilib/AltosVoice.java @@ -0,0 +1,94 @@ +/* + * Copyright © 2010 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import com.sun.speech.freetts.Voice; +import com.sun.speech.freetts.VoiceManager; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosVoice implements Runnable { + VoiceManager voice_manager; + Voice voice; + LinkedBlockingQueue phrases; + Thread thread; + boolean busy; + + final static String voice_name = "kevin16"; + + public void run() { + try { + for (;;) { + String s = phrases.take(); + voice.speak(s); + synchronized(this) { + if (phrases.isEmpty()) { + busy = false; + notifyAll(); + } + } + } + } catch (InterruptedException e) { + } + } + + public synchronized void drain() throws InterruptedException { + while (busy) + wait(); + } + + public void speak_always(String s) { + try { + if (voice != null) { + synchronized(this) { + busy = true; + phrases.put(s); + } + } + } catch (InterruptedException e) { + } + } + + public void speak(String s) { + if (AltosUIPreferences.voice()) + speak_always(s); + } + + public void speak(String format, Object... parameters) { + speak(String.format(format, parameters)); + } + + public AltosVoice () { + busy = false; + voice_manager = VoiceManager.getInstance(); + voice = voice_manager.getVoice(voice_name); + if (voice != null) { + voice.allocate(); + phrases = new LinkedBlockingQueue (); + thread = new Thread(this); + thread.start(); + } else { + System.out.printf("Voice manager failed to open %s\n", voice_name); + Voice[] voices = voice_manager.getVoices(); + System.out.printf("Available voices:\n"); + for (int i = 0; i < voices.length; i++) { + System.out.println(" " + voices[i].getName() + + " (" + voices[i].getDomain() + " domain)"); + } + } + } +} diff --git a/altosuilib/GrabNDrag.java b/altosuilib/GrabNDrag.java new file mode 100644 index 00000000..5e5fdd52 --- /dev/null +++ b/altosuilib/GrabNDrag.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2010 Anthony Towns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altosuilib_2; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; + +class GrabNDrag extends MouseInputAdapter { + private JComponent scroll; + private Point startPt = new Point(); + + public GrabNDrag(JComponent scroll) { + this.scroll = scroll; + scroll.addMouseMotionListener(this); + scroll.addMouseListener(this); + scroll.setAutoscrolls(true); + } + + public void mousePressed(MouseEvent e) { + startPt.setLocation(e.getPoint()); + } + public void mouseDragged(MouseEvent e) { + int xd = e.getX() - startPt.x; + int yd = e.getY() - startPt.y; + + Rectangle r = scroll.getVisibleRect(); + r.x -= xd; + r.y -= yd; + scroll.scrollRectToVisible(r); + } +} diff --git a/altosuilib/Makefile.am b/altosuilib/Makefile.am index 4b22af1f..b7d624e2 100644 --- a/altosuilib/Makefile.am +++ b/altosuilib/Makefile.am @@ -1,4 +1,4 @@ -AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -source 6 +AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6 JAVAROOT=bin @@ -9,8 +9,10 @@ SRC=. altosuilibdir = $(datadir)/java altosuilib_JAVA = \ + GrabNDrag.java \ AltosDevice.java \ AltosDeviceDialog.java \ + AltosFlightDisplay.java \ AltosFontListener.java \ AltosPositionListener.java \ AltosUIConfigure.java \ @@ -30,7 +32,14 @@ altosuilib_JAVA = \ AltosUIPreferences.java \ AltosUISeries.java \ AltosUIVersion.java \ - AltosUSBDevice.java + AltosUSBDevice.java \ + AltosSiteMap.java \ + AltosSiteMapCache.java \ + AltosSiteMapPreload.java \ + AltosSiteMapTile.java \ + AltosVoice.java \ + AltosDisplayThread.java \ + AltosFreqList.java JAR=altosuilib_$(ALTOSUILIB_VERSION).jar -- cgit v1.2.3 From ace5f42b5567cff07a61b622171ac364ea8c165d Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Mon, 2 Jun 2014 22:07:39 -0700 Subject: altosui: Display error message when parsing pyro channel values fails Build an exception handling chain to get numeric parse errors propagated all the way back to the original 'save' command and up into a dialog window, including the pyro channel, field and value that were in error. Signed-off-by: Keith Packard --- altoslib/AltosConfigData.java | 2 +- altoslib/AltosConfigValues.java | 2 +- altoslib/Makefile.am | 1 + altosui/AltosConfig.java | 14 ++++++++++---- altosui/AltosConfigPyroUI.java | 31 +++++++++++++++++++++++-------- altosui/AltosConfigUI.java | 2 +- 6 files changed, 37 insertions(+), 15 deletions(-) (limited to 'altoslib/Makefile.am') diff --git a/altoslib/AltosConfigData.java b/altoslib/AltosConfigData.java index 83c184cd..2f36e215 100644 --- a/altoslib/AltosConfigData.java +++ b/altoslib/AltosConfigData.java @@ -403,7 +403,7 @@ public class AltosConfigData implements Iterable { return 1024; } - public void get_values(AltosConfigValues source) { + public void get_values(AltosConfigValues source) throws AltosConfigDataException { /* HAS_FLIGHT */ if (main_deploy >= 0) diff --git a/altoslib/AltosConfigValues.java b/altoslib/AltosConfigValues.java index 37af2ed5..6ca1f5c6 100644 --- a/altoslib/AltosConfigValues.java +++ b/altoslib/AltosConfigValues.java @@ -71,7 +71,7 @@ public interface AltosConfigValues { public abstract void set_pyros(AltosPyro[] new_pyros); - public abstract AltosPyro[] pyros(); + public abstract AltosPyro[] pyros() throws AltosConfigDataException; public abstract int aprs_interval(); diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index bff09704..0f1d7a47 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -28,6 +28,7 @@ altoslib_JAVA = \ AltosLib.java \ AltosCompanion.java \ AltosConfigData.java \ + AltosConfigDataException.java \ AltosConfigValues.java \ AltosConvert.java \ AltosCRCException.java \ diff --git a/altosui/AltosConfig.java b/altosui/AltosConfig.java index 3128114f..2cf69525 100644 --- a/altosui/AltosConfig.java +++ b/altosui/AltosConfig.java @@ -242,9 +242,15 @@ public class AltosConfig implements ActionListener { /* Pull data out of the UI and stuff back into our local data record */ - data.get_values(config_ui); - - run_serial_thread(serial_mode_save); + try { + data.get_values(config_ui); + run_serial_thread(serial_mode_save); + } catch (AltosConfigDataException ae) { + JOptionPane.showMessageDialog(owner, + ae.getMessage(), + "Configuration Data Error", + JOptionPane.ERROR_MESSAGE); + } } public void actionPerformed(ActionEvent e) { @@ -298,4 +304,4 @@ public class AltosConfig implements ActionListener { } } } -} \ No newline at end of file +} diff --git a/altosui/AltosConfigPyroUI.java b/altosui/AltosConfigPyroUI.java index 6cbac316..7a298a3c 100644 --- a/altosui/AltosConfigPyroUI.java +++ b/altosui/AltosConfigPyroUI.java @@ -124,12 +124,16 @@ public class AltosConfigPyroUI return enable.isSelected(); } - public double value() { + public double value() throws AltosConfigDataException { if (value != null) { AltosUnits units = AltosPyro.pyro_to_units(flag); - if (units != null) - return units.parse(value.getText()); - return Double.parseDouble(value.getText()); + try { + if (units != null) + return units.parse(value.getText()); + return Double.parseDouble(value.getText()); + } catch (NumberFormatException e) { + throw new AltosConfigDataException("\"%s\": %s\n", value.getText(), e.getMessage()); + } } if (combo != null) return combo.getSelectedIndex() + AltosLib.ao_flight_boost; @@ -189,15 +193,21 @@ public class AltosConfigPyroUI } } - public AltosPyro get() { + public AltosPyro get() throws AltosConfigDataException { AltosPyro p = new AltosPyro(channel); int row = 0; for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) { if ((AltosPyro.pyro_all & flag) != 0) { if (items[row].enabled()) { + try { p.flags |= flag; p.set_value(flag, items[row].value()); + } catch (AltosConfigDataException ae) { + throw new AltosConfigDataException("%s, %s", + AltosPyro.pyro_to_name(flag), + ae.getMessage()); + } } row++; } @@ -256,10 +266,15 @@ public class AltosConfigPyroUI } } - public AltosPyro[] get_pyros() { + public AltosPyro[] get_pyros() throws AltosConfigDataException { AltosPyro[] pyros = new AltosPyro[columns.length]; - for (int c = 0; c < columns.length; c++) - pyros[c] = columns[c].get(); + for (int c = 0; c < columns.length; c++) { + try { + pyros[c] = columns[c].get(); + } catch (AltosConfigDataException ae) { + throw new AltosConfigDataException ("Channel %c, %s", c + 'A', ae.getMessage()); + } + } return pyros; } diff --git a/altosui/AltosConfigUI.java b/altosui/AltosConfigUI.java index 2a9d727d..bcb3e12c 100644 --- a/altosui/AltosConfigUI.java +++ b/altosui/AltosConfigUI.java @@ -1147,7 +1147,7 @@ public class AltosConfigUI pyro_ui.set_pyros(pyros); } - public AltosPyro[] pyros() { + public AltosPyro[] pyros() throws AltosConfigDataException { if (pyro_ui != null) pyros = pyro_ui.get_pyros(); return pyros; -- cgit v1.2.3 From fcea12ac416b1eab11e9e8aae801358574308f73 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Sat, 7 Jun 2014 11:46:32 -0700 Subject: altoslib: Add TeleGPS log parsing code Signed-off-by: Keith Packard --- altoslib/AltosEepromChunk.java | 5 +- altoslib/AltosEepromFile.java | 3 + altoslib/AltosEepromGPS.java | 159 +++++++++++++++++++++++++++++++++++++++++ altoslib/AltosLib.java | 1 + altoslib/Makefile.am | 1 + 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 altoslib/AltosEepromGPS.java (limited to 'altoslib/Makefile.am') diff --git a/altoslib/AltosEepromChunk.java b/altoslib/AltosEepromChunk.java index 32de96bc..91eebc5a 100644 --- a/altoslib/AltosEepromChunk.java +++ b/altoslib/AltosEepromChunk.java @@ -84,6 +84,9 @@ public class AltosEepromChunk { case AltosLib.AO_LOG_FORMAT_EASYMINI: eeprom = new AltosEepromMini(this, offset); break; + case AltosLib.AO_LOG_FORMAT_TELEGPS: + eeprom = new AltosEepromGPS(this, offset); + break; default: throw new ParseException("unknown eeprom format " + log_format, 0); } @@ -125,4 +128,4 @@ public class AltosEepromChunk { } } } -} \ No newline at end of file +} diff --git a/altoslib/AltosEepromFile.java b/altoslib/AltosEepromFile.java index 1664dc95..f59585f8 100644 --- a/altoslib/AltosEepromFile.java +++ b/altoslib/AltosEepromFile.java @@ -102,6 +102,9 @@ public class AltosEepromFile extends AltosStateIterable { case AltosLib.AO_LOG_FORMAT_EASYMINI: body = new AltosEepromIterable(AltosEepromMini.read(input)); break; + case AltosLib.AO_LOG_FORMAT_TELEGPS: + body = new AltosEepromIterable(AltosEepromGPS.read(input)); + break; default: body = new AltosEepromIterable(new LinkedList()); break; diff --git a/altoslib/AltosEepromGPS.java b/altoslib/AltosEepromGPS.java new file mode 100644 index 00000000..33172eed --- /dev/null +++ b/altoslib/AltosEepromGPS.java @@ -0,0 +1,159 @@ +/* + * Copyright © 2014 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +import java.io.*; +import java.util.*; +import java.text.*; + +public class AltosEepromGPS extends AltosEeprom { + public static final int record_length = 32; + + public static final int max_sat = 12; + + public int record_length() { return record_length; } + + /* AO_LOG_FLIGHT elements */ + public int flight() { return data16(0); } + public int start_altitude() { return data16(2); } + public int start_latitude() { return data32(4); } + public int start_longitude() { return data32(8); } + + /* AO_LOG_GPS_TIME elements */ + public int latitude() { return data32(0); } + public int longitude() { return data32(4); } + public int altitude() { return data16(8); } + public int hour() { return data8(10); } + public int minute() { return data8(11); } + public int second() { return data8(12); } + public int flags() { return data8(13); } + public int year() { return data8(14); } + public int month() { return data8(15); } + public int day() { return data8(16); } + public int course() { return data8(17); } + public int ground_speed() { return data16(18); } + public int climb_rate() { return data16(20); } + public int pdop() { return data8(22); } + public int hdop() { return data8(23); } + public int vdop() { return data8(24); } + public int mode() { return data8(25); } + public int state() { return data8(26); } + + /* AO_LOG_GPS_SAT elements */ + public int nsat() { return data16(0); } + public int svid(int n) { return data8(2 + n * 2); } + public int c_n(int n) { return data8(2 + n * 2 + 1); } + + public AltosEepromGPS (AltosEepromChunk chunk, int start) throws ParseException { + parse_chunk(chunk, start); + } + + public void update_state(AltosState state) { + super.update_state(state); + + AltosGPS gps; + + /* Flush any pending GPS changes */ + if (state.gps_pending) { + switch (cmd) { + case AltosLib.AO_LOG_GPS_LAT: + case AltosLib.AO_LOG_GPS_LON: + case AltosLib.AO_LOG_GPS_ALT: + case AltosLib.AO_LOG_GPS_SAT: + case AltosLib.AO_LOG_GPS_DATE: + break; + default: + state.set_temp_gps(); + break; + } + } + + switch (cmd) { + case AltosLib.AO_LOG_FLIGHT: + state.set_boost_tick(tick); + state.set_flight(flight()); + /* no place to log start lat/lon yet */ + break; + case AltosLib.AO_LOG_GPS_TIME: + state.set_tick(tick); + state.set_state(state()); + gps = state.make_temp_gps(false); + gps.lat = latitude() / 1e7; + gps.lon = longitude() / 1e7; + gps.alt = altitude(); + + gps.hour = hour(); + gps.minute = minute(); + gps.second = second(); + + int flags = flags(); + + gps.connected = (flags & AltosLib.AO_GPS_RUNNING) != 0; + gps.locked = (flags & AltosLib.AO_GPS_VALID) != 0; + gps.nsat = (flags & AltosLib.AO_GPS_NUM_SAT_MASK) >> + AltosLib.AO_GPS_NUM_SAT_SHIFT; + + gps.year = 2000 + year(); + gps.month = month(); + gps.day = day(); + gps.ground_speed = ground_speed() * 1.0e-2; + gps.course = course() * 2; + gps.climb_rate = climb_rate() * 1.0e-2; + gps.hdop = hdop(); + gps.vdop = vdop(); + break; + case AltosLib.AO_LOG_GPS_SAT: + state.set_tick(tick); + gps = state.make_temp_gps(true); + + int n = nsat(); + if (n > max_sat) + n = max_sat; + for (int i = 0; i < n; i++) + gps.add_sat(svid(i), c_n(i)); + break; + } + } + + public AltosEepromGPS (String line) { + parse_string(line); + } + + static public LinkedList read(FileInputStream input) { + LinkedList tgpss = new LinkedList(); + + for (;;) { + try { + String line = AltosLib.gets(input); + if (line == null) + break; + try { + AltosEepromGPS tgps = new AltosEepromGPS(line); + if (tgps.cmd != AltosLib.AO_LOG_INVALID) + tgpss.add(tgps); + } catch (Exception e) { + System.out.printf ("exception\n"); + } + } catch (IOException ie) { + break; + } + } + + return tgpss; + } +} diff --git a/altoslib/AltosLib.java b/altoslib/AltosLib.java index 3aef077a..7080ebfe 100644 --- a/altoslib/AltosLib.java +++ b/altoslib/AltosLib.java @@ -265,6 +265,7 @@ public class AltosLib { public static final int AO_LOG_FORMAT_EASYMINI = 6; public static final int AO_LOG_FORMAT_TELEMETRUM = 7; public static final int AO_LOG_FORMAT_TELEMINI = 8; + public static final int AO_LOG_FORMAT_TELEGPS = 9; public static final int AO_LOG_FORMAT_NONE = 127; public static boolean isspace(int c) { diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index 0f1d7a47..1086f858 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -47,6 +47,7 @@ altoslib_JAVA = \ AltosEepromMega.java \ AltosEepromMetrum2.java \ AltosEepromMini.java \ + AltosEepromGPS.java \ AltosEepromMonitor.java \ AltosFile.java \ AltosFlash.java \ -- cgit v1.2.3 From 3bfba8f9dbc1627a317804713f83b9d06566d008 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Fri, 13 Jun 2014 15:21:28 -0700 Subject: altoslib: Add conversion class for voltages Provide a common presentation for voltage values Signed-off-by: Keith Packard --- altoslib/AltosConvert.java | 2 ++ altoslib/AltosVoltage.java | 41 +++++++++++++++++++++++++++++++++++++++++ altoslib/Makefile.am | 1 + 3 files changed, 44 insertions(+) create mode 100644 altoslib/AltosVoltage.java (limited to 'altoslib/Makefile.am') diff --git a/altoslib/AltosConvert.java b/altoslib/AltosConvert.java index 6578e90f..87b9eaf2 100644 --- a/altoslib/AltosConvert.java +++ b/altoslib/AltosConvert.java @@ -349,6 +349,8 @@ public class AltosConvert { public static AltosOrient orient = new AltosOrient(); + public static AltosVoltage voltage = new AltosVoltage(); + public static String show_gs(String format, double a) { a = meters_to_g(a); format = format.concat(" g"); diff --git a/altoslib/AltosVoltage.java b/altoslib/AltosVoltage.java new file mode 100644 index 00000000..351bf115 --- /dev/null +++ b/altoslib/AltosVoltage.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2012 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +public class AltosVoltage extends AltosUnits { + + public double value(double v, boolean imperial_units) { + return v; + } + + public double inverse(double v, boolean imperial_units) { + return v; + } + + public String show_units(boolean imperial_units) { + return "V"; + } + + public String say_units(boolean imperial_units) { + return "volts"; + } + + public int show_fraction(int width, boolean imperial_units) { + return 2; + } +} diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index 1086f858..bd47f89f 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -119,6 +119,7 @@ altoslib_JAVA = \ AltosSpeed.java \ AltosTemperature.java \ AltosAccel.java \ + AltosVoltage.java \ AltosPyro.java \ AltosWriter.java -- cgit v1.2.3 From 451950bba9ee3b25b5d0c6e5f0b55f08a5b29f73 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Sat, 14 Jun 2014 14:33:58 -0700 Subject: altoslib: Add units converters for latitude and longitude Makes display of these values consistent across all instances Signed-off-by: Keith Packard --- altoslib/AltosConvert.java | 4 +++ altoslib/AltosLatitude.java | 23 +++++++++++++++ altoslib/AltosLocation.java | 66 ++++++++++++++++++++++++++++++++++++++++++++ altoslib/AltosLongitude.java | 23 +++++++++++++++ altoslib/Makefile.am | 3 ++ 5 files changed, 119 insertions(+) create mode 100644 altoslib/AltosLatitude.java create mode 100644 altoslib/AltosLocation.java create mode 100644 altoslib/AltosLongitude.java (limited to 'altoslib/Makefile.am') diff --git a/altoslib/AltosConvert.java b/altoslib/AltosConvert.java index 87b9eaf2..dc0fbb62 100644 --- a/altoslib/AltosConvert.java +++ b/altoslib/AltosConvert.java @@ -351,6 +351,10 @@ public class AltosConvert { public static AltosVoltage voltage = new AltosVoltage(); + public static AltosLatitude latitude = new AltosLatitude(); + + public static AltosLongitude longitude = new AltosLongitude(); + public static String show_gs(String format, double a) { a = meters_to_g(a); format = format.concat(" g"); diff --git a/altoslib/AltosLatitude.java b/altoslib/AltosLatitude.java new file mode 100644 index 00000000..6156d6dc --- /dev/null +++ b/altoslib/AltosLatitude.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2014 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +public class AltosLatitude extends AltosLocation { + public String pos() { return "N"; } + public String neg() { return "S"; } +} diff --git a/altoslib/AltosLocation.java b/altoslib/AltosLocation.java new file mode 100644 index 00000000..725a02ba --- /dev/null +++ b/altoslib/AltosLocation.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2014 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +public abstract class AltosLocation extends AltosUnits { + + public abstract String pos(); + public abstract String neg(); + + public double value(double v, boolean imperial_units) { + return v; + } + + public double inverse(double v, boolean imperial_units) { + return v; + } + + public String show_units(boolean imperial_units) { + return "°"; + } + + public String say_units(boolean imperial_units) { + return "degrees"; + } + + public int show_fraction(int width, boolean imperial_units) { + return 2; + } + + public String show(int width, double v, boolean imperial_units) { + String h = pos(); + if (v < 0) { + h = neg(); + v = -v; + } + int deg = (int) Math.floor(v); + double min = (v - Math.floor(v)) * 60.0; + return String.format("%s %4d° %9.6f", h, deg, min); + } + + public String say(double v, boolean imperial_units) { + String h = pos(); + if (v < 0) { + h = neg(); + v = -v; + } + int deg = (int) Math.floor(v); + double min = (v - Math.floor(v)) * 60.0; + return String.format("%s %d degrees %d", h, deg, (int) Math.floor(min + 0.5)); + } +} diff --git a/altoslib/AltosLongitude.java b/altoslib/AltosLongitude.java new file mode 100644 index 00000000..29a5dcd4 --- /dev/null +++ b/altoslib/AltosLongitude.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2014 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_4; + +public class AltosLongitude extends AltosLocation { + public String pos() { return "E"; } + public String neg() { return "W"; } +} diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index bd47f89f..e81418bb 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -120,6 +120,9 @@ altoslib_JAVA = \ AltosTemperature.java \ AltosAccel.java \ AltosVoltage.java \ + AltosLocation.java \ + AltosLatitude.java \ + AltosLongitude.java \ AltosPyro.java \ AltosWriter.java -- cgit v1.2.3