net.refractions.udig.catalog.wmsc.server.AbstractTileRange.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.catalog.wmsc.server.AbstractTileRange.java

Source

/* 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);
    }

}