From: Keith Packard Date: Mon, 26 May 2014 03:55:11 +0000 (-0700) Subject: altosui/altosuilib/altoslib: Move more stuff out of autosui. Reduce site map memory X-Git-Tag: 1.3.2.2~75 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=0a6c76fc0525d6588a1d88127f0085f13a02f1af 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 --- 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