Ignore:
Timestamp:
2009-04-24T19:34:33+02:00 (16 years ago)
Author:
lvarga
Message:

Applyied patch from Dave Hansen <dave@…>. His info about changes:

  1. Change tileStorage from being a set of arrays of hashmaps (one per zoom level) to being a single hash. We do this by adding a 'level' variable to the SlippyMapKey class. This simplifies the code a good amount, and gets rid of a warning when allocating the HashMap array.
  2. Introduce SlippyMapLayer.loadSingleTile(). This not only forces the image to get loaded, but also checks the state of the entire tileStorage, looking for extra tiles.
  3. Add crude timestamping to SlippyMapTile. Whenever a load of a SlippyMapTile is requested, we timestamp it. This lets us figure out which have been least recently used later.
  4. Bound the size of the tileStorage. Every so often, go look at all of the tiles. Sort them by access time. Drop the image from the tile if it isn't one of the 100 most recent ones. (Make this a preference?)
  5. Add some sanity checks to SlippyMapKey to make sure we don't add negative indexes. This keeps us from requesting urls like: http://a.tile.openstreetmap.org/2/-1/-2.png
  6. Introduce a new auto-zoom mechanism. Right now, it attempts to see how many tiles are on the screen. This is OK, but it can cause autozoom changes on just scrolling. Say you have a screen which exactly fits 6x6 tiles. If you scroll over just a bit, you'll see all of those 16 tiles, plus a few pixels of the next row over. You'll all of a sudden be seeing 6x7 tiles, and autozoom will kick in. This new mechanism attempts to detect how much tiles are being squished or stretched. Since it has to load a tile before it can actually find out the tile's size, it also has the nice side-effect of getting things drawn on the screen earlier. Should these be preferences too?
  7. Add debugging to imageUpdate, printing the URL of images that fail to load. Also add a SlippyMapTile.getImageURL() helper to facilitate this.
  8. Make sure we redraw sooner when changing zoom levels
Location:
applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java

    r13665 r14732  
    1111 *
    1212 * @author LuVar <lubomir.varga@freemap.sk>
     13 * @author Dave Hansen <dave@sr71.net>
    1314 *
    1415 */
     
    1617        private final int x;
    1718        private final int y;
     19        private final int level;
    1820       
    1921        /**
     
    2628         * @param y     y position in tiles table
    2729         */
    28         public SlippyMapKey(int x, int y) {
     30        public final boolean valid;
     31        public SlippyMapKey(int level, int x, int y) {
    2932                this.x = x;
    3033                this.y = y;
     34                this.level = level;
     35                if (level <= 0 || x < 0 || y < 0) {
     36                        this.valid = false;
     37                        System.err.println("invalid SlippyMapKey("+level+", "+x+", "+y+")");
     38                } else {
     39                        this.valid = true;
     40                }
    3141        }
    3242       
     
    4252                if (obj instanceof SlippyMapKey) {
    4353                        SlippyMapKey smk = (SlippyMapKey) obj;
    44                         if((smk.x == this.x) && (smk.y == this.y)) {
     54                        if((smk.x == this.x) && (smk.y == this.y) && (smk.level == this.level)) {
    4555                                return true;
    4656                        }
     
    5565        @Override
    5666        public int hashCode() {
    57                 return new Integer(this.x + this.y * 10000).hashCode();
     67                return new Integer(this.x + this.y * 10000 + this.level * 100000).hashCode();
    5868        }
    5969       
     
    6373        @Override
    6474        public String toString() {
    65                 return "SlippyMapKey(x=" + this.x + ",y=" + this.y + ")";
     75                return "SlippyMapKey(x=" + this.x + ",y=" + this.y + ",level=" + level + ")";
    6676        }
    6777       
  • applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java

    r13961 r14732  
    1212import java.awt.event.MouseEvent;
    1313import java.awt.image.ImageObserver;
     14import java.util.Comparator;
    1415import java.util.HashMap;
     16import java.util.TreeSet;
    1517
    1618import javax.swing.AbstractAction;
     
    3739 * @author Frederik Ramm <frederik@remote.org>
    3840 * @author LuVar <lubomir.varga@freemap.sk>
     41 * @author Dave Hansen <dave@sr71.net>
    3942 *
    4043 */
     
    4649         */
    4750        public int currentZoomLevel = SlippyMapPreferences.getMinZoomLvl();
    48         private HashMap<SlippyMapKey, SlippyMapTile>[] tileStorage = null;
     51        private HashMap<SlippyMapKey, SlippyMapTile> tileStorage = null;
    4952
    5053        Point[][] pixelpos = new Point[21][21];
     
    6871                        public void actionPerformed(ActionEvent ae) {
    6972                                if (clickedTile != null) {
    70                                         clickedTile.loadImage();
     73                                        loadSingleTile(clickedTile);
    7174                                        needRedraw = true;
    7275                                        Main.map.repaint();
     
    120123                                        public void actionPerformed(ActionEvent ae) {
    121124                                                decreaseZoomLevel();
    122                                                 needRedraw = true;
    123125                                                Main.map.repaint();
    124126                                        }
     
    162164                if (currentZoomLevel < SlippyMapPreferences.getMaxZoomLvl()) {
    163165                        currentZoomLevel++;
     166                        Main.debug("increasing zoom level to: " + currentZoomLevel);
     167                        needRedraw = true;
    164168                } else {
    165                         System.err
    166                                         .println("current zoom lvl couldnt be increased. MaxZoomLvl reached.");
     169                        System.err.println("current zoom lvl ("+currentZoomLevel+") couldnt be increased. "+
     170                                                         "MaxZoomLvl ("+SlippyMapPreferences.getMaxZoomLvl()+") reached.");
    167171                }
    168172        }
     
    173177        public void decreaseZoomLevel() {
    174178                if (currentZoomLevel > SlippyMapPreferences.getMinZoomLvl()) {
     179                        Main.debug("decreasing zoom level to: " + currentZoomLevel);
    175180                        currentZoomLevel--;
     181                        needRedraw = true;
    176182                } else {
    177                         System.err
    178                                         .println("current zoom lvl couldnt be decreased. MinZoomLvl reached.");
     183                        System.err.println("current zoom lvl couldnt be decreased. MinZoomLvl reached.");
    179184                }
    180185        }
     
    184189                // the setting isnt saved yet.
    185190                int maxZoom = 30; // SlippyMapPreferences.getMaxZoomLvl();
    186                 // +1 because of array indexed from 0.
    187                 tileStorage = new HashMap[maxZoom + 1];
    188 
    189                 for (int i = 0; i < maxZoom + 1; i++)
    190                         tileStorage[i] = new HashMap<SlippyMapKey, SlippyMapTile>();
     191                tileStorage = new HashMap<SlippyMapKey, SlippyMapTile>();
     192
     193                checkTileStorage();
     194        }
     195
     196        class TileTimeComp implements Comparator<SlippyMapTile> {
     197                        public int compare(SlippyMapTile s1, SlippyMapTile s2) {
     198                                        long t1 = s1.access_time();
     199                                        long t2 = s2.access_time();
     200                                        if (s1 == s2)
     201                                                        return 0;
     202                                        if (t1 == t2) {
     203                                                        t1 = s1.hashCode();
     204                                                        t2 = s2.hashCode();
     205                                        }
     206                                        if (t1 < t2)
     207                                                        return -1;
     208                                        return 1;
     209                        }
     210        }
     211
     212        long lastCheck = 0;
     213        /**
     214         * <p>
     215         * Check if tiles.size() is not more than max_nr_tiles. If yes, oldest tiles by timestamp
     216         * are fired out from cache.
     217         * </p>
     218         */
     219        public void checkTileStorage() {
     220                int maxZoom = 30; // SlippyMapPreferences.getMaxZoomLvl();
     221                long now = System.currentTimeMillis();
     222                if (now - lastCheck < 1000)
     223                                return;
     224                lastCheck = now;
     225                TreeSet<SlippyMapTile> tiles = new TreeSet<SlippyMapTile>(new TileTimeComp());
     226                tiles.addAll(tileStorage.values());
     227                int max_nr_tiles = 100;
     228                if (tiles.size() < max_nr_tiles) {
     229                        Main.debug("total of " + tiles.size() + " loaded tiles, size OK " + now);
     230                        return;
     231                }
     232                int nr_to_drop = tiles.size() - max_nr_tiles;;
     233                Main.debug("total of " + tiles.size() + " tiles, need to flush " + nr_to_drop + " tiles");
     234                for (SlippyMapTile t : tiles) {
     235                        if (nr_to_drop <= 0)
     236                                        break;
     237                        t.dropImage();
     238                        nr_to_drop--;
     239                }
     240        }
     241
     242        void loadSingleTile(SlippyMapTile tile)
     243        {
     244                tile.loadImage();
     245                this.checkTileStorage();
    191246        }
    192247
     
    224279                for (int x = z12x0 - 1; x <= z12x1; x++) {
    225280                        for (int y = z12y0 - 1; y <= z12y1; y++) {
    226                                 SlippyMapKey key = new SlippyMapKey(x, y);
    227 
    228                                 SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
    229 
     281                                SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
     282                                SlippyMapTile tile = tileStorage.get(key);
     283                                if (!key.valid) {
     284                                                System.out.println("paint-1() made invalid key");
     285                                                continue;
     286                                }
    230287                                if (tile == null)
    231                                         tileStorage[currentZoomLevel].put(key,
     288                                        tileStorage.put(key,
    232289                                                        tile = new SlippyMapTile(x, y, currentZoomLevel));
    233 
    234                                 if (tile.getImage() == null)
    235                                         tile.loadImage();
    236                         }
    237                 }
     290                                if (tile.getImage() == null) {
     291                                        this.loadSingleTile(tile);
     292                                }
     293                        }
     294                }
     295        }
     296
     297        /*
     298         * Attempt to approximate how much the image is
     299         * being scaled.  For instance, a 100x100 image
     300         * being scaled to 50x50 would return 0.25.
     301         */
     302        double getImageScaling(Image img, Point p0, Point p1)
     303        {
     304                int realWidth = img.getWidth(this);
     305                int realHeight = img.getHeight(this);
     306                if (realWidth == -1 || realHeight == -1)
     307                                return 1.0;
     308                int drawWidth = p1.x - p0.x;
     309                int drawHeight = p1.x - p0.x;
     310
     311                double drawArea = drawWidth * drawHeight;
     312                double realArea = realWidth * realHeight;
     313
     314                return drawArea / realArea;
    238315        }
    239316
     
    242319        @Override
    243320        public void paint(Graphics g, MapView mv) {
     321                long start = System.currentTimeMillis();
    244322                LatLon topLeft = mv.getLatLon(0, 0);
    245323                LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
    246324                Graphics oldg = g;
    247325
     326                if (botRight.lon() == 0.0 || botRight.lat() == 0) {
     327                                // probably still initializing
     328                                return;
     329                }
    248330                if (lastTopLeft != null && lastBotRight != null
    249331                                && topLeft.equalsEpsilon(lastTopLeft)
     
    298380                float fadeBackground = SlippyMapPreferences.getFadeBackground();
    299381
     382                Double imageScale = null;
     383                int count = 0;
    300384                for (int x = z12x0 - 1; x <= z12x1; x++) {
    301385                        for (int y = z12y0 - 1; y <= z12y1; y++) {
    302                                 SlippyMapKey key = new SlippyMapKey(x, y);
     386                                SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
    303387                                SlippyMapTile tile;
    304                                 try {
    305                                         tile = tileStorage[currentZoomLevel].get(key);
    306                                 } catch (IndexOutOfBoundsException ex) {
    307                                         throw new RuntimeException("currentZoomLevel="
    308                                                         + currentZoomLevel
    309                                                         + " and tile storage array have just size="
    310                                                         + tileStorage.length
    311                                                         + " and maxZoomLvl in preferences is "
    312                                                         + SlippyMapPreferences.getMaxZoomLvl() + ".", ex);
    313                                 }
    314 
     388                                tile = tileStorage.get(key);
     389                                if (!key.valid) {
     390                                                System.out.println("loadAllTiles() made invalid key");
     391                                                continue;
     392                                }
    315393                                if (tile == null) {
    316394                                        tile = new SlippyMapTile(x, y, currentZoomLevel);
    317                                         tileStorage[currentZoomLevel].put(key, tile);
     395                                        tileStorage.put(key, tile);
    318396                                        if (SlippyMapPreferences.getAutoloadTiles()) {
    319397                                                // TODO probably do on background
    320                                                 tile.loadImage();
    321                                         }
     398                                                loadSingleTile(tile);
     399                                        }
     400                                        checkTileStorage();
    322401                                }
    323402                                Image img = tile.getImage();
     
    327406                                        Point p2 = pixelpos[x - z12x0 + 2][y - z12y0 + 2];
    328407                                        g.drawImage(img, p.x, p.y, p2.x - p.x, p2.y - p.y, this);
    329 
     408                                        if (imageScale == null)
     409                                                imageScale = new Double(getImageScaling(img, p, p2));
     410                                        count++;
    330411                                        if (fadeBackground != 0f) {
    331412                                                // dimm by painting opaque rect...
     
    336417                        }// end of for
    337418                }// end of for
    338 
    339419                g.setColor(Color.red);
    340420                for (int x = z12x0 - 1; x <= z12x1; x++) {
     
    351431
    352432                        for (int y = z12y0 - 1; y <= z12y1; y++) {
    353                                 SlippyMapKey key = new SlippyMapKey(x, y);
     433                                SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
    354434                                int texty = p.y + 2 + fontHeight;
    355                                 SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
     435                                SlippyMapTile tile = tileStorage.get(key);
    356436                                if (tile == null) {
    357437                                        continue;
     438                                }
     439                                if (!key.valid) {
     440                                                System.out.println("paint-0() made invalid key");
     441                                                continue;
     442                                }
     443                                if (tile.getImage() == null) {
     444                                                loadSingleTile(tile);
    358445                                }
    359446                                p = pixelpos[x - z12x0 + 1][y - z12y0 + 2];
     
    395482                oldg.drawImage(bufferImage, 0, 0, null);
    396483
    397                 // TODO do autozoom nicer
    398                 if ((z12x1 - z12x0 < 2) || (z12y1 - z12y0 < 2)) {
    399                         if (SlippyMapPreferences.getAutozoom()) {
    400                                 increaseZoomLevel();
    401                         }
    402                         this.paint(oldg, mv);
    403                 }
    404 
    405                 if ((z12x1 - z12x0 > 6) || (z12y1 - z12y0 > 6)) {
    406                         if (SlippyMapPreferences.getAutozoom()) {
    407                                 decreaseZoomLevel();
    408                         }
    409                         this.paint(oldg, mv);
    410                 }
    411                
     484                if (imageScale != null) {
     485                        // If each source image pixel is being stretched into > 3
     486                        // drawn pixels, zoom in... getting too pixelated
     487                        if (imageScale > 3) {
     488                                if (SlippyMapPreferences.getAutozoom()) {
     489                                    Main.debug("autozoom increase: "+z12x1+" " + z12x0 + " " + z12y1 + " " + z12y0
     490                                                                        + topLeft + " " + botRight + " scale: " + imageScale);
     491                                        increaseZoomLevel();
     492                                }
     493                                this.paint(oldg, mv);
     494                        }
     495
     496                        // If each source image pixel is being squished into > 0.32
     497                        // of a drawn pixels, zoom out.
     498                        if (imageScale < 0.32) {
     499                                if (SlippyMapPreferences.getAutozoom()) {
     500                                    Main.debug("autozoom decrease: "+z12x1+" " + z12x0 + " " + z12y1 + " " + z12y0
     501                                                                        + topLeft + " " + botRight + " scale: " + imageScale);
     502                                        decreaseZoomLevel();
     503                                }
     504                                this.paint(oldg, mv);
     505                        }
     506                }       
    412507                g.setColor(Color.black);
    413508                g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);
     
    433528                        }
    434529                }
    435                 if (tiley == -1)
     530                if (tiley == -1) {
    436531                        return null;
    437 
    438                 SlippyMapKey key = new SlippyMapKey(tilex, tiley);
    439                 SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
     532                }
     533
     534                SlippyMapKey key = new SlippyMapKey(currentZoomLevel, tilex, tiley);
     535                if (!key.valid) {
     536                        System.err.println("getTileForPixelpos("+px+","+py+") made invalid key");
     537                        return null;
     538                }
     539                SlippyMapTile tile = tileStorage.get(key);
    440540                if (tile == null)
    441                         tileStorage[currentZoomLevel].put(key, tile = new SlippyMapTile(
    442                                         tilex, tiley, currentZoomLevel));
     541                        tileStorage.put(key, tile = new SlippyMapTile(tilex, tiley, currentZoomLevel));
     542                checkTileStorage();
    443543                return tile;
    444544        }
     
    506606        public boolean imageUpdate(Image img, int infoflags, int x, int y,
    507607                        int width, int height) {
    508 
    509608                boolean done = ((infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0);
     609                if ((infoflags & ERROR) != 0) {
     610                                String url = "unknown";
     611                                for (SlippyMapTile tile : tileStorage.values()) {
     612                                                if (tile.getImage() != img)
     613                                                                continue;
     614                                                url = tile.getImageURL().toString();
     615                                }
     616                                System.err.println("imageUpdate(" + img + ") error " + url +")");
     617                }
     618                if ((infoflags & SOMEBITS) != 0) {
     619                                //if (y%100 == 0)
     620                                //      System.out.println("imageUpdate("+img+") SOMEBITS ("+x+","+y+")");
     621                }
    510622                // Repaint immediately if we are done, otherwise batch up
    511623                // repaint requests every 100 milliseconds
  • applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java

    r13625 r14732  
    1616 * @author Frederik Ramm <frederik@remote.org>
    1717 * @author LuVar <lubomir.varga@freemap.sk>
     18 * @author Dave Hansen <dave@sr71.net>
    1819 *
    1920 */
     
    2122{
    2223    private Image  tileImage;
     24        long timestamp;
    2325
    2426    int x;
     
    3335        this.y = y;
    3436        this.z = z;
     37                timestamp = System.currentTimeMillis();
    3538    }
    3639
     
    4043    }
    4144
    42     public void loadImage()
     45
     46    public URL getImageURL()
    4347    {
    4448        try
    4549        {
    46             URL imageURL = new URL(SlippyMapPreferences.getMapUrl() + "/" + z
    47                     + "/" + x + "/" + y + ".png");
    48            
    49             tileImage = Toolkit.getDefaultToolkit().createImage(imageURL);
     50            return new URL(SlippyMapPreferences.getMapUrl() + "/" + z + "/" + x + "/" + y + ".png");
    5051        }
    5152        catch (MalformedURLException mfu)
     
    5354            mfu.printStackTrace();
    5455        }
     56            return null;
     57        }
     58
     59    public void loadImage()
     60    {
     61        URL imageURL = this.getImageURL();
     62        tileImage = Toolkit.getDefaultToolkit().createImage(imageURL);
     63                Toolkit.getDefaultToolkit().sync();
     64                timestamp = System.currentTimeMillis();
    5565    }
    5666
    5767    public Image getImage()
    5868    {
     69        timestamp = System.currentTimeMillis();
    5970        return tileImage;
     71    }
     72
     73    public void dropImage()
     74    {
     75                tileImage = null;
     76                //  This should work in theory but doesn't seem to actually
     77                //  reduce the X server memory usage
     78                //tileImage.flush();
    6079    }
    6180
     
    7493        catch (Exception ex)
    7594        {
    76             metadata = tr("error loading metadata");
     95            metadata = tr("error loading metadata" + ex.toString());
    7796        }
    7897
     
    88107            BufferedReader in = new BufferedReader(new InputStreamReader(devc
    89108                    .getInputStream()));
     109                        timestamp = System.currentTimeMillis();
    90110            metadata = tr("requested: {0}", tr(in.readLine()));
    91111        }
     
    94114            metadata = tr("error requesting update");
    95115        }
     116    }
     117
     118    public long access_time()
     119    {
     120        return timestamp;
    96121    }
    97122
Note: See TracChangeset for help on using the changeset viewer.