Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/dominik' into Jan

Jan Enders 8 rokov pred
rodič
commit
6dfcd917f5

+ 57 - 7
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/ButtonManager.java

@@ -1,10 +1,14 @@
 package de.tu_darmstadt.informatik.tk.scopviz.ui;
 
+import java.awt.Event;
+import java.io.IOException;
+import java.net.URLConnection;
 import java.util.ArrayList;
 
 import org.graphstream.graph.implementations.Graphs;
 import org.jxmapviewer.viewer.WaypointPainter;
 
+import de.tu_darmstadt.informatik.tk.scopviz.debug.Debug;
 import de.tu_darmstadt.informatik.tk.scopviz.graphs.MyGraph;
 import de.tu_darmstadt.informatik.tk.scopviz.main.Layer;
 import de.tu_darmstadt.informatik.tk.scopviz.main.Main;
@@ -14,7 +18,12 @@ import de.tu_darmstadt.informatik.tk.scopviz.ui.mapView.MapViewFunctions;
 import de.tu_darmstadt.informatik.tk.scopviz.ui.mapView.WorldView;
 import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
 import javafx.scene.control.Button;
+import javafx.scene.control.TableRow;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
 
 /**
  * Manager to contain the various handlers for the buttons of the UI.
@@ -90,10 +99,15 @@ public final class ButtonManager {
 
 			// show toolbox and hide VBox
 			controller.toolbox.setVisible(true);
-			controller.topLeftAPane.getChildren().remove(controller.symbolToolVBox);
+			//controller.topLeftAPane.getChildren().remove(controller.symbolToolVBox);
+			
+			controller.symbolToolVBox.setVisible(false);
 			
 			// make properties editable again
 			controller.propertiesObjectColumn.setEditable(true);
+			
+			// enabel context menu
+			controller.properties.setRowFactory(PropertiesManager.rightClickCallback);
 
 			// show graph instead of map view
 			controller.swingNodeWorldView.setVisible(false);
@@ -107,6 +121,12 @@ public final class ButtonManager {
 			controller.pane.setMouseTransparent(false);
 			controller.swingNode.setMouseTransparent(false);
 			
+			// deselect graph element
+			PropertiesManager.showNewDataSet(null);
+			
+			// reset loaded images
+			MapViewFunctions.resetImageMap();
+			
 			
 		}
 	}
@@ -184,13 +204,30 @@ public final class ButtonManager {
 			GraphDisplayManager.setCurrentLayer(Layer.SYMBOL);
 			GraphDisplayManager.addGraph(gClone, true);
 
+		}
+		
+		try {
+			// load world view 
 			activateWorldView();
 			
-			//hide metricbox/update button
-			controller.metricbox.setVisible(false);
-			controller.updateMetricButton.setVisible(false);
 			
+			
+			
+		} catch (IOException e) {
+			
+			// problems with Internet connection, maybe host not reachable, maybe no Internet connection at all
+			GraphDisplayManager.switchActiveGraph();
+			setBorderStyle((Button) arg0.getSource());
+			
+			// show "Connection Error" message
+			showConnectionErrorMsg();
+			
+			return;
 		}
+		
+		//hide metricbox/update button
+		controller.metricbox.setVisible(false);
+		controller.updateMetricButton.setVisible(false);
 
 		GraphDisplayManager.switchActiveGraph();
 		setBorderStyle((Button) arg0.getSource());
@@ -198,10 +235,23 @@ public final class ButtonManager {
 		controller.getOpenButton().setText("Open...");
 	}
 
+	/**
+	 * show an Alert dialog when OpenStreetMap could not be loaded
+	 */
+	public static void showConnectionErrorMsg() {
+		Alert alert = new Alert(AlertType.WARNING);
+		alert.setTitle("Connection Error");
+		alert.setHeaderText("Could not reach OpenStreetMap server");
+		alert.setContentText(null);
+
+		alert.showAndWait();
+	}
+
 	/**
 	 * Initializes the WorldView, sets data and paints them.
+	 * @throws IOException 
 	 */
-	private static void activateWorldView() {
+	private static void activateWorldView() throws IOException {
 
 		// dont show graph and toolbox
 		controller.toolbox.setVisible(false);
@@ -219,8 +269,8 @@ public final class ButtonManager {
 		controller.swingNode.setMouseTransparent(true);
 
 		// show VBox for map options
-		controller.topLeftAPane.getChildren().add(controller.symbolToolVBox);
-		
+		controller.symbolToolVBox.setVisible(true);
+
 		WorldView.loadWorldView();
 
 		MapViewFunctions.checkVBoxChanged();

+ 30 - 0
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/GraphDisplayManager.java

@@ -17,6 +17,8 @@ import de.tu_darmstadt.informatik.tk.scopviz.io.GraphMLImporter;
 import de.tu_darmstadt.informatik.tk.scopviz.main.CreationMode;
 import de.tu_darmstadt.informatik.tk.scopviz.main.Layer;
 import de.tu_darmstadt.informatik.tk.scopviz.main.Main;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.event.EventHandler;
 import javafx.scene.input.ScrollEvent;
 import javafx.scene.layout.Pane;
@@ -49,6 +51,28 @@ public final class GraphDisplayManager {
 
 	/** The currently active Layer. */
 	private static Layer currentLayer = Layer.UNDERLAY;
+	
+	
+	/**
+	 * Observable boolean value, true when currentLayer = symbol layer, false otherwise
+	 */
+	private static BooleanProperty inSymbolLayer = new SimpleBooleanProperty(); 
+	
+	/**
+	 * set inSymbolLayer to true
+	 */
+	private static final void changeToSymbolLayer(){inSymbolLayer.set(true);};
+	
+	/**
+	 * set inSymbolLayer to false
+	 */
+	private static final void changeToOtherLayer(){inSymbolLayer.set(false);};
+	
+	/**
+	 * 
+	 * @return inSymbolLayer property 
+	 */
+	public static BooleanProperty inSymbolLayerProperty(){return inSymbolLayer;};
 
 	/**
 	 * An empty GraphManager to use with Layers not yet filled with another
@@ -293,6 +317,12 @@ public final class GraphDisplayManager {
 			initMappingLayer();
 		}
 		GraphDisplayManager.currentLayer = currentLayer;
+		
+		if(currentLayer.equals(Layer.SYMBOL)){
+			changeToSymbolLayer();
+		}else{
+			changeToOtherLayer();
+		}
 	}
 
 	/**

+ 18 - 3
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/PropertiesManager.java

@@ -1,6 +1,7 @@
 package de.tu_darmstadt.informatik.tk.scopviz.ui;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Optional;
 
 import org.graphstream.algorithm.Toolkit;
@@ -57,6 +58,9 @@ public final class PropertiesManager {
 	public static boolean nameSet;
 	/** Flag whether the value has been set. */
 	public static boolean valueSet;
+	
+	
+	public static HashSet<TableRow<KeyValuePair>> tableRows = new HashSet<TableRow<KeyValuePair>>();
 
 	/**
 	 * Private Constructor to prevent Instantiation.
@@ -120,7 +124,7 @@ public final class PropertiesManager {
 				Debug.out("Edited float Attribute " + key);
 
 			} else if (classType.equals(Double.class) && newValue.matches(IS_FLOAT)) {
-				selected.changeAttribute(key, Float.valueOf(newValue));
+				selected.changeAttribute(key, Double.valueOf(newValue));
 				editedPair.setValue(newValue);
 				Debug.out("Edited double Attribute " + key);
 
@@ -170,15 +174,23 @@ public final class PropertiesManager {
 				properties.getItems().remove(row.getItem());
 			});
 
+			// Disable MenuItem in symbol layer
+			//TODO
+			onlyAddPropMenuItem.disableProperty().bind(GraphDisplayManager.inSymbolLayerProperty()); 
+			addPropMenuItem.disableProperty().bind(GraphDisplayManager.inSymbolLayerProperty()); 
+			deletePropMenuItem.disableProperty().bind(GraphDisplayManager.inSymbolLayerProperty()); 
+			
 			// add MenuItem to ContextMenu
 			menuOnEmptyRows.getItems().add(onlyAddPropMenuItem);
 			menuOnNonEmptyRows.getItems().addAll(addPropMenuItem, deletePropMenuItem);
+			
 
 			// when empty row right-clicked open special menu (only add),
 			// otherwise normal menu (add & delete)
 			row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
 					.then(menuOnNonEmptyRows).otherwise(menuOnEmptyRows));
 
+			tableRows.add(row);
 			return row;
 		}
 	};
@@ -212,11 +224,14 @@ public final class PropertiesManager {
 	 * @param newData
 	 */
 	public static void showNewDataSet(Element selected) {
+
+		ObservableList<KeyValuePair> newData = FXCollections.observableArrayList();
+		
 		if (selected == null) {
+			properties.setItems(newData);
 			return;
 		}
-
-		ObservableList<KeyValuePair> newData = FXCollections.observableArrayList();
+		
 		// fix for concurrentModification exception
 		String[] temp = new String[0];
 		temp = selected.getAttributeKeySet().toArray(temp);

+ 26 - 3
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/ToolboxManager.java

@@ -59,7 +59,7 @@ public final class ToolboxManager {
 	}
 
 	/**
-	 * 
+	 * Create Row Elements for underlay toolbox view
 	 */
 	public static void setUnderlayItems() {
 
@@ -74,6 +74,9 @@ public final class ToolboxManager {
 		controller.toolbox.getItems().setAll(data);
 	}
 
+	/**
+	 * create row elements for operator toolbox view
+	 */
 	public static void setOperatorItems() {
 
 		@SuppressWarnings("unchecked")
@@ -87,6 +90,9 @@ public final class ToolboxManager {
 
 	}
 
+	/**
+	 * create row elements for mapping toolbox view
+	 */
 	public static void setMappingItems() {
 
 		@SuppressWarnings("unchecked")
@@ -167,12 +173,20 @@ public final class ToolboxManager {
 			Main.getInstance().setCreationMode(currentMode);
 	}
 
-	// TODO: Create Documentation for this, together with Dominik, ich versteh
-	// das zeug hier net.
+	/**
+	 * create a pair object under given picture and name
+	 * @param picture
+	 * @param name
+	 * @return
+	 */
 	private static Pair<Object, String> pair(Object picture, String name) {
 		return new Pair<>(picture, name);
 	}
 
+	/**
+	 * Class for getting the string out of the pair elements in each row
+	 *
+	 */
 	public static class PairKeyFactory
 			implements Callback<TableColumn.CellDataFeatures<Pair<Object, String>, String>, ObservableValue<String>> {
 		@Override
@@ -181,6 +195,10 @@ public final class ToolboxManager {
 		}
 	}
 
+	/**
+	 * Class for getting the picture out of the pair elements in each row
+	 *
+	 */
 	public static class PairValueFactory
 			implements Callback<TableColumn.CellDataFeatures<Pair<Object, String>, Object>, ObservableValue<Object>> {
 		@SuppressWarnings("unchecked")
@@ -192,6 +210,11 @@ public final class ToolboxManager {
 		}
 	}
 
+	/**
+	 * The actual TableCell, that renders the images of nodes and edges. (Image, String)-Cell
+	 * additional support for (String, String), (Integer, String), (Boolean, String), ("N/A", String) table cells
+	 *
+	 */
 	public static class PairValueCell extends TableCell<Pair<Object, String>, Object> {
 		@Override
 		protected void updateItem(Object item, boolean empty) {

+ 27 - 9
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/CustomMapClickListener.java

@@ -15,19 +15,35 @@ import de.tu_darmstadt.informatik.tk.scopviz.ui.PropertiesManager;
 
 public class CustomMapClickListener extends MapClickListener {
 
-	private final HashSet<CustomWaypoint> nodePositions;
+	/*
+	 * World view viewer
+	 */
 	private final JXMapViewer viewer;
 
-	private static CustomWaypoint selected;
+	/*
+	 * selected waypoint
+	 */
+	public static CustomWaypoint selected;
 
-	private static HashSet<Edge> edges;
+	/*
+	 * all edges of the graph
+	 */
+	private final static HashSet<Edge> edges = WorldView.edges;
+
+	/*
+	 * all waypoints of the graph
+	 */
+	private final static HashSet<CustomWaypoint> waypoints = WorldView.waypoints;
 
-	public CustomMapClickListener(JXMapViewer viewer, HashSet<CustomWaypoint> waypoints, HashSet<Edge> edges) {
+	/**
+	 * Constructor sets viewer
+	 * 
+	 * @param viewer
+	 */
+	public CustomMapClickListener(JXMapViewer viewer) {
 		super(viewer);
 
 		this.viewer = viewer;
-		this.nodePositions = waypoints;
-		CustomMapClickListener.edges = edges;
 
 	}
 
@@ -40,7 +56,7 @@ public class CustomMapClickListener extends MapClickListener {
 		// a waypoint was clicked
 		Boolean wayPointSelected = false;
 
-		for (CustomWaypoint nodeWaypoint : this.nodePositions) {
+		for (CustomWaypoint nodeWaypoint : CustomMapClickListener.waypoints) {
 			// transform GeoPosition to point on screen
 			nodePoint = this.viewer.getTileFactory().geoToPixel(nodeWaypoint.getPosition(), this.viewer.getZoom());
 
@@ -64,8 +80,8 @@ public class CustomMapClickListener extends MapClickListener {
 
 				// deselect old waypoint and select new clicked waypoint
 				deselectAll();
-				selected = nodeWaypoint;
 				nodeWaypoint.select();
+				selected = nodeWaypoint;
 				viewer.repaint();
 				break;
 			}
@@ -111,8 +127,10 @@ public class CustomMapClickListener extends MapClickListener {
 	 * deselect all edges and the selected node
 	 */
 	public static void deselectAll() {
-		if (selected != null)
+		if (selected != null) {
 			selected.deselect();
+			selected = null;
+		}
 		for (Edge edge : edges) {
 			edge.changeAttribute("ui.map.selected", false);
 		}

+ 201 - 0
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/CustomTile.java

@@ -0,0 +1,201 @@
+package de.tu_darmstadt.informatik.tk.scopviz.ui.mapView;
+
+import java.awt.image.BufferedImage;
+import java.lang.ref.SoftReference;
+
+import org.jxmapviewer.viewer.Tile;
+
+/**
+ * The Tile class represents a particular square image piece of the world bitmap
+ * at a particular zoom level.
+ * 
+ * @author Dominik Renkel
+ */
+
+public class CustomTile extends Tile {
+
+	/**
+	 * Indicates that loading has succeeded. A PropertyChangeEvent will be fired
+	 * when the loading is completed
+	 */
+	private boolean loaded = false;
+
+	/**
+	 * loading priority
+	 */
+	private Priority priority = Priority.High;
+
+	/**
+	 * the tileFactory this tile is referenced to
+	 */
+	private CustomTileFactory dtf;
+
+	/**
+	 * is tile currently loading
+	 */
+	private boolean isLoading = false;
+
+	/**
+	 * The url of the image to load for this tile
+	 */
+	private String url;
+
+	/**
+	 * The image loaded for this Tile
+	 */
+	SoftReference<BufferedImage> image = new SoftReference<BufferedImage>(null);
+
+	/**
+	 * if image was loaded, when no internet connection was established, it
+	 * needs to reload
+	 */
+	private Boolean reloadNeeded = false;
+
+	/**
+	 * Create a new Tile at the specified tile point and zoom level
+	 * 
+	 * @param x
+	 *            the x value
+	 * @param y
+	 *            the y value
+	 * @param zoom
+	 *            the zoom level
+	 */
+	public CustomTile(int x, int y, int zoom) {
+		super(x, y, zoom);
+	}
+
+	/**
+	 * Create a new Tile that loads its data from the given URL. The URL must
+	 * resolve to an image
+	 * 
+	 * @param x
+	 *            the x value
+	 * @param y
+	 *            the y value
+	 * @param zoom
+	 *            the zoom level
+	 * @param url
+	 *            the URL
+	 * @param priority
+	 *            the priority
+	 * @param dtf
+	 *            the tile factory
+	 */
+	public CustomTile(int x, int y, int zoom, String url, Priority priority, CustomTileFactory dtf) {
+		super(x, y, zoom);
+		this.url = url;
+		this.priority = priority;
+		this.dtf = dtf;
+	}
+
+	/**
+	 * @return the Image associated with this Tile. This is a read only property
+	 *         This may return null at any time, however if this returns null, a
+	 *         load operation will automatically be started for it.
+	 */
+	public BufferedImage getImage() {
+		BufferedImage img = image.get();
+		if (img == null) {
+			setLoaded(false);
+
+			// tile factory can be null if the tile has invalid coords or zoom
+			if (dtf != null) {
+				dtf.startLoading(this);
+			}
+		}
+
+		return img;
+	}
+
+	/**
+	 * Indicates if this tile's underlying image has been successfully loaded
+	 * yet.
+	 * 
+	 * @return true if the Tile has been loaded
+	 */
+	public synchronized boolean isLoaded() {
+		return loaded;
+	}
+
+	/**
+	 * Toggles the loaded state, and fires the appropriate property change
+	 * notification
+	 * 
+	 * @param loaded
+	 *            the loaded flag
+	 */
+	public synchronized void setLoaded(boolean loaded) {
+		boolean old = isLoaded();
+		this.loaded = loaded;
+		firePropertyChange("loaded", old, isLoaded());
+
+		// image loaded, so no reload needed
+		if (this.loaded) {
+			this.reloadNeeded = false;
+		}
+	}
+
+	/**
+	 * @return the isLoading
+	 */
+	public boolean isLoading() {
+		return isLoading;
+	}
+
+	/**
+	 * @param isLoading
+	 *            the isLoading to set
+	 */
+	public void setLoading(boolean isLoading) {
+		this.isLoading = isLoading;
+	}
+
+	/**
+	 * tile could not be loaded, so reload when Internet connection is
+	 * reestablished
+	 * 
+	 * @param reload
+	 */
+	public void setReloadNeeded(boolean reload) {
+		this.reloadNeeded = reload;
+	}
+
+	/**
+	 * get the reloadNeeded property
+	 * 
+	 * @return reload needed
+	 */
+	public Boolean getReloadNeeded() {
+		return this.reloadNeeded;
+	}
+
+	/**
+	 * Gets the loading priority of this tile.
+	 * 
+	 * @return the priority
+	 */
+	public Priority getPriority() {
+		return priority;
+	}
+
+	/**
+	 * Set the loading priority of this tile.
+	 * 
+	 * @param priority
+	 *            the priority to set
+	 */
+	public void setPriority(Priority priority) {
+		this.priority = priority;
+	}
+
+	/**
+	 * Gets the URL of this tile.
+	 * 
+	 * @return the url
+	 */
+	public String getURL() {
+		return url;
+	}
+
+}

+ 375 - 0
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/CustomTileFactory.java

@@ -0,0 +1,375 @@
+package de.tu_darmstadt.informatik.tk.scopviz.ui.mapView;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+import javax.imageio.ImageIO;
+import javax.swing.SwingUtilities;
+
+import org.jxmapviewer.util.ProjectProperties;
+import org.jxmapviewer.viewer.AbstractTileFactory;
+import org.jxmapviewer.viewer.Tile;
+import org.jxmapviewer.viewer.TileCache;
+import org.jxmapviewer.viewer.TileFactoryInfo;
+import org.jxmapviewer.viewer.util.GeoUtil;
+
+import de.tu_darmstadt.informatik.tk.scopviz.debug.Debug;
+
+/**
+ * Custom tile factory for handling connection problems
+ * 
+ * @author Dominik Renkel
+ *
+ */
+public class CustomTileFactory extends AbstractTileFactory {
+
+	/**
+	 * Note that the name and version are actually set by Gradle so there is no
+	 * need to bump a version manually when new release is made.
+	 */
+	private static final String DEFAULT_USER_AGENT = ProjectProperties.INSTANCE.getName() + "/"
+			+ ProjectProperties.INSTANCE.getVersion();
+
+	/**
+	 * userAgent needed to request data from OpenStreetMap servers
+	 */
+	private String userAgent = DEFAULT_USER_AGENT;
+
+	/**
+	 * standard thread count, that are used to load tiles
+	 */
+	private int threadPoolSize = 4;
+
+	/**
+	 * Handling Threads
+	 */
+	private ExecutorService service;
+
+	/**
+	 * saves loaded tiles
+	 */
+	private Map<String, Tile> tileMap = new HashMap<String, Tile>();
+
+	/**
+	 * Actual Loaded Tile chache
+	 */
+	private TileCache cache = new TileCache();
+
+	/**
+	 * constructor with super reference
+	 * 
+	 * @param info
+	 */
+	public CustomTileFactory(TileFactoryInfo info) {
+		super(info);
+	}
+
+	/**
+	 * Returns the tile that is located at the given tilePoint for this zoom.
+	 * For example, if getMapSize() returns 10x20 for this zoom, and the
+	 * tilePoint is (3,5), then the appropriate tile will be located and
+	 * returned.
+	 */
+	@Override
+	public CustomTile getTile(int x, int y, int zoom) {
+		return (CustomTile) getTile(x, y, zoom, true);
+	}
+
+	/**
+	 * Get a tile like above, but load image when not already in TileCache
+	 * 
+	 * @param tpx
+	 * @param tpy
+	 * @param zoom
+	 * @param eagerLoad
+	 *            load all tiles with same priority
+	 * @return
+	 */
+	private CustomTile getTile(int tpx, int tpy, int zoom, boolean eagerLoad) {
+		// wrap the tiles horizontally --> mod the X with the max width
+		// and use that
+		int tileX = tpx;// tilePoint.getX();
+		int numTilesWide = (int) getMapSize(zoom).getWidth();
+		if (tileX < 0) {
+			tileX = numTilesWide - (Math.abs(tileX) % numTilesWide);
+		}
+
+		tileX = tileX % numTilesWide;
+		int tileY = tpy;
+		String url = getInfo().getTileUrl(tileX, tileY, zoom);
+
+		Tile.Priority pri = Tile.Priority.High;
+		if (!eagerLoad) {
+			pri = Tile.Priority.Low;
+		}
+		CustomTile tile;
+
+		if (!tileMap.containsKey(url)) {
+			if (!GeoUtil.isValidTile(tileX, tileY, zoom, getInfo())) {
+				tile = new CustomTile(tileX, tileY, zoom);
+			} else {
+				tile = new CustomTile(tileX, tileY, zoom, url, pri, this);
+				startLoading(tile);
+			}
+			tileMap.put(url, tile);
+		} else {
+			tile = (CustomTile) tileMap.get(url);
+			// if its in the map but is low and isn't loaded yet
+			// but we are in high mode
+			if (tile.getPriority() == Tile.Priority.Low && eagerLoad && !tile.isLoaded()) {
+
+				// tile.promote();
+				promote(tile);
+			}
+		}
+
+		return tile;
+	}
+
+	/** ==== threaded tile loading stuff === */
+	/**
+	 * Thread pool for loading the tiles
+	 */
+	private BlockingQueue<Tile> tileQueue = new PriorityBlockingQueue<Tile>(5, new Comparator<Tile>() {
+		@Override
+		public int compare(Tile o1, Tile o2) {
+			if (o1.getPriority() == Tile.Priority.Low && o2.getPriority() == Tile.Priority.High) {
+				return 1;
+			}
+			if (o1.getPriority() == Tile.Priority.High && o2.getPriority() == Tile.Priority.Low) {
+				return -1;
+			}
+			return 0;
+
+		}
+	});
+
+	/**
+	 * @return the tile cache
+	 */
+	public TileCache getTileCache() {
+		return cache;
+	}
+
+	/**
+	 * @param cache
+	 *            the tile cache
+	 */
+	public void setTileCache(TileCache cache) {
+		this.cache = cache;
+	}
+
+	/**
+	 * Subclasses may override this method to provide their own executor
+	 * services. This method will be called each time a tile needs to be loaded.
+	 * Implementations should cache the ExecutorService when possible.
+	 * 
+	 * @return ExecutorService to load tiles with
+	 */
+	protected synchronized ExecutorService getService() {
+		if (service == null) {
+			// System.out.println("creating an executor service with a
+			// threadpool of size " + threadPoolSize);
+			service = Executors.newFixedThreadPool(threadPoolSize, new ThreadFactory() {
+				private int count = 0;
+
+				@Override
+				public Thread newThread(Runnable r) {
+					Thread t = new Thread(r, "tile-pool-" + count++);
+					t.setPriority(Thread.MIN_PRIORITY);
+					t.setDaemon(true);
+					return t;
+				}
+			});
+		}
+		return service;
+	}
+
+	@Override
+	public void dispose() {
+		if (service != null) {
+			service.shutdown();
+			service = null;
+		}
+	}
+
+	/**
+	 * Set the number of threads to use for loading the tiles. This controls the
+	 * number of threads used by the ExecutorService returned from getService().
+	 * Note, this method should be called before loading the first tile. Calls
+	 * after the first tile are loaded will have no effect by default.
+	 * 
+	 * @param size
+	 *            the thread pool size
+	 */
+	public void setThreadPoolSize(int size) {
+		if (size <= 0) {
+			throw new IllegalArgumentException(
+					"size invalid: " + size + ". The size of the threadpool must be greater than 0.");
+		}
+		threadPoolSize = size;
+	}
+
+	@Override
+	protected synchronized void startLoading(Tile tile) {
+		if (tile.isLoading()) {
+			// System.out.println("already loading. bailing");
+			return;
+		}
+		tile.setLoading(true);
+		try {
+			tileQueue.put(tile);
+			getService().submit(createTileRunner(tile));
+		} catch (Exception ex) {
+			ex.printStackTrace();
+		}
+	}
+
+	/**
+	 * Increase the priority of this tile so it will be loaded sooner.
+	 * 
+	 * @param tile
+	 *            the tile
+	 */
+	public synchronized void promote(CustomTile tile) {
+		if (tileQueue.contains(tile)) {
+			try {
+				tileQueue.remove(tile);
+				tile.setPriority(Tile.Priority.High);
+				tileQueue.put(tile);
+			} catch (Exception ex) {
+				ex.printStackTrace();
+			}
+		}
+	}
+
+	@Override
+	protected Runnable createTileRunner(Tile tile) {
+		return new CustomTileRunner();
+	}
+
+	/**
+	 * An inner class which actually loads the tiles. Used by the thread queue.
+	 * Subclasses can override this if necessary.
+	 */
+	private class CustomTileRunner implements Runnable {
+		/**
+		 * Gets the full URI of a tile.
+		 * 
+		 * @param tile
+		 *            the tile
+		 * @throws URISyntaxException
+		 *             if the URI is invalid
+		 * @return a URI for the tile
+		 */
+		protected URI getURI(Tile tile) throws URISyntaxException {
+			if (tile.getURL() == null) {
+				return null;
+			}
+			return new URI(tile.getURL());
+		}
+
+		/**
+		 * implementation of the Runnable interface.
+		 */
+		@Override
+		public void run() {
+			/*
+			 * 3 strikes and you're out. Attempt to load the url. If it fails,
+			 * decrement the number of tries left and try again. Log failures.
+			 * If I run out of try s just get out. This way, if there is some
+			 * kind of serious failure, I can get out and let other tiles try to
+			 * load.
+			 */
+			final CustomTile tile = (CustomTile) tileQueue.remove();
+
+			int trys = 3;
+			while (!tile.isLoaded() && trys >= 0) {
+				try {
+					BufferedImage img;
+					URI uri = getURI(tile);
+					img = cache.get(uri);
+
+					if (img == null) {
+						// put image in chache when loaded
+						byte[] bimg = cacheInputStream(uri.toURL());
+						img = ImageIO.read(new ByteArrayInputStream(bimg));
+						cache.put(uri, bimg, img);
+						img = cache.get(uri);
+					}
+					if (img != null) {
+						// set image to tile when loaded
+						final BufferedImage i = img;
+						SwingUtilities.invokeAndWait(new Runnable() {
+							@Override
+							public void run() {
+								tile.image = new SoftReference<BufferedImage>(i);
+								tile.setLoaded(true);
+								tile.setLoading(false);
+								fireTileLoadedEvent(tile);
+							}
+						});
+					} else {
+						// something has not been loaded correctly
+						trys--;
+					}
+
+				} catch (OutOfMemoryError memErr) {
+					// Cache out of memory order more
+					cache.needMoreMemory();
+
+				} catch (Throwable e) {
+					// tile could not be loaded
+
+					if (trys == 0) {
+						if (!tileQueue.contains(tile)) {
+							tileQueue.add(tile);
+						}
+					} else {
+						trys--;
+					}
+				}
+			}
+		}
+
+		// fetch data from OpenStreetMap server
+		private byte[] cacheInputStream(URL url) throws IOException {
+			URLConnection connection = url.openConnection();
+			connection.setRequestProperty("User-Agent", userAgent);
+			InputStream ins = connection.getInputStream();
+
+			ByteArrayOutputStream bout = new ByteArrayOutputStream();
+			byte[] buf = new byte[256];
+			while (true) {
+				int n = ins.read(buf);
+				if (n == -1)
+					break;
+				bout.write(buf, 0, n);
+			}
+			ins.close();
+
+			byte[] data = bout.toByteArray();
+			bout.close();
+			return data;
+		}
+	}
+
+}

+ 31 - 2
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/CustomWaypoint.java

@@ -8,28 +8,49 @@ import org.jxmapviewer.viewer.GeoPosition;
 /**
  * A waypoint that also has a color and a label
  * 
- * @author Martin Steiger
+ * @author Dominik Renkel
  */
 public class CustomWaypoint extends DefaultWaypoint {
 
+	/**
+	 * Label of the referenced graph node
+	 */
 	private final String label;
+
+	/**
+	 * Resource of the shown picture (device type picture)
+	 */
 	private final URL resource;
+
+	/**
+	 * the referenced, graph based, node id
+	 */
 	private final String nodeID;
 
+	/**
+	 * the device type of the referenced node
+	 */
+	private final String deviceType;
+
+	/**
+	 * is the node currently selected -> show it in red
+	 */
 	private Boolean isSelected = false;
 
 	/**
 	 * @param label
 	 *            the text
+	 * @param deviceType
 	 * @param color
 	 *            the color
 	 * @param coord
 	 *            the coordinate
 	 */
-	public CustomWaypoint(String label, String nodeID, URL resource, GeoPosition coord) {
+	public CustomWaypoint(String label, String nodeID, URL resource, String deviceType, GeoPosition coord) {
 		super(coord);
 		this.label = label;
 		this.resource = resource;
+		this.deviceType = deviceType;
 		this.nodeID = nodeID;
 	}
 
@@ -55,6 +76,14 @@ public class CustomWaypoint extends DefaultWaypoint {
 		return nodeID;
 	}
 
+	/**
+	 * 
+	 * @return the device type
+	 */
+	public String getDeviceType() {
+		return deviceType;
+	}
+
 	/**
 	 * change isSelected value to true
 	 */

+ 37 - 25
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/CustomWaypointRenderer.java

@@ -8,47 +8,58 @@ import java.awt.RenderingHints;
 import java.awt.geom.Point2D;
 import java.awt.image.BufferedImage;
 
-import javax.imageio.ImageIO;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.jxmapviewer.JXMapViewer;
 import org.jxmapviewer.viewer.WaypointRenderer;
 
 public class CustomWaypointRenderer implements WaypointRenderer<CustomWaypoint> {
 
-	private static final Log log = LogFactory.getLog(CustomWaypointRenderer.class);
-
+	/**
+	 * The font in which labels are drawn
+	 */
 	private final Font font = new Font("Lucida Sans", Font.BOLD, 15);
 
+	/**
+	 * show labels property
+	 */
 	private Boolean showLabels = true;
 
-	private static final Color STANDARD = Color.BLACK;
+	/**
+	 * the standard background color of images
+	 */
+	public static final Color STANDARD = Color.BLACK;
+
+	/**
+	 * the color of an image, when it was clicked
+	 */
+	public static final Color CLICKED = Color.RED;
+
+	/**
+	 * an rgb alpha value for computing only
+	 */
+	public static final int ALPHA = 255;
 
-	private static final Color CLICKED = Color.RED;
+	/**
+	 * the standard width of the shown images, after scaling it
+	 */
+	public static final int SCALEWIDTH = 60;
 
-	private static final int ALPHA = 255;
+	/**
+	 * the standard height of the shwon images, after scaling it
+	 */
+	public static final int SCALEHEIGHT = 60;
 
 	@Override
 	public void paintWaypoint(Graphics2D g, JXMapViewer viewer, CustomWaypoint w) {
 
 		g = (Graphics2D) g.create();
-		BufferedImage origImage = null;
 
-		try {
-			origImage = ImageIO.read(w.getResource());
-			if (w.getIsSelected()) {
-				origImage = MapViewFunctions.colorImage(origImage, STANDARD, CLICKED, ALPHA);
-			}
-		} catch (Exception ex) {
-			log.warn("couldn't read Waypoint png", ex);
-		}
+		// get pre loaded image
+		BufferedImage loadedImg = MapViewFunctions.imageMap.get(w.getDeviceType());
 
-		if (origImage == null)
-			return;
-
-		// scale image down
-		BufferedImage myImg = MapViewFunctions.scaleImage(origImage, 60, 60);
+		if (w.getIsSelected()) {
+			loadedImg = MapViewFunctions.colorImage(loadedImg, CustomWaypointRenderer.STANDARD,
+					CustomWaypointRenderer.CLICKED, CustomWaypointRenderer.ALPHA);
+		}
 
 		// get waypoint position
 		Point2D point = viewer.getTileFactory().geoToPixel(w.getPosition(), viewer.getZoom());
@@ -56,7 +67,8 @@ public class CustomWaypointRenderer implements WaypointRenderer<CustomWaypoint>
 		int x = (int) point.getX();
 		int y = (int) point.getY();
 
-		g.drawImage(myImg, x - myImg.getWidth() / 2, y - myImg.getHeight(), null);
+		// draw image on map
+		g.drawImage(loadedImg, x - loadedImg.getWidth() / 2, y - loadedImg.getHeight(), null);
 
 		if (showLabels) {
 
@@ -76,7 +88,7 @@ public class CustomWaypointRenderer implements WaypointRenderer<CustomWaypoint>
 
 			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 			// Show label left middle of deviceType picture
-			g.drawString(label, x - myImg.getWidth() / 2 - tw - 5, y + th / 2 - myImg.getHeight() / 2);
+			g.drawString(label, x - loadedImg.getWidth() / 2 - tw - 5, y + th / 2 - loadedImg.getHeight() / 2);
 
 			g.dispose();
 		}

+ 19 - 1
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/EdgePainter.java

@@ -21,16 +21,34 @@ import org.jxmapviewer.viewer.GeoPosition;
  */
 public class EdgePainter implements Painter<JXMapViewer> {
 
+	/**
+	 * show edges property
+	 */
 	private boolean showEdges = true;
 
+	/**
+	 * standard color in which edges are drawn
+	 */
 	private static final Color STANDARD = Color.BLACK;
 
+	/**
+	 * color in which edges are drawn when clicked
+	 */
 	private static final Color CLICKED = Color.RED;
 
+	/**
+	 * anti aliasing property
+	 */
 	private boolean antiAlias = true;
 
+	/**
+	 * the edges of the currently shown graph
+	 */
 	private static HashSet<Edge> edges;
 
+	/**
+	 * show weights property
+	 */
 	private Boolean showWeights = true;
 
 	/**
@@ -169,7 +187,7 @@ public class EdgePainter implements Painter<JXMapViewer> {
 	}
 
 	/**
-	 * Sets the removeEdges attribute
+	 * Sets the showEdges attribute
 	 * 
 	 * @param showEdges
 	 */

+ 72 - 6
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/MapViewFunctions.java

@@ -4,8 +4,13 @@ import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.HashSet;
 
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.graphstream.graph.Edge;
 import org.graphstream.graph.Node;
 import org.jxmapviewer.JXMapViewer;
@@ -21,9 +26,18 @@ import de.tu_darmstadt.informatik.tk.scopviz.graphs.GraphManager;
 import de.tu_darmstadt.informatik.tk.scopviz.main.Layer;
 import de.tu_darmstadt.informatik.tk.scopviz.main.MainApp;
 import de.tu_darmstadt.informatik.tk.scopviz.ui.GraphDisplayManager;
+import de.tu_darmstadt.informatik.tk.scopviz.ui.PropertiesManager;
 
 public final class MapViewFunctions {
 
+	private static final Log log = LogFactory.getLog(MapViewFunctions.class);
+
+	/**
+	 * Hash map to save, scaled images
+	 */
+	public static HashMap<String, BufferedImage> imageMap = new HashMap<String, BufferedImage>(
+			WorldView.waypoints.size());
+
 	/**
 	 * private constructor to avoid instantiation
 	 */
@@ -31,6 +45,48 @@ public final class MapViewFunctions {
 
 	}
 
+	/**
+	 * resets the hash map with the pictures
+	 */
+	public static void resetImageMap() {
+		imageMap = new HashMap<String, BufferedImage>(WorldView.waypoints.size());
+	}
+
+	/**
+	 * load and scale waypoint images and save them in a HashMap
+	 */
+	public static void initializeWaypointImages() {
+
+		for (CustomWaypoint w : WorldView.waypoints) {
+
+			BufferedImage origImage = null;
+
+			// image not loaded
+			if (!imageMap.containsKey(w.getDeviceType())) {
+
+				// try load image
+				try {
+					origImage = ImageIO.read(w.getResource());
+
+				} catch (Exception ex) {
+					log.warn("couldn't read Waypoint png", ex);
+				}
+
+				// loading complete
+				if (origImage != null) {
+					// scale image down
+					BufferedImage myImg = MapViewFunctions.scaleImage(origImage, CustomWaypointRenderer.SCALEWIDTH,
+							CustomWaypointRenderer.SCALEHEIGHT);
+
+					// save image in hash map
+					imageMap.put(w.getDeviceType(), myImg);
+				}
+			}
+
+		}
+
+	}
+
 	/**
 	 * Scale a given BufferedImage down to given width w and given height h
 	 * 
@@ -65,17 +121,19 @@ public final class MapViewFunctions {
 		int width = image.getWidth();
 		int height = image.getHeight();
 
+		BufferedImage imgOut = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
 		for (int xx = 0; xx < width; xx++) {
 			for (int yy = 0; yy < height; yy++) {
 				Color originalColor = new Color(image.getRGB(xx, yy), true);
 
-				// Pixel is Black -> paint it Red
+				// pixel needs to be changed
 				if (originalColor.equals(toChange) && originalColor.getAlpha() == alpha) {
-					image.setRGB(xx, yy, changeWith.getRGB());
+					imgOut.setRGB(xx, yy, changeWith.getRGB());
 				}
 			}
 		}
-		return image;
+		return imgOut;
 	}
 
 	/**
@@ -111,13 +169,21 @@ public final class MapViewFunctions {
 
 				// Create waypoints with device type dependent pictures
 				String deviceType = (String) node.getAttribute("typeofDevice");
-				URL resource = getDeviceType(deviceType);
+				URL resource = getDeviceTypeURL(deviceType);
 
 				// create a new waypoint with the node information
-				waypoints.add(new CustomWaypoint(node.getAttribute("ui.label"), node.getId(), resource, geoPos));
+				waypoints.add(
+						new CustomWaypoint(node.getAttribute("ui.label"), node.getId(), resource, deviceType, geoPos));
 
 			}
 		}
+
+		// deselect all previously clicked waypoints or edges
+		PropertiesManager.showNewDataSet(null);
+
+		// load and save waypoint images
+		MapViewFunctions.initializeWaypointImages();
+
 	}
 
 	/**
@@ -126,7 +192,7 @@ public final class MapViewFunctions {
 	 * @param deviceType
 	 * @return
 	 */
-	public static URL getDeviceType(String deviceType) {
+	public static URL getDeviceTypeURL(String deviceType) {
 
 		URL image = MainApp.class.getResource("/png/symbol_icons/" + deviceType + ".png");
 

+ 38 - 7
scopviz/src/main/java/de/tu_darmstadt/informatik/tk/scopviz/ui/mapView/WorldView.java

@@ -1,5 +1,9 @@
 package de.tu_darmstadt.informatik.tk.scopviz.ui.mapView;
 
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -9,7 +13,6 @@ import org.jxmapviewer.JXMapViewer;
 import org.jxmapviewer.OSMTileFactoryInfo;
 import org.jxmapviewer.painter.CompoundPainter;
 import org.jxmapviewer.painter.Painter;
-import org.jxmapviewer.viewer.DefaultTileFactory;
 import org.jxmapviewer.viewer.GeoPosition;
 import org.jxmapviewer.viewer.TileFactoryInfo;
 import org.jxmapviewer.viewer.WaypointPainter;
@@ -38,6 +41,16 @@ public class WorldView {
 	 */
 	public static GUIController controller;
 
+	/*
+	 * All waypoints in the WorldView
+	 */
+	public static HashSet<CustomWaypoint> waypoints;
+
+	/*
+	 * All edges in the WorldView
+	 */
+	public static HashSet<Edge> edges;
+
 	/**
 	 * private constructor to avoid instantiation
 	 */
@@ -57,12 +70,14 @@ public class WorldView {
 
 	/**
 	 * load map elements based on current underlay graph
+	 * 
+	 * @throws IOException
 	 */
-	public static void loadWorldView() {
+	public static void loadWorldView() throws IOException {
 
 		HashSet<GeoPosition> nodePositions = new HashSet<GeoPosition>();
-		HashSet<CustomWaypoint> waypoints = new HashSet<CustomWaypoint>();
-		HashSet<Edge> edges = new HashSet<Edge>();
+		waypoints = new HashSet<CustomWaypoint>();
+		edges = new HashSet<Edge>();
 
 		// Get GeoPositions of nodes and get all waypoints created
 		MapViewFunctions.fetchGraphData(nodePositions, waypoints, edges);
@@ -77,14 +92,30 @@ public class WorldView {
 
 		// Create a compound painter that uses all painters
 		List<Painter<JXMapViewer>> painters = new ArrayList<Painter<JXMapViewer>>();
-		painters.add(waypointPainter);
 		painters.add(edgePainter);
+		painters.add(waypointPainter);
 
 		CompoundPainter<JXMapViewer> painter = new CompoundPainter<JXMapViewer>(painters);
 
 		// Create a TileFactoryInfo for OpenStreetMap
 		TileFactoryInfo info = new OSMTileFactoryInfo();
-		DefaultTileFactory tileFactory = new DefaultTileFactory(info);
+
+		// try to load OpenStreesMap, when errors occur, throw and handle
+		// Exceptions
+		URL osmWebPage;
+		try {
+			// try to connect to OpenStreetMap server
+			osmWebPage = new URL(info.getBaseURL());
+			URLConnection connection = osmWebPage.openConnection();
+			connection.connect();
+
+		} catch (MalformedURLException e) {
+			// TODO add Dialog with eroor msg and stack trace
+			e.printStackTrace();
+
+		}
+
+		CustomTileFactory tileFactory = new CustomTileFactory(info);
 		internMapViewer.setTileFactory(tileFactory);
 
 		// Use 8 threads in parallel to load the tiles
@@ -96,7 +127,7 @@ public class WorldView {
 		internMapViewer.setOverlayPainter(painter);
 
 		// "click on waypoints" listener
-		internMapViewer.addMouseListener(new CustomMapClickListener(internMapViewer, waypoints, edges));
+		internMapViewer.addMouseListener(new CustomMapClickListener(internMapViewer));
 
 		internMapViewer.repaint();
 	}