Changeset 9288 in osm for applications/viewer


Ignore:
Timestamp:
2008-07-25T17:13:25+02:00 (16 years ago)
Author:
stotz
Message:

Improved implementation of MemoryTileCache;
JMapViwer now paints map tiles in a spiral starting from the center, therefore the inner tiles are loaded first

Location:
applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java

    r9263 r9288  
    1313import java.awt.image.BufferedImage;
    1414import java.io.IOException;
     15import java.io.InputStream;
     16import java.net.HttpURLConnection;
     17import java.net.URL;
    1518import java.util.LinkedList;
    1619import java.util.List;
     
    3740
    3841        private static final long serialVersionUID = 1L;
     42
     43        /**
     44         * Vectors for clock-wise tile painting
     45         */
     46        protected static final Point[] move =
     47                        { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
    3948
    4049        public static final int MAX_ZOOM = 18;
     
    188197                        return;
    189198
    190                 // Substract the calculated values so that we get the x/y index of the
    191                 // upper left tile again including fraction where in the tile is our
    192                 // map origin point (0,0)
    193 
    194199                // Get the plain tile number
    195200                Point p = new Point();
     
    279284                super.paintComponent(g);
    280285
    281                 // Optimization for loading the centered tile in a lower zoom first
    282                 if (zoom > MIN_ZOOM) {
    283                         int center_tx = center.x / Tile.WIDTH;
    284                         int center_ty = center.y / Tile.HEIGHT;
    285                         Tile centerTile = tileCache.getTile(center_tx, center_ty, zoom);
    286                         if (centerTile == null || !centerTile.isLoaded()) {
    287                                 // tile in the center of the screen is not loaded, for faster
    288                                 // displaying anything in the center we first load a tile of a
    289                                 // lower zoom level
    290                                 getTile(center_tx / 2, center_ty / 2, zoom - 1);
     286                int iMove = 0;
     287
     288                int tilex = center.x / Tile.WIDTH;
     289                int tiley = center.y / Tile.HEIGHT;
     290                int off_x = (center.x % Tile.WIDTH);
     291                int off_y = (center.y % Tile.HEIGHT);
     292
     293                int posx = getWidth() / 2 - off_x;
     294                int posy = getHeight() / 2 - off_y;
     295
     296                int diff_left = off_x;
     297                int diff_right = Tile.WIDTH - off_x;
     298                int diff_top = off_y;
     299                int diff_bottom = Tile.HEIGHT - off_y;
     300
     301                boolean start_left = diff_left < diff_right;
     302                boolean start_top = diff_top < diff_bottom;
     303
     304                if (start_top) {
     305                        if (start_left)
     306                                iMove = 2;
     307                        else
     308                                iMove = 3;
     309                } else {
     310                        if (start_left)
     311                                iMove = 1;
     312                        else
     313                                iMove = 0;
     314                } // calculate the visibility borders
     315                int x_min = -Tile.WIDTH;
     316                int y_min = -Tile.HEIGHT;
     317                int x_max = getWidth();
     318                int y_max = getHeight();
     319
     320                boolean painted = true;
     321                int x = 0;
     322                while (painted) {
     323                        painted = false;
     324                        for (int y = 0; y < 4; y++) {
     325                                if (y % 2 == 0)
     326                                        x++;
     327                                for (int z = 0; z < x; z++) {
     328                                        if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) { // tile
     329                                                // is
     330                                                // visible
     331                                                Tile tile = getTile(tilex, tiley, zoom);
     332                                                if (tile != null) {
     333                                                        painted = true;
     334                                                        tile.paint(g, posx, posy);
     335                                                        if (tileGridVisible)
     336                                                                g.drawRect(posx, posy, Tile.WIDTH, Tile.HEIGHT);
     337                                                }
     338                                        }
     339                                        Point p = move[iMove];
     340                                        posx += p.x * Tile.WIDTH;
     341                                        posy += p.y * Tile.HEIGHT;
     342                                        tilex += p.x;
     343                                        tiley += p.y;
     344                                }
     345                                iMove = (iMove + 1) % move.length;
    291346                        }
    292347                }
    293                 // Regular tile painting
    294                 int left = center.x - getWidth() / 2;
    295                 int top = center.y - getHeight() / 2;
    296 
    297                 int tilex = left / Tile.WIDTH;
    298                 int tiley = top / Tile.HEIGHT;
    299                 int off_x = left % Tile.WIDTH;
    300                 int off_y = top % Tile.HEIGHT;
    301                 for (int x = -off_x; x < getWidth(); x += Tile.WIDTH) {
    302                         int tiley_tmp = tiley;
    303                         for (int y = -off_y; y < getHeight(); y += Tile.HEIGHT) {
    304                                 Tile tile = getTile(tilex, tiley_tmp, zoom);
    305                                 if (tile != null) {
    306                                         tile.paint(g, x, y);
    307                                         if (tileGridVisible)
    308                                                 g.drawRect(x, y, Tile.WIDTH, Tile.HEIGHT);
    309                                 }
    310                                 tiley_tmp++;
    311                         }
    312                         tilex++;
    313                 }
    314                 g.fillOval(getWidth()/2 - 5, getHeight()/2 - 5, 10, 10);
    315                 g.drawString("Test", 50, 10);
     348                g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
    316349                if (!mapMarkersVisible || mapMarkerList == null)
    317350                        return;
     
    394427                }
    395428                if (!tile.isLoaded()) {
    396                         jobDispatcher.addJob(new Runnable() {
     429                        jobDispatcher.addJob(new Job() {
     430
     431                                InputStream input = null;
    397432
    398433                                public void run() {
    399434                                        Tile tile = tileCache.getTile(tilex, tiley, zoom);
    400                                         if (tile.isLoaded())
     435                                        if (tile == null || tile.isLoaded())
    401436                                                return;
    402437                                        try {
    403                                                 Thread.sleep(500);
    404                                                 tile.loadTileImage();
     438                                                //Thread.sleep(500);
     439                                                URL url;
     440                                                url = new URL("http://tile.openstreetmap.org/" + tile.getKey() + ".png");
     441                                                HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
     442                                                urlConn.setReadTimeout(30000); // 30 seconds read
     443                                                                                                                // timeout
     444                                                input = urlConn.getInputStream();
     445                                                tile.setImage(ImageIO.read(input));
     446                                                tile.setLoaded(true);
    405447                                                repaint();
     448                                                input.close();
     449                                                input = null;
    406450                                        } catch (Exception e) {
    407                                                 System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
    408                                                                 + " " + e.getMessage());
     451                                                if (input == null /* || !input.isStopped() */)
     452                                                        System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
     453                                                                        + " " + e.getMessage());
     454                                        }
     455                                }
     456
     457                                /**
     458                                 * Terminating all transfers that are currently in progress
     459                                 */
     460                                public void stop() {
     461
     462                                        try {
     463                                                // if (input != null)
     464                                                // input.stop();
     465                                        } catch (Exception e) {
    409466                                        }
    410467                                }
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java

    r9095 r9288  
    1616        protected BlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
    1717
    18         Thread[] threads;
     18        JobThread[] threads;
    1919
    2020        public JobDispatcher(int threadCound) {
    21                 threads = new Thread[threadCound];
     21                threads = new JobThread[threadCound];
    2222                for (int i = 0; i < threadCound; i++) {
    2323                        threads[i] = new JobThread(i + 1);
     
    2626
    2727        /**
    28          * Removes all jobs from the queue that are currently not being processed.
     28         * Removes all jobs from the queue that are currently not being processed
     29         * and stops those currently being processed.
    2930         */
    3031        public void cancelOutstandingJobs() {
     
    3233                for (int i = 0; i < threads.length; i++) {
    3334                        try {
    34                                 threads[i].interrupt();
    35                                 threads[i] = new JobThread(i + 1);
     35                                Runnable job = threads[i].getJob();
     36                                if ((job != null) && (job instanceof Job))
     37                                        ((Job) job).stop();
    3638                        } catch (Exception e) {
    3739                                e.printStackTrace();
     
    4951        protected class JobThread extends Thread {
    5052
     53                Runnable job;
     54
    5155                public JobThread(int threadId) {
    5256                        super("OSMJobThread " + threadId);
     57                        job = null;
    5358                        start();
    5459                }
     
    5762                public void run() {
    5863                        while (!isInterrupted()) {
    59                                 Runnable job;
    6064                                try {
    6165                                        job = jobQueue.take();
     
    6569                                try {
    6670                                        job.run();
     71                                        job = null;
    6772                                } catch (Exception e) {
    6873                                        e.printStackTrace();
     
    7075                        }
    7176                }
     77
     78                /**
     79                 * @return the job being executed at the moment or <code>null</code> if
     80                 *         the thread is idle.
     81                 */
     82                public Runnable getJob() {
     83                        return job;
     84                }
     85
    7286        }
    7387
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java

    r9095 r9288  
    33//License: GPL. Copyright 2008 by Jan Peter Stotz
    44
    5 import java.util.Comparator;
    65import java.util.Hashtable;
    7 import java.util.Map;
    8 import java.util.Set;
    9 import java.util.TreeSet;
    10 import java.util.Map.Entry;
    116import java.util.logging.Logger;
    127
    138/**
     9 * {@link TileCache} implementation that stores all {@link Tile} objects in
     10 * memory up to a certain limit ({@link #getCacheSize()}). If the limit is
     11 * exceeded the least recently used {@link Tile} objects will be deleted.
    1412 *
    1513 * @author Jan Peter Stotz
     
    1715public class MemoryTileCache implements TileCache {
    1816
    19         private static final Logger log = Logger.getLogger(MemoryTileCache.class
    20                         .getName());
    21 
    22         protected int cacheSizeMax = 200;
     17        private static final Logger log = Logger.getLogger(MemoryTileCache.class.getName());
    2318
    2419        /**
    25          * Number of tiles after a cache cleanup via {@link #removeOldTiles()};
     20         * Default cache size
    2621         */
    27         protected int cacheSizeDefault = 100;
     22        protected int cacheSize = 200;
    2823
    2924        protected Hashtable<String, CacheEntry> hashtable;
    3025
    3126        /**
    32          * A logical clock that "ticks" every time a tile is retrieved from the
    33          * cache
     27         * List of all tiles in their last recently used order
    3428         */
    35         protected int currentAccessTime = 0;
     29        protected CacheLinkedListElement lruTiles;
    3630
    3731        public MemoryTileCache() {
    38                 hashtable = new Hashtable<String, CacheEntry>(200);
     32                hashtable = new Hashtable<String, CacheEntry>(cacheSize);
     33                lruTiles = new CacheLinkedListElement();
    3934        }
    4035
    41         public synchronized void addTile(Tile tile) {
    42                 hashtable.put(tile.getKey(), new CacheEntry(tile, currentAccessTime));
    43                 if (hashtable.size() > cacheSizeMax)
     36        public void addTile(Tile tile) {
     37                CacheEntry entry = new CacheEntry(tile);
     38                hashtable.put(tile.getKey(), entry);
     39                lruTiles.addFirst(entry);
     40                if (hashtable.size() > cacheSize)
    4441                        removeOldTiles();
    4542        }
     
    4946                if (entry == null)
    5047                        return null;
    51                 currentAccessTime++;
    52                 entry.lastAccess = currentAccessTime;
    53                 // We are right before an integer overflow!!
    54                 if (currentAccessTime == Integer.MAX_VALUE)
    55                         removeOldTiles();
     48                // We don't care about placeholder tiles and hourglass image tiles, the
     49                // important tiles are the loaded ones
     50                if (entry.tile.isLoaded())
     51                        lruTiles.moveElementToFirstPos(entry);
    5652                return entry.tile;
    5753        }
    5854
    5955        /**
    60          * Removes the least recently used tiles and rewrites the
    61          * {@link CacheEntry#lastAccess} of all remaining entries (-n to 0).
    62          *
    63          * WARNING: While this method is running modifying the {@link #hashtable} is
    64          * forbidden! Therefore this method and {@link #addTile(Tile)} are declared
    65          * as synchronized.
     56         * Removes the least recently used tiles
    6657         */
    67         protected synchronized void removeOldTiles() {
    68                 try {
    69                         Set<Map.Entry<String, CacheEntry>> entries = hashtable.entrySet();
    70                         TreeSet<Map.Entry<String, CacheEntry>> sortedEntries;
    71                         // Sort the entries according to their access time
    72                         sortedEntries = new TreeSet<Map.Entry<String, CacheEntry>>(
    73                                         new MEComparator());
    74                         sortedEntries.addAll(entries);
    75                         // System.out.println("Tiles in Cache: " + hashtable.size() +
    76                         // " lru=" + currentAccessTime);
    77                         int tilecount = 0;
    78                         for (Map.Entry<String, CacheEntry> entry : sortedEntries) {
    79                                 tilecount++;
    80                                 if (tilecount < cacheSizeDefault) {
    81                                         entry.getValue().lastAccess = -tilecount;
    82                                 } else {
    83                                         // System.out.println("removing entry :"
    84                                         // + entry.getValue().lastAccess);
    85                                         entries.remove(entry);
     58        protected void removeOldTiles() {
     59                synchronized (lruTiles) {
     60                        try {
     61                                while (lruTiles.getElementCount() > cacheSize) {
     62                                        CacheEntry entry = lruTiles.getLastElement();
     63                                        hashtable.remove(entry.tile.getKey());
     64                                        lruTiles.removeEntry(entry);
    8665                                }
     66                        } catch (Exception e) {
     67                                log.warning(e.getMessage());
    8768                        }
    88                         // We can now safely reset the the logical clock
    89                         currentAccessTime = 1;
    90                         // System.out.println("Tiles in Cache: " + hashtable.size() +
    91                         // " lru=" + currentAccessTime);
    92                 } catch (Exception e) {
    93                         log.severe(e.toString());
    9469                }
    9570        }
    9671
    97         public int getCacheSizeMax() {
    98                 return cacheSizeMax;
     72        public int getTileCount() {
     73                return hashtable.size();
    9974        }
    10075
    101         public void setCacheSizeMax(int cacheSizeMax) {
    102                 this.cacheSizeMax = cacheSizeMax;
    103                 this.cacheSizeDefault = cacheSizeMax / 2;
     76        public int getCacheSize() {
     77                return cacheSize;
    10478        }
    10579
    106         protected static class CacheEntry implements Comparable<CacheEntry> {
    107                 int lastAccess;
     80        /**
     81         * Changes the maximum number of {@link Tile} objects that this cache holds.
     82         *
     83         * @param cacheSize
     84         *            new maximum number of tiles
     85         */
     86        public void setCacheSize(int cacheSize) {
     87                this.cacheSize = cacheSize;
     88                if (hashtable.size() > cacheSize)
     89                        removeOldTiles();
     90        }
     91
     92        /**
     93         * Linked list element holding the {@link Tile} and links to the
     94         * {@link #next} and {@link #prev} item in the list.
     95         */
     96        protected static class CacheEntry {
    10897                Tile tile;
    10998
    110                 protected CacheEntry(Tile tile, int currentAccessTime) {
     99                CacheEntry next;
     100                CacheEntry prev;
     101
     102                protected CacheEntry(Tile tile) {
    111103                        this.tile = tile;
    112                         lastAccess = currentAccessTime;
    113104                }
    114105
    115                 public int compareTo(CacheEntry o) {
    116                         if (lastAccess > o.lastAccess)
    117                                 return -1;
    118                         else
    119                                 return 1;
    120                 }
    121106        }
    122107
    123         protected static class MEComparator implements
    124                         Comparator<Map.Entry<String, CacheEntry>> {
     108        /**
     109         * Special implementation of a double linked list for {@link CacheEntry}
     110         * elements. It supports element removal in constant time - in difference to
     111         * the Java implementation which needs O(n).
     112         *
     113         * @author Jan Peter Stotz
     114         */
     115        protected static class CacheLinkedListElement {
     116                protected CacheEntry firstElement = null;
     117                protected CacheEntry lastElement;
     118                protected int elementCount;
    125119
    126                 public int compare(Entry<String, CacheEntry> o1,
    127                                 Entry<String, CacheEntry> o2) {
    128                         return o1.getValue().compareTo(o2.getValue());
     120                public CacheLinkedListElement() {
     121                        elementCount = 0;
     122                        firstElement = null;
     123                        lastElement = null;
     124                }
     125
     126                /**
     127                 * Add the element to the head of the list.
     128                 *
     129                 * @param new element to be added
     130                 */
     131                public synchronized void addFirst(CacheEntry element) {
     132                        if (elementCount == 0) {
     133                                firstElement = element;
     134                                lastElement = element;
     135                                element.prev = null;
     136                                element.next = null;
     137                        } else {
     138                                element.next = firstElement;
     139                                firstElement.prev = element;
     140                                element.prev = null;
     141                                firstElement = element;
     142                        }
     143                        elementCount++;
     144                }
     145
     146                /**
     147                 * Removes the specified elemntent form the list.
     148                 *
     149                 * @param element
     150                 *            to be removed
     151                 */
     152                public synchronized void removeEntry(CacheEntry element) {
     153                        if (element.next != null) {
     154                                element.next.prev = element.prev;
     155                        }
     156                        if (element.prev != null) {
     157                                element.prev.next = element.next;
     158                        }
     159                        if (element == firstElement)
     160                                firstElement = element.next;
     161                        if (element == lastElement)
     162                                lastElement = element.prev;
     163                        element.next = null;
     164                        element.prev = null;
     165                        elementCount--;
     166                }
     167
     168                public synchronized void moveElementToFirstPos(CacheEntry entry) {
     169                        if (firstElement == entry)
     170                                return;
     171                        removeEntry(entry);
     172                        addFirst(entry);
     173                }
     174
     175                public int getElementCount() {
     176                        return elementCount;
     177                }
     178
     179                public CacheEntry getLastElement() {
     180                        return lastElement;
    129181                }
    130182        }
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java

    r9263 r9288  
    77import java.awt.geom.AffineTransform;
    88import java.awt.image.BufferedImage;
    9 import java.io.DataInputStream;
    10 import java.io.IOException;
    11 import java.net.URL;
    12 import java.net.URLConnection;
    13 
    14 import javax.imageio.ImageIO;
    159
    1610/**
     
    3024        public static final int WIDTH = 256;
    3125        public static final int HEIGHT = 256;
    32         public static final int WIDTH_HALF = 128;
    33         public static final int HEIGHT_HALF = 128;
    3426
    3527        /**
     
    6355                Graphics2D g = (Graphics2D) tmpImage.getGraphics();
    6456                // g.drawImage(image, 0, 0, null);
    65                 for (int zoomDiff = 1; zoomDiff < 3; zoomDiff++) {
     57                for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
    6658                        // first we check if there are already the 2^x tiles
    6759                        // of a higher detail level
    6860                        int zoom_high = zoom + zoomDiff;
    69                         if (zoom_high <= JMapViewer.MAX_ZOOM) {
     61                        if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
    7062                                int factor = 1 << zoomDiff;
    7163                                int xtile_high = xtile << zoomDiff;
     
    135127        }
    136128
     129        public void setImage(BufferedImage image) {
     130                this.image = image;
     131        }
     132
    137133        /**
    138134         * @return key that identifies a tile
     
    146142        }
    147143
    148         public synchronized void loadTileImage() throws IOException {
    149                 if (loaded)
    150                         return;
    151                 URL url;
    152                 URLConnection urlConn;
    153                 DataInputStream input;
    154                 url = new URL("http://tile.openstreetmap.org/" + zoom + "/" + xtile + "/" + ytile + ".png");
    155                 // System.out.println(url);
    156                 urlConn = url.openConnection();
    157                 // urlConn.setUseCaches(false);
    158                 input = new DataInputStream(urlConn.getInputStream());
    159                 image = ImageIO.read(input);
    160                 input.close();
    161                 loaded = true;
     144        public void setLoaded(boolean loaded) {
     145                this.loaded = loaded;
    162146        }
    163147
     
    179163
    180164        @Override
     165        public String toString() {
     166                return "Tile " + getTileKey(xtile, ytile, zoom);
     167        }
     168
     169        @Override
    181170        public boolean equals(Object obj) {
    182171                if (!(obj instanceof Tile))
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java

    r9095 r9288  
    3434         */
    3535        public void addTile(Tile tile);
     36
     37        /**
     38         * @return the number of tiles hold by the cache
     39         */
     40        public int getTileCount();
    3641}
Note: See TracChangeset for help on using the changeset viewer.