altosuilib: Add multiple zoom levels and content types to map
authorKeith Packard <keithp@keithp.com>
Tue, 27 May 2014 17:58:53 +0000 (10:58 -0700)
committerKeith Packard <keithp@keithp.com>
Tue, 27 May 2014 17:58:53 +0000 (10:58 -0700)
Also changes the file format for hybrid, satellite and terrain maps to
jpg to save disk space.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosuilib/AltosSiteMap.java
altosuilib/AltosSiteMapCache.java
altosuilib/AltosSiteMapPreload.java
altosuilib/AltosSiteMapTile.java

index 1cfbc8b5f3be25384d8ddd74df75d60a75ceede4..f4143fe2433d84fbdcfeb072daee35f19772381e 100644 (file)
 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.Point2D;
+import java.util.*;
 import java.util.concurrent.*;
 import org.altusmetrum.altoslib_4.*;
 
-public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
+class MapPoint {
+       double  lat, lon;
+       int     state;
+
+       public MapPoint(double lat, double lon, int state) {
+               this.lat = lat;
+               this.lon = lon;
+               this.state = state;
+       }
+
+       public boolean equals(MapPoint other) {
+               if (other == null)
+                       return false;
+               if (other.lat != lat)
+                       return false;
+               if (other.lon != lon)
+                       return false;
+               if (other.state != state)
+                       return false;
+               return true;
+       }
+}
+
+public class AltosSiteMap extends JComponent 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
@@ -35,6 +60,29 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
 
        static final int MAX_TILE_DELTA = 100;
 
+       static final int maptype_hybrid = 0;
+       static final int maptype_roadmap = 1;
+       static final int maptype_satellite = 2;
+       static final int maptype_terrain = 3;
+
+       int maptype = maptype_hybrid;
+
+       static final String[] maptype_names = {
+               "hybrid",
+               "roadmap",
+               "satellite",
+               "terrain"
+       };
+
+       static final String[] maptype_labels = {
+               "Hybrid",
+               "Roadmap",
+               "Satellite",
+               "Terrain"
+       };
+
+       LinkedList<MapPoint> points = new LinkedList<MapPoint>();
+
        private static Point2D.Double translatePoint(Point2D.Double p,
                        Point2D.Double d)
        {
@@ -89,7 +137,12 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                return new LatLng(lat,lng);
        }
 
-       int zoom;
+       static final int default_zoom = 15;
+       static final int min_zoom = 3;
+       static final int max_zoom = 21;
+
+       int zoom = default_zoom;
+
        double scale_x, scale_y;
 
        int radius;     /* half width/height of tiles to load */
@@ -112,7 +165,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
 
        private Point2D.Double tileCoordOffset(Point p) {
                return new Point2D.Double(centre.x - p.x*px_size,
-                                         centre.y - p.y * px_size);
+                                         centre.y - p.y*px_size);
        }
 
        private Point tileOffset(Point2D.Double p) {
@@ -121,19 +174,11 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
        }
 
        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);
+               Point2D.Double locn = pt(0,0), north_step;
+
+               scale_x = 256/360.0 * Math.pow(2, zoom);
+               scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom);
+               locn = pt(lat, lng);
                locn.x = -px_size * Math.floor(locn.x/px_size);
                locn.y = -px_size * Math.floor(locn.y/px_size);
                return locn;
@@ -144,23 +189,38 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
        }
 
        public void set_font() {
-               // nothing
+               for (AltosSiteMapTile tile : mapTiles.values())
+                       tile.set_font(AltosUILib.value_font);
        }
 
-       private void loadMap(final AltosSiteMapTile tile,
-                            final File pngfile, String pngurl)
+       static final int load_mode_cached = 1;
+       static final int load_mode_uncached = 2;
+
+       private boolean load_map(final AltosSiteMapTile tile,
+                                final File pngfile, String pngurl,
+                                int load_mode)
        {
-               boolean loaded = AltosSiteMapCache.fetchMap(pngfile, pngurl);
-               if (loaded) {
+               boolean has_map = AltosSiteMapCache.has_map(pngfile, pngurl);
+               if ((load_mode & load_mode_uncached) == 0 && !has_map)
+                       return false;
+               if ((load_mode & load_mode_cached) == 0 && has_map)
+                       return false;
+
+               tile.set_status(AltosSiteMapCache.loading);
+               int status = AltosSiteMapCache.fetch_map(pngfile, pngurl);
+               if (status == AltosSiteMapCache.success) {
                        SwingUtilities.invokeLater(new Runnable() {
                                        public void run() {
-                                               tile.loadMap(pngfile);
+                                               tile.load_map(pngfile);
                                        }
                                });
                } else {
-                       System.out.printf("# Failed to fetch file %s\n", pngfile);
+                       tile.set_status(status);
+                       System.out.printf("# Failed to fetch file %s (status %d)\n", pngfile, status);
                        System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl);
+                       System.out.printf(" sleep 1\n");
                }
+               return true;
        }
 
 
@@ -177,11 +237,11 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                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.pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom, maptype_hybrid);
+               prefetch.pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom, maptype_hybrid);
+               if (AltosSiteMapCache.has_map(prefetch.pngfile, prefetch.pngurl)) {
                        prefetch.result = 1;
-               } else if (AltosSiteMapCache.fetchMap(prefetch.pngfile, prefetch.pngurl)) {
+               } else if (AltosSiteMapCache.fetch_map(prefetch.pngfile, prefetch.pngurl) == AltosSiteMapCache.success) {
                        prefetch.result = 0;
                } else {
                        prefetch.result = -1;
@@ -218,33 +278,45 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                }
        }
 
-       public String initMap(Point offset) {
+       public File init_map(Point offset, int load_mode) {
                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();
+               File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom, maptype);
+               String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom, maptype);
+               load_map(tile, pngfile, pngurl, load_mode);
+               return pngfile;
        }
 
        public void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) {
                Thread thread = new Thread() {
                                public void run() {
-                                       initMap(offset);
+                                       init_map(offset, load_mode_cached|load_mode_uncached);
                                        finishTileLater(tile, offset);
                                }
                        };
                thread.start();
        }
 
+       double  lat, lon;
+       boolean base_location_set = false;
+
+       public void clear_base_location() {
+               base_location_set = false;
+               circle_set = false;
+               points = new LinkedList<MapPoint>();
+               for (AltosSiteMapTile tile : mapTiles.values())
+                       tile.clearMap();
+       }
+
        public void setBaseLocation(double lat, double lng) {
-               for (Point k : mapTiles.keySet()) {
-                       AltosSiteMapTile tile = mapTiles.get(k);
+               this.lat = lat;
+               this.lon = lng;
+               base_location_set = true;
+               for (AltosSiteMapTile tile : mapTiles.values())
                        tile.clearMap();
-               }
 
                centre = getBaseLocation(lat, lng);
                scrollRocketToVisible(pt(lat,lng));
@@ -256,28 +328,42 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                Thread thread = new Thread() {
                                public void run() {
                                        for (Point k : mapTiles.keySet())
-                                               initMap(k);
+                                               init_map(k, load_mode_cached);
+                                       for (Point k : mapTiles.keySet())
+                                               init_map(k, load_mode_uncached);
                                }
                        };
                thread.start();
        }
 
-       private static File MapFile(double lat, double lng, int zoom) {
+       private static File MapFile(double lat, double lng, int zoom, int maptype) {
                char chlat = lat < 0 ? 'S' : 'N';
                char chlng = lng < 0 ? 'W' : 'E';
                if (lat < 0) lat = -lat;
                if (lng < 0) lng = -lng;
+               String maptype_string = String.format("%s-", maptype_names[maptype]);
+               String format_string;
+               if (maptype == maptype_hybrid || maptype == maptype_satellite || maptype == maptype_terrain)
+                       format_string = "jpg";
+               else
+                       format_string = "png";
                return new File(AltosUIPreferences.mapdir(),
-                               String.format("map-%c%.6f,%c%.6f-%d.png",
-                                             chlat, lat, chlng, lng, zoom));
+                               String.format("map-%c%.6f,%c%.6f-%s%d.%s",
+                                             chlat, lat, chlng, lng, maptype_string, zoom, format_string));
        }
 
-       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);
+       private static String MapURL(double lat, double lng, int zoom, int maptype) {
+               String format_string;
+               if (maptype == maptype_hybrid || maptype == maptype_satellite || maptype == maptype_terrain)
+                       format_string = "jpg";
+               else
+                       format_string = "png32";
+               return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=%s&format=%s",
+                                    lat, lng, zoom, px_size, px_size, maptype_names[maptype], format_string);
        }
 
        boolean initialised = false;
-       Point2D.Double last_pt = null;
+       MapPoint last_point = null;
        int last_state = -1;
 
        public void show(double lat, double lon) {
@@ -286,42 +372,44 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
 //             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;
+       JLabel  zoom_label;
 
-               if (!gps.locked && gps.nsat < 4)
-                       return;
+       public void set_zoom_label() {
+               zoom_label.setText(String.format("- %d -", zoom - default_zoom));
+       }
 
-               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;
-                       }
+       public void set_zoom(int zoom) {
+               if (min_zoom <= zoom && zoom <= max_zoom) {
+                       this.zoom = zoom;
+                       if (base_location_set)
+                               initMaps(lat, lon);
+                       redraw();
+                       set_zoom_label();
                }
+       }
 
-               final Point2D.Double pt = pt(gps.lat, gps.lon);
-               if (last_pt == pt && last_state == state.state)
-                       return;
+       public int get_zoom() {
+               return zoom;
+       }
 
-               if (last_pt == null) {
-                       last_pt = pt;
+       public void draw(MapPoint last_point, MapPoint point) {
+               boolean force_ensure = false;
+               if (last_point == null) {
+                       force_ensure = true;
+                       last_point = point;
                }
+
+               Point2D.Double pt = pt(point.lat, point.lon);
+               Point2D.Double last_pt = pt(last_point.lat, last_point.lon);
+
                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);
+                       tile.show(point.state, lref, ref);
                        if (0 <= ref.x && ref.x < px_size)
                                if (0 <= ref.y && ref.y < px_size)
                                        in_any = true;
@@ -334,18 +422,60 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                        lref = translatePoint(last_pt, tileCoordOffset(offset));
 
                        AltosSiteMapTile tile = createTile(offset);
-                       tile.show(state, listener_state, lref, ref);
+                       tile.show(point.state, lref, ref);
                        initAndFinishMapAsync(tile, offset);
                }
 
                scrollRocketToVisible(pt);
 
-               if (offset != tileOffset(last_pt)) {
+               if (force_ensure || offset != tileOffset(last_pt)) {
                        ensureTilesAround(offset);
                }
+       }
+
+       public void redraw() {
+               MapPoint        last_point = null;
+
+               for (MapPoint point : points) {
+                       draw(last_point, point);
+                       last_point = point;
+               }
+               if (circle_set)
+                       draw_circle(circle_lat, circle_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;
+                       }
+               }
 
-               last_pt = pt;
-               last_state = state.state;
+               MapPoint        point = new MapPoint(gps.lat, gps.lon, state.state);
+
+               if (point.equals(last_point))
+                       return;
+
+               points.add(point);
+
+               draw(last_point, point);
+
+               last_point = point;
        }
 
        public void centre(Point2D.Double pt) {
@@ -355,6 +485,8 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                int dy = (int)copt.y - r.height/2 - r.y;
                r.x += dx;
                r.y += dy;
+               r.width = 1;
+               r.height = 1;
                comp.scrollRectToVisible(r);
        }
 
@@ -364,8 +496,15 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                centre(pt(state.gps.lat, state.gps.lon));
        }
 
+       private double circle_lat, circle_lon;
+       private boolean circle_set = false;
+
        public void draw_circle(double lat, double lon) {
-               final Point2D.Double pt = pt(lat, lon);
+               circle_lat = lat;
+               circle_lon = lon;
+               circle_set = true;
+
+               Point2D.Double pt = pt(lat, lon);
 
                for (Point offset : mapTiles.keySet()) {
                        AltosSiteMapTile tile = mapTiles.get(offset);
@@ -376,6 +515,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
 
        private AltosSiteMapTile createTile(Point offset) {
                AltosSiteMapTile tile = new AltosSiteMapTile(px_size);
+               tile.set_font(AltosUILib.value_font);
                mapTiles.put(offset, tile);
                return tile;
        }
@@ -405,6 +545,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
        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) {
@@ -446,9 +587,9 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                layout.setConstraints(tile, c);
 
                comp.add(tile);
-               if (review) {
-                       comp.scrollRectToVisible(r);
-               }
+//             if (review) {
+//                     comp.scrollRectToVisible(r);
+//             }
        }
 
        private AltosSiteMap(boolean knowWhatYouAreDoing) {
@@ -460,6 +601,8 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
        JComponent comp = new JComponent() { };
        private GridBagLayout layout = new GridBagLayout();
 
+       JScrollPane     pane = new JScrollPane();
+
        public AltosSiteMap(int in_radius) {
                radius = in_radius;
 
@@ -474,8 +617,108 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
                                addTileAt(t, offset);
                        }
                }
-               setViewportView(comp);
-               setPreferredSize(new Dimension(500,500));
+               pane.setViewportView(comp);
+               pane.setPreferredSize(new Dimension(500,500));
+               pane.setVisible(true);
+               pane.setEnabled(true);
+
+               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(pane, 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(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);
+
+               final JComboBox<String> 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) {
+                                       maptype = maptype_combo.getSelectedIndex();
+                                       if (base_location_set)
+                                               initMaps(lat, lon);
+                                       redraw();
+                               }
+                       });
+
+               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);
        }
 
        public AltosSiteMap() {
index cf93016a574ab5d233b0578abbaca034b990206a..acc84ab081a115f7ed47e8eefe4db8222c074451 100644 (file)
@@ -22,9 +22,7 @@ import javax.imageio.ImageIO;
 import java.awt.image.*;
 import java.awt.*;
 import java.io.*;
-import java.net.URL;
-import java.net.URLConnection;
-
+import java.net.*;
 
 class AltosCacheImage {
        Component       component;
@@ -36,9 +34,12 @@ class AltosCacheImage {
 
        public void load_image() throws IOException {
                BufferedImage   bimg = ImageIO.read(file);
+               if (bimg == null)
+                       throw new IOException("Can't load image file");
                Graphics2D      g = image.createGraphics();
                g.drawImage(bimg, 0, 0, null);
                bimg.flush();
+               bimg = null;
        }
 
        public Image validate() {
@@ -79,14 +80,31 @@ class AltosCacheImage {
        }
 }
 
-public class AltosSiteMapCache extends JLabel {
+public class AltosSiteMapCache {
        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) {
+       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;
+
+       public static synchronized boolean has_map(File file, String url) {
+               return file.exists();
+       }
+
+       static long     forbidden_time;
+       static boolean  forbidden_set = false;
+       static final long       forbidden_interval = 60l * 1000l * 1000l * 1000l;
+
+       public static synchronized int fetch_map(File file, String url) {
                if (file.exists())
-                       return true;
+                       return success;
+
+               if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval)
+                       return forbidden;
 
                URL u;
                long startTime = System.nanoTime();
@@ -94,13 +112,26 @@ public class AltosSiteMapCache extends JLabel {
                try {
                        u = new URL(url);
                } catch (java.net.MalformedURLException e) {
-                       return false;
+                       return bad_request;
                }
 
                byte[] data;
+               URLConnection uc = null;
                try {
-                       URLConnection uc = u.openConnection();
+                       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:
+                                       forbidden_time = System.nanoTime();
+                                       forbidden_set = true;
+                                       return forbidden;
+                               }
+                       }
                        InputStream in = new BufferedInputStream(uc.getInputStream());
                        int bytesRead = 0;
                        int offset = 0;
@@ -113,11 +144,11 @@ public class AltosSiteMapCache extends JLabel {
                        }
                        in.close();
 
-                       if (offset != contentLength) {
-                               return false;
-                       }
+                       if (offset != contentLength)
+                               return failed;
+
                } catch (IOException e) {
-                       return false;
+                       return failed;
                }
 
                try {
@@ -126,12 +157,11 @@ public class AltosSiteMapCache extends JLabel {
                        out.flush();
                        out.close();
                } catch (FileNotFoundException e) {
-                       return false;
+                       return bad_request;
                } catch (IOException e) {
-                       if (file.exists()) {
+                       if (file.exists())
                                file.delete();
-                       }
-                       return false;
+                       return bad_request;
                }
 
                long duration_ms = (System.nanoTime() - startTime) / 1000000;
@@ -143,21 +173,16 @@ public class AltosSiteMapCache extends JLabel {
                        }
                }
 
-               return true;
+               return success;
        }
 
-       static int                      cache_size = 9;
+       static final int                cache_size = 12;
 
        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) {
+       public static synchronized Image get_image(Component component, File file, int width, int height) {
                int             oldest = -1;
                long            age = used;
                AltosCacheImage image;
index baa7fc378c572a4c15660b5faef4c45eb3b3dce8..107b09f828a14707eff6caf03b3d650e09f84a30 100644 (file)
@@ -265,9 +265,10 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
                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));
+                                       File    pngfile;
+                                       pngfile = map.init_map(new Point(x,y),
+                                                              AltosSiteMap.load_mode_cached|AltosSiteMap.load_mode_uncached);
+                                       SwingUtilities.invokeLater(new updatePbar(x, y, pngfile.toString()));
                                }
                        }
                }
@@ -305,6 +306,7 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
                                try {
                                        final double    latitude = lat.get_value();
                                        final double    longitude = lon.get_value();
+                                       map.clear_base_location();
                                        map.setBaseLocation(latitude,longitude);
                                        map.draw_circle(latitude,longitude);
                                        loading = true;
index 1046d6bd0191af519a608f2a62a5e752a676b0c7..9610b248c994e5856dc5cb446e8fb41b6913e49a 100644 (file)
@@ -21,10 +21,10 @@ 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.awt.geom.*;
 import java.io.*;
 import java.util.*;
+import java.awt.RenderingHints.*;
 import org.altusmetrum.altoslib_4.*;
 
 class AltosPoint {
@@ -40,15 +40,38 @@ class AltosPoint {
 public class AltosSiteMapTile extends JComponent {
        int px_size;
        File file;
+       int status;
 
        Point2D.Double  boost;
        Point2D.Double  landed;
 
        LinkedList<AltosPoint>  points;
 
-       public void loadMap(File pngFile) {
+       public synchronized void queue_repaint() {
+               SwingUtilities.invokeLater(new Runnable() {
+                               public void run() {
+                                       repaint();
+                               }
+                       });
+       }
+
+       public void load_map(File pngFile) {
                file = pngFile;
-               repaint();
+               queue_repaint();
+       }
+
+       private Font    font = null;
+
+       public void set_font(Font font) {
+               this.font = font;
+               this.status = AltosSiteMapCache.success;
+               queue_repaint();
+       }
+
+       public void set_status(int status) {
+               file = null;
+               this.status = status;
+               queue_repaint();
        }
 
        public void clearMap() {
@@ -56,6 +79,8 @@ public class AltosSiteMapTile extends JComponent {
                landed = null;
                points = null;
                file = null;
+               status = AltosSiteMapCache.success;
+               queue_repaint();
        }
 
        static Color stateColors[] = {
@@ -78,7 +103,7 @@ public class AltosSiteMapTile extends JComponent {
 
        public void set_boost(Point2D.Double boost) {
                this.boost = boost;
-               repaint();
+               queue_repaint();
        }
 
        public void paint(Graphics g) {
@@ -94,6 +119,34 @@ public class AltosSiteMapTile extends JComponent {
                } else {
                        g2d.setColor(Color.GRAY);
                        g2d.fillRect(0, 0, getWidth(), getHeight());
+                       String  message = null;
+                       switch (status) {
+                       case AltosSiteMapCache.loading:
+                               message = "Loading...";
+                               break;
+                       case AltosSiteMapCache.bad_request:
+                               message = "Internal error";
+                               break;
+                       case AltosSiteMapCache.failed:
+                               message = "Network error, check connection";
+                               break;
+                       case AltosSiteMapCache.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;
+                               bounds = font.getStringBounds(message, g2d.getFontRenderContext());
+
+                               float x = getWidth() / 2.0f;
+                               float y = getHeight() / 2.0f;
+                               x = x - (float) bounds.getWidth() / 2.0f;
+                               y = y - (float) bounds.getHeight() / 2.0f;
+                               g2d.setColor(Color.BLACK);
+                               g2d.drawString(message, x, y);
+                       }
                }
 
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
@@ -120,19 +173,18 @@ public class AltosSiteMapTile extends JComponent {
                }
        }
 
-       public synchronized void show(AltosState state, AltosListenerState listener_state,
-                                     Point2D.Double last_pt, Point2D.Double pt)
+       public synchronized void show(int state, Point2D.Double last_pt, Point2D.Double pt)
        {
                if (points == null)
                        points = new LinkedList<AltosPoint>();
 
-               points.add(new AltosPoint(pt, state.state));
+               points.add(new AltosPoint(pt, state));
 
-               if (state.state == AltosLib.ao_flight_boost && boost == null)
+               if (state == AltosLib.ao_flight_boost && boost == null)
                        boost = pt;
-               if (state.state == AltosLib.ao_flight_landed && landed == null)
+               if (state == AltosLib.ao_flight_landed && landed == null)
                        landed = pt;
-               repaint();
+               queue_repaint();
        }
 
        public AltosSiteMapTile(int in_px_size) {