Index: /applications/viewer/jmapviewer/build.xml
===================================================================
--- /applications/viewer/jmapviewer/build.xml	(revision 9095)
+++ /applications/viewer/jmapviewer/build.xml	(revision 9095)
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project default="all" name="Compile and build java classes plus jar archives">
+
+	<target name="all" depends="clean,build,pack,create_run_jar" />
+
+	<target name="clean">
+		<delete>
+			<fileset dir="bin">
+				<include name="**" />
+			</fileset>
+		</delete>
+	</target>
+
+	<target name="build">
+		<javac srcdir="src" destdir="bin" target="1.5" />
+		<copy todir="bin">
+			<fileset dir="src">
+				<include name="**/*.png" />
+			</fileset>
+		</copy>
+	</target>
+
+	<target name="pack">
+		<delete file="JMapViewer.jar" />
+		<delete file="JMapViewer_src.jar" />
+		<jar destfile="JMapViewer.jar" filesetmanifest="mergewithoutmain">
+			<fileset dir="bin" includes="**/jmapviewer/**" />
+		</jar>
+		<jar destfile="JMapViewer_src.jar" filesetmanifest="mergewithoutmain">
+			<fileset dir="src" includes="**/jmapviewer/**" />
+		</jar>
+	</target>
+
+	<target name="create_run_jar">
+		<delete file="JMapViewer_Demo.jar" />
+		<jar destfile="JMapViewer_Demo.jar" filesetmanifest="mergewithoutmain">
+			<manifest>
+				<attribute name="Built-By" value="${user.name}" />
+				<attribute name="Main-Class" value="org.openstreetmap.gui.jmapviewer.Demo" />
+				<attribute name="Class-Path" value="." />
+			</manifest>
+			<fileset dir="bin" includes="**/jmapviewer/**" />
+			<fileset dir="src" includes="**/jmapviewer/**/*.java" />
+		</jar>
+	</target>
+</project>
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java	(revision 9095)
@@ -0,0 +1,130 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+
+/**
+ * Default map controller which implements map moving by pressing the right
+ * mouse button and zooming by double click or by mouse wheel.
+ * 
+ * @author Jan Peter Stotz
+ * 
+ */
+public class DefaultMapController extends JMapController implements MouseListener,
+		MouseMotionListener, MouseWheelListener {
+
+	public DefaultMapController(JMapViewer map) {
+		super(map);
+	}
+
+	private Point lastDragPoint;
+
+	private boolean isMoving = false;
+
+	private boolean movementEnabled = true;
+
+	private int movementMouseButton = MouseEvent.BUTTON3;
+
+	private boolean wheelZoomEnabled = true;
+	private boolean doubleClickZoomEnabled = true;
+
+	public void mouseDragged(MouseEvent e) {
+		if (!movementEnabled || !isMoving)
+			return;
+		if (lastDragPoint != null) {
+			Point p = e.getPoint();
+			int diffx = lastDragPoint.x - p.x;
+			int diffy = lastDragPoint.y - p.y;
+			map.move(diffx, diffy);
+		}
+		lastDragPoint = e.getPoint();
+	}
+
+	public void mouseMoved(MouseEvent e) {
+	}
+
+	public void mouseClicked(MouseEvent e) {
+		if (doubleClickZoomEnabled && e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1)
+			map.setZoom(map.getZoom() + 1, e.getPoint());
+	}
+
+	public void mouseEntered(MouseEvent e) {
+	}
+
+	public void mouseExited(MouseEvent e) {
+	}
+
+	public void mousePressed(MouseEvent e) {
+		if (e.getButton() == movementMouseButton) {
+			lastDragPoint = null;
+			isMoving = true;
+		}
+	}
+
+	public void mouseReleased(MouseEvent e) {
+		if (e.getButton() == movementMouseButton) {
+			lastDragPoint = null;
+			isMoving = false;
+		}
+	}
+
+	public void mouseWheelMoved(MouseWheelEvent e) {
+		if (wheelZoomEnabled)
+			map.setZoom(map.getZoom() - e.getWheelRotation(), e.getPoint());
+	}
+
+	public boolean isMovementEnabled() {
+		return movementEnabled;
+	}
+
+	/**
+	 * Enables or disables that the map pane can be moved using the mouse.
+	 * 
+	 * @param movementEnabled
+	 */
+	public void setMovementEnabled(boolean movementEnabled) {
+		this.movementEnabled = movementEnabled;
+	}
+
+	public int getMovementMouseButton() {
+		return movementMouseButton;
+	}
+
+	/**
+	 * Sets the mouse button that is used for moving the map. Possible values
+	 * are:
+	 * <ul>
+	 * <li>{@link MouseEvent#BUTTON1} (left mouse button)</li>
+	 * <li>{@link MouseEvent#BUTTON2} (middle mouse button)</li>
+	 * <li>{@link MouseEvent#BUTTON3} (right mouse button)</li>
+	 * </ul>
+	 * 
+	 * @param movementMouseButton
+	 */
+	public void setMovementMouseButton(int movementMouseButton) {
+		this.movementMouseButton = movementMouseButton;
+	}
+
+	public boolean isWheelZoomEnabled() {
+		return wheelZoomEnabled;
+	}
+
+	public void setWheelZoomEnabled(boolean wheelZoomEnabled) {
+		this.wheelZoomEnabled = wheelZoomEnabled;
+	}
+
+	public boolean isDoubleClickZoomEnabled() {
+		return doubleClickZoomEnabled;
+	}
+
+	public void setDoubleClickZoomEnabled(boolean doubleClickZoomEnabled) {
+		this.doubleClickZoomEnabled = doubleClickZoomEnabled;
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9095)
@@ -0,0 +1,91 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * 
+ * Demonstrates the usage of {@link JMapViewer}
+ * 
+ * @author Jan Peter Stotz
+ * 
+ */
+public class Demo extends JFrame {
+
+	public Demo() {
+		super("JMapViewer Demo");
+		setSize(400, 400);
+		final JMapViewer map = new JMapViewer();
+		setLayout(new BorderLayout());
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		setExtendedState(JFrame.MAXIMIZED_BOTH);
+		JPanel panel = new JPanel();
+		add(panel, BorderLayout.NORTH);
+		JLabel label =
+				new JLabel(
+						"Use right mouse button to move,\n left double click or mouse wheel to zoom.");
+		panel.add(label);
+		JButton button = new JButton("setDisplayToFitMapMarkers");
+		button.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				map.setDisplayToFitMapMarkers();
+			}
+		});
+		panel.add(button);
+		final JCheckBox showMapMarker = new JCheckBox("Map markers visible");
+		showMapMarker.setSelected(map.getMapMarkersVisible());
+		showMapMarker.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				map.setMapMarkerVisible(showMapMarker.isSelected());
+			}
+		});
+		panel.add(showMapMarker);
+		final JCheckBox showTileGrid = new JCheckBox("Tile grid visible");
+		showTileGrid.setSelected(map.isTileGridVisible());
+		showTileGrid.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				map.setTileGridVisible(showTileGrid.isSelected());
+			}
+		});
+		panel.add(showTileGrid);
+		final JCheckBox showZoomControls = new JCheckBox("Show zoom controls");
+		showZoomControls.setSelected(map.getZoomContolsVisible());
+		showZoomControls.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				map.setZoomContolsVisible(showZoomControls.isSelected());
+			}
+		});
+		panel.add(showZoomControls);
+		add(map, BorderLayout.CENTER);
+
+		//
+		map.addMapMarker(new MapMarkerDot(49.814284999, 8.642065999));
+		map.addMapMarker(new MapMarkerDot(49.91, 8.24));
+		map.addMapMarker(new MapMarkerDot(49.71, 8.64));
+		map.addMapMarker(new MapMarkerDot(48.71, -1));
+		map.addMapMarker(new MapMarkerDot(49.807, 8.644));
+
+		// map.setDisplayPositionByLatLon(49.807, 8.644, 11);
+	}
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		new Demo().setVisible(true);
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java	(revision 9095)
@@ -0,0 +1,35 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelListener;
+
+/**
+ * Abstract base class for all mouse controller implementations. For
+ * implementing your own controller create a class that derives from this one
+ * and implements one or more of the following interfaces:
+ * <ul>
+ * <li>{@link MouseListener}</li>
+ * <li>{@link MouseMotionListener}</li>
+ * <li>{@link MouseWheelListener}</li>
+ * </ul>
+ * 
+ * @author Jan Peter Stotz
+ */
+public abstract class JMapController {
+
+	JMapViewer map;
+
+	public JMapController(JMapViewer map) {
+		this.map = map;
+		if (this instanceof MouseListener)
+			map.addMouseListener((MouseListener) this);
+		if (this instanceof MouseWheelListener)
+			map.addMouseWheelListener((MouseWheelListener) this);
+		if (this instanceof MouseMotionListener)
+			map.addMouseMotionListener((MouseMotionListener) this);
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9095)
@@ -0,0 +1,472 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.openstreetmap.gui.jmapviewer.JobDispatcher.JobThread;
+
+/**
+ * 
+ * Provides a simple panel that displays pre-rendered map tiles loaded from the
+ * OpenStreetMap project.
+ * 
+ * @author Jan Peter Stotz
+ * 
+ */
+public class JMapViewer extends JPanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public static final int MAX_ZOOM = 18;
+	public static final int MIN_ZOOM = 0;
+
+	protected TileCache tileCache;
+	protected List<MapMarker> mapMarkerList;
+	protected boolean mapMarkersVisible;
+	protected boolean tileGridVisible;
+
+	/**
+	 * x- and y-position of the center of this map-panel on the world map
+	 * denoted in screen pixel regarding the current zoom level.
+	 */
+	protected Point center;
+
+	/**
+	 * Current zoom level
+	 */
+	protected int zoom;
+
+	protected JSlider zoomSlider;
+	protected JButton zoomInButton;
+	protected JButton zoomOutButton;
+
+	/**
+	 * Hourglass image that is displayed until a map tile has been loaded
+	 */
+	protected BufferedImage loadingImage;
+
+	JobDispatcher jobDispatcher;
+
+	/**
+	 * Creates a standard {@link JMapViewer} instance that can be controlled via
+	 * mouse: hold right mouse button for moving, double click left mouse button
+	 * or use mouse wheel for zooming. Loaded tiles are stored the
+	 * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
+	 * retrieving the tiles.
+	 */
+	public JMapViewer() {
+		this(new MemoryTileCache(), 4);
+		new DefaultMapController(this);
+	}
+
+	public JMapViewer(TileCache tileCache, int downloadThreadCount) {
+		super();
+		this.tileCache = tileCache;
+		jobDispatcher = new JobDispatcher(downloadThreadCount);
+		mapMarkerList = new LinkedList<MapMarker>();
+		mapMarkersVisible = true;
+		tileGridVisible = false;
+		setLayout(null);
+		initializeZoomSlider();
+		setMinimumSize(new Dimension(Tile.WIDTH, Tile.HEIGHT));
+		setPreferredSize(new Dimension(400, 400));
+		try {
+			loadingImage = ImageIO.read(getClass().getResourceAsStream("images/hourglass.png"));
+		} catch (IOException e1) {
+		}
+		setDisplayPositionByLatLon(50, 9, 3);
+	}
+
+	protected void initializeZoomSlider() {
+		zoomSlider = new JSlider(MIN_ZOOM, MAX_ZOOM);
+		zoomSlider.setOrientation(JSlider.VERTICAL);
+		zoomSlider.setBounds(10, 10, 30, 150);
+		zoomSlider.setOpaque(false);
+		zoomSlider.addChangeListener(new ChangeListener() {
+			public void stateChanged(ChangeEvent e) {
+				setZoom(zoomSlider.getValue());
+			}
+		});
+		add(zoomSlider);
+		int size = 18;
+		try {
+			ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
+			zoomInButton = new JButton(icon);
+		} catch (Exception e) {
+			zoomInButton = new JButton("+");
+			zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
+			zoomInButton.setMargin(new Insets(0, 0, 0, 0));
+		}
+		zoomInButton.setBounds(4, 155, size, size);
+		zoomInButton.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				zoomIn();
+			}
+		});
+		add(zoomInButton);
+		try {
+			ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
+			zoomOutButton = new JButton(icon);
+		} catch (Exception e) {
+			zoomOutButton = new JButton("-");
+			zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
+			zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
+		}
+		zoomOutButton.setBounds(8 + size, 155, size, size);
+		zoomOutButton.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				zoomOut();
+			}
+		});
+		add(zoomOutButton);
+	}
+
+	/**
+	 * Changes the map pane so that it is centered on the specified coordinate
+	 * at the given zoom level.
+	 * 
+	 * @param lat
+	 *            latitude of the specified coordinate
+	 * @param lon
+	 *            longitude of the specified coordinate
+	 * @param zoom
+	 *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
+	 */
+	public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
+		setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
+	}
+
+	/**
+	 * Changes the map pane so that the specified coordinate at the given zoom
+	 * level is displayed on the map at the screen coordinate
+	 * <code>mapPoint</code>.
+	 * 
+	 * @param mapPoint
+	 *            point on the map denoted in pixels where the coordinate should
+	 *            be set
+	 * @param lat
+	 *            latitude of the specified coordinate
+	 * @param lon
+	 *            longitude of the specified coordinate
+	 * @param zoom
+	 *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
+	 */
+	public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
+		int x = OsmMercator.LonToX(lon, zoom);
+		int y = OsmMercator.LatToY(lat, zoom);
+		setDisplayPosition(mapPoint, x, y, zoom);
+	}
+
+	public void setDisplayPosition(int x, int y, int zoom) {
+		setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
+	}
+
+	public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
+		if (zoom > MAX_ZOOM || zoom < MIN_ZOOM)
+			return;
+
+		// Substract the calculated values so that we get the x/y index of the
+		// upper left tile again including fraction where in the tile is our
+		// map origin point (0,0)
+
+		// Get the plain tile number
+		Point p = new Point();
+		p.x = x - mapPoint.x + getWidth() / 2;
+		p.y = y - mapPoint.y + getHeight() / 2;
+		center = p;
+		this.zoom = zoom;
+		if (zoomSlider.getValue() != zoom)
+			zoomSlider.setValue(zoom);
+		repaint();
+	}
+
+	/**
+	 * Sets the displayed map pane and zoom level so that all map markers are
+	 * visible.
+	 */
+	public void setDisplayToFitMapMarkers() {
+		if (mapMarkerList == null || mapMarkerList.size() == 0)
+			return;
+		int x_min = Integer.MAX_VALUE;
+		int y_min = Integer.MAX_VALUE;
+		int x_max = Integer.MIN_VALUE;
+		int y_max = Integer.MIN_VALUE;
+		for (MapMarker marker : mapMarkerList) {
+			int x = OsmMercator.LonToX(marker.getLon(), MAX_ZOOM);
+			int y = OsmMercator.LatToY(marker.getLat(), MAX_ZOOM);
+			x_max = Math.max(x_max, x);
+			y_max = Math.max(y_max, y);
+			x_min = Math.min(x_min, x);
+			y_min = Math.min(y_min, y);
+		}
+		int height = Math.max(0, getHeight());
+		int width = Math.max(0, getWidth());
+		// System.out.println(x_min + " < x < " + x_max);
+		// System.out.println(y_min + " < y < " + y_max);
+		// System.out.println("tiles: " + width + " " + height);
+		int zoom = MAX_ZOOM;
+		int x = x_max - x_min;
+		int y = y_max - y_min;
+		while (x > width || y > height) {
+			// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
+			zoom--;
+			x >>= 1;
+			y >>= 1;
+		}
+		x = x_min + (x_max - x_min) / 2;
+		y = y_min + (y_max - y_min) / 2;
+		int z = 1 << (MAX_ZOOM - zoom);
+		x /= z;
+		y /= z;
+		setDisplayPosition(x, y, zoom);
+	}
+
+	public Point2D.Double getPosition() {
+		double lon = OsmMercator.XToLon(center.x, zoom);
+		double lat = OsmMercator.YToLat(center.y, zoom);
+		return new Point2D.Double(lat, lon);
+	}
+
+	public Point2D.Double getPosition(Point mapPoint) {
+		int x = center.x + mapPoint.x - getWidth() / 2;
+		int y = center.y + mapPoint.y - getHeight() / 2;
+		double lon = OsmMercator.XToLon(x, zoom);
+		double lat = OsmMercator.YToLat(y, zoom);
+		return new Point2D.Double(lat, lon);
+	}
+
+	/**
+	 * Calculates the position on the map of a given coordinate
+	 * 
+	 * @param lat
+	 * @param lon
+	 * @return point on the map or <code>null</code> if the point is not visible
+	 */
+	public Point getMapPosition(double lat, double lon) {
+		int x = OsmMercator.LonToX(lon, zoom);
+		int y = OsmMercator.LatToY(lat, zoom);
+		x -= center.x - getWidth() / 2;
+		y -= center.y - getHeight() / 2;
+		if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
+			return null;
+		return new Point(x, y);
+	}
+
+	@Override
+	protected void paintComponent(Graphics g) {
+		super.paintComponent(g);
+
+		// Optimization for loading the centered tile in a lower zoom first
+		if (zoom > MIN_ZOOM) {
+			int center_tx = center.x / Tile.WIDTH;
+			int center_ty = center.y / Tile.HEIGHT;
+			Tile centerTile = tileCache.getTile(center_tx, center_ty, zoom);
+			if (centerTile == null || !centerTile.isLoaded()) {
+				// tile in the center of the screen is not loaded, for faster
+				// displaying anything in the center we first load a tile of a
+				// lower zoom level
+				getTile(center_tx / 2, center_ty / 2, zoom - 1);
+			}
+		}
+		// Regular tile painting
+		int left = center.x - getWidth() / 2;
+		int top = center.y - getHeight() / 2;
+
+		int tilex = left / Tile.WIDTH;
+		int tiley = top / Tile.HEIGHT;
+		int off_x = left % Tile.WIDTH;
+		int off_y = top % Tile.HEIGHT;
+		for (int x = -off_x; x < getWidth(); x += Tile.WIDTH) {
+			int tiley_tmp = tiley;
+			for (int y = -off_y; y < getHeight(); y += Tile.HEIGHT) {
+				Tile tile = getTile(tilex, tiley_tmp, zoom);
+				if (tile != null) {
+					if (!tile.isLoaded()) {
+						// Paint stretched preview from the next lower zoom
+						// level (if already loaded)
+						Tile parent = tile.getParentTile(tileCache);
+						if (parent != null && parent.isLoaded()) {
+							int parentx = tile.getXtile() % 2;
+							int parenty = tile.getYtile() % 2;
+							parent.parentPaint(g, x, y, parentx, parenty);
+						} else
+							tile.paint(g, x, y);
+					} else
+						tile.paint(g, x, y);
+				}
+				if (tileGridVisible)
+					g.drawRect(x, y, Tile.WIDTH, Tile.HEIGHT);
+				tiley_tmp++;
+			}
+			tilex++;
+		}
+		if (!mapMarkersVisible || mapMarkerList == null)
+			return;
+		for (MapMarker marker : mapMarkerList) {
+			Point p = getMapPosition(marker.getLat(), marker.getLon());
+			// System.out.println(marker + " -> " + p);
+			if (p != null)
+				marker.paint(g, p);
+		}
+	}
+
+	/**
+	 * Moves the visible map pane.
+	 * 
+	 * @param x
+	 *            horizontal movement in pixel.
+	 * @param y
+	 *            vertical movement in pixel
+	 */
+	public void move(int x, int y) {
+		center.x += x;
+		center.y += y;
+		repaint();
+	}
+
+	/**
+	 * @return the current zoom level
+	 */
+	public int getZoom() {
+		return zoom;
+	}
+
+	/**
+	 * Increases the current zoom level by one
+	 */
+	public void zoomIn() {
+		setZoom(zoom + 1);
+	}
+
+	/**
+	 * Decreases the current zoom level by one
+	 */
+	public void zoomOut() {
+		setZoom(zoom - 1);
+	}
+
+	public void setZoom(int zoom, Point mapPoint) {
+		if (zoom > MAX_ZOOM || zoom == this.zoom)
+			return;
+		Point2D.Double zoomPos = getPosition(mapPoint);
+		// addMapMarker(new MapMarkerDot(Color.RED, zoomPos.x, zoomPos.y));
+		jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
+		// requests
+		setDisplayPositionByLatLon(mapPoint, zoomPos.x, zoomPos.y, zoom);
+	}
+
+	public void setZoom(int zoom) {
+		setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
+	}
+
+	/**
+	 * retrieves a tile from the cache. If the tile is not present in the cache
+	 * a load job is added to the working queue of {@link JobThread}.
+	 * 
+	 * @param tilex
+	 * @param tiley
+	 * @param zoom
+	 * @return specified tile from the cache or <code>null</code> if the tile
+	 *         was not found in the cache.
+	 */
+	protected Tile getTile(final int tilex, final int tiley, final int zoom) {
+		int max = (1 << zoom);
+		if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
+			return null;
+		Tile tile = tileCache.getTile(tilex, tiley, zoom);
+		if (tile == null) {
+			tile = new Tile(tilex, tiley, zoom, loadingImage);
+			tileCache.addTile(tile);
+		}
+		if (!tile.isLoaded()) {
+			jobDispatcher.addJob(new Runnable() {
+
+				public void run() {
+					Tile tile = tileCache.getTile(tilex, tiley, zoom);
+					if (tile.isLoaded())
+						return;
+					try {
+						// Thread.sleep(500);
+						tile.loadTileImage();
+						repaint();
+					} catch (Exception e) {
+						System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
+								+ " " + e.getMessage());
+					}
+				}
+			});
+		}
+		return tile;
+	}
+
+	public boolean isTileGridVisible() {
+		return tileGridVisible;
+	}
+
+	public void setTileGridVisible(boolean tileGridVisible) {
+		this.tileGridVisible = tileGridVisible;
+		repaint();
+	}
+
+	public boolean getMapMarkersVisible() {
+		return mapMarkersVisible;
+	}
+
+	/**
+	 * Enables or disables painting of the {@link MapMarker}
+	 * 
+	 * @param mapMarkersVisible
+	 * @see #addMapMarker(MapMarker)
+	 * @see #getMapMarkerList()
+	 */
+	public void setMapMarkerVisible(boolean mapMarkersVisible) {
+		this.mapMarkersVisible = mapMarkersVisible;
+		repaint();
+	}
+
+	public void setMapMarkerList(List<MapMarker> mapMarkerList) {
+		this.mapMarkerList = mapMarkerList;
+		repaint();
+	}
+
+	public List<MapMarker> getMapMarkerList() {
+		return mapMarkerList;
+	}
+
+	public void addMapMarker(MapMarker marker) {
+		mapMarkerList.add(marker);
+	}
+
+	public void setZoomContolsVisible(boolean visible) {
+		zoomSlider.setVisible(visible);
+		zoomInButton.setVisible(visible);
+		zoomOutButton.setVisible(visible);
+	}
+
+	public boolean getZoomContolsVisible() {
+		return zoomSlider.isVisible();
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9095)
@@ -0,0 +1,74 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A generic class that processes a list of {@link Runnable} one-by-one using
+ * one or more {@link Thread}-instances.
+ * 
+ * @author Jan Peter Stotz
+ */
+public class JobDispatcher {
+
+	protected BlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
+
+	Thread[] threads;
+
+	public JobDispatcher(int threadCound) {
+		threads = new Thread[threadCound];
+		for (int i = 0; i < threadCound; i++) {
+			threads[i] = new JobThread(i + 1);
+		}
+	}
+
+	/**
+	 * Removes all jobs from the queue that are currently not being processed.
+	 */
+	public void cancelOutstandingJobs() {
+		jobQueue.clear();
+		for (int i = 0; i < threads.length; i++) {
+			try {
+				threads[i].interrupt();
+				threads[i] = new JobThread(i + 1);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	public void addJob(Runnable job) {
+		try {
+			jobQueue.put(job);
+		} catch (InterruptedException e) {
+		}
+	}
+
+	protected class JobThread extends Thread {
+
+		public JobThread(int threadId) {
+			super("OSMJobThread " + threadId);
+			start();
+		}
+
+		@Override
+		public void run() {
+			while (!isInterrupted()) {
+				Runnable job;
+				try {
+					job = jobQueue.take();
+				} catch (InterruptedException e1) {
+					return;
+				}
+				try {
+					job.run();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarker.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarker.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarker.java	(revision 9095)
@@ -0,0 +1,35 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+/**
+ * Interface to be implemented by all elements that can be displayed on the map.
+ * 
+ * @author Jan Peter Stotz
+ * @see JMapViewer#addMapMarker(MapMarker)
+ * @see JMapViewer#getMapMarkerList()
+ */
+public interface MapMarker {
+
+	/**
+	 * @return Latitude of the map marker position
+	 */
+	public double getLat();
+
+	/**
+	 * @return Longitude of the map marker position
+	 */
+	public double getLon();
+
+	/**
+	 * Paints the map marker on the map. The <code>position</code> specifies the
+	 * coordinates within <code>g</code>
+	 * 
+	 * @param g
+	 * @param position
+	 */
+	public void paint(Graphics g, Point position);
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java	(revision 9095)
@@ -0,0 +1,56 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+
+/**
+ * A simple implementation of the {@link MapMarker} interface. Each map marker
+ * is painted as a circle with a black border line and filled with a specified
+ * color.
+ * 
+ * @author Jan Peter Stotz
+ * 
+ */
+public class MapMarkerDot implements MapMarker {
+
+	double lat;
+	double lon;
+	Color color;
+
+	public MapMarkerDot(double lat, double lon) {
+		this(Color.YELLOW, lat, lon);
+	}
+
+	public MapMarkerDot(Color color, double lat, double lon) {
+		super();
+		this.color = color;
+		this.lat = lat;
+		this.lon = lon;
+	}
+
+	public double getLat() {
+		return lat;
+	}
+
+	public double getLon() {
+		return lon;
+	}
+
+	public void paint(Graphics g, Point position) {
+		int size_h = 5;
+		int size = size_h * 2;
+		g.setColor(color);
+		g.fillOval(position.x - size_h, position.y - size_h, size, size);
+		g.setColor(Color.BLACK);
+		g.drawOval(position.x - size_h, position.y - size_h, size, size);
+	}
+
+	@Override
+	public String toString() {
+		return "MapMarker at " + lat + " " + lon;
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9095)
@@ -0,0 +1,131 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+
+/**
+ * 
+ * @author Jan Peter Stotz
+ */
+public class MemoryTileCache implements TileCache {
+
+	private static final Logger log = Logger.getLogger(MemoryTileCache.class
+			.getName());
+
+	protected int cacheSizeMax = 200;
+
+	/**
+	 * Number of tiles after a cache cleanup via {@link #removeOldTiles()};
+	 */
+	protected int cacheSizeDefault = 100;
+
+	protected Hashtable<String, CacheEntry> hashtable;
+
+	/**
+	 * A logical clock that "ticks" every time a tile is retrieved from the
+	 * cache
+	 */
+	protected int currentAccessTime = 0;
+
+	public MemoryTileCache() {
+		hashtable = new Hashtable<String, CacheEntry>(200);
+	}
+
+	public synchronized void addTile(Tile tile) {
+		hashtable.put(tile.getKey(), new CacheEntry(tile, currentAccessTime));
+		if (hashtable.size() > cacheSizeMax)
+			removeOldTiles();
+	}
+
+	public Tile getTile(int x, int y, int z) {
+		CacheEntry entry = hashtable.get(Tile.getTileKey(x, y, z));
+		if (entry == null)
+			return null;
+		currentAccessTime++;
+		entry.lastAccess = currentAccessTime;
+		// We are right before an integer overflow!!
+		if (currentAccessTime == Integer.MAX_VALUE)
+			removeOldTiles();
+		return entry.tile;
+	}
+
+	/**
+	 * Removes the least recently used tiles and rewrites the
+	 * {@link CacheEntry#lastAccess} of all remaining entries (-n to 0).
+	 * 
+	 * WARNING: While this method is running modifying the {@link #hashtable} is
+	 * forbidden! Therefore this method and {@link #addTile(Tile)} are declared
+	 * as synchronized.
+	 */
+	protected synchronized void removeOldTiles() {
+		try {
+			Set<Map.Entry<String, CacheEntry>> entries = hashtable.entrySet();
+			TreeSet<Map.Entry<String, CacheEntry>> sortedEntries;
+			// Sort the entries according to their access time
+			sortedEntries = new TreeSet<Map.Entry<String, CacheEntry>>(
+					new MEComparator());
+			sortedEntries.addAll(entries);
+			// System.out.println("Tiles in Cache: " + hashtable.size() +
+			// " lru=" + currentAccessTime);
+			int tilecount = 0;
+			for (Map.Entry<String, CacheEntry> entry : sortedEntries) {
+				tilecount++;
+				if (tilecount < cacheSizeDefault) {
+					entry.getValue().lastAccess = -tilecount;
+				} else {
+					// System.out.println("removing entry :"
+					// + entry.getValue().lastAccess);
+					entries.remove(entry);
+				}
+			}
+			// We can now safely reset the the logical clock
+			currentAccessTime = 1;
+			// System.out.println("Tiles in Cache: " + hashtable.size() +
+			// " lru=" + currentAccessTime);
+		} catch (Exception e) {
+			log.severe(e.toString());
+		}
+	}
+
+	public int getCacheSizeMax() {
+		return cacheSizeMax;
+	}
+
+	public void setCacheSizeMax(int cacheSizeMax) {
+		this.cacheSizeMax = cacheSizeMax;
+		this.cacheSizeDefault = cacheSizeMax / 2;
+	}
+
+	protected static class CacheEntry implements Comparable<CacheEntry> {
+		int lastAccess;
+		Tile tile;
+
+		protected CacheEntry(Tile tile, int currentAccessTime) {
+			this.tile = tile;
+			lastAccess = currentAccessTime;
+		}
+
+		public int compareTo(CacheEntry o) {
+			if (lastAccess > o.lastAccess)
+				return -1;
+			else
+				return 1;
+		}
+	}
+
+	protected static class MEComparator implements
+			Comparator<Map.Entry<String, CacheEntry>> {
+
+		public int compare(Entry<String, CacheEntry> o1,
+				Entry<String, CacheEntry> o2) {
+			return o1.getValue().compareTo(o2.getValue());
+		}
+	}
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java	(revision 9095)
@@ -0,0 +1,106 @@
+package org.openstreetmap.gui.jmapviewer;
+
+// License: GPL. Copyright 2007 by Tim Haussmann
+
+/**
+ * This class implements the Mercator Projection as it is used by Openstreetmap
+ * (and google). It provides methods to translate coordinates from 'map space'
+ * into latitude and longitude (on the WGS84 ellipsoid) and vice versa. Map
+ * space is measured in pixels. The origin of the map space is the top left
+ * corner. The map space origin (0,0) has latitude ~85 and longitude -180
+ * 
+ * @author Tim Haussmann
+ * 
+ */
+
+public class OsmMercator {
+
+	private static int TILE_SIZE = 256;
+	public static final double MAX_LAT = 85.05112877980659;
+	public static final double MIN_LAT = -85.05112877980659;
+
+	public static double radius(int aZoomlevel) {
+		return (TILE_SIZE * Math.pow(2, aZoomlevel)) / (2 * Math.PI);
+	}
+
+	/**
+	 * Returns the absolut number of pixels in y or x, defined as: 2^Zoomlevel *
+	 * TILE_WIDTH where TILE_WIDTH is the width of a tile in pixels
+	 * 
+	 * @param aZoomlevel
+	 * @return
+	 */
+	public static int getMaxPixels(int aZoomlevel) {
+		return (int) (TILE_SIZE * Math.pow(2, aZoomlevel));
+	}
+
+	public static int falseEasting(int aZoomlevel) {
+		return (int) getMaxPixels(aZoomlevel) / 2;
+	}
+
+	public static int falseNorthing(int aZoomlevel) {
+		return (int) (-1 * getMaxPixels(aZoomlevel) / 2);
+	}
+
+	/**
+	 * Transform longitude to pixelspace
+	 * 
+	 * @param aLongitude
+	 *            [-180..180]
+	 * @return [0..2^Zoomlevel*TILE_SIZE[
+	 */
+	public static int LonToX(double aLongitude, int aZoomlevel) {
+		double longitude = Math.toRadians(aLongitude);
+		return (int) ((radius(aZoomlevel) * longitude) + falseEasting(aZoomlevel));
+	}
+
+	/**
+	 * Transforms latitude to pixelspace
+	 * 
+	 * @param aLat
+	 *            [-90...90]
+	 * @return [0..2^Zoomlevel*TILE_SIZE[
+	 */
+	public static int LatToY(double aLat, int aZoomlevel) {
+		if (aLat < MIN_LAT)
+			aLat = MIN_LAT;
+		else if (aLat > MAX_LAT)
+			aLat = MAX_LAT;
+		double latitude = Math.toRadians(aLat);
+		return (int) (-1
+				* (radius(aZoomlevel) / 2.0 * Math.log((1.0 + Math
+						.sin(latitude))
+						/ (1.0 - Math.sin(latitude)))) - falseNorthing(aZoomlevel));
+	}
+
+	/**
+	 * Transforms pixel coordinate X to longitude
+	 * 
+	 * @param aX
+	 *            [0..2^Zoomlevel*TILE_WIDTH[
+	 * @return ]-180..180[
+	 */
+	public static double XToLon(int aX, int aZoomlevel) {
+		aX -= falseEasting(aZoomlevel);
+		double longRadians = aX / radius(aZoomlevel);
+		double longDegrees = Math.toDegrees(longRadians);
+		double rotations = Math.floor((longDegrees + 180) / 360);
+		double longitude = longDegrees - (rotations * 360);
+		return longitude;
+	}
+
+	/**
+	 * Transforms pixel coordinate Y to latitude
+	 * 
+	 * @param aY
+	 *            [0..2^Zoomlevel*TILE_WIDTH[
+	 * @return [MIN_LAT..MAX_LAT] is about [-85..85]
+	 */
+	public static double YToLat(int aY, int aZoomlevel) {
+		aY += falseNorthing(aZoomlevel);
+		double latitude = (Math.PI / 2)
+				- (2 * Math.atan(Math.exp(-1.0 * aY / radius(aZoomlevel))));
+		return -1 * Math.toDegrees(latitude);
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9095)
@@ -0,0 +1,187 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Holds one map tile. Additionally the code for loading the tile image and
+ * painting it is also included in this class.
+ * 
+ * @author Jan Peter Stotz
+ */
+public class Tile {
+
+	protected int xtile;
+	protected int ytile;
+	protected int zoom;
+	protected BufferedImage image;
+	protected String key;
+	protected boolean loaded = false;
+	public static final int WIDTH = 256;
+	public static final int HEIGHT = 256;
+	public static final int WIDTH_HALF = 128;
+	public static final int HEIGHT_HALF = 128;
+
+	/**
+	 * Creates a tile with empty image.
+	 * 
+	 * @param xtile
+	 * @param ytile
+	 * @param zoom
+	 */
+	public Tile(int xtile, int ytile, int zoom) {
+		super();
+		this.xtile = xtile;
+		this.ytile = ytile;
+		this.zoom = zoom;
+		this.image = null;
+		this.key = getTileKey(xtile, ytile, zoom);
+	}
+
+	public Tile(int xtile, int ytile, int zoom, BufferedImage image) {
+		this(xtile, ytile, zoom);
+		this.image = image;
+	}
+
+	/**
+	 * @return tile number on the x axis of this tile
+	 */
+	public int getXtile() {
+		return xtile;
+	}
+
+	/**
+	 * @return tile number on the y axis of this tile
+	 */
+	public int getYtile() {
+		return ytile;
+	}
+
+	/**
+	 * @return zoom level of this tile
+	 */
+	public int getZoom() {
+		return zoom;
+	}
+
+	public BufferedImage getImage() {
+		return image;
+	}
+
+	/**
+	 * @return key that identifies a tile
+	 */
+	public String getKey() {
+		return key;
+	}
+
+	public boolean isLoaded() {
+		return loaded;
+	}
+
+	/**
+	 * Retrieves the "parent tile" from the specified tile cache. A parent tile
+	 * is the tile of a lower zoom level (coarser resolution) at the same
+	 * position of the map. Parent tiles are used for a preview until the tile
+	 * has been loaded.
+	 * 
+	 * @param tileCache
+	 * @return
+	 */
+	public Tile getParentTile(TileCache tileCache) {
+		if (zoom < 1)
+			return null;
+		return tileCache.getTile(xtile / 2, ytile / 2, zoom - 1);
+	}
+
+	public synchronized void loadTileImage() throws IOException {
+		if (loaded)
+			return;
+		URL url;
+		URLConnection urlConn;
+		DataInputStream input;
+		url = new URL("http://tile.openstreetmap.org/" + zoom + "/" + xtile + "/" + ytile + ".png");
+		// System.out.println(url);
+		urlConn = url.openConnection();
+		// urlConn.setUseCaches(false);
+		input = new DataInputStream(urlConn.getInputStream());
+		image = ImageIO.read(input);
+		input.close();
+		loaded = true;
+	}
+
+	/**
+	 * Paints the tile-image on the {@link Graphics} <code>g</code> at the
+	 * position <code>x</code>/<code>y</code>.
+	 * 
+	 * @param g
+	 * @param x
+	 *            x-coordinate in <code>g</code>
+	 * @param y
+	 *            y-coordinate in <code>g</code>
+	 */
+	public void paint(Graphics g, int x, int y) {
+		if (image == null)
+			return;
+		/*
+		 * if (image.getHeight() < OSMMap.TILE_HEIGHT || image.getWidth() <
+		 * OSMMap.TILE_WIDTH) { x += (OSMMap.TILE_WIDTH - image.getWidth()) / 2;
+		 * y += (OSMMap.TILE_HEIGHT - image.getHeight()) / 2; }
+		 */
+		g.drawImage(image, x, y, null);
+	}
+
+	public void paint(Graphics g, int x, int y, int stretch) {
+		if (image == null)
+			return;
+		int tx = x * stretch;
+		int ty = y = stretch;
+		g.drawImage(image, x, y, tx, ty, 0, 0, image.getWidth(), image.getHeight(), null);
+	}
+
+	/**
+	 * Paints one-fourth of the tile image {@link Graphics} <code>g</code> at
+	 * the position <code>x</code>/<code>y</code>.
+	 * 
+	 * @param g
+	 * @param x
+	 * @param y
+	 * @param partx
+	 *            (0 or 1) selects if the left or right part of tile should be
+	 *            painted
+	 * @param party
+	 *            (0 or 1) selects if the upper or lower part of tile should be
+	 *            painted
+	 */
+	public void parentPaint(Graphics g, int x, int y, int partx, int party) {
+		int sx = 0;
+		int sy = 0;
+		if (partx == 1)
+			sx = WIDTH_HALF;
+		if (party == 1)
+			sy = HEIGHT_HALF;
+		g.drawImage(image, x, y, x + WIDTH, y + HEIGHT, sx, sy, sx + WIDTH_HALF, sy + HEIGHT_HALF,
+				null);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof Tile))
+			return false;
+		Tile tile = (Tile) obj;
+		return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
+	}
+
+	public static String getTileKey(int xtile, int ytile, int zoom) {
+		return zoom + "/" + xtile + "/" + ytile;
+	}
+
+}
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java	(revision 9095)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java	(revision 9095)
@@ -0,0 +1,36 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+/**
+ * Implement this interface for creating your custom tile cache for
+ * {@link JMapViewer}.
+ * 
+ * @author Jan Peter Stotz
+ */
+public interface TileCache {
+
+	/**
+	 * Retrieves a tile from the cache if present, otherwise <code>null</code>
+	 * will be returned.
+	 * 
+	 * @param x
+	 *            tile number on the x axis of the tile to be retrieved
+	 * @param y
+	 *            tile number on the y axis of the tile to be retrieved
+	 * @param z
+	 *            zoom level of the tile to be retrieved
+	 * @return the requested tile or <code>null</code> if the tile is not
+	 *         present in the cache
+	 */
+	public Tile getTile(int x, int y, int z);
+
+	/**
+	 * Adds a tile to the cache. How long after adding a tile can be retrieved
+	 * via {@link #getTile(int, int, int)} is unspecified and depends on the
+	 * implementation.
+	 * 
+	 * @param tile
+	 */
+	public void addTile(Tile tile);
+}
