altosuilib: Update map preloading UI to include zoom and maptypes
authorKeith Packard <keithp@keithp.com>
Wed, 28 May 2014 17:16:38 +0000 (10:16 -0700)
committerKeith Packard <keithp@keithp.com>
Wed, 28 May 2014 17:16:38 +0000 (10:16 -0700)
This lets you specify precisely which maps to load.

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

index d9ea564ce1c6dde2d34e510009f8920cddba6856..461886c7f0e3f2e53f2160d19dd73da87d58153e 100644 (file)
@@ -74,7 +74,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                "terrain"
        };
 
-       static final String[] maptype_labels = {
+       public static final String[] maptype_labels = {
                "Hybrid",
                "Roadmap",
                "Satellite",
@@ -240,7 +240,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                String  pngurl;
        }
 
-       public AltosSiteMapPrefetch prefetchMap(int x, int y) {
+       private AltosSiteMapPrefetch prefetchMap(int x, int y) {
                AltosSiteMapPrefetch    prefetch = new AltosSiteMapPrefetch();
                LatLng map_latlng = latlng(
                        -centre.x + x*px_size + px_size/2,
@@ -257,28 +257,34 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                return prefetch;
        }
 
-       public static void prefetchMaps(double lat, double lng) {
-               int w = AltosSiteMapPreload.width;
-               int h = AltosSiteMapPreload.height;
+       public static void prefetchMaps(double lat, double lng, int radius, int maptypes, int min_zoom, int max_zoom) {
                AltosSiteMap asm = new AltosSiteMap(true);
-               asm.centre = asm.getBaseLocation(lat, lng);
-
-               int dx = -w/2, dy = -h/2;
-               for (int y = dy; y < h+dy; y++) {
-                       for (int x = dx; x < w+dx; x++) {
-                               AltosSiteMapPrefetch prefetch = asm.prefetchMap(x, y);
-                               switch (prefetch.result) {
-                               case 1:
-                                       System.out.printf("Already have %s\n", prefetch.pngfile);
-                                       break;
-                               case 0:
-                                       System.out.printf("Fetched map %s\n", prefetch.pngfile);
-                                       break;
-                               case -1:
-                                       System.out.printf("# Failed to fetch file %s\n", prefetch.pngfile);
-                                       System.out.printf(" wget -O '%s' ''\n",
-                                                         prefetch.pngfile, prefetch.pngurl);
-                                       break;
+
+               for (int z = min_zoom; z <= max_zoom; z++) {
+                       asm.zoom = z;
+                       asm.set_radius(radius);
+                       asm.centre = asm.getBaseLocation(lat, lng);
+                       for (int t = maptype_hybrid; t <= maptype_terrain; t++) {
+                               if ((maptypes & (1 << t)) !=0) {
+                                       asm.maptype = t;
+                                       for (int y = -radius; y <= radius; y++) {
+                                               for (int x = -radius; x <= radius; x++) {
+                                                       AltosSiteMapPrefetch prefetch = asm.prefetchMap(x, y);
+                                                       switch (prefetch.result) {
+                                                       case 1:
+                                                               System.out.printf("Already have %s\n", prefetch.pngfile);
+                                                               break;
+                                                       case 0:
+                                                               System.out.printf("Fetched map %s\n", prefetch.pngfile);
+                                                               break;
+                                                       case -1:
+                                                               System.out.printf("# Failed to fetch file %s\n", prefetch.pngfile);
+                                                               System.out.printf(" wget -O '%s' ''\n",
+                                                                                 prefetch.pngfile, prefetch.pngurl);
+                                                               break;
+                                                       }
+                                               }
+                                       }
                                }
                        }
                }
@@ -296,7 +302,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                return pngfile;
        }
 
-       public void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) {
+       private void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) {
                Thread thread = new Thread() {
                                public void run() {
                                        init_map(offset, load_mode_cached|load_mode_uncached);
@@ -318,6 +324,10 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                circle_set = false;
                points = new LinkedList<MapPoint>();
                line_start = line_end = null;
+               for (AltosSiteMapTile tile : mapTiles.values()) {
+                       tile.clearMap();
+                       tile.set_status(AltosSiteMapCache.loading);
+               }
        }
 
        public void setBaseLocation(double lat, double lng) {
@@ -382,7 +392,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
        MapPoint last_point = null;
        int last_state = -1;
 
-       public void show(double lat, double lon) {
+       private void show(double lat, double lon) {
                System.out.printf ("show %g %g\n", lat, lon);
                return;
 //             initMaps(lat, lon);
@@ -391,15 +401,17 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
 
        JLabel  zoom_label;
 
-       public void set_zoom_label() {
+       private void set_zoom_label() {
                zoom_label.setText(String.format("Zoom %d", zoom - default_zoom));
        }
 
        public void set_zoom(int zoom) {
                if (min_zoom <= zoom && zoom <= max_zoom) {
                        this.zoom = zoom;
-                       if (base_location_set)
+                       if (base_location_set) {
+                               set_tiles();
                                initMaps(lat, lon);
+                       }
                        redraw();
                        set_zoom_label();
                }
@@ -409,7 +421,15 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                return zoom;
        }
 
-       public void draw(MapPoint last_point, MapPoint point) {
+       public void set_maptype(int type) {
+               maptype = type;
+               maptype_combo.setSelectedIndex(type);
+               if (base_location_set)
+                       initMaps(lat, lon);
+               redraw();
+       }
+
+       private void draw(MapPoint last_point, MapPoint point) {
                boolean force_ensure = false;
                if (last_point == null) {
                        force_ensure = true;
@@ -449,7 +469,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                }
        }
 
-       public void redraw() {
+       private void redraw() {
                MapPoint        last_point = null;
 
                for (MapPoint point : points) {
@@ -496,7 +516,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                last_point = point;
        }
 
-       public void centre(Point2D.Double pt) {
+       private void centre(Point2D.Double pt) {
                Rectangle r = comp.getVisibleRect();
                Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft));
                int dx = (int)copt.x - r.width/2 - r.x;
@@ -508,7 +528,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                comp.scrollRectToVisible(r);
        }
 
-       public void centre(AltosState state) {
+       private void centre(AltosState state) {
                if (!state.gps.locked && state.gps.nsat < 4)
                        return;
                centre(pt(state.gps.lat, state.gps.lon));
@@ -550,6 +570,32 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                }
        }
 
+       private void set_tiles() {
+               for (int x = -radius; x <= radius; x++) {
+                       for (int y = -radius; y <= radius; y++) {
+                               Point offset = new Point(x, y);
+                               if (mapTiles.containsKey(offset))
+                                       continue;
+                               AltosSiteMapTile t = createTile(offset);
+                               addTileAt(t, offset);
+                       }
+               }
+               for (Point offset : mapTiles.keySet()) {
+                       if (offset.x < -radius || offset.x > radius ||
+                           offset.y < -radius || offset.y > radius)
+                       {
+                               removeTileAt(offset);
+                       }
+               }
+       }
+
+       public void set_radius(int radius) {
+               if (radius != this.radius) {
+                       this.radius = radius;
+                       set_tiles();
+               }
+       }
+
        private Point topleft = new Point(0,0);
        private void scrollRocketToVisible(Point2D.Double pt) {
                Rectangle r = comp.getVisibleRect();
@@ -573,18 +619,11 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                        return;
                }
 
-               boolean review = false;
-               Rectangle r = comp.getVisibleRect();
-               if (offset.x < topleft.x) {
-                       r.x += (topleft.x - offset.x) * px_size;
+               if (offset.x < topleft.x)
                        topleft.x = offset.x;
-                       review = true;
-               }
-               if (offset.y < topleft.y) {
-                       r.y += (topleft.y - offset.y) * px_size;
+               if (offset.y < topleft.y)
                        topleft.y = offset.y;
-                       review = true;
-               }
+
                GridBagConstraints c = new GridBagConstraints();
                c.anchor = GridBagConstraints.CENTER;
                c.fill = GridBagConstraints.BOTH;
@@ -596,9 +635,6 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                layout.setConstraints(tile, c);
 
                comp.add(tile);
-//             if (review) {
-//                     comp.scrollRectToVisible(r);
-//             }
        }
 
        private AltosSiteMap(boolean knowWhatYouAreDoing) {
@@ -607,6 +643,21 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                }
        }
 
+       private void removeTileAt(Point offset) {
+               AltosSiteMapTile        tile = mapTiles.get(offset);
+
+               mapTiles.remove(offset);
+               comp.remove(tile);
+
+               topleft = new Point(MAX_TILE_DELTA, MAX_TILE_DELTA);
+               for (Point o : mapTiles.keySet()) {
+                       if (o.x < topleft.x)
+                               topleft.x = o.x;
+                       if (o.y < topleft.y)
+                               topleft.y = o.y;
+               }
+       }
+
        JComponent comp;
 
        private GridBagLayout layout = new GridBagLayout();
@@ -634,11 +685,13 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
        }
 
        static void debug_component(Component who, String where) {
-//             Rectangle       r = who.getBounds();
-//             int             x = r.x / px_size;
-//             int             y = r.y / px_size;
-//
-//             System.out.printf ("%3d, %3d: %s\n", x, y, where);
+/*
+               Rectangle       r = who.getBounds();
+               int             x = r.x / px_size;
+               int             y = r.y / px_size;
+
+               System.out.printf ("%3d, %3d: %s\n", x, y, where);
+*/
        }
 
        LatLng latlng(MouseEvent e) {
@@ -684,7 +737,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
        public void mouseReleased(MouseEvent e) {
        }
 
-       public void set_cache_size() {
+       private void set_cache_size() {
                Rectangle       r = comp.getVisibleRect();
 
                int     width_tiles = (r.width + 2*px_size) / px_size;
@@ -704,6 +757,8 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
 
        JScrollPane     pane = new JScrollPane();
 
+       JComboBox<String>       maptype_combo;
+
        public AltosSiteMap(int in_radius) {
                radius = in_radius;
 
@@ -717,13 +772,8 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
 
                comp.setLayout(layout);
 
-               for (int x = -radius; x <= radius; x++) {
-                       for (int y = -radius; y <= radius; y++) {
-                               Point offset = new Point(x, y);
-                               AltosSiteMapTile t = createTile(offset);
-                               addTileAt(t, offset);
-                       }
-               }
+               set_tiles();
+
                pane.setViewportView(comp);
                pane.setPreferredSize(new Dimension(500,500));
                pane.setVisible(true);
@@ -805,7 +855,7 @@ public class AltosSiteMap extends JComponent implements AltosFlightDisplay, Mous
                c.weighty = 0;
                add(zoom_out, c);
 
-               final JComboBox<String> maptype_combo = new JComboBox<String>(maptype_labels);
+               maptype_combo = new JComboBox<String>(maptype_labels);
 
                maptype_combo.setEditable(false);
                maptype_combo.setMaximumRowCount(maptype_combo.getItemCount());
index 7f436ef0729b595da087f7c706098dd854fc4eca..3e08e1b3c91a992cde955b981286a1aaccaf45bb 100644 (file)
@@ -176,6 +176,7 @@ public class AltosSiteMapCache {
                return new Point (x, y);
        }
 
+/*
        private static void dump_cache() {
                int     min_x = 1000, max_x = -1000, min_y = 1000, max_y = -1000;
 
@@ -208,6 +209,7 @@ public class AltosSiteMapCache {
                        System.out.printf("\n");
                }
        }
+*/
 
        public static AltosSiteMapImage get_image(AltosSiteMapTile tile, File file, int width, int height) {
                int             oldest = -1;
index f08c0b261dd3e6db52b7d903aa5d59a940e19dfa..ae32418fc1011b63229b54d5bcc8cdc3100c8026 100644 (file)
@@ -34,7 +34,7 @@ public class AltosSiteMapImage {
 
        Thread  load_thread;
 
-       public boolean validate() {
+       public boolean validate(final int serial) {
                if (image != null) {
                        AltosSiteMap.debug_component(tile, "valid");
                        return true;
@@ -42,16 +42,15 @@ public class AltosSiteMapImage {
                        AltosSiteMap.debug_component(tile, "loading");
                        load_thread = new Thread() {
                                        public void run() {
-                                               image = null;
                                                try {
                                                        image = ImageIO.read(file);
                                                } catch (Exception e) {
                                                }
                                                SwingUtilities.invokeLater( new Runnable() {
                                                                public void run() {
-                                                                       AltosSiteMap.debug_component(tile, "later");
+                                                                       AltosSiteMap.debug_component(tile, file.toString());
                                                                        Graphics2D g2d = (Graphics2D) tile.getGraphics();
-                                                                       tile.paint_graphics(g2d, image);
+                                                                       tile.paint_graphics(g2d, image, serial);
                                                                        load_thread = null;
                                                                }
                                                        });
index 107b09f828a14707eff6caf03b3d650e09f84a30..6b8066b36c350f47f427cfa682a116dcd445a193 100644 (file)
@@ -206,18 +206,15 @@ class AltosSites extends Thread {
        }
 }
 
-public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener, ItemListener {
+public class AltosSiteMapPreload extends AltosUIFrame implements ActionListener, ItemListener {
        AltosUIFrame    owner;
        AltosSiteMap    map;
 
        AltosMapPos     lat;
        AltosMapPos     lon;
 
-       final static int        radius = 5;
-       final static int        width = (radius * 2 + 1);
-       final static int        height = (radius * 2 + 1);
-
        JProgressBar    pbar;
+       int             pbar_max;
 
        AltosSites      sites;
        JLabel          site_list_label;
@@ -227,6 +224,15 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
        boolean         loading;
        JButton         close_button;
 
+       JCheckBox[]     maptypes = new JCheckBox[AltosSiteMap.maptype_terrain - AltosSiteMap.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" };
 
@@ -234,15 +240,16 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
                int             n;
                String          s;
 
-               public updatePbar(int x, int y, String in_s) {
-                       n = (x + radius) + (y + radius) * width + 1;
+               public updatePbar(int n, String in_s) {
+                       this.n = n;
                        s = in_s;
                }
 
                public void run() {
                        pbar.setValue(n);
                        pbar.setString(s);
-                       if (n < width * height) {
+                       if (n < pbar_max) {
+                               pbar.setMaximum(pbar_max);
                                pbar.setValue(n);
                                pbar.setString(s);
                        } else {
@@ -258,17 +265,54 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
 
                AltosSiteMap    map;
 
-               public bgLoad(AltosSiteMap in_map) {
+               double          lat, lon;
+
+               int             types = 0;
+               int             r;
+
+               int     min_z = (Integer) min_zoom.getSelectedItem();
+               int     max_z = (Integer) max_zoom.getSelectedItem();
+
+               public bgLoad(AltosSiteMap in_map, double lat, double lon) {
                        map = in_map;
+                       this.lat = lat;
+                       this.lon = lon;
+                       if (max_z < min_z)
+                               max_z = min_z;
+                       int ntype = 0;
+                       for (int t = AltosSiteMap.maptype_hybrid; t <= AltosSiteMap.maptype_terrain; t++)
+                               if (maptypes[t].isSelected()) {
+                                       types |= (1 << t);
+                                       ntype++;
+                               }
+                       if (ntype == 0) {
+                               types |= (1 << AltosSiteMap.maptype_hybrid);
+                               ntype = 1;
+                       }
+                       r = (Integer) radius.getSelectedItem();
+                       pbar_max = (max_z - min_z + 1) * ntype * (r * 2 + 1) * (r * 2 + 1);
                }
 
                public void run() {
-                       for (int y = -map.radius; y <= map.radius; y++) {
-                               for (int x = -map.radius; x <= map.radius; x++) {
-                                       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()));
+                       int     i = 0;
+                       for (int z = min_z; z <= max_z; z++) {
+                               for (int t = AltosSiteMap.maptype_hybrid; t <= AltosSiteMap.maptype_terrain; t++) {
+                                       if ((types & (1 << t)) == 0)
+                                               continue;
+                                       map.clear_base_location();
+                                       map.set_zoom(z + AltosSiteMap.default_zoom);
+                                       map.set_maptype(t);
+                                       map.set_radius(r);
+                                       map.setBaseLocation(lat, lon);
+                                       map.draw_circle(lat, lon);
+                                       for (int y = -r; y <= r; y++) {
+                                               for (int x = -r; x <= r; x++) {
+                                                       File    pngfile;
+                                                       pngfile = map.init_map(new Point(x,y),
+                                                                              AltosSiteMap.load_mode_cached|AltosSiteMap.load_mode_uncached);
+                                                       SwingUtilities.invokeLater(new updatePbar(++i, pngfile.toString()));
+                                               }
+                                       }
                                }
                        }
                }
@@ -310,7 +354,7 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
                                        map.setBaseLocation(latitude,longitude);
                                        map.draw_circle(latitude,longitude);
                                        loading = true;
-                                       bgLoad thread = new bgLoad(map);
+                                       bgLoad thread = new bgLoad(map, latitude, longitude);
                                        thread.start();
                                } catch (NumberFormatException ne) {
                                        load_button.setSelected(false);
@@ -326,9 +370,11 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
                GridBagConstraints      c = new GridBagConstraints();
                Insets                  i = new Insets(4,4,4,4);
 
+               setTitle("AltOS Load Maps");
+
                pane.setLayout(new GridBagLayout());
 
-               map = new AltosSiteMap(radius);
+               map = new AltosSiteMap(2);
 
                c.fill = GridBagConstraints.BOTH;
                c.anchor = GridBagConstraints.CENTER;
@@ -338,14 +384,14 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
 
                c.gridx = 0;
                c.gridy = 0;
-               c.gridwidth = 2;
+               c.gridwidth = 10;
                c.anchor = GridBagConstraints.CENTER;
 
                pane.add(map, c);
 
                pbar = new JProgressBar();
                pbar.setMinimum(0);
-               pbar.setMaximum(width * height);
+               pbar.setMaximum(1);
                pbar.setValue(0);
                pbar.setString("");
                pbar.setStringPainted(true);
@@ -358,7 +404,7 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
 
                c.gridx = 0;
                c.gridy = 1;
-               c.gridwidth = 2;
+               c.gridwidth = 10;
 
                pane.add(pbar, c);
 
@@ -462,6 +508,59 @@ public class AltosSiteMapPreload extends AltosUIDialog implements ActionListener
 
                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 = AltosSiteMap.maptype_hybrid; type <= AltosSiteMap.maptype_terrain; type++) {
+                       maptypes[type] = new JCheckBox(AltosSiteMap.maptype_labels[type],
+                                                      type == AltosSiteMap.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(false);
+               c.gridx = 5;
+               c.gridy = 4;
+               pane.add(radius, c);
+
                pack();
                setLocationRelativeTo(owner);
                setVisible(true);
index 136fbd7a23095b00c7b7c2283fd6516e36e0077f..f8b924a82cd7444ed74abf08a380271eb62a0a5b 100644 (file)
@@ -155,9 +155,16 @@ public class AltosSiteMapTile extends JComponent {
                return String.format(format, distance);
        }
 
-       boolean painting;
+       int     painting_serial;
+       int     painted_serial;
+
+       public void paint_graphics(Graphics2D g2d, Image image, int serial) {
+
+               if (serial < painted_serial)
+                       return;
+
+               painted_serial = serial;
 
-       public void paint_graphics(Graphics2D g2d, Image image) {
                if (image != null) {
                        AltosSiteMap.debug_component(this, "paint_graphics");
                        g2d.drawImage(image, 0, 0, null);
@@ -239,7 +246,6 @@ public class AltosSiteMapTile extends JComponent {
                        }
                        g2d.drawString(message, x, y);
                }
-               painting = false;
        }
 
        public void paint(Graphics g) {
@@ -247,26 +253,23 @@ public class AltosSiteMapTile extends JComponent {
                Image                   image = null;
                boolean                 queued = false;
 
-               if (painting) {
-                       AltosSiteMap.debug_component(this, "already painting");
-                       return;
-               }
                AltosSiteMap.debug_component(this, "paint");
 
+               ++painting_serial;
+
                if (file != null) {
                        AltosSiteMapImage       aimage;
 
                        aimage = AltosSiteMapCache.get_image(this, file, px_size, px_size);
                        if (aimage != null) {
-                               if (aimage.validate())
+                               if (aimage.validate(painting_serial))
                                        image = aimage.image;
                                else
                                        queued = true;
                        }
                }
                if (!queued)
-                       paint_graphics(g2d, image);
-               painting = queued;
+                       paint_graphics(g2d, image, painting_serial);
        }
 
        public void show(int state, Point2D.Double last_pt, Point2D.Double pt)