Use a single large Canvas and draw images on top by hand.
Signed-off-by: Keith Packard <keithp@keithp.com>
AltosDescent descent;
AltosLanded landed;
AltosCompanionInfo companion;
- AltosSiteMap sitemap;
+ AltosUIMap sitemap;
boolean has_map;
boolean has_companion;
boolean has_state;
has_companion = false;
has_state = false;
- sitemap = new AltosSiteMap();
+ sitemap = new AltosUIMap();
has_map = false;
/* Make the tabbed pane use the rest of the window space */
JTabbedPane pane;
AltosGraph graph;
AltosUIEnable enable;
- AltosSiteMap map;
+ AltosUIMap map;
AltosState state;
AltosGraphDataSet graphDataSet;
AltosFlightStats stats;
for (AltosState state : states) {
if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) {
if (map == null)
- map = new AltosSiteMap();
+ map = new AltosUIMap();
map.show(state, null);
has_gps = true;
}
}
void LoadMaps() {
- new AltosSiteMapPreload(AltosUI.this);
+ new AltosUIMapPreload(AltosUI.this);
}
void LaunchController() {
} else {
double lat = Double.parseDouble(args[i+1]);
double lon = Double.parseDouble(args[i+2]);
- AltosSiteMap.prefetchMaps(lat, lon);
+// AltosSiteMap.prefetchMaps(lat, lon);
i += 2;
}
} else if (args[i].equals("--replay"))
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUILatLon {
+ public double lat;
+ public double lon;
+
+ public boolean equals(AltosUILatLon other) {
+ if (other == null)
+ return false;
+ return lat == other.lat && lon == other.lon;
+ }
+
+ public AltosUILatLon(double lat, double lon) {
+ this.lat = lat;
+ this.lon = lon;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMap extends JComponent implements AltosFlightDisplay, AltosUIMapZoomListener {
+
+ static final int px_size = 512;
+
+ static final int maptype_hybrid = 0;
+ static final int maptype_roadmap = 1;
+ static final int maptype_satellite = 2;
+ static final int maptype_terrain = 3;
+ static final int maptype_default = maptype_hybrid;
+
+ static final String[] maptype_names = {
+ "hybrid",
+ "roadmap",
+ "satellite",
+ "terrain"
+ };
+
+ public static final String[] maptype_labels = {
+ "Hybrid",
+ "Roadmap",
+ "Satellite",
+ "Terrain"
+ };
+
+ public static final 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
+ Color.BLACK, // invalid
+ Color.CYAN, // stateless
+ };
+
+ public void reset() {
+ // nothing
+ }
+
+ public void font_size_changed(int font_size) {
+ view.set_font();
+ }
+
+ public void units_changed(boolean imperial_units) {
+ repaint();
+ }
+
+ JLabel zoom_label;
+
+ private void set_zoom_label() {
+ zoom_label.setText(String.format("Zoom %d", view.zoom() - view.default_zoom));
+ }
+
+ public void zoom_changed(int zoom) {
+ set_zoom_label();
+ }
+
+ public void set_zoom(int zoom) {
+ view.set_zoom(zoom);
+ }
+
+ public int get_zoom() {
+ return view.zoom();
+ }
+
+ public void set_maptype(int type) {
+ view.set_maptype(type);
+ maptype_combo.setSelectedIndex(type);
+ }
+
+ public void show(AltosState state, AltosListenerState listener_state) {
+ view.show(state, listener_state);
+ }
+
+ public void centre(double lat, double lon) {
+ view.centre(lat, lon);
+ }
+
+ public void centre(AltosState state) {
+ if (!state.gps.locked && state.gps.nsat < 4)
+ return;
+ centre(state.gps.lat, state.gps.lon);
+ }
+
+ public void add_mark(double lat, double lon, int state) {
+ view.add_mark(lat, lon, state);
+ }
+
+ public void clear_marks() {
+ view.clear_marks();
+ }
+
+ AltosUIMapView view;
+
+ private GridBagLayout layout = new GridBagLayout();
+
+ JComboBox<String> maptype_combo;
+
+ public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
+ view.set_load_params(lat, lon, radius, listener);
+ }
+
+ public boolean all_fetched() {
+ return view.all_fetched();
+ }
+
+ public static void prefetch_maps(double lat, double lon) {
+ }
+
+ public AltosUIMap() {
+
+ view = new AltosUIMapView();
+
+ view.setPreferredSize(new Dimension(500,500));
+ view.setVisible(true);
+ view.setEnabled(true);
+ view.add_zoom_listener(this);
+
+ GridBagLayout my_layout = new GridBagLayout();
+
+ setLayout(my_layout);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.BOTH;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 10;
+ c.weightx = 1;
+ c.weighty = 1;
+ add(view, c);
+
+ int y = 0;
+
+ zoom_label = new JLabel("", JLabel.CENTER);
+ set_zoom_label();
+
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = y++;
+ c.weightx = 0;
+ c.weighty = 0;
+ add(zoom_label, c);
+
+ JButton zoom_reset = new JButton("0");
+ zoom_reset.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ set_zoom(view.default_zoom);
+ }
+ });
+
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = y++;
+ c.weightx = 0;
+ c.weighty = 0;
+ add(zoom_reset, c);
+
+ JButton zoom_in = new JButton("+");
+ zoom_in.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ set_zoom(get_zoom() + 1);
+ }
+ });
+
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = y++;
+ c.weightx = 0;
+ c.weighty = 0;
+ add(zoom_in, c);
+
+ JButton zoom_out = new JButton("-");
+ zoom_out.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ set_zoom(get_zoom() - 1);
+ }
+ });
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = y++;
+ c.weightx = 0;
+ c.weighty = 0;
+ add(zoom_out, c);
+
+ maptype_combo = new JComboBox<String>(maptype_labels);
+
+ maptype_combo.setEditable(false);
+ maptype_combo.setMaximumRowCount(maptype_combo.getItemCount());
+ maptype_combo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ view.set_maptype(maptype_combo.getSelectedIndex());
+ }
+ });
+
+ c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = y++;
+ c.weightx = 0;
+ c.weighty = 0;
+ add(maptype_combo, c);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * 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.*;
+
+public class AltosUIMapCache {
+ static final int success = 0;
+ static final int loading = 1;
+ static final int failed = 2;
+ static final int bad_request = 3;
+ static final int forbidden = 4;
+
+ static private Object fetch_lock = new Object();
+
+ static final int min_cache_size = 9;
+ static final int max_cache_size = 24;
+
+ static int cache_size = min_cache_size;
+
+ static AltosUIMapImage[] images = new AltosUIMapImage[cache_size];
+
+ static Object cache_lock = new Object();
+
+ public static void set_cache_size(int new_size) {
+ if (new_size < min_cache_size)
+ new_size = min_cache_size;
+ if (new_size > max_cache_size)
+ new_size = max_cache_size;
+ if (new_size == cache_size)
+ return;
+
+ synchronized(cache_lock) {
+ AltosUIMapImage[] new_images = new AltosUIMapImage[new_size];
+
+ for (int i = 0; i < cache_size; i++) {
+ if (i < new_size)
+ new_images[i] = images[i];
+ else if (images[i] != null)
+ images[i].flush();
+ }
+ images = new_images;
+ cache_size = new_size;
+ }
+ }
+
+ static long used;
+
+ public static Image get(AltosUIMapTile tile, AltosUIMapStore store, int width, int height) {
+ int oldest = -1;
+ long age = used;
+
+ synchronized(cache_lock) {
+ AltosUIMapImage image = null;
+ for (int i = 0; i < cache_size; i++) {
+ image = images[i];
+
+ if (image == null) {
+ oldest = i;
+ break;
+ }
+ if (store.equals(image.store)) {
+ image.used = used++;
+ return image.image;
+ }
+ if (image.used < age) {
+ oldest = i;
+ age = image.used;
+ }
+ }
+
+ try {
+ image = new AltosUIMapImage(tile, store);
+ image.used = used++;
+ if (images[oldest] != null)
+ images[oldest].flush();
+
+ images[oldest] = image;
+
+ if (image.image == null)
+ tile.set_status(loading);
+ else
+ tile.set_status(success);
+
+ return image.image;
+ } catch (IOException e) {
+ tile.set_status(failed);
+ return null;
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * 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.*;
+
+public class AltosUIMapImage implements AltosUIMapStoreListener {
+ 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.
+
+ static final int success = 0;
+ static final int loading = 1;
+ static final int failed = 2;
+ static final int bad_request = 3;
+ static final int forbidden = 4;
+
+ static long forbidden_time;
+ static boolean forbidden_set = false;
+ static final long forbidden_interval = 60l * 1000l * 1000l * 1000l;
+
+ AltosUIMapTile tile; /* Notify when image has been loaded */
+ Image image;
+ AltosUIMapStore store;
+ long used;
+
+ class loader implements Runnable {
+ public void run() {
+ if (image != null)
+ tile.notify_image(image);
+ try {
+ image = ImageIO.read(store.file);
+ } catch (Exception ex) {
+ }
+ if (image == null)
+ tile.set_status(failed);
+ else
+ tile.set_status(success);
+ tile.notify_image(image);
+ }
+ }
+
+ private void load() {
+ loader l = new loader();
+ Thread lt = new Thread(l);
+ lt.start();
+ }
+
+ public void flush() {
+ if (image != null) {
+ image.flush();
+ image = null;
+ }
+ }
+
+ public boolean has_map() {
+ return store.status() == AltosUIMapStore.success;
+ }
+
+ public synchronized void notify_store(AltosUIMapStore store, int status) {
+ switch (status) {
+ case AltosUIMapStore.loading:
+ break;
+ case AltosUIMapStore.success:
+ load();
+ break;
+ default:
+ tile.set_status(status);
+ tile.notify_image(null);
+ }
+ }
+
+ public AltosUIMapImage(AltosUIMapTile tile, AltosUIMapStore store) throws IOException {
+ this.tile = tile;
+ this.image = null;
+ this.store = store;
+ this.used = 0;
+
+ int status = store.status();
+ switch (status) {
+ case AltosUIMapStore.loading:
+ store.add_listener(this);
+ break;
+ case AltosUIMapStore.success:
+ load();
+ break;
+ default:
+ tile.set_status(status);
+ tile.notify_image(null);
+ break;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMapLine {
+ AltosUILatLon start, end;
+
+ private Font font = null;
+
+ public void set_font(Font font) {
+ this.font = font;
+ }
+
+ private AltosUILatLon lat_lon(MouseEvent e, AltosUIMapTransform t) {
+ return t.screen_lat_lon(e.getPoint());
+ }
+
+ public void dragged(MouseEvent e, AltosUIMapTransform t) {
+ end = lat_lon(e, t);
+ }
+
+ public void pressed(MouseEvent e, AltosUIMapTransform t) {
+ start = lat_lon(e, t);
+ end = null;
+ }
+
+ private String line_dist() {
+ String format;
+ AltosGreatCircle g = new AltosGreatCircle(start.lat, start.lon,
+ end.lat, end.lon);
+ double distance = g.distance;
+
+ if (AltosConvert.imperial_units) {
+ distance = AltosConvert.meters_to_feet(distance);
+ if (distance < 10000) {
+ format = "%4.0fft";
+ } else {
+ distance /= 5280;
+ if (distance < 10)
+ format = "%5.3fmi";
+ else if (distance < 100)
+ format = "%5.2fmi";
+ else if (distance < 1000)
+ format = "%5.1fmi";
+ else
+ format = "%5.0fmi";
+ }
+ } else {
+ if (distance < 10000) {
+ format = "%4.0fm";
+ } else {
+ distance /= 1000;
+ if (distance < 100)
+ format = "%5.2fkm";
+ else if (distance < 1000)
+ format = "%5.1fkm";
+ else
+ format = "%5.0fkm";
+ }
+ }
+ return String.format(format, distance);
+ }
+
+ public void paint(Graphics2D g, AltosUIMapTransform t) {
+ g.setColor(Color.BLUE);
+
+ if (start == null || end == null)
+ return;
+
+ Line2D.Double line = new Line2D.Double(t.screen(start),
+ t.screen(end));
+
+ g.draw(line);
+
+ String message = line_dist();
+ g.setFont(font);
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ Rectangle2D bounds;
+ bounds = font.getStringBounds(message, g.getFontRenderContext());
+
+ float x = (float) line.x1;
+ float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
+
+ if (line.x1 < line.x2) {
+ x -= (float) bounds.getWidth() + 2.0f;
+ } else {
+ x += 2.0f;
+ }
+ g.drawString(message, x, y);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMapMark {
+
+ AltosUILatLon lat_lon;
+ int state;
+
+ static public int stroke_width = 6;
+
+ public void paint(Graphics2D g, AltosUIMapTransform t) {
+
+ Point2D.Double pt = t.screen(lat_lon);
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ if (0 <= state && state < AltosUIMap.stateColors.length)
+ g.setColor(AltosUIMap.stateColors[state]);
+ else
+ g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
+
+ 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 AltosUIMapMark (double lat, double lon, int state) {
+ lat_lon = new AltosUILatLon(lat, lon);
+ this.state = state;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+class PathPoint {
+ AltosUILatLon lat_lon;
+ int state;
+
+ public PathPoint(AltosUILatLon lat_lon, int state) {
+ this.lat_lon = lat_lon;
+ this.state = state;
+ }
+
+ public boolean equals(PathPoint other) {
+ if (other == null)
+ return false;
+
+ return lat_lon.equals(other.lat_lon) && state == other.state;
+ }
+}
+
+public class AltosUIMapPath {
+
+ LinkedList<PathPoint> points = new LinkedList<PathPoint>();
+ PathPoint last_point = null;
+
+ static public int stroke_width = 6;
+
+ public void paint(Graphics2D g, AltosUIMapTransform t) {
+ Point2D.Double prev = null;
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ for (PathPoint point : points) {
+ Point2D.Double cur = t.screen(point.lat_lon);
+ if (prev != null) {
+ Line2D.Double line = new Line2D.Double (prev, cur);
+ Rectangle bounds = line.getBounds();
+
+ if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) {
+ if (0 <= point.state && point.state < AltosUIMap.stateColors.length)
+ g.setColor(AltosUIMap.stateColors[point.state]);
+ else
+ g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
+
+ g.draw(line);
+ }
+ }
+ prev = cur;
+ }
+ }
+
+ public AltosUIMapRectangle add(double lat, double lon, int state) {
+ PathPoint point = new PathPoint(new AltosUILatLon (lat, lon), state);
+ AltosUIMapRectangle rect = null;
+
+ if (!point.equals(last_point)) {
+ if (last_point != null)
+ rect = new AltosUIMapRectangle(last_point.lat_lon, point.lat_lon);
+ points.add (point);
+ last_point = point;
+ }
+ return rect;
+ }
+
+ public void clear () {
+ points = new LinkedList<PathPoint>();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * 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 AltosUIMapPos 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 AltosUIMapPos(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<String>(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 AltosUISite {
+ String name;
+ double latitude;
+ double longitude;
+
+ public String toString() {
+ return name;
+ }
+
+ public AltosUISite(String in_name, double in_latitude, double in_longitude) {
+ name = in_name;
+ latitude = in_latitude;
+ longitude = in_longitude;
+ }
+
+ public AltosUISite(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 AltosUISites extends Thread {
+ AltosUIMapPreload preload;
+ URL url;
+ LinkedList<AltosUISite> sites;
+
+ void notify_complete() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ preload.set_sites();
+ }
+ });
+ }
+
+ void add(AltosUISite site) {
+ sites.add(site);
+ }
+
+ void add(String line) {
+ try {
+ add(new AltosUISite(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 AltosUISites(AltosUIMapPreload in_preload) {
+ sites = new LinkedList<AltosUISite>();
+ preload = in_preload;
+ try {
+ url = new URL(AltosLib.launch_sites_url);
+ } catch (java.net.MalformedURLException e) {
+ notify_complete();
+ }
+ start();
+ }
+}
+
+public class AltosUIMapPreload extends AltosUIFrame implements ActionListener, ItemListener, AltosUIMapTileListener {
+ AltosUIFrame owner;
+ AltosUIMap map;
+
+ AltosUIMapPos lat;
+ AltosUIMapPos lon;
+
+ JProgressBar pbar;
+ int pbar_max;
+ int pbar_cur;
+
+ AltosUISites sites;
+ JLabel site_list_label;
+ JComboBox<AltosUISite> site_list;
+
+ JToggleButton load_button;
+ boolean loading;
+ JButton close_button;
+
+ JCheckBox[] maptypes = new JCheckBox[AltosUIMap.maptype_terrain - AltosUIMap.maptype_hybrid + 1];
+
+ JComboBox<Integer> min_zoom;
+ JComboBox<Integer> max_zoom;
+ JComboBox<Integer> radius;
+
+ Integer[] zooms = { -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6 };
+ Integer[] radii = { 1, 2, 3, 4, 5 };
+
+ static final String[] lat_hemi_names = { "N", "S" };
+ static final String[] lon_hemi_names = { "E", "W" };
+
+ class updatePbar implements Runnable {
+ String s;
+
+ public updatePbar(String in_s) {
+ s = in_s;
+ }
+
+ public void run() {
+ int n = ++pbar_cur;
+
+ pbar.setMaximum(pbar_max);
+ pbar.setValue(n);
+ pbar.setString(s);
+ }
+ }
+
+ double latitude, longitude;
+ int min_z;
+ int max_z;
+ int cur_z;
+ int all_types;
+ int cur_type;
+ int r;
+
+ int tiles_per_layer;
+ int tiles_loaded;
+ int layers_total;
+ int layers_loaded;
+
+
+ private void do_load() {
+ tiles_loaded = 0;
+ map.set_zoom(cur_z + AltosUIMapView.default_zoom);
+ map.set_maptype(cur_type);
+ map.set_load_params(latitude, longitude, r, this);
+ }
+
+ private int next_type(int start) {
+ int next_type;
+ for (next_type = start;
+ next_type <= AltosUIMap.maptype_terrain && (all_types & (1 << next_type)) == 0;
+ next_type++)
+ ;
+ return next_type;
+ }
+
+ private void next_load() {
+ int next_type = next_type(cur_type + 1);
+
+ if (next_type > AltosUIMap.maptype_terrain) {
+ if (cur_z == max_z) {
+ return;
+ } else {
+ cur_z++;
+ }
+ next_type = next_type(0);
+ }
+ cur_type = next_type;
+ do_load();
+ }
+
+ private void start_load() {
+ cur_z = min_z;
+ int ntype = 0;
+ all_types = 0;
+ for (int t = AltosUIMap.maptype_hybrid; t <= AltosUIMap.maptype_terrain; t++)
+ if (maptypes[t].isSelected()) {
+ all_types |= (1 << t);
+ ntype++;
+ }
+ if (ntype == 0) {
+ all_types |= (1 << AltosUIMap.maptype_hybrid);
+ ntype = 1;
+ }
+
+ cur_type = next_type(0);
+ tiles_per_layer = (r * 2 + 1) * (r * 2 + 1);
+ layers_total = (max_z - min_z + 1) * ntype;
+ layers_loaded = 0;
+ pbar_max = layers_total * tiles_per_layer;
+ pbar_cur = 0;
+
+ map.clear_marks();
+ map.add_mark(latitude,longitude, AltosLib.ao_flight_boost);
+ do_load();
+ }
+
+ /* AltosUIMapTileListener methods */
+
+ public void notify_tile(AltosUIMapTile tile, int status) {
+ if (status == AltosUIMapStore.loading)
+ return;
+
+ SwingUtilities.invokeLater(new updatePbar(tile.store.file.toString()));
+ ++tiles_loaded;
+ if (tiles_loaded == tiles_per_layer) {
+ ++layers_loaded;
+ if (layers_loaded == layers_total) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ pbar.setValue(0);
+ pbar.setString("");
+ load_button.setSelected(false);
+ loading = false;
+ }
+ });
+ } else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ next_load();
+ }
+ });
+ }
+ }
+ }
+
+ public void set_sites() {
+ int i = 1;
+ for (AltosUISite 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 AltosUISite) {
+ AltosUISite site = (AltosUISite) 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 {
+ latitude = lat.get_value();
+ longitude = lon.get_value();
+ min_z = (Integer) min_zoom.getSelectedItem();
+ max_z = (Integer) max_zoom.getSelectedItem();
+ if (max_z < min_z)
+ max_z = min_z;
+ r = (Integer) radius.getSelectedItem();
+ loading = true;
+ } catch (NumberFormatException ne) {
+ load_button.setSelected(false);
+ }
+ start_load();
+ }
+ }
+ }
+
+ public AltosUIMapPreload(AltosUIFrame in_owner) {
+ System.out.printf("start creating preload ui\n");
+
+ owner = in_owner;
+
+ Container pane = getContentPane();
+ GridBagConstraints c = new GridBagConstraints();
+ Insets i = new Insets(4,4,4,4);
+
+ setTitle("AltOS Load Maps");
+
+ pane.setLayout(new GridBagLayout());
+
+ map = new AltosUIMap();
+
+ 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 = 10;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(map, c);
+
+ pbar = new JProgressBar();
+ pbar.setMinimum(0);
+ pbar.setMaximum(1);
+ 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 = 10;
+
+ 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<AltosUISite>(new AltosUISite[] { new AltosUISite("Site List", 0, 0) });
+ site_list.addItemListener(this);
+
+ sites = new AltosUISites(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 AltosUIMapPos(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 AltosUIMapPos(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);
+
+ JLabel types_label = new JLabel("Map Types");
+ c.gridx = 2;
+ c.gridwidth = 2;
+ c.gridy = 2;
+ pane.add(types_label, c);
+
+ c.gridwidth = 1;
+
+ for (int type = AltosUIMap.maptype_hybrid; type <= AltosUIMap.maptype_terrain; type++) {
+ maptypes[type] = new JCheckBox(AltosUIMap.maptype_labels[type],
+ type == AltosUIMap.maptype_hybrid);
+ c.gridx = 2 + (type >> 1);
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy = (type & 1) + 3;
+ pane.add(maptypes[type], c);
+ }
+
+ JLabel min_zoom_label = new JLabel("Minimum Zoom");
+ c.gridx = 4;
+ c.gridy = 2;
+ pane.add(min_zoom_label, c);
+
+ min_zoom = new JComboBox<Integer>(zooms);
+ min_zoom.setSelectedItem(zooms[10]);
+ min_zoom.setEditable(false);
+ c.gridx = 5;
+ c.gridy = 2;
+ pane.add(min_zoom, c);
+
+ JLabel max_zoom_label = new JLabel("Maximum Zoom");
+ c.gridx = 4;
+ c.gridy = 3;
+ pane.add(max_zoom_label, c);
+
+ max_zoom = new JComboBox<Integer>(zooms);
+ max_zoom.setSelectedItem(zooms[14]);
+ max_zoom.setEditable(false);
+ c.gridx = 5;
+ c.gridy = 3;
+ pane.add(max_zoom, c);
+
+ JLabel radius_label = new JLabel("Tile Radius");
+ c.gridx = 4;
+ c.gridy = 4;
+ pane.add(radius_label, c);
+
+ radius = new JComboBox<Integer>(radii);
+ radius.setSelectedItem(radii[4]);
+ radius.setEditable(true);
+ c.gridx = 5;
+ c.gridy = 4;
+ pane.add(radius, c);
+
+ pack();
+ setLocationRelativeTo(owner);
+ setVisible(true);
+
+ System.out.printf("done creating preload ui\n");
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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;
+
+public class AltosUIMapRectangle {
+ AltosUILatLon ul, lr;
+
+ public AltosUIMapRectangle(AltosUILatLon a, AltosUILatLon b) {
+ double ul_lat, ul_lon;
+ double lr_lat, lr_lon;
+
+ if (a.lat > b.lat) {
+ ul_lat = a.lat;
+ lr_lat = b.lat;
+ } else {
+ ul_lat = b.lat;
+ lr_lat = a.lat;
+ }
+ if (a.lon < b.lon) {
+ ul_lon = a.lon;
+ lr_lon = b.lon;
+ } else {
+ ul_lon = b.lon;
+ lr_lon = a.lon;
+ }
+
+ ul = new AltosUILatLon(ul_lat, ul_lon);
+ lr = new AltosUILatLon(lr_lat, lr_lon);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.io.*;
+import java.net.*;
+import java.util.*;
+
+public class AltosUIMapStore {
+ String url;
+ File file;
+ LinkedList<AltosUIMapStoreListener> listeners = new LinkedList<AltosUIMapStoreListener>();
+
+ static final int success = 0;
+ static final int loading = 1;
+ static final int failed = 2;
+ static final int bad_request = 3;
+ static final int forbidden = 4;
+
+ int status;
+
+ public int status() {
+ return status;
+ }
+
+ public synchronized void add_listener(AltosUIMapStoreListener listener) {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+
+ public synchronized void remove_listener(AltosUIMapStoreListener listener) {
+ listeners.remove(listener);
+ }
+
+ private synchronized void notify_listeners(int status) {
+ this.status = status;
+ for (AltosUIMapStoreListener listener : listeners)
+ listener.notify_store(this, status);
+ }
+
+ static Object forbidden_lock = new Object();
+ static long forbidden_time;
+ static boolean forbidden_set;
+
+ private int fetch_url() {
+ URL u;
+
+ try {
+ u = new URL(url);
+ } catch (java.net.MalformedURLException e) {
+ return bad_request;
+ }
+
+ byte[] data;
+ URLConnection uc = null;
+ try {
+ uc = u.openConnection();
+ String type = uc.getContentType();
+ int contentLength = uc.getContentLength();
+ if (uc instanceof HttpURLConnection) {
+ int response = ((HttpURLConnection) uc).getResponseCode();
+ switch (response) {
+ case HttpURLConnection.HTTP_FORBIDDEN:
+ case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ synchronized (forbidden_lock) {
+ forbidden_time = System.nanoTime();
+ forbidden_set = true;
+ return forbidden;
+ }
+ }
+ }
+ 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 failed;
+
+ } catch (IOException e) {
+ return failed;
+ }
+
+ try {
+ FileOutputStream out = new FileOutputStream(file);
+ out.write(data);
+ out.flush();
+ out.close();
+ } catch (FileNotFoundException e) {
+ return bad_request;
+ } catch (IOException e) {
+ if (file.exists())
+ file.delete();
+ return bad_request;
+ }
+ return success;
+ }
+
+ static Object fetch_lock = new Object();
+
+ static final long forbidden_interval = 60l * 1000l * 1000l * 1000l;
+ static final long google_maps_ratelimit_ms = 1200;
+
+ class loader implements Runnable {
+
+ public void run() {
+ if (file.exists()) {
+ notify_listeners(success);
+ return;
+ }
+
+ synchronized(forbidden_lock) {
+ if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) {
+ notify_listeners(forbidden);
+ return;
+ }
+ }
+
+ int new_status;
+
+ if (!AltosUIVersion.has_google_maps_api_key()) {
+ synchronized (fetch_lock) {
+ long startTime = System.nanoTime();
+ new_status = fetch_url();
+ if (new_status == success) {
+ 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();
+ }
+ }
+ }
+ }
+ } else {
+ new_status = fetch_url();
+ }
+ notify_listeners(new_status);
+ }
+ }
+
+ private void load() {
+ loader l = new loader();
+ Thread lt = new Thread(l);
+ lt.start();
+ }
+
+ private AltosUIMapStore (String url, File file) {
+ this.url = url;
+ this.file = file;
+
+ if (file.exists())
+ status = success;
+ else {
+ status = loading;
+ load();
+ }
+ }
+
+ public boolean equals(AltosUIMapStore other) {
+ return url.equals(other.url);
+ }
+
+ static HashMap<String,AltosUIMapStore> stores = new HashMap<String,AltosUIMapStore>();
+
+ public static AltosUIMapStore get(String url, File file) {
+ AltosUIMapStore store;
+ synchronized(stores) {
+ if (stores.containsKey(url)) {
+ store = stores.get(url);
+ } else {
+ store = new AltosUIMapStore(url, file);
+ stores.put(url, store);
+ }
+ }
+ return store;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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;
+
+public interface AltosUIMapStoreListener {
+ abstract void notify_store(AltosUIMapStore store, int status);
+}
--- /dev/null
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * 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.*;
+import java.io.*;
+import java.util.*;
+import java.awt.RenderingHints.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMapTile {
+ AltosUIMapTileListener listener;
+ AltosUILatLon upper_left, center;
+ int px_size;
+ int zoom;
+ int maptype;
+ AltosUIMapStore store;
+ int status;
+
+ private File map_file() {
+ double lat = center.lat;
+ double lon = center.lon;
+ char chlat = lat < 0 ? 'S' : 'N';
+ char chlon = lon < 0 ? 'W' : 'E';
+
+ if (lat < 0) lat = -lat;
+ if (lon < 0) lon = -lon;
+ String maptype_string = String.format("%s-", AltosUIMap.maptype_names[maptype]);
+ String format_string;
+ if (maptype == AltosUIMap.maptype_hybrid || maptype == AltosUIMap.maptype_satellite || maptype == AltosUIMap.maptype_terrain)
+ format_string = "jpg";
+ else
+ format_string = "png";
+ return new File(AltosUIPreferences.mapdir(),
+ String.format("map-%c%.6f,%c%.6f-%s%d.%s",
+ chlat, lat, chlon, lon, maptype_string, zoom, format_string));
+ }
+
+ private String map_url() {
+ String format_string;
+ if (maptype == AltosUIMap.maptype_hybrid || maptype == AltosUIMap.maptype_satellite || maptype == AltosUIMap.maptype_terrain)
+ format_string = "jpg";
+ else
+ format_string = "png32";
+
+ if (AltosUIVersion.has_google_maps_api_key())
+ return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s&key=%s",
+ center.lat, center.lon, zoom, px_size, px_size, AltosUIMap.maptype_names[maptype], format_string, AltosUIVersion.google_maps_api_key);
+ else
+ return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s",
+ center.lat, center.lon, zoom, px_size, px_size, AltosUIMap.maptype_names[maptype], format_string);
+ }
+ private Font font = null;
+
+ public void set_font(Font font) {
+ this.font = font;
+ }
+
+ int painting_serial;
+ int painted_serial;
+
+ Image image;
+
+ public void paint_graphics(Graphics2D g2d, AltosUIMapTransform t, int serial) {
+ if (serial < painted_serial)
+ return;
+
+ Point2D.Double point_double = t.screen(upper_left);
+ Point point = new Point((int) (point_double.x + 0.5),
+ (int) (point_double.y + 0.5));
+
+ painted_serial = serial;
+
+ if (!g2d.hitClip(point.x, point.y, px_size, px_size))
+ return;
+
+ if (image != null) {
+ g2d.drawImage(image, point.x, point.y, null);
+ image = null;
+ } else {
+ g2d.setColor(Color.GRAY);
+ g2d.fillRect(point.x, point.y, px_size, px_size);
+
+ if (t.has_location()) {
+ String message = null;
+ switch (status) {
+ case AltosUIMapCache.loading:
+ message = "Loading...";
+ break;
+ case AltosUIMapCache.bad_request:
+ message = "Internal error";
+ break;
+ case AltosUIMapCache.failed:
+ message = "Network error, check connection";
+ break;
+ case AltosUIMapCache.forbidden:
+ message = "Too many requests, try later";
+ break;
+ }
+ if (message != null && font != null) {
+ g2d.setFont(font);
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ Rectangle2D bounds = font.getStringBounds(message, g2d.getFontRenderContext());
+
+ float x = px_size / 2.0f;
+ float y = px_size / 2.0f;
+ x = x - (float) bounds.getWidth() / 2.0f;
+ y = y + (float) bounds.getHeight() / 2.0f;
+ g2d.setColor(Color.BLACK);
+ g2d.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
+ }
+ }
+ }
+ }
+
+ public void set_status(int status) {
+ this.status = status;
+ listener.notify_tile(this, status);
+ }
+
+ public void notify_image(Image image) {
+ listener.notify_tile(this, status);
+ }
+
+ public void paint(Graphics g, AltosUIMapTransform t) {
+ Graphics2D g2d = (Graphics2D) g;
+ boolean queued = false;
+
+ Point2D.Double point = t.screen(upper_left);
+
+ if (!g.hitClip((int) (point.x + 0.5), (int) (point.y + 0.5), px_size, px_size))
+ return;
+
+ ++painting_serial;
+
+ if (image == null && t.has_location())
+ image = AltosUIMapCache.get(this, store, px_size, px_size);
+
+ paint_graphics(g2d, t, painting_serial);
+ }
+
+ public int store_status() {
+ return store.status();
+ }
+
+ public void add_store_listener(AltosUIMapStoreListener listener) {
+ store.add_listener(listener);
+ }
+
+ public void remove_store_listener(AltosUIMapStoreListener listener) {
+ store.remove_listener(listener);
+ }
+
+ public AltosUIMapTile(AltosUIMapTileListener listener, AltosUILatLon upper_left, AltosUILatLon center, int zoom, int maptype, int px_size, Font font) {
+ this.listener = listener;
+ this.upper_left = upper_left;
+
+ while (center.lon < -180.0)
+ center.lon += 360.0;
+ while (center.lon > 180.0)
+ center.lon -= 360.0;
+
+ this.center = center;
+ this.zoom = zoom;
+ this.maptype = maptype;
+ this.px_size = px_size;
+ this.font = font;
+ status = AltosUIMapCache.loading;
+ store = AltosUIMapStore.get(map_url(), map_file());
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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;
+
+public interface AltosUIMapTileListener {
+ abstract public void notify_tile(AltosUIMapTile tile, int status);
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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.lang.Math;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMapTransform {
+
+ double scale_x, scale_y;
+
+ double offset_x, offset_y;
+
+ public AltosUILatLon lat_lon (Point2D.Double point) {
+ double lat, lon;
+ double rads;
+
+ lon = point.x/scale_x;
+ rads = 2 * Math.atan(Math.exp(-point.y/scale_y));
+ lat = Math.toDegrees(rads - Math.PI/2);
+
+ return new AltosUILatLon(lat,lon);
+ }
+
+ public Point2D.Double screen_point(Point screen) {
+ return new Point2D.Double(screen.x + offset_x, screen.y + offset_y);
+ }
+
+ public AltosUILatLon screen_lat_lon(Point screen) {
+ return lat_lon(screen_point(screen));
+ }
+
+ public Point2D.Double point(AltosUILatLon lat_lon) {
+ double x, y;
+ double e;
+
+ x = lat_lon.lon * scale_x;
+
+ e = Math.sin(Math.toRadians(lat_lon.lat));
+ e = Math.max(e,-(1-1.0E-15));
+ e = Math.min(e, 1-1.0E-15 );
+
+ y = 0.5*Math.log((1+e)/(1-e))*-scale_y;
+
+ return new Point2D.Double(x, y);
+ }
+
+ public Point2D.Double screen(Point2D.Double point) {
+ return new Point2D.Double(point.x - offset_x, point.y - offset_y);
+ }
+
+ public Point screen(Point point) {
+ return new Point((int) (point.x - offset_x + 0.5),
+ (int) (point.y - offset_y + 0.5));
+ }
+
+ public Rectangle screen(AltosUIMapRectangle map_rect) {
+ Point2D.Double ul = screen(map_rect.ul);
+ Point2D.Double lr = screen(map_rect.lr);
+
+ return new Rectangle((int) ul.x, (int) ul.y, (int) (lr.x - ul.x), (int) (lr.y - ul.y));
+ }
+
+ public Point2D.Double screen(AltosUILatLon lat_lon) {
+ return screen(point(lat_lon));
+ }
+
+ private boolean has_location;
+
+ public boolean has_location() {
+ return has_location;
+ }
+
+ public AltosUIMapTransform(int width, int height, int zoom, AltosUILatLon centre_lat_lon) {
+ scale_x = 256/360.0 * Math.pow(2, zoom);
+ scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom);
+
+ Point2D.Double centre_pt = point(centre_lat_lon);
+
+ has_location = (centre_lat_lon.lat != 0 || centre_lat_lon.lon != 0);
+ offset_x = centre_pt.x - width / 2.0;
+ offset_y = centre_pt.y - height / 2.0;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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 java.awt.image.*;
+import javax.swing.*;
+import java.io.*;
+import java.lang.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.concurrent.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosUIMapView extends Canvas implements MouseMotionListener, MouseListener, MouseWheelListener, ComponentListener, AltosUIMapTileListener, AltosUIMapStoreListener {
+
+ AltosUIMapPath path = new AltosUIMapPath();
+
+ AltosUIMapLine line = new AltosUIMapLine();
+
+ LinkedList<AltosUIMapMark> marks = new LinkedList<AltosUIMapMark>();
+
+ LinkedList<AltosUIMapZoomListener> zoom_listeners = new LinkedList<AltosUIMapZoomListener>();
+
+ boolean have_boost = false;
+ boolean have_landed = false;
+
+ ConcurrentHashMap<Point,AltosUIMapTile> tiles = new ConcurrentHashMap<Point,AltosUIMapTile>();
+
+ static final int default_zoom = 15;
+ static final int min_zoom = 3;
+ static final int max_zoom = 21;
+ static final int px_size = 512;
+
+ int load_radius;
+ AltosUILatLon load_centre = null;
+ AltosUIMapTileListener load_listener;
+
+ int zoom = default_zoom;
+ int maptype = AltosUIMap.maptype_default;
+
+ long user_input_time;
+
+ /* Milliseconds to wait after user action before auto-scrolling
+ */
+ static final long auto_scroll_delay = 20 * 1000;
+
+ AltosUIMapTransform transform;
+ AltosUILatLon centre;
+
+ public void set_font() {
+ line.set_font(AltosUILib.value_font);
+ for (AltosUIMapTile tile : tiles.values())
+ tile.set_font(AltosUILib.value_font);
+ }
+
+ private boolean is_drag_event(MouseEvent e) {
+ return e.getModifiers() == InputEvent.BUTTON1_MASK;
+ }
+
+ Point drag_start;
+
+ private void drag(MouseEvent e) {
+ if (drag_start == null)
+ return;
+
+ int dx = e.getPoint().x - drag_start.x;
+ int dy = e.getPoint().y - drag_start.y;
+
+ AltosUILatLon new_centre = transform.screen_lat_lon(new Point(getWidth() / 2 - dx, getHeight() / 2 - dy));
+ centre (new_centre.lat, new_centre.lon);
+ drag_start = e.getPoint();
+ }
+
+ private void drag_start(MouseEvent e) {
+ drag_start = e.getPoint();
+ }
+
+ private void notice_user_input() {
+ user_input_time = System.currentTimeMillis();
+ }
+
+ private boolean recent_user_input() {
+ return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay;
+ }
+
+ /* MouseMotionListener methods */
+
+ public void mouseDragged(MouseEvent e) {
+ notice_user_input();
+ if (is_drag_event(e))
+ drag(e);
+ else {
+ line.dragged(e, transform);
+ repaint();
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ }
+
+ /* MouseListener methods */
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ public void mouseExited(MouseEvent e) {
+ }
+
+ public void mousePressed(MouseEvent e) {
+ notice_user_input();
+ if (is_drag_event(e))
+ drag_start(e);
+ else
+ line.pressed(e, transform);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ /* MouseWheelListener methods */
+
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ int zoom_change = e.getWheelRotation();
+
+ notice_user_input();
+ AltosUILatLon mouse_lat_lon = transform.screen_lat_lon(e.getPoint());
+ set_zoom(zoom() - zoom_change);
+
+ Point2D.Double new_mouse = transform.screen(mouse_lat_lon);
+
+ int dx = getWidth()/2 - e.getPoint().x;
+ int dy = getHeight()/2 - e.getPoint().y;
+
+ AltosUILatLon new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy));
+
+ centre(new_centre.lat, new_centre.lon);
+ }
+
+ /* ComponentListener methods */
+
+ public void componentHidden(ComponentEvent e) {
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ }
+
+ public void componentResized(ComponentEvent e) {
+ set_transform();
+ }
+
+ public void componentShown(ComponentEvent e) {
+ set_transform();
+ }
+
+ public void repaint(Rectangle r, int pad) {
+ repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2);
+ }
+
+ public void repaint(AltosUIMapRectangle rect, int pad) {
+ repaint (transform.screen(rect), pad);
+ }
+
+ private boolean far_from_centre(AltosUILatLon lat_lon) {
+
+ if (centre == null || transform == null)
+ return true;
+
+ Point2D.Double screen = transform.screen(lat_lon);
+
+ int width = getWidth();
+ int dx = Math.abs ((int) screen.x - width/2);
+
+ if (dx > width / 4)
+ return true;
+
+ int height = getHeight();
+ int dy = Math.abs ((int) screen.y - height/2);
+
+ if (dy > height / 4)
+ return true;
+
+ return false;
+ }
+
+ public void show(AltosState state, 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;
+
+ AltosUIMapRectangle damage = path.add(gps.lat, gps.lon, state.state);
+
+ switch (state.state) {
+ case AltosLib.ao_flight_boost:
+ if (!have_boost) {
+ add_mark(gps.lat, gps.lon, state.state);
+ have_boost = true;
+ }
+ break;
+ case AltosLib.ao_flight_landed:
+ if (!have_landed) {
+ add_mark(gps.lat, gps.lon, state.state);
+ have_landed = true;
+ }
+ break;
+ }
+
+ if (damage != null)
+ repaint(damage, AltosUIMapPath.stroke_width);
+ maybe_centre(gps.lat, gps.lon);
+ }
+
+ private void set_transform() {
+ Rectangle bounds = getBounds();
+
+ transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre);
+ repaint();
+ }
+
+ public boolean set_zoom(int zoom) {
+ if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) {
+ this.zoom = zoom;
+ tiles.clear();
+ set_transform();
+
+ for (AltosUIMapZoomListener listener : zoom_listeners)
+ listener.zoom_changed(this.zoom);
+
+ return true;
+ }
+ return false;
+ }
+
+ public void add_zoom_listener(AltosUIMapZoomListener listener) {
+ if (!zoom_listeners.contains(listener))
+ zoom_listeners.add(listener);
+ }
+
+ public void remove_zoom_listener(AltosUIMapZoomListener listener) {
+ zoom_listeners.remove(listener);
+ }
+
+ public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
+ load_centre = new AltosUILatLon(lat, lon);
+ load_radius = radius;
+ load_listener = listener;
+ centre(lat, lon);
+ make_tiles();
+ for (AltosUIMapTile tile : tiles.values()) {
+ tile.add_store_listener(this);
+ if (tile.store_status() != AltosUIMapStore.loading)
+ listener.notify_tile(tile, tile.store_status());
+ }
+ repaint();
+ }
+
+ public boolean all_fetched() {
+ for (AltosUIMapTile tile : tiles.values()) {
+ if (tile.store_status() == AltosUIMapStore.loading)
+ return false;
+ }
+ return true;
+ }
+
+ public boolean set_maptype(int maptype) {
+ if (maptype != this.maptype) {
+ this.maptype = maptype;
+ tiles.clear();
+ repaint();
+ return true;
+ }
+ return false;
+ }
+
+ public int get_maptype() {
+ return maptype;
+ }
+
+ public int zoom() {
+ return zoom;
+ }
+
+ public void centre(AltosUILatLon lat_lon) {
+ centre = lat_lon;
+ set_transform();
+ }
+
+ public void centre(double lat, double lon) {
+ centre(new AltosUILatLon(lat, lon));
+ }
+
+ public void maybe_centre(double lat, double lon) {
+ AltosUILatLon lat_lon = new AltosUILatLon(lat, lon);
+ if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
+ centre(lat_lon);
+ }
+
+ private VolatileImage create_back_buffer() {
+ return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
+ }
+
+ private Point floor(Point2D.Double point) {
+ return new Point ((int) Math.floor(point.x / px_size) * px_size,
+ (int) Math.floor(point.y / px_size) * px_size);
+ }
+
+ private Point ceil(Point2D.Double point) {
+ return new Point ((int) Math.ceil(point.x / px_size) * px_size,
+ (int) Math.ceil(point.y / px_size) * px_size);
+ }
+
+ private void make_tiles() {
+ Point upper_left;
+ Point lower_right;
+
+ if (load_centre != null) {
+ Point centre = floor(transform.point(load_centre));
+
+ upper_left = new Point(centre.x - load_radius * px_size,
+ centre.y - load_radius * px_size);
+ lower_right = new Point(centre.x + load_radius * px_size,
+ centre.y + load_radius * px_size);
+ } else {
+ upper_left = floor(transform.screen_point(new Point(0, 0)));
+ lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight())));
+ }
+ LinkedList<Point> to_remove = new LinkedList<Point>();
+
+ for (Point point : tiles.keySet()) {
+ if (point.x < upper_left.x || lower_right.x < point.x ||
+ point.y < upper_left.y || lower_right.y < point.y) {
+ to_remove.add(point);
+ }
+ }
+
+ for (Point point : to_remove)
+ tiles.remove(point);
+
+ AltosUIMapCache.set_cache_size(((lower_right.y - upper_left.y) / px_size + 1) * ((lower_right.x - upper_left.x) / px_size + 1));
+ for (int y = upper_left.y; y <= lower_right.y; y += px_size) {
+ for (int x = upper_left.x; x <= lower_right.x; x += px_size) {
+ Point point = new Point(x, y);
+
+ if (!tiles.containsKey(point)) {
+ AltosUILatLon ul = transform.lat_lon(new Point2D.Double(x, y));
+ AltosUILatLon center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2));
+ AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype,
+ px_size, AltosUILib.value_font);
+ tiles.put(point, tile);
+ }
+ }
+ }
+ }
+
+ /* AltosUIMapTileListener methods */
+ public void notify_tile(AltosUIMapTile tile, int status) {
+ for (Point point : tiles.keySet()) {
+ if (tile == tiles.get(point)) {
+ Point screen = transform.screen(point);
+ repaint(screen.x, screen.y, px_size, px_size);
+ }
+ }
+ }
+
+ /* AltosUIMapStoreListener methods */
+ public void notify_store(AltosUIMapStore store, int status) {
+ if (load_listener != null) {
+ for (AltosUIMapTile tile : tiles.values())
+ if (store.equals(tile.store))
+ load_listener.notify_tile(tile, status);
+ }
+ }
+
+ private void do_paint(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g;
+
+ make_tiles();
+
+ for (AltosUIMapTile tile : tiles.values())
+ tile.paint(g2d, transform);
+
+ synchronized(marks) {
+ for (AltosUIMapMark mark : marks)
+ mark.paint(g2d, transform);
+ }
+
+ path.paint(g2d, transform);
+
+ line.paint(g2d, transform);
+ }
+
+ public void paint(Graphics g) {
+
+ VolatileImage back_buffer = create_back_buffer();
+ do {
+ GraphicsConfiguration gc = getGraphicsConfiguration();
+ int code = back_buffer.validate(gc);
+ if (code == VolatileImage.IMAGE_INCOMPATIBLE)
+ back_buffer = create_back_buffer();
+
+ Graphics g_back = back_buffer.getGraphics();
+ g_back.setClip(g.getClip());
+ do_paint(g_back);
+ g_back.dispose();
+
+ g.drawImage(back_buffer, 0, 0, this);
+ } while (back_buffer.contentsLost());
+ back_buffer.flush();
+ }
+
+ public void update(Graphics g) {
+ paint(g);
+ }
+
+ public void add_mark(double lat, double lon, int state) {
+ synchronized(marks) {
+ marks.add(new AltosUIMapMark(lat, lon, state));
+ }
+ repaint();
+ }
+
+ public void clear_marks() {
+ synchronized(marks) {
+ marks.clear();
+ }
+ }
+
+ public AltosUIMapView() {
+ centre(0, 0);
+
+ addComponentListener(this);
+ addMouseMotionListener(this);
+ addMouseListener(this);
+ addMouseWheelListener(this);
+ set_font();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2014 Keith Packard <keithp@keithp.com>
+ *
+ * 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;
+
+public interface AltosUIMapZoomListener {
+ abstract public void zoom_changed(int zoom);
+}
AltosUISeries.java \
AltosUIVersion.java \
AltosUSBDevice.java \
- AltosSiteMap.java \
- AltosSiteMapCache.java \
- AltosSiteMapPreload.java \
- AltosSiteMapTile.java \
- AltosSiteMapImage.java \
AltosVoice.java \
AltosDisplayThread.java \
AltosDeviceUIDialog.java \
AltosBTDevice.java \
AltosBTDeviceIterator.java \
AltosBTManage.java \
- AltosBTKnown.java
-
+ AltosBTKnown.java \
+ AltosUIMap.java \
+ AltosUIMapView.java \
+ AltosUIMapLine.java \
+ AltosUIMapMark.java \
+ AltosUIMapPath.java \
+ AltosUIMapTile.java \
+ AltosUIMapCache.java \
+ AltosUIMapImage.java \
+ AltosUIMapTransform.java \
+ AltosUIMapRectangle.java \
+ AltosUIMapZoomListener.java \
+ AltosUIMapTileListener.java \
+ AltosUIMapPreload.java \
+ AltosUIMapStore.java \
+ AltosUIMapStoreListener.java \
+ AltosUILatLon.java
JAR=altosuilib_$(ALTOSUILIB_VERSION).jar
JTabbedPane pane;
- AltosSiteMap sitemap;
+ AltosUIMap map;
TeleGPSInfo gps_info;
AltosInfoTable info_table;
}
void load_maps() {
- new AltosSiteMapPreload(this);
+ new AltosUIMapPreload(this);
}
void disconnect() {
c.gridwidth = 2;
bag.add(pane, c);
- sitemap = new AltosSiteMap();
- pane.add("Site Map", sitemap);
- displays.add(sitemap);
+ map = new AltosUIMap();
+ pane.add("Map", map);
+ displays.add(map);
gps_info = new TeleGPSInfo();
pane.add("Info", gps_info);
} else {
double lat = Double.parseDouble(args[i+1]);
double lon = Double.parseDouble(args[i+2]);
- AltosSiteMap.prefetchMaps(lat, lon);
+ AltosUIMap.prefetch_maps(lat, lon);
i += 2;
}
} else if (args[i].equals("--replay"))
JTabbedPane pane;
AltosGraph graph;
AltosUIEnable enable;
- AltosSiteMap map;
+ AltosUIMap map;
AltosState state;
AltosFlightStats stats;
AltosGraphDataSet graphDataSet;
graph = new AltosGraph(enable, stats, graphDataSet);
statsTable = new AltosFlightStatsTable(stats);
- map = new AltosSiteMap();
+ map = new AltosUIMap();
pane.add("Flight Graph", graph.panel);
pane.add("Configure Graph", enable);