Java tutorial
/* uDig - User Friendly Desktop Internet GIS client * http://udig.refractions.net * (C) 2008-2011, Refractions Research Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD * License v1.0 (http://udig.refractions.net/files/bsd3-v10.html). */ package net.refractions.udig.catalog.wmsc.server; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.collections.MapUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.geotools.data.ows.AbstractOpenWebService; import com.vividsolutions.jts.geom.Envelope; /** * An AbstractTileRange represents a set of Tiles in a given bounds. * This TileRange is responsible for fetching the individual tiles if they are not yet * "complete" and ready to paint, which it does through a TiledWebMapServer. The * TileRange really only lives for the cycle of fetching all the tiles. Once the * TileRange has finished loading (when all the tiles are complete), its use is * pretty much done. * * Every time a tile is "completed", all listeners will be notified. * * @author GDavis * @since 1.2.0 */ public abstract class AbstractTileRange implements TileRange { protected Map<String, Tile> allTiles; protected Envelope bounds; protected AbstractOpenWebService<?, ?> server; protected TileSet tileset; protected TileWorkerQueue requestTileWorkQueue; // queue of threads for requesting tiles protected boolean using_threadpools = false; // set in the constructor protected final static boolean testing = false; // for testing output /** keep track of all tile listeners **/ protected Set<TileListener> tileListeners = new HashSet<TileListener>(); /** * This lock controls access to tilesWaitingToLoad. */ protected ReentrantReadWriteLock tilesWaitingToLoad_lock = new ReentrantReadWriteLock(); /** * Map of tiles that are not yet loaded; may be empty. */ protected volatile Map<String, Tile> tilesWaitingToLoad = new HashMap<String, Tile>(); /** * A TileRange implementation where you can fill in the fetching protocol. * @param server The Server to fetch the tiles from * @param tileset TileSet of rendered content * @param bounds Optional bounds (may be null) that contain the provided tiles * @param tiles The tiles that we wish to fetch; must be from the provided tileset * @param requestTileWorkQueue Queue of worker threads that can be used to fetch tiles */ public AbstractTileRange(AbstractOpenWebService<?, ?> server, TileSet tileset, Envelope bounds, Map<String, Tile> tiles, TileWorkerQueue requestTileWorkQueue) { this.server = server; this.tileset = tileset; if (tiles != null) { this.allTiles = tiles; } else { this.allTiles = new HashMap<String, Tile>(); } if (bounds != null) { this.bounds = bounds; } else { this.bounds = calculateBounds(); } if (requestTileWorkQueue == null) { using_threadpools = false; //this.requestTileWorkQueue = new TileWorkerQueue(TileWorkerQueue.defaultWorkingQueueSize); } else { using_threadpools = true; this.requestTileWorkQueue = requestTileWorkQueue; } // calculate what tiles are not yet loaded setTilesNotLoaded(); } /** * Go through every tile and pick out the ones that are not yet loaded and set * them in the notLoaded list. */ protected void setTilesNotLoaded() { try { tilesWaitingToLoad_lock.writeLock().lock(); for (Iterator<Entry<String, Tile>> iterator = allTiles.entrySet().iterator(); iterator.hasNext();) { Entry<String, Tile> tileentry = (Entry<String, Tile>) iterator.next(); if (tileentry.getValue().getBufferedImage() == null) { tilesWaitingToLoad.put(tileentry.getKey(), tileentry.getValue()); } } } finally { // unlock the write lock tilesWaitingToLoad_lock.writeLock().unlock(); } } /** * This is the entry point of this class once it has been created. It will * ensure all the tiles are loaded. If a tile is not yet loaded it will * create a request thread to fetch it, blocking until all the tiles are loaded. * * @param monitor */ public void loadTiles(IProgressMonitor monitor) { // load each tile not yet loaded. Lock on the list of // tiles to load. Set<String> removeTiles = new HashSet<String>(); try { tilesWaitingToLoad_lock.writeLock().lock(); // now that we have a lock, check if this has been canceled while waiting if (monitor.isCanceled()) { dispose(); return; } Set<Entry<String, Tile>> entrySet = tilesWaitingToLoad.entrySet(); for (Entry<String, Tile> set : entrySet) { String tileid = set.getKey(); Tile tile = set.getValue(); // only send a request to get the tile if we don't have it already if (tile.getBufferedImage() == null) { try { loadTile(tile, new NullProgressMonitor()); } catch (Exception e) { // there was some error getting the tile, so instead of blocking // forever because of the error, set the tile to be removed // from the loading list and continue. e.printStackTrace(); removeTiles.add(tileid); } } else { //we already have this image; somehow it was filled in for us between when we first //asked for tiles and now. So we need to notify any listeners tileLoaded(tile); removeTiles.add(tileid); // set tile to be removed from loading list } } // remove all successfully completed tiles and any error tiles from the list // of loading tiles to prevent endless blocking. for (String key : removeTiles) { tilesWaitingToLoad.remove(key); } } finally { // unlock the write lock // until this is released any jobs would get stuck waiting to update it tilesWaitingToLoad_lock.writeLock().unlock(); } } /** * remove all listeners and do any other cleanup */ public void dispose() { // remove all listeners for (TileListener listener : tileListeners) { removeListener(listener); } } /** * Notify any listeners and update the tile list that the given * tile is now loaded * * @param tile */ public void tileLoaded(Tile tile) { // notify any listeners that the tile is ready notifyListenersTileReady(tile); } /** * Remove the given tile from the loading list and ensure the lock is obtained before * modifying it. * * @return */ protected void removeTileFromLoadingList(Tile tile) { // get a write lock on the tile loading list to remove this tile // from the loading list try { tilesWaitingToLoad_lock.writeLock().lock(); tilesWaitingToLoad.remove(tile.getId()); } finally { tilesWaitingToLoad_lock.writeLock().unlock(); } } protected Envelope calculateBounds() { Envelope newBounds = new Envelope(); for (String key : allTiles.keySet()) { newBounds.expandToInclude(allTiles.get(key).getBounds()); } return newBounds; } public Tile getTile(String tileId) { if (tileId == null) { return null; } return allTiles.get(tileId); } /** * Returns the tiles in the given range as an unmodifiable * map. * * @return */ @SuppressWarnings("unchecked") public Map<String, Tile> getTiles() { return MapUtils.unmodifiableMap(allTiles); } public int getTileCount() { return allTiles.keySet().size(); } /** * Returns whether every tile in the range is complete and ready * to paint * * @return boolean */ public boolean isComplete() { for (Iterator<Entry<String, Tile>> iterator = allTiles.entrySet().iterator(); iterator.hasNext();) { Entry<String, Tile> value = (Entry<String, Tile>) iterator.next(); if (value.getValue().getBufferedImage() == null) { return false; } } return true; } public Envelope getRangeBounds() { return bounds; } /** * This method will see if we are using thread pools and either create a runnable * to load the tile using the pool or create a single thread to do it. It will * lock on the tile. * * @param tile * @param monitor * @throws Exception */ protected void loadTile(final Tile tile, final IProgressMonitor monitor) throws Exception { if (using_threadpools) { Runnable r = new Runnable() { public void run() { internalLoadTile(tile, monitor); } }; requestTileWorkQueue.execute(r); } else { Thread t = new Thread() { @Override public void run() { internalLoadTile(tile, monitor); } }; t.start(); } } /** * Do the work of loading a tile * * @param tile * @param monitor */ private void internalLoadTile(final Tile tile, final IProgressMonitor monitor) { tile.loadTile(monitor); // now that the tile lock is unlocked, notify any listeners and // update the not loaded list (we want to do this whether we got // the tile or not because we don't want the blocking queue // in the rendering to wait blocked forever for missing tiles) tileLoaded(tile); removeTileFromLoadingList(tile); cacheTile(tile); // cache the tile with whatever method is setup } protected BufferedImage createErrorImage() { BufferedImage bf = new BufferedImage(tileset.getWidth(), tileset.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g = bf.createGraphics(); g.setColor(Color.RED); g.drawLine(0, 0, tileset.getWidth(), tileset.getHeight()); g.drawLine(0, tileset.getHeight(), tileset.getWidth(), 0); return bf; } /** * Notify all tile listeners that a tile can now be drawn * * @param tile to draw */ protected void notifyListenersTileReady(Tile tile) { for (TileListener listener : tileListeners) { listener.notifyTileReady(tile); } } public void addListener(TileListener listener) { tileListeners.add(listener); } public void removeListener(TileListener listener) { tileListeners.remove(listener); } }