[jmapviewer] 01/07: Imported Upstream version 1.06+dfsg
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Thu Mar 5 07:51:34 UTC 2015
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository jmapviewer.
commit 43dc3940d645144d571bfdb3d240f41cb9e10cbd
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Thu Mar 5 08:31:12 2015 +0100
Imported Upstream version 1.06+dfsg
---
Readme.txt | 6 +-
.../gui/jmapviewer/AbstractLayer.java | 176 +-
.../openstreetmap/gui/jmapviewer/Coordinate.java | 158 +-
.../gui/jmapviewer/DefaultMapController.java | 358 ++--
src/org/openstreetmap/gui/jmapviewer/Demo.java | 528 ++---
.../gui/jmapviewer/JMapController.java | 68 +-
.../openstreetmap/gui/jmapviewer/JMapViewer.java | 2220 ++++++++++----------
.../gui/jmapviewer/JobDispatcher.java | 352 ++--
.../gui/jmapviewer/MapMarkerCircle.java | 212 +-
.../openstreetmap/gui/jmapviewer/MapMarkerDot.java | 104 +-
.../gui/jmapviewer/MemoryTileCache.java | 452 ++--
.../gui/jmapviewer/OsmFileCacheTileLoader.java | 1007 ++++-----
.../openstreetmap/gui/jmapviewer/OsmMercator.java | 348 +--
.../gui/jmapviewer/OsmTileLoader.java | 256 +--
src/org/openstreetmap/gui/jmapviewer/Tile.java | 668 +++---
.../gui/jmapviewer/TileController.java | 174 +-
.../gui/jmapviewer/interfaces/MapMarker.java | 108 +-
.../gui/jmapviewer/interfaces/MapRectangle.java | 76 +-
.../gui/jmapviewer/interfaces/TileCache.java | 100 +-
.../gui/jmapviewer/interfaces/TileJob.java | 40 +-
.../gui/jmapviewer/interfaces/TileLoader.java | 46 +-
.../jmapviewer/interfaces/TileLoaderListener.java | 32 +-
.../gui/jmapviewer/interfaces/TileSource.java | 316 +--
.../tilesources/AbstractTMSTileSource.java | 250 +--
.../tilesources/BingAerialTileSource.java | 610 +++---
.../gui/jmapviewer/tilesources/OsmTileSource.java | 156 +-
.../jmapviewer/tilesources/ScanexTileSource.java | 316 +--
.../gui/jmapviewer/tilesources/TMSTileSource.java | 68 +-
.../tilesources/TemplatedTMSTileSource.java | 183 +-
29 files changed, 4699 insertions(+), 4689 deletions(-)
diff --git a/Readme.txt b/Readme.txt
index ef51e62..4c90acf 100644
--- a/Readme.txt
+++ b/Readme.txt
@@ -2,15 +2,15 @@ JMapViewer
(c) 2007, Tim Haussmann
(c) 2008-2012, Jan Peter Stotz
-(c) 2009-2013, Dirk Stöcker
+(c) 2009-2015, Dirk Stöcker
(c) 2009, Stefan Zeller
(c) 2009, Karl Guggisberg
(c) 2009, Dave Hansen
(c) 2010-2011, Ian Dees
(c) 2010-2011, Michael Vigovsky
-(c) 2011-2013, Paul Hartmann
+(c) 2011-2015, Paul Hartmann
(c) 2011-2014, Gleb Smirnoff
-(c) 2011-2014, Vincent Privat
+(c) 2011-2015, Vincent Privat
(c) 2011, Jason Huntley
(c) 2012, Simon Legner
(c) 2012, Teemu Koskinen
diff --git a/src/org/openstreetmap/gui/jmapviewer/AbstractLayer.java b/src/org/openstreetmap/gui/jmapviewer/AbstractLayer.java
index e288087..cc79f22 100644
--- a/src/org/openstreetmap/gui/jmapviewer/AbstractLayer.java
+++ b/src/org/openstreetmap/gui/jmapviewer/AbstractLayer.java
@@ -1,88 +1,88 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AbstractLayer {
- private LayerGroup parent;
- private String name;
- private String description;
- private Style style;
- private Boolean visible;
- private Boolean visibleTexts=true;
-
- public AbstractLayer(String name){
- this(name, (String)null);
- }
- public AbstractLayer(String name, String description){
- this(name, description, MapMarkerCircle.getDefaultStyle());
- }
- public AbstractLayer(String name, Style style){
- this(name, null, style);
- }
- public AbstractLayer(String name, String description, Style style){
- this(null, name, description, style);
- }
- public AbstractLayer(LayerGroup parent, String name){
- this(parent, name, MapMarkerCircle.getDefaultStyle());
- }
- public AbstractLayer(LayerGroup parent, String name, Style style){
- this(parent, name, null, style);
- }
- public AbstractLayer(LayerGroup parent, String name, String description, Style style){
- setParent(parent);
- setName(name);
- setDescription(description);
- setStyle(style);
- setVisible(true);
-
- if(parent!=null) parent.add(this);
- }
- public LayerGroup getParent() {
- return parent;
- }
- public void setParent(LayerGroup parent) {
- this.parent = parent;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getDescription() {
- return description;
- }
- public void setDescription(String description) {
- this.description = description;
- }
- public Style getStyle() {
- return style;
- }
- public void setStyle(Style style) {
- this.style = style;
- }
- public Boolean isVisible() {
- return visible;
- }
- public void setVisible(Boolean visible) {
- this.visible = visible;
- }
- public static <E> List<E> add(List<E> list, E element) {
- if(element!=null){
- if(list==null) list = new ArrayList<>();
- if(!list.contains(element)) list.add(element);
- }
- return list;
- }
- public Boolean isVisibleTexts() {
- return visibleTexts;
- }
- public void setVisibleTexts(Boolean visibleTexts) {
- this.visibleTexts = visibleTexts;
- }
- public String toString(){
- return name;
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AbstractLayer {
+ private LayerGroup parent;
+ private String name;
+ private String description;
+ private Style style;
+ private Boolean visible;
+ private Boolean visibleTexts=true;
+
+ public AbstractLayer(String name){
+ this(name, (String)null);
+ }
+ public AbstractLayer(String name, String description){
+ this(name, description, MapMarkerCircle.getDefaultStyle());
+ }
+ public AbstractLayer(String name, Style style){
+ this(name, null, style);
+ }
+ public AbstractLayer(String name, String description, Style style){
+ this(null, name, description, style);
+ }
+ public AbstractLayer(LayerGroup parent, String name){
+ this(parent, name, MapMarkerCircle.getDefaultStyle());
+ }
+ public AbstractLayer(LayerGroup parent, String name, Style style){
+ this(parent, name, null, style);
+ }
+ public AbstractLayer(LayerGroup parent, String name, String description, Style style){
+ setParent(parent);
+ setName(name);
+ setDescription(description);
+ setStyle(style);
+ setVisible(true);
+
+ if(parent!=null) parent.add(this);
+ }
+ public LayerGroup getParent() {
+ return parent;
+ }
+ public void setParent(LayerGroup parent) {
+ this.parent = parent;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getDescription() {
+ return description;
+ }
+ public void setDescription(String description) {
+ this.description = description;
+ }
+ public Style getStyle() {
+ return style;
+ }
+ public void setStyle(Style style) {
+ this.style = style;
+ }
+ public Boolean isVisible() {
+ return visible;
+ }
+ public void setVisible(Boolean visible) {
+ this.visible = visible;
+ }
+ public static <E> List<E> add(List<E> list, E element) {
+ if(element!=null){
+ if(list==null) list = new ArrayList<>();
+ if(!list.contains(element)) list.add(element);
+ }
+ return list;
+ }
+ public Boolean isVisibleTexts() {
+ return visibleTexts;
+ }
+ public void setVisibleTexts(Boolean visibleTexts) {
+ this.visibleTexts = visibleTexts;
+ }
+ public String toString(){
+ return name;
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/Coordinate.java b/src/org/openstreetmap/gui/jmapviewer/Coordinate.java
index 25a482d..a4a9e97 100644
--- a/src/org/openstreetmap/gui/jmapviewer/Coordinate.java
+++ b/src/org/openstreetmap/gui/jmapviewer/Coordinate.java
@@ -1,79 +1,79 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.awt.geom.Point2D;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.util.Objects;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
-
-/**
- * This class encapsulates a Point2D.Double and provide access
- * via <tt>lat</tt> and <tt>lon</tt>.
- *
- * @author Jan Peter Stotz
- *
- */
-public class Coordinate implements Serializable, ICoordinate {
- private transient Point2D.Double data;
-
- public Coordinate(double lat, double lon) {
- data = new Point2D.Double(lon, lat);
- }
-
- public double getLat() {
- return data.y;
- }
-
- public void setLat(double lat) {
- data.y = lat;
- }
-
- public double getLon() {
- return data.x;
- }
-
- public void setLon(double lon) {
- data.x = lon;
- }
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.writeObject(data.x);
- out.writeObject(data.y);
- }
-
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- data = new Point2D.Double();
- data.x = (Double) in.readObject();
- data.y = (Double) in.readObject();
- }
-
- public String toString() {
- return "Coordinate[" + data.y + ", " + data.x + "]";
- }
-
- @Override
- public int hashCode() {
- int hash = 3;
- hash = 61 * hash + Objects.hashCode(this.data);
- return hash;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final Coordinate other = (Coordinate) obj;
- if (!Objects.equals(this.data, other.data)) {
- return false;
- }
- return true;
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Objects;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
+
+/**
+ * This class encapsulates a Point2D.Double and provide access
+ * via <tt>lat</tt> and <tt>lon</tt>.
+ *
+ * @author Jan Peter Stotz
+ *
+ */
+public class Coordinate implements Serializable, ICoordinate {
+ private transient Point2D.Double data;
+
+ public Coordinate(double lat, double lon) {
+ data = new Point2D.Double(lon, lat);
+ }
+
+ public double getLat() {
+ return data.y;
+ }
+
+ public void setLat(double lat) {
+ data.y = lat;
+ }
+
+ public double getLon() {
+ return data.x;
+ }
+
+ public void setLon(double lon) {
+ data.x = lon;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeObject(data.x);
+ out.writeObject(data.y);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ data = new Point2D.Double();
+ data.x = (Double) in.readObject();
+ data.y = (Double) in.readObject();
+ }
+
+ public String toString() {
+ return "Coordinate[" + data.y + ", " + data.x + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 61 * hash + Objects.hashCode(this.data);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Coordinate other = (Coordinate) obj;
+ if (!Objects.equals(this.data, other.data)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java b/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java
index e254c42..20c8c12 100644
--- a/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java
+++ b/src/org/openstreetmap/gui/jmapviewer/DefaultMapController.java
@@ -1,179 +1,179 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-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 {
-
- private static final int MOUSE_BUTTONS_MASK = MouseEvent.BUTTON3_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK
- | MouseEvent.BUTTON2_DOWN_MASK;
-
- private static final int MAC_MOUSE_BUTTON3_MASK = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK;
- public DefaultMapController(JMapViewer map) {
- super(map);
- }
-
- private Point lastDragPoint;
-
- private boolean isMoving = false;
-
- private boolean movementEnabled = true;
-
- private int movementMouseButton = MouseEvent.BUTTON3;
- private int movementMouseButtonMask = MouseEvent.BUTTON3_DOWN_MASK;
-
- private boolean wheelZoomEnabled = true;
- private boolean doubleClickZoomEnabled = true;
-
- public void mouseDragged(MouseEvent e) {
- if (!movementEnabled || !isMoving)
- return;
- // Is only the selected mouse button pressed?
- if ((e.getModifiersEx() & MOUSE_BUTTONS_MASK) == movementMouseButtonMask || isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK) {
- Point p = e.getPoint();
- if (lastDragPoint != null) {
- int diffx = lastDragPoint.x - p.x;
- int diffy = lastDragPoint.y - p.y;
- map.moveMap(diffx, diffy);
- }
- lastDragPoint = p;
- }
- }
-
- public void mouseClicked(MouseEvent e) {
- if (doubleClickZoomEnabled && e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
- map.zoomIn(e.getPoint());
- }
- }
-
- public void mousePressed(MouseEvent e) {
- if (e.getButton() == movementMouseButton || isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK) {
- lastDragPoint = null;
- isMoving = true;
- }
- }
-
- public void mouseReleased(MouseEvent e) {
- if (e.getButton() == movementMouseButton || isPlatformOsx() && e.getButton() == MouseEvent.BUTTON1) {
- 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;
- switch (movementMouseButton) {
- case MouseEvent.BUTTON1:
- movementMouseButtonMask = MouseEvent.BUTTON1_DOWN_MASK;
- break;
- case MouseEvent.BUTTON2:
- movementMouseButtonMask = MouseEvent.BUTTON2_DOWN_MASK;
- break;
- case MouseEvent.BUTTON3:
- movementMouseButtonMask = MouseEvent.BUTTON3_DOWN_MASK;
- break;
- default:
- throw new RuntimeException("Unsupported button");
- }
- }
-
- 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;
- }
-
- public void mouseEntered(MouseEvent e) {
- }
-
- public void mouseExited(MouseEvent e) {
- }
-
- public void mouseMoved(MouseEvent e) {
- // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired.
- //
- if (isPlatformOsx()) {
- if (!movementEnabled || !isMoving)
- return;
- // Is only the selected mouse button pressed?
- if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) {
- Point p = e.getPoint();
- if (lastDragPoint != null) {
- int diffx = lastDragPoint.x - p.x;
- int diffy = lastDragPoint.y - p.y;
- map.moveMap(diffx, diffy);
- }
- lastDragPoint = p;
- }
-
- }
-
- }
-
- /**
- * Replies true if we are currently running on OSX
- *
- * @return true if we are currently running on OSX
- */
- public static boolean isPlatformOsx() {
- String os = System.getProperty("os.name");
- return os != null && os.toLowerCase().startsWith("mac os x");
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+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 {
+
+ private static final int MOUSE_BUTTONS_MASK = MouseEvent.BUTTON3_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK
+ | MouseEvent.BUTTON2_DOWN_MASK;
+
+ private static final int MAC_MOUSE_BUTTON3_MASK = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK;
+ public DefaultMapController(JMapViewer map) {
+ super(map);
+ }
+
+ private Point lastDragPoint;
+
+ private boolean isMoving = false;
+
+ private boolean movementEnabled = true;
+
+ private int movementMouseButton = MouseEvent.BUTTON3;
+ private int movementMouseButtonMask = MouseEvent.BUTTON3_DOWN_MASK;
+
+ private boolean wheelZoomEnabled = true;
+ private boolean doubleClickZoomEnabled = true;
+
+ public void mouseDragged(MouseEvent e) {
+ if (!movementEnabled || !isMoving)
+ return;
+ // Is only the selected mouse button pressed?
+ if ((e.getModifiersEx() & MOUSE_BUTTONS_MASK) == movementMouseButtonMask || isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK) {
+ Point p = e.getPoint();
+ if (lastDragPoint != null) {
+ int diffx = lastDragPoint.x - p.x;
+ int diffy = lastDragPoint.y - p.y;
+ map.moveMap(diffx, diffy);
+ }
+ lastDragPoint = p;
+ }
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ if (doubleClickZoomEnabled && e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
+ map.zoomIn(e.getPoint());
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {
+ if (e.getButton() == movementMouseButton || isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK) {
+ lastDragPoint = null;
+ isMoving = true;
+ }
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ if (e.getButton() == movementMouseButton || isPlatformOsx() && e.getButton() == MouseEvent.BUTTON1) {
+ 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;
+ switch (movementMouseButton) {
+ case MouseEvent.BUTTON1:
+ movementMouseButtonMask = MouseEvent.BUTTON1_DOWN_MASK;
+ break;
+ case MouseEvent.BUTTON2:
+ movementMouseButtonMask = MouseEvent.BUTTON2_DOWN_MASK;
+ break;
+ case MouseEvent.BUTTON3:
+ movementMouseButtonMask = MouseEvent.BUTTON3_DOWN_MASK;
+ break;
+ default:
+ throw new RuntimeException("Unsupported button");
+ }
+ }
+
+ 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;
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ public void mouseExited(MouseEvent e) {
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired.
+ //
+ if (isPlatformOsx()) {
+ if (!movementEnabled || !isMoving)
+ return;
+ // Is only the selected mouse button pressed?
+ if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) {
+ Point p = e.getPoint();
+ if (lastDragPoint != null) {
+ int diffx = lastDragPoint.x - p.x;
+ int diffy = lastDragPoint.y - p.y;
+ map.moveMap(diffx, diffy);
+ }
+ lastDragPoint = p;
+ }
+
+ }
+
+ }
+
+ /**
+ * Replies true if we are currently running on OSX
+ *
+ * @return true if we are currently running on OSX
+ */
+ public static boolean isPlatformOsx() {
+ String os = System.getProperty("os.name");
+ return os != null && os.toLowerCase().startsWith("mac os x");
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/Demo.java b/src/org/openstreetmap/gui/jmapviewer/Demo.java
index 57ee480..ddbb451 100644
--- a/src/org/openstreetmap/gui/jmapviewer/Demo.java
+++ b/src/org/openstreetmap/gui/jmapviewer/Demo.java
@@ -1,264 +1,264 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.awt.BorderLayout;
-import java.awt.Cursor;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.io.IOException;
-
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
-import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
-import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;
-import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOpenAerialTileSource;
-import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOsmTileSource;
-import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
-
-/**
- * Demonstrates the usage of {@link JMapViewer}
- *
- * @author Jan Peter Stotz
- *
- */
-public class Demo extends JFrame implements JMapViewerEventListener {
-
- private static final long serialVersionUID = 1L;
-
- private JMapViewerTree treeMap = null;
-
- private JLabel zoomLabel=null;
- private JLabel zoomValue=null;
-
- private JLabel mperpLabelName=null;
- private JLabel mperpLabelValue = null;
-
- /**
- * Constructs the {@code Demo}.
- */
- public Demo() {
- super("JMapViewer Demo");
- setSize(400, 400);
-
- treeMap = new JMapViewerTree("Zones");
-
- // Listen to the map viewer for user operations so components will
- // receive events and update
- map().addJMVListener(this);
-
- setLayout(new BorderLayout());
- setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- setExtendedState(JFrame.MAXIMIZED_BOTH);
- JPanel panel = new JPanel();
- JPanel panelTop = new JPanel();
- JPanel panelBottom = new JPanel();
- JPanel helpPanel = new JPanel();
-
- mperpLabelName=new JLabel("Meters/Pixels: ");
- mperpLabelValue=new JLabel(String.format("%s",map().getMeterPerPixel()));
-
- zoomLabel=new JLabel("Zoom: ");
- zoomValue=new JLabel(String.format("%s", map().getZoom()));
-
- add(panel, BorderLayout.NORTH);
- add(helpPanel, BorderLayout.SOUTH);
- panel.setLayout(new BorderLayout());
- panel.add(panelTop, BorderLayout.NORTH);
- panel.add(panelBottom, BorderLayout.SOUTH);
- JLabel helpLabel = new JLabel("Use right mouse button to move,\n "
- + "left double click or mouse wheel to zoom.");
- helpPanel.add(helpLabel);
- JButton button = new JButton("setDisplayToFitMapMarkers");
- button.addActionListener(new ActionListener() {
-
- public void actionPerformed(ActionEvent e) {
- map().setDisplayToFitMapMarkers();
- }
- });
- JComboBox<TileSource> tileSourceSelector = new JComboBox<>(new TileSource[] {
- new OsmTileSource.Mapnik(),
- new OsmTileSource.CycleMap(),
- new BingAerialTileSource(),
- new MapQuestOsmTileSource(),
- new MapQuestOpenAerialTileSource() });
- tileSourceSelector.addItemListener(new ItemListener() {
- public void itemStateChanged(ItemEvent e) {
- map().setTileSource((TileSource) e.getItem());
- }
- });
- JComboBox<TileLoader> tileLoaderSelector;
- try {
- tileLoaderSelector = new JComboBox<>(new TileLoader[] { new OsmFileCacheTileLoader(map()), new OsmTileLoader(map()) });
- } catch (IOException e) {
- tileLoaderSelector = new JComboBox<>(new TileLoader[] { new OsmTileLoader(map()) });
- }
- tileLoaderSelector.addItemListener(new ItemListener() {
- public void itemStateChanged(ItemEvent e) {
- map().setTileLoader((TileLoader) e.getItem());
- }
- });
- map().setTileLoader((TileLoader) tileLoaderSelector.getSelectedItem());
- panelTop.add(tileSourceSelector);
- panelTop.add(tileLoaderSelector);
- 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());
- }
- });
- panelBottom.add(showMapMarker);
- ///
- final JCheckBox showTreeLayers = new JCheckBox("Tree Layers visible");
- showTreeLayers.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- treeMap.setTreeVisible(showTreeLayers.isSelected());
- }
- });
- panelBottom.add(showTreeLayers);
- ///
- final JCheckBox showToolTip = new JCheckBox("ToolTip visible");
- showToolTip.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- map().setToolTipText(null);
- }
- });
- panelBottom.add(showToolTip);
- ///
- 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());
- }
- });
- panelBottom.add(showTileGrid);
- final JCheckBox showZoomControls = new JCheckBox("Show zoom controls");
- showZoomControls.setSelected(map().getZoomControlsVisible());
- showZoomControls.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- map().setZoomContolsVisible(showZoomControls.isSelected());
- }
- });
- panelBottom.add(showZoomControls);
- final JCheckBox scrollWrapEnabled = new JCheckBox("Scrollwrap enabled");
- scrollWrapEnabled.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- map().setScrollWrapEnabled(scrollWrapEnabled.isSelected());
- }
- });
- panelBottom.add(scrollWrapEnabled);
- panelBottom.add(button);
-
- panelTop.add(zoomLabel);
- panelTop.add(zoomValue);
- panelTop.add(mperpLabelName);
- panelTop.add(mperpLabelValue);
-
- add(treeMap, BorderLayout.CENTER);
-
- //
- LayerGroup germanyGroup = new LayerGroup("Germany");
- Layer germanyWestLayer = germanyGroup.addLayer("Germany West");
- Layer germanyEastLayer = germanyGroup.addLayer("Germany East");
- MapMarkerDot eberstadt = new MapMarkerDot(germanyEastLayer, "Eberstadt", 49.814284999, 8.642065999);
- MapMarkerDot ebersheim = new MapMarkerDot(germanyWestLayer, "Ebersheim", 49.91, 8.24);
- MapMarkerDot empty = new MapMarkerDot(germanyEastLayer, 49.71, 8.64);
- MapMarkerDot darmstadt = new MapMarkerDot(germanyEastLayer, "Darmstadt", 49.8588, 8.643);
- map().addMapMarker(eberstadt);
- map().addMapMarker(ebersheim);
- map().addMapMarker(empty);
- Layer franceLayer = treeMap.addLayer("France");
- map().addMapMarker(new MapMarkerDot(franceLayer, "La Gallerie", 48.71, -1));
- map().addMapMarker(new MapMarkerDot(43.604, 1.444));
- map().addMapMarker(new MapMarkerCircle(53.343, -6.267, 0.666));
- map().addMapRectangle(new MapRectangleImpl(new Coordinate(53.343, -6.267), new Coordinate(43.604, 1.444)));
- map().addMapMarker(darmstadt);
- treeMap.addLayer(germanyWestLayer);
- treeMap.addLayer(germanyEastLayer);
-
- MapPolygon bermudas = new MapPolygonImpl(c(49,1), c(45,10), c(40,5));
- map().addMapPolygon( bermudas );
- map().addMapPolygon( new MapPolygonImpl(germanyEastLayer, "Riedstadt", ebersheim, darmstadt, eberstadt, empty));
-
- map().addMapMarker(new MapMarkerCircle(germanyWestLayer, "North of Suisse", new Coordinate(48, 7), .5));
- Layer spain = treeMap.addLayer("Spain");
- map().addMapMarker(new MapMarkerCircle(spain, "La Garena", new Coordinate(40.4838, -3.39), .002));
- spain.setVisible(false);
-
- Layer wales = treeMap.addLayer("UK");
- map().addMapRectangle(new MapRectangleImpl(wales, "Wales", c(53.35,-4.57), c(51.64,-2.63)));
-
- // map.setDisplayPosition(new Coordinate(49.807, 8.6), 11);
- // map.setTileGridVisible(true);
-
- map().addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- if (e.getButton() == MouseEvent.BUTTON1) {
- map().getAttribution().handleAttribution(e.getPoint(), true);
- }
- }
- });
-
- map().addMouseMotionListener(new MouseAdapter() {
- @Override
- public void mouseMoved(MouseEvent e) {
- Point p = e.getPoint();
- boolean cursorHand = map().getAttribution().handleAttributionCursor(p);
- if (cursorHand) {
- map().setCursor(new Cursor(Cursor.HAND_CURSOR));
- } else {
- map().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
- }
- if(showToolTip.isSelected()) map().setToolTipText(map().getPosition(p).toString());
- }
- });
- }
- private JMapViewer map(){
- return treeMap.getViewer();
- }
- private static Coordinate c(double lat, double lon){
- return new Coordinate(lat, lon);
- }
-
- /**
- * @param args
- */
- public static void main(String[] args) {
- // java.util.Properties systemProperties = System.getProperties();
- // systemProperties.setProperty("http.proxyHost", "localhost");
- // systemProperties.setProperty("http.proxyPort", "8008");
- new Demo().setVisible(true);
- }
-
- private void updateZoomParameters() {
- if (mperpLabelValue!=null)
- mperpLabelValue.setText(String.format("%s",map().getMeterPerPixel()));
- if (zoomValue!=null)
- zoomValue.setText(String.format("%s", map().getZoom()));
- }
-
- @Override
- public void processCommand(JMVCommandEvent command) {
- if (command.getCommand().equals(JMVCommandEvent.COMMAND.ZOOM) ||
- command.getCommand().equals(JMVCommandEvent.COMMAND.MOVE)) {
- updateZoomParameters();
- }
- }
-
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
+import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;
+import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOpenAerialTileSource;
+import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOsmTileSource;
+import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
+
+/**
+ * Demonstrates the usage of {@link JMapViewer}
+ *
+ * @author Jan Peter Stotz
+ *
+ */
+public class Demo extends JFrame implements JMapViewerEventListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private JMapViewerTree treeMap = null;
+
+ private JLabel zoomLabel=null;
+ private JLabel zoomValue=null;
+
+ private JLabel mperpLabelName=null;
+ private JLabel mperpLabelValue = null;
+
+ /**
+ * Constructs the {@code Demo}.
+ */
+ public Demo() {
+ super("JMapViewer Demo");
+ setSize(400, 400);
+
+ treeMap = new JMapViewerTree("Zones");
+
+ // Listen to the map viewer for user operations so components will
+ // receive events and update
+ map().addJMVListener(this);
+
+ setLayout(new BorderLayout());
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setExtendedState(JFrame.MAXIMIZED_BOTH);
+ JPanel panel = new JPanel();
+ JPanel panelTop = new JPanel();
+ JPanel panelBottom = new JPanel();
+ JPanel helpPanel = new JPanel();
+
+ mperpLabelName=new JLabel("Meters/Pixels: ");
+ mperpLabelValue=new JLabel(String.format("%s",map().getMeterPerPixel()));
+
+ zoomLabel=new JLabel("Zoom: ");
+ zoomValue=new JLabel(String.format("%s", map().getZoom()));
+
+ add(panel, BorderLayout.NORTH);
+ add(helpPanel, BorderLayout.SOUTH);
+ panel.setLayout(new BorderLayout());
+ panel.add(panelTop, BorderLayout.NORTH);
+ panel.add(panelBottom, BorderLayout.SOUTH);
+ JLabel helpLabel = new JLabel("Use right mouse button to move,\n "
+ + "left double click or mouse wheel to zoom.");
+ helpPanel.add(helpLabel);
+ JButton button = new JButton("setDisplayToFitMapMarkers");
+ button.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ map().setDisplayToFitMapMarkers();
+ }
+ });
+ JComboBox<TileSource> tileSourceSelector = new JComboBox<>(new TileSource[] {
+ new OsmTileSource.Mapnik(),
+ new OsmTileSource.CycleMap(),
+ new BingAerialTileSource(),
+ new MapQuestOsmTileSource(),
+ new MapQuestOpenAerialTileSource() });
+ tileSourceSelector.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ map().setTileSource((TileSource) e.getItem());
+ }
+ });
+ JComboBox<TileLoader> tileLoaderSelector;
+ try {
+ tileLoaderSelector = new JComboBox<>(new TileLoader[] { new OsmFileCacheTileLoader(map()), new OsmTileLoader(map()) });
+ } catch (IOException e) {
+ tileLoaderSelector = new JComboBox<>(new TileLoader[] { new OsmTileLoader(map()) });
+ }
+ tileLoaderSelector.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ map().setTileLoader((TileLoader) e.getItem());
+ }
+ });
+ map().setTileLoader((TileLoader) tileLoaderSelector.getSelectedItem());
+ panelTop.add(tileSourceSelector);
+ panelTop.add(tileLoaderSelector);
+ 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());
+ }
+ });
+ panelBottom.add(showMapMarker);
+ ///
+ final JCheckBox showTreeLayers = new JCheckBox("Tree Layers visible");
+ showTreeLayers.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ treeMap.setTreeVisible(showTreeLayers.isSelected());
+ }
+ });
+ panelBottom.add(showTreeLayers);
+ ///
+ final JCheckBox showToolTip = new JCheckBox("ToolTip visible");
+ showToolTip.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ map().setToolTipText(null);
+ }
+ });
+ panelBottom.add(showToolTip);
+ ///
+ 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());
+ }
+ });
+ panelBottom.add(showTileGrid);
+ final JCheckBox showZoomControls = new JCheckBox("Show zoom controls");
+ showZoomControls.setSelected(map().getZoomControlsVisible());
+ showZoomControls.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ map().setZoomContolsVisible(showZoomControls.isSelected());
+ }
+ });
+ panelBottom.add(showZoomControls);
+ final JCheckBox scrollWrapEnabled = new JCheckBox("Scrollwrap enabled");
+ scrollWrapEnabled.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ map().setScrollWrapEnabled(scrollWrapEnabled.isSelected());
+ }
+ });
+ panelBottom.add(scrollWrapEnabled);
+ panelBottom.add(button);
+
+ panelTop.add(zoomLabel);
+ panelTop.add(zoomValue);
+ panelTop.add(mperpLabelName);
+ panelTop.add(mperpLabelValue);
+
+ add(treeMap, BorderLayout.CENTER);
+
+ //
+ LayerGroup germanyGroup = new LayerGroup("Germany");
+ Layer germanyWestLayer = germanyGroup.addLayer("Germany West");
+ Layer germanyEastLayer = germanyGroup.addLayer("Germany East");
+ MapMarkerDot eberstadt = new MapMarkerDot(germanyEastLayer, "Eberstadt", 49.814284999, 8.642065999);
+ MapMarkerDot ebersheim = new MapMarkerDot(germanyWestLayer, "Ebersheim", 49.91, 8.24);
+ MapMarkerDot empty = new MapMarkerDot(germanyEastLayer, 49.71, 8.64);
+ MapMarkerDot darmstadt = new MapMarkerDot(germanyEastLayer, "Darmstadt", 49.8588, 8.643);
+ map().addMapMarker(eberstadt);
+ map().addMapMarker(ebersheim);
+ map().addMapMarker(empty);
+ Layer franceLayer = treeMap.addLayer("France");
+ map().addMapMarker(new MapMarkerDot(franceLayer, "La Gallerie", 48.71, -1));
+ map().addMapMarker(new MapMarkerDot(43.604, 1.444));
+ map().addMapMarker(new MapMarkerCircle(53.343, -6.267, 0.666));
+ map().addMapRectangle(new MapRectangleImpl(new Coordinate(53.343, -6.267), new Coordinate(43.604, 1.444)));
+ map().addMapMarker(darmstadt);
+ treeMap.addLayer(germanyWestLayer);
+ treeMap.addLayer(germanyEastLayer);
+
+ MapPolygon bermudas = new MapPolygonImpl(c(49,1), c(45,10), c(40,5));
+ map().addMapPolygon( bermudas );
+ map().addMapPolygon( new MapPolygonImpl(germanyEastLayer, "Riedstadt", ebersheim, darmstadt, eberstadt, empty));
+
+ map().addMapMarker(new MapMarkerCircle(germanyWestLayer, "North of Suisse", new Coordinate(48, 7), .5));
+ Layer spain = treeMap.addLayer("Spain");
+ map().addMapMarker(new MapMarkerCircle(spain, "La Garena", new Coordinate(40.4838, -3.39), .002));
+ spain.setVisible(false);
+
+ Layer wales = treeMap.addLayer("UK");
+ map().addMapRectangle(new MapRectangleImpl(wales, "Wales", c(53.35,-4.57), c(51.64,-2.63)));
+
+ // map.setDisplayPosition(new Coordinate(49.807, 8.6), 11);
+ // map.setTileGridVisible(true);
+
+ map().addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ map().getAttribution().handleAttribution(e.getPoint(), true);
+ }
+ }
+ });
+
+ map().addMouseMotionListener(new MouseAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ Point p = e.getPoint();
+ boolean cursorHand = map().getAttribution().handleAttributionCursor(p);
+ if (cursorHand) {
+ map().setCursor(new Cursor(Cursor.HAND_CURSOR));
+ } else {
+ map().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ if(showToolTip.isSelected()) map().setToolTipText(map().getPosition(p).toString());
+ }
+ });
+ }
+ private JMapViewer map(){
+ return treeMap.getViewer();
+ }
+ private static Coordinate c(double lat, double lon){
+ return new Coordinate(lat, lon);
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ // java.util.Properties systemProperties = System.getProperties();
+ // systemProperties.setProperty("http.proxyHost", "localhost");
+ // systemProperties.setProperty("http.proxyPort", "8008");
+ new Demo().setVisible(true);
+ }
+
+ private void updateZoomParameters() {
+ if (mperpLabelValue!=null)
+ mperpLabelValue.setText(String.format("%s",map().getMeterPerPixel()));
+ if (zoomValue!=null)
+ zoomValue.setText(String.format("%s", map().getZoom()));
+ }
+
+ @Override
+ public void processCommand(JMVCommandEvent command) {
+ if (command.getCommand().equals(JMVCommandEvent.COMMAND.ZOOM) ||
+ command.getCommand().equals(JMVCommandEvent.COMMAND.MOVE)) {
+ updateZoomParameters();
+ }
+ }
+
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/JMapController.java b/src/org/openstreetmap/gui/jmapviewer/JMapController.java
index 8de5300..0417563 100644
--- a/src/org/openstreetmap/gui/jmapviewer/JMapController.java
+++ b/src/org/openstreetmap/gui/jmapviewer/JMapController.java
@@ -1,34 +1,34 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-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 {
-
- protected 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);
- }
-
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+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 {
+
+ protected 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);
+ }
+
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java b/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
index 6ccbe2e..d0d1237 100644
--- a/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
+++ b/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
@@ -1,1110 +1,1110 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-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.event.MouseEvent;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-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 javax.swing.event.EventListenerList;
-
-import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
-import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent.COMMAND;
-import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
-import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
-import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
-import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
-import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
-
-/**
- * Provides a simple panel that displays pre-rendered map tiles loaded from the
- * OpenStreetMap project.
- *
- * @author Jan Peter Stotz
- *
- */
-public class JMapViewer extends JPanel implements TileLoaderListener {
-
- public static boolean debug = false;
-
- /**
- * Vectors for clock-wise tile painting
- */
- protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
-
- public static final int MAX_ZOOM = 22;
- public static final int MIN_ZOOM = 0;
-
- protected List<MapMarker> mapMarkerList;
- protected List<MapRectangle> mapRectangleList;
- protected List<MapPolygon> mapPolygonList;
-
- protected boolean mapMarkersVisible;
- protected boolean mapRectanglesVisible;
- protected boolean mapPolygonsVisible;
-
- protected boolean tileGridVisible;
- protected boolean scrollWrapEnabled;
-
- protected TileController tileController;
-
- /**
- * 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;
-
- public static enum ZOOM_BUTTON_STYLE {
- HORIZONTAL,
- VERTICAL
- }
-
- protected ZOOM_BUTTON_STYLE zoomButtonStyle;
-
- protected TileSource tileSource;
-
- protected AttributionSupport attribution = new AttributionSupport();
-
- /**
- * 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 in a
- * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
- * retrieving the tiles.
- */
- public JMapViewer() {
- this(new MemoryTileCache(), 8);
- new DefaultMapController(this);
- }
-
- /**
- * Creates a new {@link JMapViewer} instance.
- * @param tileCache The cache where to store tiles
- * @param downloadThreadCount The number of parallel threads for retrieving the tiles
- */
- public JMapViewer(TileCache tileCache, int downloadThreadCount) {
- JobDispatcher.setMaxWorkers(downloadThreadCount);
- tileSource = new OsmTileSource.Mapnik();
- tileController = new TileController(tileSource, tileCache, this);
- mapMarkerList = Collections.synchronizedList(new LinkedList<MapMarker>());
- mapPolygonList = Collections.synchronizedList(new LinkedList<MapPolygon>());
- mapRectangleList = Collections.synchronizedList(new LinkedList<MapRectangle>());
- mapMarkersVisible = true;
- mapRectanglesVisible = true;
- mapPolygonsVisible = true;
- tileGridVisible = false;
- setLayout(null);
- initializeZoomSlider();
- setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
- setPreferredSize(new Dimension(400, 400));
- setDisplayPosition(new Coordinate(50, 9), 3);
- }
-
- @Override
- public String getToolTipText(MouseEvent event) {
- return super.getToolTipText(event);
- }
-
- protected void initializeZoomSlider() {
- zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom());
- 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());
- }
- });
- zoomSlider.setFocusable(false);
- add(zoomSlider);
- int size = 18;
- try {
- ImageIcon icon = new ImageIcon(JMapViewer.class.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();
- }
- });
- zoomInButton.setFocusable(false);
- add(zoomInButton);
- try {
- ImageIcon icon = new ImageIcon(JMapViewer.class.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();
- }
- });
- zoomOutButton.setFocusable(false);
- add(zoomOutButton);
- }
-
- /**
- * Changes the map pane so that it is centered on the specified coordinate
- * at the given zoom level.
- *
- * @param to
- * specified coordinate
- * @param zoom
- * {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
- */
- public void setDisplayPosition(Coordinate to, int zoom) {
- setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), to, 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 to
- * specified coordinate
- * @param zoom
- * {@link #MIN_ZOOM} <= zoom level <=
- * {@link TileSource#getMaxZoom()}
- */
- public void setDisplayPosition(Point mapPoint, Coordinate to, int zoom) {
- int x = tileSource.LonToX(to.getLon(), zoom);
- int y = tileSource.LatToY(to.getLat(), 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 > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
- return;
-
- // 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;
- setIgnoreRepaint(true);
- try {
- int oldZoom = this.zoom;
- this.zoom = zoom;
- if (oldZoom != zoom) {
- zoomChanged(oldZoom);
- }
- if (zoomSlider.getValue() != zoom) {
- zoomSlider.setValue(zoom);
- }
- } finally {
- setIgnoreRepaint(false);
- repaint();
- }
- }
-
- /**
- * Sets the displayed map pane and zoom level so that all chosen map elements are visible.
- */
- public void setDisplayToFitMapElements(boolean markers, boolean rectangles, boolean polygons) {
- int nbElemToCheck = 0;
- if (markers && mapMarkerList != null)
- nbElemToCheck += mapMarkerList.size();
- if (rectangles && mapRectangleList != null)
- nbElemToCheck += mapRectangleList.size();
- if (polygons && mapPolygonList != null)
- nbElemToCheck += mapPolygonList.size();
- if (nbElemToCheck == 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;
- int mapZoomMax = tileController.getTileSource().getMaxZoom();
-
- if (markers) {
- synchronized (mapMarkerList) {
- for (MapMarker marker : mapMarkerList) {
- if (marker.isVisible()) {
- int x = tileSource.LonToX(marker.getLon(), mapZoomMax);
- int y = tileSource.LatToY(marker.getLat(), mapZoomMax);
- 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);
- }
- }
- }
- }
-
- if (rectangles) {
- synchronized (mapRectangleList) {
- for (MapRectangle rectangle : mapRectangleList) {
- if (rectangle.isVisible()) {
- x_max = Math.max(x_max, tileSource.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
- y_max = Math.max(y_max, tileSource.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
- x_min = Math.min(x_min, tileSource.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
- y_min = Math.min(y_min, tileSource.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
- }
- }
- }
- }
-
- if (polygons) {
- synchronized (mapPolygonList) {
- for (MapPolygon polygon : mapPolygonList) {
- if (polygon.isVisible()) {
- for (ICoordinate c : polygon.getPoints()) {
- int x = tileSource.LonToX(c.getLon(), mapZoomMax);
- int y = tileSource.LatToY(c.getLat(), mapZoomMax);
- 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());
- int newZoom = mapZoomMax;
- int x = x_max - x_min;
- int y = y_max - y_min;
- while (x > width || y > height) {
- newZoom--;
- x >>= 1;
- y >>= 1;
- }
- x = x_min + (x_max - x_min) / 2;
- y = y_min + (y_max - y_min) / 2;
- int z = 1 << (mapZoomMax - newZoom);
- x /= z;
- y /= z;
- setDisplayPosition(x, y, newZoom);
- }
-
- /**
- * Sets the displayed map pane and zoom level so that all map markers are visible.
- */
- public void setDisplayToFitMapMarkers() {
- setDisplayToFitMapElements(true, false, false);
- }
-
- /**
- * Sets the displayed map pane and zoom level so that all map rectangles are visible.
- */
- public void setDisplayToFitMapRectangles() {
- setDisplayToFitMapElements(false, true, false);
- }
-
- /**
- * Sets the displayed map pane and zoom level so that all map polygons are visible.
- */
- public void setDisplayToFitMapPolygons() {
- setDisplayToFitMapElements(false, false, true);
- }
-
- /**
- * @return the center
- */
- public Point getCenter() {
- return center;
- }
-
- /**
- * @param center the center to set
- */
- public void setCenter(Point center) {
- this.center = center;
- }
-
- /**
- * Calculates the latitude/longitude coordinate of the center of the
- * currently displayed map area.
- *
- * @return latitude / longitude
- */
- public Coordinate getPosition() {
- double lon = tileSource.XToLon(center.x, zoom);
- double lat = tileSource.YToLat(center.y, zoom);
- return new Coordinate(lat, lon);
- }
-
- /**
- * Converts the relative pixel coordinate (regarding the top left corner of
- * the displayed map) into a latitude / longitude coordinate
- *
- * @param mapPoint
- * relative pixel coordinate regarding the top left corner of the
- * displayed map
- * @return latitude / longitude
- */
- public Coordinate getPosition(Point mapPoint) {
- return getPosition(mapPoint.x, mapPoint.y);
- }
-
- /**
- * Converts the relative pixel coordinate (regarding the top left corner of
- * the displayed map) into a latitude / longitude coordinate
- *
- * @param mapPointX
- * @param mapPointY
- * @return latitude / longitude
- */
- public Coordinate getPosition(int mapPointX, int mapPointY) {
- int x = center.x + mapPointX - getWidth() / 2;
- int y = center.y + mapPointY - getHeight() / 2;
- double lon = tileSource.XToLon(x, zoom);
- double lat = tileSource.YToLat(y, zoom);
- return new Coordinate(lat, lon);
- }
-
- /**
- * Calculates the position on the map of a given coordinate
- *
- * @param lat
- * @param lon
- * @param checkOutside
- * @return point on the map or <code>null</code> if the point is not visible
- * and checkOutside set to <code>true</code>
- */
- public Point getMapPosition(double lat, double lon, boolean checkOutside) {
- int x = tileSource.LonToX(lon, zoom);
- int y = tileSource.LatToY(lat, zoom);
- x -= center.x - getWidth() / 2;
- y -= center.y - getHeight() / 2;
- if (checkOutside) {
- if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
- return null;
- }
- return new Point(x, y);
- }
-
- /**
- * Calculates the position on the map of a given coordinate
- *
- * @param lat Latitude
- * @param offset Offset respect Latitude
- * @param checkOutside
- * @return Integer the radius in pixels
- */
- public Integer getLatOffset(double lat, double offset, boolean checkOutside) {
- int y = tileSource.LatToY(lat + offset, zoom);
- y -= center.y - getHeight() / 2;
- if (checkOutside) {
- if (y < 0 || y > getHeight())
- return null;
- }
- return y;
- }
-
- /**
- * 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) {
- return getMapPosition(lat, lon, true);
- }
-
- /**
- * Calculates the position on the map of a given coordinate
- *
- * @param marker MapMarker object that define the x,y coordinate
- * @return Integer the radius in pixels
- */
- public Integer getRadius(MapMarker marker, Point p) {
- if (marker.getMarkerStyle() == MapMarker.STYLE.FIXED)
- return (int) marker.getRadius();
- else if (p != null) {
- Integer radius = getLatOffset(marker.getLat(), marker.getRadius(), false);
- radius = radius == null ? null : p.y - radius.intValue();
- return radius;
- } else
- return null;
- }
-
- /**
- * Calculates the position on the map of a given coordinate
- *
- * @param coord
- * @return point on the map or <code>null</code> if the point is not visible
- */
- public Point getMapPosition(Coordinate coord) {
- if (coord != null)
- return getMapPosition(coord.getLat(), coord.getLon());
- else
- return null;
- }
-
- /**
- * Calculates the position on the map of a given coordinate
- *
- * @param coord
- * @return point on the map or <code>null</code> if the point is not visible
- * and checkOutside set to <code>true</code>
- */
- public Point getMapPosition(ICoordinate coord, boolean checkOutside) {
- if (coord != null)
- return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
- else
- return null;
- }
-
- /**
- * Gets the meter per pixel.
- *
- * @return the meter per pixel
- * @author Jason Huntley
- */
- public double getMeterPerPixel() {
- Point origin = new Point(5, 5);
- Point center = new Point(getWidth() / 2, getHeight() / 2);
-
- double pDistance = center.distance(origin);
-
- Coordinate originCoord = getPosition(origin);
- Coordinate centerCoord = getPosition(center);
-
- double mDistance = tileSource.getDistance(originCoord.getLat(), originCoord.getLon(),
- centerCoord.getLat(), centerCoord.getLon());
-
- return mDistance / pDistance;
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
-
- int iMove = 0;
-
- int tilesize = tileSource.getTileSize();
- int tilex = center.x / tilesize;
- int tiley = center.y / tilesize;
- int off_x = (center.x % tilesize);
- int off_y = (center.y % tilesize);
-
- int w2 = getWidth() / 2;
- int h2 = getHeight() / 2;
- int posx = w2 - off_x;
- int posy = h2 - off_y;
-
- int diff_left = off_x;
- int diff_right = tilesize - off_x;
- int diff_top = off_y;
- int diff_bottom = tilesize - off_y;
-
- boolean start_left = diff_left < diff_right;
- boolean start_top = diff_top < diff_bottom;
-
- if (start_top) {
- if (start_left) {
- iMove = 2;
- } else {
- iMove = 3;
- }
- } else {
- if (start_left) {
- iMove = 1;
- } else {
- iMove = 0;
- }
- } // calculate the visibility borders
- int x_min = -tilesize;
- int y_min = -tilesize;
- int x_max = getWidth();
- int y_max = getHeight();
-
- // calculate the length of the grid (number of squares per edge)
- int gridLength = 1 << zoom;
-
- // paint the tiles in a spiral, starting from center of the map
- boolean painted = true;
- int x = 0;
- while (painted) {
- painted = false;
- for (int i = 0; i < 4; i++) {
- if (i % 2 == 0) {
- x++;
- }
- for (int j = 0; j < x; j++) {
- if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
- // tile is visible
- Tile tile;
- if (scrollWrapEnabled) {
- // in case tilex is out of bounds, grab the tile to use for wrapping
- int tilexWrap = (((tilex % gridLength) + gridLength) % gridLength);
- tile = tileController.getTile(tilexWrap, tiley, zoom);
- } else {
- tile = tileController.getTile(tilex, tiley, zoom);
- }
- if (tile != null) {
- tile.paint(g, posx, posy);
- if (tileGridVisible) {
- g.drawRect(posx, posy, tilesize, tilesize);
- }
- }
- painted = true;
- }
- Point p = move[iMove];
- posx += p.x * tilesize;
- posy += p.y * tilesize;
- tilex += p.x;
- tiley += p.y;
- }
- iMove = (iMove + 1) % move.length;
- }
- }
- // outer border of the map
- int mapSize = tilesize << zoom;
- if (scrollWrapEnabled) {
- g.drawLine(0, h2 - center.y, getWidth(), h2 - center.y);
- g.drawLine(0, h2 - center.y + mapSize, getWidth(), h2 - center.y + mapSize);
- } else {
- g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
- }
-
- // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
-
- // keep x-coordinates from growing without bound if scroll-wrap is enabled
- if (scrollWrapEnabled) {
- center.x = center.x % mapSize;
- }
-
- if (mapPolygonsVisible && mapPolygonList != null) {
- synchronized (mapPolygonList) {
- for (MapPolygon polygon : mapPolygonList) {
- if (polygon.isVisible())
- paintPolygon(g, polygon);
- }
- }
- }
-
- if (mapRectanglesVisible && mapRectangleList != null) {
- synchronized (mapRectangleList) {
- for (MapRectangle rectangle : mapRectangleList) {
- if (rectangle.isVisible())
- paintRectangle(g, rectangle);
- }
- }
- }
-
- if (mapMarkersVisible && mapMarkerList != null) {
- synchronized (mapMarkerList) {
- for (MapMarker marker : mapMarkerList) {
- if (marker.isVisible())
- paintMarker(g, marker);
- }
- }
- }
-
- attribution.paintAttribution(g, getWidth(), getHeight(), getPosition(0, 0), getPosition(getWidth(), getHeight()), zoom, this);
- }
-
- /**
- * Paint a single marker.
- */
- protected void paintMarker(Graphics g, MapMarker marker) {
- Point p = getMapPosition(marker.getLat(), marker.getLon(), marker.getMarkerStyle() == MapMarker.STYLE.FIXED);
- Integer radius = getRadius(marker, p);
- if (scrollWrapEnabled) {
- int tilesize = tileSource.getTileSize();
- int mapSize = tilesize << zoom;
- if (p == null) {
- p = getMapPosition(marker.getLat(), marker.getLon(), false);
- radius = getRadius(marker, p);
- }
- marker.paint(g, p, radius);
- int xSave = p.x;
- int xWrap = xSave;
- // overscan of 15 allows up to 30-pixel markers to gracefully scroll off the edge of the panel
- while ((xWrap -= mapSize) >= -15) {
- p.x = xWrap;
- marker.paint(g, p, radius);
- }
- xWrap = xSave;
- while ((xWrap += mapSize) <= getWidth() + 15) {
- p.x = xWrap;
- marker.paint(g, p, radius);
- }
- } else {
- if (p != null) {
- marker.paint(g, p, radius);
- }
- }
- }
-
- /**
- * Paint a single rectangle.
- */
- protected void paintRectangle(Graphics g, MapRectangle rectangle) {
- Coordinate topLeft = rectangle.getTopLeft();
- Coordinate bottomRight = rectangle.getBottomRight();
- if (topLeft != null && bottomRight != null) {
- Point pTopLeft = getMapPosition(topLeft, false);
- Point pBottomRight = getMapPosition(bottomRight, false);
- if (pTopLeft != null && pBottomRight != null) {
- rectangle.paint(g, pTopLeft, pBottomRight);
- if (scrollWrapEnabled) {
- int tilesize = tileSource.getTileSize();
- int mapSize = tilesize << zoom;
- int xTopLeftSave = pTopLeft.x;
- int xTopLeftWrap = xTopLeftSave;
- int xBottomRightSave = pBottomRight.x;
- int xBottomRightWrap = xBottomRightSave;
- while ((xBottomRightWrap -= mapSize) >= 0) {
- xTopLeftWrap -= mapSize;
- pTopLeft.x = xTopLeftWrap;
- pBottomRight.x = xBottomRightWrap;
- rectangle.paint(g, pTopLeft, pBottomRight);
- }
- xTopLeftWrap = xTopLeftSave;
- xBottomRightWrap = xBottomRightSave;
- while ((xTopLeftWrap += mapSize) <= getWidth()) {
- xBottomRightWrap += mapSize;
- pTopLeft.x = xTopLeftWrap;
- pBottomRight.x = xBottomRightWrap;
- rectangle.paint(g, pTopLeft, pBottomRight);
- }
-
- }
- }
- }
- }
-
- /**
- * Paint a single polygon.
- */
- protected void paintPolygon(Graphics g, MapPolygon polygon) {
- List<? extends ICoordinate> coords = polygon.getPoints();
- if (coords != null && coords.size() >= 3) {
- List<Point> points = new LinkedList<>();
- for (ICoordinate c : coords) {
- Point p = getMapPosition(c, false);
- if (p == null) {
- return;
- }
- points.add(p);
- }
- polygon.paint(g, points);
- if (scrollWrapEnabled) {
- int tilesize = tileSource.getTileSize();
- int mapSize = tilesize << zoom;
- List<Point> pointsWrapped = new LinkedList<>(points);
- boolean keepWrapping = true;
- while (keepWrapping) {
- for (Point p : pointsWrapped) {
- p.x -= mapSize;
- if (p.x < 0) {
- keepWrapping = false;
- }
- }
- polygon.paint(g, pointsWrapped);
- }
- pointsWrapped = new LinkedList<>(points);
- keepWrapping = true;
- while (keepWrapping) {
- for (Point p : pointsWrapped) {
- p.x += mapSize;
- if (p.x > getWidth()) {
- keepWrapping = false;
- }
- }
- polygon.paint(g, pointsWrapped);
- }
- }
- }
- }
-
- /**
- * Moves the visible map pane.
- *
- * @param x
- * horizontal movement in pixel.
- * @param y
- * vertical movement in pixel
- */
- public void moveMap(int x, int y) {
- tileController.cancelOutstandingJobs(); // Clear outstanding load
- center.x += x;
- center.y += y;
- repaint();
- this.fireJMVEvent(new JMVCommandEvent(COMMAND.MOVE, this));
- }
-
- /**
- * @return the current zoom level
- */
- public int getZoom() {
- return zoom;
- }
-
- /**
- * Increases the current zoom level by one
- */
- public void zoomIn() {
- setZoom(zoom + 1);
- }
-
- /**
- * Increases the current zoom level by one
- * @param mapPoint point to choose as center for new zoom level
- */
- public void zoomIn(Point mapPoint) {
- setZoom(zoom + 1, mapPoint);
- }
-
- /**
- * Decreases the current zoom level by one
- */
- public void zoomOut() {
- setZoom(zoom - 1);
- }
-
- /**
- * Decreases the current zoom level by one
- *
- * @param mapPoint point to choose as center for new zoom level
- */
- public void zoomOut(Point mapPoint) {
- setZoom(zoom - 1, mapPoint);
- }
-
- /**
- * Set the zoom level and center point for display
- *
- * @param zoom new zoom level
- * @param mapPoint point to choose as center for new zoom level
- */
- public void setZoom(int zoom, Point mapPoint) {
- if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom()
- || zoom == this.zoom)
- return;
- Coordinate zoomPos = getPosition(mapPoint);
- tileController.cancelOutstandingJobs(); // Clearing outstanding load
- // requests
- setDisplayPosition(mapPoint, zoomPos, zoom);
-
- this.fireJMVEvent(new JMVCommandEvent(COMMAND.ZOOM, this));
- }
-
- /**
- * Set the zoom level
- *
- * @param zoom new zoom level
- */
- public void setZoom(int zoom) {
- setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
- }
-
- /**
- * Every time the zoom level changes this method is called. Override it in
- * derived implementations for adapting zoom dependent values. The new zoom
- * level can be obtained via {@link #getZoom()}.
- *
- * @param oldZoom
- * the previous zoom level
- */
- protected void zoomChanged(int oldZoom) {
- zoomSlider.setToolTipText("Zoom level " + zoom);
- zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
- zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
- zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom());
- zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
- }
-
- 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 setMapRectangleList(List<MapRectangle> mapRectangleList) {
- this.mapRectangleList = mapRectangleList;
- repaint();
- }
-
- public List<MapRectangle> getMapRectangleList() {
- return mapRectangleList;
- }
-
- public void setMapPolygonList(List<MapPolygon> mapPolygonList) {
- this.mapPolygonList = mapPolygonList;
- repaint();
- }
-
- public List<MapPolygon> getMapPolygonList() {
- return mapPolygonList;
- }
-
- public void addMapMarker(MapMarker marker) {
- mapMarkerList.add(marker);
- repaint();
- }
-
- public void removeMapMarker(MapMarker marker) {
- mapMarkerList.remove(marker);
- repaint();
- }
-
- public void removeAllMapMarkers() {
- mapMarkerList.clear();
- repaint();
- }
-
- public void addMapRectangle(MapRectangle rectangle) {
- mapRectangleList.add(rectangle);
- repaint();
- }
-
- public void removeMapRectangle(MapRectangle rectangle) {
- mapRectangleList.remove(rectangle);
- repaint();
- }
-
- public void removeAllMapRectangles() {
- mapRectangleList.clear();
- repaint();
- }
-
- public void addMapPolygon(MapPolygon polygon) {
- mapPolygonList.add(polygon);
- repaint();
- }
-
- public void removeMapPolygon(MapPolygon polygon) {
- mapPolygonList.remove(polygon);
- repaint();
- }
-
- public void removeAllMapPolygons() {
- mapPolygonList.clear();
- repaint();
- }
-
- public void setZoomContolsVisible(boolean visible) {
- zoomSlider.setVisible(visible);
- zoomInButton.setVisible(visible);
- zoomOutButton.setVisible(visible);
- }
-
- public boolean getZoomControlsVisible() {
- return zoomSlider.isVisible();
- }
-
- public void setTileSource(TileSource tileSource) {
- if (tileSource.getMaxZoom() > MAX_ZOOM)
- throw new RuntimeException("Maximum zoom level too high");
- if (tileSource.getMinZoom() < MIN_ZOOM)
- throw new RuntimeException("Minimum zoom level too low");
- Coordinate position = getPosition();
- this.tileSource = tileSource;
- tileController.setTileSource(tileSource);
- zoomSlider.setMinimum(tileSource.getMinZoom());
- zoomSlider.setMaximum(tileSource.getMaxZoom());
- tileController.cancelOutstandingJobs();
- if (zoom > tileSource.getMaxZoom()) {
- setZoom(tileSource.getMaxZoom());
- }
- attribution.initialize(tileSource);
- setDisplayPosition(position, zoom);
- repaint();
- }
-
- public void tileLoadingFinished(Tile tile, boolean success) {
- repaint();
- }
-
- public boolean isMapRectanglesVisible() {
- return mapRectanglesVisible;
- }
-
- /**
- * Enables or disables painting of the {@link MapRectangle}
- *
- * @param mapRectanglesVisible
- * @see #addMapRectangle(MapRectangle)
- * @see #getMapRectangleList()
- */
- public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
- this.mapRectanglesVisible = mapRectanglesVisible;
- repaint();
- }
-
- public boolean isMapPolygonsVisible() {
- return mapPolygonsVisible;
- }
-
- /**
- * Enables or disables painting of the {@link MapPolygon}
- *
- * @param mapPolygonsVisible
- * @see #addMapPolygon(MapPolygon)
- * @see #getMapPolygonList()
- */
- public void setMapPolygonsVisible(boolean mapPolygonsVisible) {
- this.mapPolygonsVisible = mapPolygonsVisible;
- repaint();
- }
-
- public boolean isScrollWrapEnabled() {
- return scrollWrapEnabled;
- }
-
- public void setScrollWrapEnabled(boolean scrollWrapEnabled) {
- this.scrollWrapEnabled = scrollWrapEnabled;
- repaint();
- }
-
- public ZOOM_BUTTON_STYLE getZoomButtonStyle() {
- return zoomButtonStyle;
- }
-
- public void setZoomButtonStyle(ZOOM_BUTTON_STYLE style) {
- zoomButtonStyle = style;
- if (zoomSlider == null || zoomInButton == null || zoomOutButton == null) {
- return;
- }
- switch (style) {
- case HORIZONTAL:
- zoomSlider.setBounds(10, 10, 30, 150);
- zoomInButton.setBounds(4, 155, 18, 18);
- zoomOutButton.setBounds(26, 155, 18, 18);
- break;
- case VERTICAL:
- zoomSlider.setBounds(10, 27, 30, 150);
- zoomInButton.setBounds(14, 8, 20, 20);
- zoomOutButton.setBounds(14, 176, 20, 20);
- break;
- default:
- zoomSlider.setBounds(10, 10, 30, 150);
- zoomInButton.setBounds(4, 155, 18, 18);
- zoomOutButton.setBounds(26, 155, 18, 18);
- break;
- }
- repaint();
- }
-
- public TileController getTileController() {
- return tileController;
- }
-
- /**
- * Return tile information caching class
- * @see TileLoaderListener#getTileCache()
- */
- public TileCache getTileCache() {
- return tileController.getTileCache();
- }
-
- public void setTileLoader(TileLoader loader) {
- tileController.setTileLoader(loader);
- }
-
- public AttributionSupport getAttribution() {
- return attribution;
- }
-
- protected EventListenerList evtListenerList = new EventListenerList();
-
- /**
- * @param listener listener to set
- */
- public void addJMVListener(JMapViewerEventListener listener) {
- evtListenerList.add(JMapViewerEventListener.class, listener);
- }
-
- /**
- * @param listener listener to remove
- */
- public void removeJMVListener(JMapViewerEventListener listener) {
- evtListenerList.remove(JMapViewerEventListener.class, listener);
- }
-
- /**
- * Send an update to all objects registered with viewer
- *
- * @param evt event to dispatch
- */
- void fireJMVEvent(JMVCommandEvent evt) {
- Object[] listeners = evtListenerList.getListenerList();
- for (int i = 0; i < listeners.length; i += 2) {
- if (listeners[i] == JMapViewerEventListener.class) {
- ((JMapViewerEventListener) listeners[i + 1]).processCommand(evt);
- }
- }
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+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.event.MouseEvent;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+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 javax.swing.event.EventListenerList;
+
+import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
+import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent.COMMAND;
+import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
+import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
+import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
+
+/**
+ * Provides a simple panel that displays pre-rendered map tiles loaded from the
+ * OpenStreetMap project.
+ *
+ * @author Jan Peter Stotz
+ *
+ */
+public class JMapViewer extends JPanel implements TileLoaderListener {
+
+ public static boolean debug = false;
+
+ /**
+ * Vectors for clock-wise tile painting
+ */
+ protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
+
+ public static final int MAX_ZOOM = 22;
+ public static final int MIN_ZOOM = 0;
+
+ protected List<MapMarker> mapMarkerList;
+ protected List<MapRectangle> mapRectangleList;
+ protected List<MapPolygon> mapPolygonList;
+
+ protected boolean mapMarkersVisible;
+ protected boolean mapRectanglesVisible;
+ protected boolean mapPolygonsVisible;
+
+ protected boolean tileGridVisible;
+ protected boolean scrollWrapEnabled;
+
+ protected TileController tileController;
+
+ /**
+ * 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;
+
+ public static enum ZOOM_BUTTON_STYLE {
+ HORIZONTAL,
+ VERTICAL
+ }
+
+ protected ZOOM_BUTTON_STYLE zoomButtonStyle;
+
+ protected TileSource tileSource;
+
+ protected AttributionSupport attribution = new AttributionSupport();
+
+ /**
+ * 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 in a
+ * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
+ * retrieving the tiles.
+ */
+ public JMapViewer() {
+ this(new MemoryTileCache(), 8);
+ new DefaultMapController(this);
+ }
+
+ /**
+ * Creates a new {@link JMapViewer} instance.
+ * @param tileCache The cache where to store tiles
+ * @param downloadThreadCount The number of parallel threads for retrieving the tiles
+ */
+ public JMapViewer(TileCache tileCache, int downloadThreadCount) {
+ JobDispatcher.setMaxWorkers(downloadThreadCount);
+ tileSource = new OsmTileSource.Mapnik();
+ tileController = new TileController(tileSource, tileCache, this);
+ mapMarkerList = Collections.synchronizedList(new LinkedList<MapMarker>());
+ mapPolygonList = Collections.synchronizedList(new LinkedList<MapPolygon>());
+ mapRectangleList = Collections.synchronizedList(new LinkedList<MapRectangle>());
+ mapMarkersVisible = true;
+ mapRectanglesVisible = true;
+ mapPolygonsVisible = true;
+ tileGridVisible = false;
+ setLayout(null);
+ initializeZoomSlider();
+ setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
+ setPreferredSize(new Dimension(400, 400));
+ setDisplayPosition(new Coordinate(50, 9), 3);
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent event) {
+ return super.getToolTipText(event);
+ }
+
+ protected void initializeZoomSlider() {
+ zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom());
+ 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());
+ }
+ });
+ zoomSlider.setFocusable(false);
+ add(zoomSlider);
+ int size = 18;
+ try {
+ ImageIcon icon = new ImageIcon(JMapViewer.class.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();
+ }
+ });
+ zoomInButton.setFocusable(false);
+ add(zoomInButton);
+ try {
+ ImageIcon icon = new ImageIcon(JMapViewer.class.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();
+ }
+ });
+ zoomOutButton.setFocusable(false);
+ add(zoomOutButton);
+ }
+
+ /**
+ * Changes the map pane so that it is centered on the specified coordinate
+ * at the given zoom level.
+ *
+ * @param to
+ * specified coordinate
+ * @param zoom
+ * {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
+ */
+ public void setDisplayPosition(Coordinate to, int zoom) {
+ setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), to, 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 to
+ * specified coordinate
+ * @param zoom
+ * {@link #MIN_ZOOM} <= zoom level <=
+ * {@link TileSource#getMaxZoom()}
+ */
+ public void setDisplayPosition(Point mapPoint, Coordinate to, int zoom) {
+ int x = tileSource.LonToX(to.getLon(), zoom);
+ int y = tileSource.LatToY(to.getLat(), 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 > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
+ return;
+
+ // 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;
+ setIgnoreRepaint(true);
+ try {
+ int oldZoom = this.zoom;
+ this.zoom = zoom;
+ if (oldZoom != zoom) {
+ zoomChanged(oldZoom);
+ }
+ if (zoomSlider.getValue() != zoom) {
+ zoomSlider.setValue(zoom);
+ }
+ } finally {
+ setIgnoreRepaint(false);
+ repaint();
+ }
+ }
+
+ /**
+ * Sets the displayed map pane and zoom level so that all chosen map elements are visible.
+ */
+ public void setDisplayToFitMapElements(boolean markers, boolean rectangles, boolean polygons) {
+ int nbElemToCheck = 0;
+ if (markers && mapMarkerList != null)
+ nbElemToCheck += mapMarkerList.size();
+ if (rectangles && mapRectangleList != null)
+ nbElemToCheck += mapRectangleList.size();
+ if (polygons && mapPolygonList != null)
+ nbElemToCheck += mapPolygonList.size();
+ if (nbElemToCheck == 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;
+ int mapZoomMax = tileController.getTileSource().getMaxZoom();
+
+ if (markers) {
+ synchronized (mapMarkerList) {
+ for (MapMarker marker : mapMarkerList) {
+ if (marker.isVisible()) {
+ int x = tileSource.LonToX(marker.getLon(), mapZoomMax);
+ int y = tileSource.LatToY(marker.getLat(), mapZoomMax);
+ 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);
+ }
+ }
+ }
+ }
+
+ if (rectangles) {
+ synchronized (mapRectangleList) {
+ for (MapRectangle rectangle : mapRectangleList) {
+ if (rectangle.isVisible()) {
+ x_max = Math.max(x_max, tileSource.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
+ y_max = Math.max(y_max, tileSource.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
+ x_min = Math.min(x_min, tileSource.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
+ y_min = Math.min(y_min, tileSource.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
+ }
+ }
+ }
+ }
+
+ if (polygons) {
+ synchronized (mapPolygonList) {
+ for (MapPolygon polygon : mapPolygonList) {
+ if (polygon.isVisible()) {
+ for (ICoordinate c : polygon.getPoints()) {
+ int x = tileSource.LonToX(c.getLon(), mapZoomMax);
+ int y = tileSource.LatToY(c.getLat(), mapZoomMax);
+ 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());
+ int newZoom = mapZoomMax;
+ int x = x_max - x_min;
+ int y = y_max - y_min;
+ while (x > width || y > height) {
+ newZoom--;
+ x >>= 1;
+ y >>= 1;
+ }
+ x = x_min + (x_max - x_min) / 2;
+ y = y_min + (y_max - y_min) / 2;
+ int z = 1 << (mapZoomMax - newZoom);
+ x /= z;
+ y /= z;
+ setDisplayPosition(x, y, newZoom);
+ }
+
+ /**
+ * Sets the displayed map pane and zoom level so that all map markers are visible.
+ */
+ public void setDisplayToFitMapMarkers() {
+ setDisplayToFitMapElements(true, false, false);
+ }
+
+ /**
+ * Sets the displayed map pane and zoom level so that all map rectangles are visible.
+ */
+ public void setDisplayToFitMapRectangles() {
+ setDisplayToFitMapElements(false, true, false);
+ }
+
+ /**
+ * Sets the displayed map pane and zoom level so that all map polygons are visible.
+ */
+ public void setDisplayToFitMapPolygons() {
+ setDisplayToFitMapElements(false, false, true);
+ }
+
+ /**
+ * @return the center
+ */
+ public Point getCenter() {
+ return center;
+ }
+
+ /**
+ * @param center the center to set
+ */
+ public void setCenter(Point center) {
+ this.center = center;
+ }
+
+ /**
+ * Calculates the latitude/longitude coordinate of the center of the
+ * currently displayed map area.
+ *
+ * @return latitude / longitude
+ */
+ public Coordinate getPosition() {
+ double lon = tileSource.XToLon(center.x, zoom);
+ double lat = tileSource.YToLat(center.y, zoom);
+ return new Coordinate(lat, lon);
+ }
+
+ /**
+ * Converts the relative pixel coordinate (regarding the top left corner of
+ * the displayed map) into a latitude / longitude coordinate
+ *
+ * @param mapPoint
+ * relative pixel coordinate regarding the top left corner of the
+ * displayed map
+ * @return latitude / longitude
+ */
+ public Coordinate getPosition(Point mapPoint) {
+ return getPosition(mapPoint.x, mapPoint.y);
+ }
+
+ /**
+ * Converts the relative pixel coordinate (regarding the top left corner of
+ * the displayed map) into a latitude / longitude coordinate
+ *
+ * @param mapPointX
+ * @param mapPointY
+ * @return latitude / longitude
+ */
+ public Coordinate getPosition(int mapPointX, int mapPointY) {
+ int x = center.x + mapPointX - getWidth() / 2;
+ int y = center.y + mapPointY - getHeight() / 2;
+ double lon = tileSource.XToLon(x, zoom);
+ double lat = tileSource.YToLat(y, zoom);
+ return new Coordinate(lat, lon);
+ }
+
+ /**
+ * Calculates the position on the map of a given coordinate
+ *
+ * @param lat
+ * @param lon
+ * @param checkOutside
+ * @return point on the map or <code>null</code> if the point is not visible
+ * and checkOutside set to <code>true</code>
+ */
+ public Point getMapPosition(double lat, double lon, boolean checkOutside) {
+ int x = tileSource.LonToX(lon, zoom);
+ int y = tileSource.LatToY(lat, zoom);
+ x -= center.x - getWidth() / 2;
+ y -= center.y - getHeight() / 2;
+ if (checkOutside) {
+ if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
+ return null;
+ }
+ return new Point(x, y);
+ }
+
+ /**
+ * Calculates the position on the map of a given coordinate
+ *
+ * @param lat Latitude
+ * @param offset Offset respect Latitude
+ * @param checkOutside
+ * @return Integer the radius in pixels
+ */
+ public Integer getLatOffset(double lat, double offset, boolean checkOutside) {
+ int y = tileSource.LatToY(lat + offset, zoom);
+ y -= center.y - getHeight() / 2;
+ if (checkOutside) {
+ if (y < 0 || y > getHeight())
+ return null;
+ }
+ return y;
+ }
+
+ /**
+ * 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) {
+ return getMapPosition(lat, lon, true);
+ }
+
+ /**
+ * Calculates the position on the map of a given coordinate
+ *
+ * @param marker MapMarker object that define the x,y coordinate
+ * @return Integer the radius in pixels
+ */
+ public Integer getRadius(MapMarker marker, Point p) {
+ if (marker.getMarkerStyle() == MapMarker.STYLE.FIXED)
+ return (int) marker.getRadius();
+ else if (p != null) {
+ Integer radius = getLatOffset(marker.getLat(), marker.getRadius(), false);
+ radius = radius == null ? null : p.y - radius.intValue();
+ return radius;
+ } else
+ return null;
+ }
+
+ /**
+ * Calculates the position on the map of a given coordinate
+ *
+ * @param coord
+ * @return point on the map or <code>null</code> if the point is not visible
+ */
+ public Point getMapPosition(Coordinate coord) {
+ if (coord != null)
+ return getMapPosition(coord.getLat(), coord.getLon());
+ else
+ return null;
+ }
+
+ /**
+ * Calculates the position on the map of a given coordinate
+ *
+ * @param coord
+ * @return point on the map or <code>null</code> if the point is not visible
+ * and checkOutside set to <code>true</code>
+ */
+ public Point getMapPosition(ICoordinate coord, boolean checkOutside) {
+ if (coord != null)
+ return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
+ else
+ return null;
+ }
+
+ /**
+ * Gets the meter per pixel.
+ *
+ * @return the meter per pixel
+ * @author Jason Huntley
+ */
+ public double getMeterPerPixel() {
+ Point origin = new Point(5, 5);
+ Point center = new Point(getWidth() / 2, getHeight() / 2);
+
+ double pDistance = center.distance(origin);
+
+ Coordinate originCoord = getPosition(origin);
+ Coordinate centerCoord = getPosition(center);
+
+ double mDistance = tileSource.getDistance(originCoord.getLat(), originCoord.getLon(),
+ centerCoord.getLat(), centerCoord.getLon());
+
+ return mDistance / pDistance;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ int iMove = 0;
+
+ int tilesize = tileSource.getTileSize();
+ int tilex = center.x / tilesize;
+ int tiley = center.y / tilesize;
+ int off_x = (center.x % tilesize);
+ int off_y = (center.y % tilesize);
+
+ int w2 = getWidth() / 2;
+ int h2 = getHeight() / 2;
+ int posx = w2 - off_x;
+ int posy = h2 - off_y;
+
+ int diff_left = off_x;
+ int diff_right = tilesize - off_x;
+ int diff_top = off_y;
+ int diff_bottom = tilesize - off_y;
+
+ boolean start_left = diff_left < diff_right;
+ boolean start_top = diff_top < diff_bottom;
+
+ if (start_top) {
+ if (start_left) {
+ iMove = 2;
+ } else {
+ iMove = 3;
+ }
+ } else {
+ if (start_left) {
+ iMove = 1;
+ } else {
+ iMove = 0;
+ }
+ } // calculate the visibility borders
+ int x_min = -tilesize;
+ int y_min = -tilesize;
+ int x_max = getWidth();
+ int y_max = getHeight();
+
+ // calculate the length of the grid (number of squares per edge)
+ int gridLength = 1 << zoom;
+
+ // paint the tiles in a spiral, starting from center of the map
+ boolean painted = true;
+ int x = 0;
+ while (painted) {
+ painted = false;
+ for (int i = 0; i < 4; i++) {
+ if (i % 2 == 0) {
+ x++;
+ }
+ for (int j = 0; j < x; j++) {
+ if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
+ // tile is visible
+ Tile tile;
+ if (scrollWrapEnabled) {
+ // in case tilex is out of bounds, grab the tile to use for wrapping
+ int tilexWrap = (((tilex % gridLength) + gridLength) % gridLength);
+ tile = tileController.getTile(tilexWrap, tiley, zoom);
+ } else {
+ tile = tileController.getTile(tilex, tiley, zoom);
+ }
+ if (tile != null) {
+ tile.paint(g, posx, posy);
+ if (tileGridVisible) {
+ g.drawRect(posx, posy, tilesize, tilesize);
+ }
+ }
+ painted = true;
+ }
+ Point p = move[iMove];
+ posx += p.x * tilesize;
+ posy += p.y * tilesize;
+ tilex += p.x;
+ tiley += p.y;
+ }
+ iMove = (iMove + 1) % move.length;
+ }
+ }
+ // outer border of the map
+ int mapSize = tilesize << zoom;
+ if (scrollWrapEnabled) {
+ g.drawLine(0, h2 - center.y, getWidth(), h2 - center.y);
+ g.drawLine(0, h2 - center.y + mapSize, getWidth(), h2 - center.y + mapSize);
+ } else {
+ g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
+ }
+
+ // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
+
+ // keep x-coordinates from growing without bound if scroll-wrap is enabled
+ if (scrollWrapEnabled) {
+ center.x = center.x % mapSize;
+ }
+
+ if (mapPolygonsVisible && mapPolygonList != null) {
+ synchronized (mapPolygonList) {
+ for (MapPolygon polygon : mapPolygonList) {
+ if (polygon.isVisible())
+ paintPolygon(g, polygon);
+ }
+ }
+ }
+
+ if (mapRectanglesVisible && mapRectangleList != null) {
+ synchronized (mapRectangleList) {
+ for (MapRectangle rectangle : mapRectangleList) {
+ if (rectangle.isVisible())
+ paintRectangle(g, rectangle);
+ }
+ }
+ }
+
+ if (mapMarkersVisible && mapMarkerList != null) {
+ synchronized (mapMarkerList) {
+ for (MapMarker marker : mapMarkerList) {
+ if (marker.isVisible())
+ paintMarker(g, marker);
+ }
+ }
+ }
+
+ attribution.paintAttribution(g, getWidth(), getHeight(), getPosition(0, 0), getPosition(getWidth(), getHeight()), zoom, this);
+ }
+
+ /**
+ * Paint a single marker.
+ */
+ protected void paintMarker(Graphics g, MapMarker marker) {
+ Point p = getMapPosition(marker.getLat(), marker.getLon(), marker.getMarkerStyle() == MapMarker.STYLE.FIXED);
+ Integer radius = getRadius(marker, p);
+ if (scrollWrapEnabled) {
+ int tilesize = tileSource.getTileSize();
+ int mapSize = tilesize << zoom;
+ if (p == null) {
+ p = getMapPosition(marker.getLat(), marker.getLon(), false);
+ radius = getRadius(marker, p);
+ }
+ marker.paint(g, p, radius);
+ int xSave = p.x;
+ int xWrap = xSave;
+ // overscan of 15 allows up to 30-pixel markers to gracefully scroll off the edge of the panel
+ while ((xWrap -= mapSize) >= -15) {
+ p.x = xWrap;
+ marker.paint(g, p, radius);
+ }
+ xWrap = xSave;
+ while ((xWrap += mapSize) <= getWidth() + 15) {
+ p.x = xWrap;
+ marker.paint(g, p, radius);
+ }
+ } else {
+ if (p != null) {
+ marker.paint(g, p, radius);
+ }
+ }
+ }
+
+ /**
+ * Paint a single rectangle.
+ */
+ protected void paintRectangle(Graphics g, MapRectangle rectangle) {
+ Coordinate topLeft = rectangle.getTopLeft();
+ Coordinate bottomRight = rectangle.getBottomRight();
+ if (topLeft != null && bottomRight != null) {
+ Point pTopLeft = getMapPosition(topLeft, false);
+ Point pBottomRight = getMapPosition(bottomRight, false);
+ if (pTopLeft != null && pBottomRight != null) {
+ rectangle.paint(g, pTopLeft, pBottomRight);
+ if (scrollWrapEnabled) {
+ int tilesize = tileSource.getTileSize();
+ int mapSize = tilesize << zoom;
+ int xTopLeftSave = pTopLeft.x;
+ int xTopLeftWrap = xTopLeftSave;
+ int xBottomRightSave = pBottomRight.x;
+ int xBottomRightWrap = xBottomRightSave;
+ while ((xBottomRightWrap -= mapSize) >= 0) {
+ xTopLeftWrap -= mapSize;
+ pTopLeft.x = xTopLeftWrap;
+ pBottomRight.x = xBottomRightWrap;
+ rectangle.paint(g, pTopLeft, pBottomRight);
+ }
+ xTopLeftWrap = xTopLeftSave;
+ xBottomRightWrap = xBottomRightSave;
+ while ((xTopLeftWrap += mapSize) <= getWidth()) {
+ xBottomRightWrap += mapSize;
+ pTopLeft.x = xTopLeftWrap;
+ pBottomRight.x = xBottomRightWrap;
+ rectangle.paint(g, pTopLeft, pBottomRight);
+ }
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Paint a single polygon.
+ */
+ protected void paintPolygon(Graphics g, MapPolygon polygon) {
+ List<? extends ICoordinate> coords = polygon.getPoints();
+ if (coords != null && coords.size() >= 3) {
+ List<Point> points = new LinkedList<>();
+ for (ICoordinate c : coords) {
+ Point p = getMapPosition(c, false);
+ if (p == null) {
+ return;
+ }
+ points.add(p);
+ }
+ polygon.paint(g, points);
+ if (scrollWrapEnabled) {
+ int tilesize = tileSource.getTileSize();
+ int mapSize = tilesize << zoom;
+ List<Point> pointsWrapped = new LinkedList<>(points);
+ boolean keepWrapping = true;
+ while (keepWrapping) {
+ for (Point p : pointsWrapped) {
+ p.x -= mapSize;
+ if (p.x < 0) {
+ keepWrapping = false;
+ }
+ }
+ polygon.paint(g, pointsWrapped);
+ }
+ pointsWrapped = new LinkedList<>(points);
+ keepWrapping = true;
+ while (keepWrapping) {
+ for (Point p : pointsWrapped) {
+ p.x += mapSize;
+ if (p.x > getWidth()) {
+ keepWrapping = false;
+ }
+ }
+ polygon.paint(g, pointsWrapped);
+ }
+ }
+ }
+ }
+
+ /**
+ * Moves the visible map pane.
+ *
+ * @param x
+ * horizontal movement in pixel.
+ * @param y
+ * vertical movement in pixel
+ */
+ public void moveMap(int x, int y) {
+ tileController.cancelOutstandingJobs(); // Clear outstanding load
+ center.x += x;
+ center.y += y;
+ repaint();
+ this.fireJMVEvent(new JMVCommandEvent(COMMAND.MOVE, this));
+ }
+
+ /**
+ * @return the current zoom level
+ */
+ public int getZoom() {
+ return zoom;
+ }
+
+ /**
+ * Increases the current zoom level by one
+ */
+ public void zoomIn() {
+ setZoom(zoom + 1);
+ }
+
+ /**
+ * Increases the current zoom level by one
+ * @param mapPoint point to choose as center for new zoom level
+ */
+ public void zoomIn(Point mapPoint) {
+ setZoom(zoom + 1, mapPoint);
+ }
+
+ /**
+ * Decreases the current zoom level by one
+ */
+ public void zoomOut() {
+ setZoom(zoom - 1);
+ }
+
+ /**
+ * Decreases the current zoom level by one
+ *
+ * @param mapPoint point to choose as center for new zoom level
+ */
+ public void zoomOut(Point mapPoint) {
+ setZoom(zoom - 1, mapPoint);
+ }
+
+ /**
+ * Set the zoom level and center point for display
+ *
+ * @param zoom new zoom level
+ * @param mapPoint point to choose as center for new zoom level
+ */
+ public void setZoom(int zoom, Point mapPoint) {
+ if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom()
+ || zoom == this.zoom)
+ return;
+ Coordinate zoomPos = getPosition(mapPoint);
+ tileController.cancelOutstandingJobs(); // Clearing outstanding load
+ // requests
+ setDisplayPosition(mapPoint, zoomPos, zoom);
+
+ this.fireJMVEvent(new JMVCommandEvent(COMMAND.ZOOM, this));
+ }
+
+ /**
+ * Set the zoom level
+ *
+ * @param zoom new zoom level
+ */
+ public void setZoom(int zoom) {
+ setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
+ }
+
+ /**
+ * Every time the zoom level changes this method is called. Override it in
+ * derived implementations for adapting zoom dependent values. The new zoom
+ * level can be obtained via {@link #getZoom()}.
+ *
+ * @param oldZoom
+ * the previous zoom level
+ */
+ protected void zoomChanged(int oldZoom) {
+ zoomSlider.setToolTipText("Zoom level " + zoom);
+ zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
+ zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
+ zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom());
+ zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
+ }
+
+ 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 setMapRectangleList(List<MapRectangle> mapRectangleList) {
+ this.mapRectangleList = mapRectangleList;
+ repaint();
+ }
+
+ public List<MapRectangle> getMapRectangleList() {
+ return mapRectangleList;
+ }
+
+ public void setMapPolygonList(List<MapPolygon> mapPolygonList) {
+ this.mapPolygonList = mapPolygonList;
+ repaint();
+ }
+
+ public List<MapPolygon> getMapPolygonList() {
+ return mapPolygonList;
+ }
+
+ public void addMapMarker(MapMarker marker) {
+ mapMarkerList.add(marker);
+ repaint();
+ }
+
+ public void removeMapMarker(MapMarker marker) {
+ mapMarkerList.remove(marker);
+ repaint();
+ }
+
+ public void removeAllMapMarkers() {
+ mapMarkerList.clear();
+ repaint();
+ }
+
+ public void addMapRectangle(MapRectangle rectangle) {
+ mapRectangleList.add(rectangle);
+ repaint();
+ }
+
+ public void removeMapRectangle(MapRectangle rectangle) {
+ mapRectangleList.remove(rectangle);
+ repaint();
+ }
+
+ public void removeAllMapRectangles() {
+ mapRectangleList.clear();
+ repaint();
+ }
+
+ public void addMapPolygon(MapPolygon polygon) {
+ mapPolygonList.add(polygon);
+ repaint();
+ }
+
+ public void removeMapPolygon(MapPolygon polygon) {
+ mapPolygonList.remove(polygon);
+ repaint();
+ }
+
+ public void removeAllMapPolygons() {
+ mapPolygonList.clear();
+ repaint();
+ }
+
+ public void setZoomContolsVisible(boolean visible) {
+ zoomSlider.setVisible(visible);
+ zoomInButton.setVisible(visible);
+ zoomOutButton.setVisible(visible);
+ }
+
+ public boolean getZoomControlsVisible() {
+ return zoomSlider.isVisible();
+ }
+
+ public void setTileSource(TileSource tileSource) {
+ if (tileSource.getMaxZoom() > MAX_ZOOM)
+ throw new RuntimeException("Maximum zoom level too high");
+ if (tileSource.getMinZoom() < MIN_ZOOM)
+ throw new RuntimeException("Minimum zoom level too low");
+ Coordinate position = getPosition();
+ this.tileSource = tileSource;
+ tileController.setTileSource(tileSource);
+ zoomSlider.setMinimum(tileSource.getMinZoom());
+ zoomSlider.setMaximum(tileSource.getMaxZoom());
+ tileController.cancelOutstandingJobs();
+ if (zoom > tileSource.getMaxZoom()) {
+ setZoom(tileSource.getMaxZoom());
+ }
+ attribution.initialize(tileSource);
+ setDisplayPosition(position, zoom);
+ repaint();
+ }
+
+ public void tileLoadingFinished(Tile tile, boolean success) {
+ repaint();
+ }
+
+ public boolean isMapRectanglesVisible() {
+ return mapRectanglesVisible;
+ }
+
+ /**
+ * Enables or disables painting of the {@link MapRectangle}
+ *
+ * @param mapRectanglesVisible
+ * @see #addMapRectangle(MapRectangle)
+ * @see #getMapRectangleList()
+ */
+ public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
+ this.mapRectanglesVisible = mapRectanglesVisible;
+ repaint();
+ }
+
+ public boolean isMapPolygonsVisible() {
+ return mapPolygonsVisible;
+ }
+
+ /**
+ * Enables or disables painting of the {@link MapPolygon}
+ *
+ * @param mapPolygonsVisible
+ * @see #addMapPolygon(MapPolygon)
+ * @see #getMapPolygonList()
+ */
+ public void setMapPolygonsVisible(boolean mapPolygonsVisible) {
+ this.mapPolygonsVisible = mapPolygonsVisible;
+ repaint();
+ }
+
+ public boolean isScrollWrapEnabled() {
+ return scrollWrapEnabled;
+ }
+
+ public void setScrollWrapEnabled(boolean scrollWrapEnabled) {
+ this.scrollWrapEnabled = scrollWrapEnabled;
+ repaint();
+ }
+
+ public ZOOM_BUTTON_STYLE getZoomButtonStyle() {
+ return zoomButtonStyle;
+ }
+
+ public void setZoomButtonStyle(ZOOM_BUTTON_STYLE style) {
+ zoomButtonStyle = style;
+ if (zoomSlider == null || zoomInButton == null || zoomOutButton == null) {
+ return;
+ }
+ switch (style) {
+ case HORIZONTAL:
+ zoomSlider.setBounds(10, 10, 30, 150);
+ zoomInButton.setBounds(4, 155, 18, 18);
+ zoomOutButton.setBounds(26, 155, 18, 18);
+ break;
+ case VERTICAL:
+ zoomSlider.setBounds(10, 27, 30, 150);
+ zoomInButton.setBounds(14, 8, 20, 20);
+ zoomOutButton.setBounds(14, 176, 20, 20);
+ break;
+ default:
+ zoomSlider.setBounds(10, 10, 30, 150);
+ zoomInButton.setBounds(4, 155, 18, 18);
+ zoomOutButton.setBounds(26, 155, 18, 18);
+ break;
+ }
+ repaint();
+ }
+
+ public TileController getTileController() {
+ return tileController;
+ }
+
+ /**
+ * Return tile information caching class
+ * @see TileController#getTileCache()
+ */
+ public TileCache getTileCache() {
+ return tileController.getTileCache();
+ }
+
+ public void setTileLoader(TileLoader loader) {
+ tileController.setTileLoader(loader);
+ }
+
+ public AttributionSupport getAttribution() {
+ return attribution;
+ }
+
+ protected EventListenerList evtListenerList = new EventListenerList();
+
+ /**
+ * @param listener listener to set
+ */
+ public void addJMVListener(JMapViewerEventListener listener) {
+ evtListenerList.add(JMapViewerEventListener.class, listener);
+ }
+
+ /**
+ * @param listener listener to remove
+ */
+ public void removeJMVListener(JMapViewerEventListener listener) {
+ evtListenerList.remove(JMapViewerEventListener.class, listener);
+ }
+
+ /**
+ * Send an update to all objects registered with viewer
+ *
+ * @param evt event to dispatch
+ */
+ void fireJMVEvent(JMVCommandEvent evt) {
+ Object[] listeners = evtListenerList.getListenerList();
+ for (int i = 0; i < listeners.length; i += 2) {
+ if (listeners[i] == JMapViewerEventListener.class) {
+ ((JMapViewerEventListener) listeners[i + 1]).processCommand(evt);
+ }
+ }
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java b/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
index dfd6103..ea22fbc 100644
--- a/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
+++ b/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
@@ -1,176 +1,176 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
-
-/**
- * A generic class that processes a list of {@link Runnable} one-by-one using
- * one or more {@link Thread}-instances. The number of instances varies between
- * 1 and {@link #workerThreadMaxCount} (default: 8). If an instance is idle
- * more than {@link #workerThreadTimeout} seconds (default: 30), the instance
- * ends itself.
- *
- * @author Jan Peter Stotz
- */
-public class JobDispatcher {
-
- private static final JobDispatcher instance = new JobDispatcher();
-
- /**
- * @return the singelton instance of the {@link JobDispatcher}
- */
- public static JobDispatcher getInstance() {
- return instance;
- }
-
- private JobDispatcher() {
- addWorkerThread().firstThread = true;
- }
-
- protected BlockingDeque<TileJob> jobQueue = new LinkedBlockingDeque<>();
-
- protected static int workerThreadMaxCount = 8;
-
- /**
- * Specifies the time span in seconds that a worker thread waits for new
- * jobs to perform. If the time span has elapsed the worker thread
- * terminates itself. Only the first worker thread works differently, it
- * ignores the timeout and will never terminate itself.
- */
- protected static int workerThreadTimeout = 30;
-
- /**
- * Type of queue, FIFO if <code>false</code>, LIFO if <code>true</code>
- */
- protected boolean modeLIFO = false;
-
- /**
- * Total number of worker threads currently idle or active
- */
- protected int workerThreadCount = 0;
-
- /**
- * Number of worker threads currently idle
- */
- protected int workerThreadIdleCount = 0;
-
- /**
- * Just an id for identifying an worker thread instance
- */
- protected int workerThreadId = 0;
-
- /**
- * Removes all jobs from the queue that are currently not being processed.
- */
- public void cancelOutstandingJobs() {
- jobQueue.clear();
- }
-
- /**
- * Function to set the maximum number of workers for tile loading.
- */
- static public void setMaxWorkers(int workers) {
- workerThreadMaxCount = workers;
- }
-
- /**
- * Function to set the LIFO/FIFO mode for tile loading job.
- *
- * @param lifo <code>true</code> for LIFO mode, <code>false</code> for FIFO mode
- */
- public void setLIFO(boolean lifo) {
- modeLIFO = lifo;
- }
-
- /**
- * Adds a job to the queue.
- * Jobs for tiles already contained in the are ignored (using a <code>null</code> tile
- * prevents skipping).
- *
- * @param job the the job to be added
- */
- public void addJob(TileJob job) {
- try {
- if(job.getTile() != null) {
- for(TileJob oldJob : jobQueue) {
- if(oldJob.getTile() == job.getTile()) {
- return;
- }
- }
- }
- jobQueue.put(job);
- if (workerThreadIdleCount == 0 && workerThreadCount < workerThreadMaxCount)
- addWorkerThread();
- } catch (InterruptedException e) {
- }
- }
-
- protected JobThread addWorkerThread() {
- JobThread jobThread = new JobThread(++workerThreadId);
- synchronized (this) {
- workerThreadCount++;
- }
- jobThread.start();
- return jobThread;
- }
-
- public class JobThread extends Thread {
-
- Runnable job;
- boolean firstThread = false;
-
- public JobThread(int threadId) {
- super("OSMJobThread " + threadId);
- setDaemon(true);
- job = null;
- }
-
- @Override
- public void run() {
- executeJobs();
- synchronized (instance) {
- workerThreadCount--;
- }
- }
-
- protected void executeJobs() {
- while (!isInterrupted()) {
- try {
- synchronized (instance) {
- workerThreadIdleCount++;
- }
- if(modeLIFO) {
- if (firstThread)
- job = jobQueue.takeLast();
- else
- job = jobQueue.pollLast(workerThreadTimeout, TimeUnit.SECONDS);
- } else {
- if (firstThread)
- job = jobQueue.take();
- else
- job = jobQueue.poll(workerThreadTimeout, TimeUnit.SECONDS);
- }
- } catch (InterruptedException e1) {
- return;
- } finally {
- synchronized (instance) {
- workerThreadIdleCount--;
- }
- }
- if (job == null)
- return;
- try {
- job.run();
- job = null;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
-
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
+
+/**
+ * A generic class that processes a list of {@link Runnable} one-by-one using
+ * one or more {@link Thread}-instances. The number of instances varies between
+ * 1 and {@link #workerThreadMaxCount} (default: 8). If an instance is idle
+ * more than {@link #workerThreadTimeout} seconds (default: 30), the instance
+ * ends itself.
+ *
+ * @author Jan Peter Stotz
+ */
+public class JobDispatcher {
+
+ private static final JobDispatcher instance = new JobDispatcher();
+
+ /**
+ * @return the singelton instance of the {@link JobDispatcher}
+ */
+ public static JobDispatcher getInstance() {
+ return instance;
+ }
+
+ private JobDispatcher() {
+ addWorkerThread().firstThread = true;
+ }
+
+ protected BlockingDeque<TileJob> jobQueue = new LinkedBlockingDeque<>();
+
+ protected static int workerThreadMaxCount = 8;
+
+ /**
+ * Specifies the time span in seconds that a worker thread waits for new
+ * jobs to perform. If the time span has elapsed the worker thread
+ * terminates itself. Only the first worker thread works differently, it
+ * ignores the timeout and will never terminate itself.
+ */
+ protected static int workerThreadTimeout = 30;
+
+ /**
+ * Type of queue, FIFO if <code>false</code>, LIFO if <code>true</code>
+ */
+ protected boolean modeLIFO = false;
+
+ /**
+ * Total number of worker threads currently idle or active
+ */
+ protected int workerThreadCount = 0;
+
+ /**
+ * Number of worker threads currently idle
+ */
+ protected int workerThreadIdleCount = 0;
+
+ /**
+ * Just an id for identifying an worker thread instance
+ */
+ protected int workerThreadId = 0;
+
+ /**
+ * Removes all jobs from the queue that are currently not being processed.
+ */
+ public void cancelOutstandingJobs() {
+ jobQueue.clear();
+ }
+
+ /**
+ * Function to set the maximum number of workers for tile loading.
+ */
+ static public void setMaxWorkers(int workers) {
+ workerThreadMaxCount = workers;
+ }
+
+ /**
+ * Function to set the LIFO/FIFO mode for tile loading job.
+ *
+ * @param lifo <code>true</code> for LIFO mode, <code>false</code> for FIFO mode
+ */
+ public void setLIFO(boolean lifo) {
+ modeLIFO = lifo;
+ }
+
+ /**
+ * Adds a job to the queue.
+ * Jobs for tiles already contained in the are ignored (using a <code>null</code> tile
+ * prevents skipping).
+ *
+ * @param job the the job to be added
+ */
+ public void addJob(TileJob job) {
+ try {
+ if(job.getTile() != null) {
+ for(TileJob oldJob : jobQueue) {
+ if(oldJob.getTile() == job.getTile()) {
+ return;
+ }
+ }
+ }
+ jobQueue.put(job);
+ if (workerThreadIdleCount == 0 && workerThreadCount < workerThreadMaxCount)
+ addWorkerThread();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ protected JobThread addWorkerThread() {
+ JobThread jobThread = new JobThread(++workerThreadId);
+ synchronized (this) {
+ workerThreadCount++;
+ }
+ jobThread.start();
+ return jobThread;
+ }
+
+ public class JobThread extends Thread {
+
+ Runnable job;
+ boolean firstThread = false;
+
+ public JobThread(int threadId) {
+ super("OSMJobThread " + threadId);
+ setDaemon(true);
+ job = null;
+ }
+
+ @Override
+ public void run() {
+ executeJobs();
+ synchronized (instance) {
+ workerThreadCount--;
+ }
+ }
+
+ protected void executeJobs() {
+ while (!isInterrupted()) {
+ try {
+ synchronized (instance) {
+ workerThreadIdleCount++;
+ }
+ if(modeLIFO) {
+ if (firstThread)
+ job = jobQueue.takeLast();
+ else
+ job = jobQueue.pollLast(workerThreadTimeout, TimeUnit.SECONDS);
+ } else {
+ if (firstThread)
+ job = jobQueue.take();
+ else
+ job = jobQueue.poll(workerThreadTimeout, TimeUnit.SECONDS);
+ }
+ } catch (InterruptedException e1) {
+ return;
+ } finally {
+ synchronized (instance) {
+ workerThreadIdleCount--;
+ }
+ }
+ if (job == null)
+ return;
+ try {
+ job.run();
+ job = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/MapMarkerCircle.java b/src/org/openstreetmap/gui/jmapviewer/MapMarkerCircle.java
index fb2276b..bfdf7be 100644
--- a/src/org/openstreetmap/gui/jmapviewer/MapMarkerCircle.java
+++ b/src/org/openstreetmap/gui/jmapviewer/MapMarkerCircle.java
@@ -1,106 +1,106 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
-
-/**
- * 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 MapMarkerCircle extends MapObjectImpl implements MapMarker {
-
- Coordinate coord;
- double radius;
- STYLE markerStyle;
-
- public MapMarkerCircle(Coordinate coord, double radius) {
- this(null, null, coord, radius);
- }
- public MapMarkerCircle(String name, Coordinate coord, double radius) {
- this(null, name, coord, radius);
- }
- public MapMarkerCircle(Layer layer, Coordinate coord, double radius) {
- this(layer, null, coord, radius);
- }
- public MapMarkerCircle(double lat, double lon, double radius) {
- this(null, null, new Coordinate(lat,lon), radius);
- }
- public MapMarkerCircle(Layer layer, double lat, double lon, double radius) {
- this(layer, null, new Coordinate(lat,lon), radius);
- }
- public MapMarkerCircle(Layer layer, String name, Coordinate coord, double radius) {
- this(layer, name, coord, radius, STYLE.VARIABLE, getDefaultStyle());
- }
- public MapMarkerCircle(Layer layer, String name, Coordinate coord, double radius, STYLE markerStyle, Style style) {
- super(layer, name, style);
- this.markerStyle = markerStyle;
- this.coord = coord;
- this.radius = radius;
- }
-
- public Coordinate getCoordinate(){
- return coord;
- }
- public double getLat() {
- return coord.getLat();
- }
-
- public double getLon() {
- return coord.getLon();
- }
-
- public double getRadius() {
- return radius;
- }
-
- public STYLE getMarkerStyle() {
- return markerStyle;
- }
-
- public void paint(Graphics g, Point position, int radio) {
- int size_h = radio;
- int size = size_h * 2;
-
- if (g instanceof Graphics2D && getBackColor()!=null) {
- Graphics2D g2 = (Graphics2D) g;
- Composite oldComposite = g2.getComposite();
- g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
- g2.setPaint(getBackColor());
- g.fillOval(position.x - size_h, position.y - size_h, size, size);
- g2.setComposite(oldComposite);
- }
- g.setColor(getColor());
- g.drawOval(position.x - size_h, position.y - size_h, size, size);
-
- if(getLayer()==null||getLayer().isVisibleTexts()) paintText(g, position);
- }
-
- public static Style getDefaultStyle(){
- return new Style(Color.ORANGE, new Color(200,200,200,200), null, getDefaultFont());
- }
- @Override
- public String toString() {
- return "MapMarker at " + getLat() + " " + getLon();
- }
- @Override
- public void setLat(double lat) {
- if(coord==null) coord = new Coordinate(lat,0);
- else coord.setLat(lat);
- }
- @Override
- public void setLon(double lon) {
- if(coord==null) coord = new Coordinate(0,lon);
- else coord.setLon(lon);
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
+
+/**
+ * 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 MapMarkerCircle extends MapObjectImpl implements MapMarker {
+
+ Coordinate coord;
+ double radius;
+ STYLE markerStyle;
+
+ public MapMarkerCircle(Coordinate coord, double radius) {
+ this(null, null, coord, radius);
+ }
+ public MapMarkerCircle(String name, Coordinate coord, double radius) {
+ this(null, name, coord, radius);
+ }
+ public MapMarkerCircle(Layer layer, Coordinate coord, double radius) {
+ this(layer, null, coord, radius);
+ }
+ public MapMarkerCircle(double lat, double lon, double radius) {
+ this(null, null, new Coordinate(lat,lon), radius);
+ }
+ public MapMarkerCircle(Layer layer, double lat, double lon, double radius) {
+ this(layer, null, new Coordinate(lat,lon), radius);
+ }
+ public MapMarkerCircle(Layer layer, String name, Coordinate coord, double radius) {
+ this(layer, name, coord, radius, STYLE.VARIABLE, getDefaultStyle());
+ }
+ public MapMarkerCircle(Layer layer, String name, Coordinate coord, double radius, STYLE markerStyle, Style style) {
+ super(layer, name, style);
+ this.markerStyle = markerStyle;
+ this.coord = coord;
+ this.radius = radius;
+ }
+
+ public Coordinate getCoordinate(){
+ return coord;
+ }
+ public double getLat() {
+ return coord.getLat();
+ }
+
+ public double getLon() {
+ return coord.getLon();
+ }
+
+ public double getRadius() {
+ return radius;
+ }
+
+ public STYLE getMarkerStyle() {
+ return markerStyle;
+ }
+
+ public void paint(Graphics g, Point position, int radio) {
+ int size_h = radio;
+ int size = size_h * 2;
+
+ if (g instanceof Graphics2D && getBackColor()!=null) {
+ Graphics2D g2 = (Graphics2D) g;
+ Composite oldComposite = g2.getComposite();
+ g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
+ g2.setPaint(getBackColor());
+ g.fillOval(position.x - size_h, position.y - size_h, size, size);
+ g2.setComposite(oldComposite);
+ }
+ g.setColor(getColor());
+ g.drawOval(position.x - size_h, position.y - size_h, size, size);
+
+ if(getLayer()==null||getLayer().isVisibleTexts()) paintText(g, position);
+ }
+
+ public static Style getDefaultStyle(){
+ return new Style(Color.ORANGE, new Color(200,200,200,200), null, getDefaultFont());
+ }
+ @Override
+ public String toString() {
+ return "MapMarker at " + getLat() + " " + getLon();
+ }
+ @Override
+ public void setLat(double lat) {
+ if(coord==null) coord = new Coordinate(lat,0);
+ else coord.setLat(lat);
+ }
+ @Override
+ public void setLon(double lon) {
+ if(coord==null) coord = new Coordinate(0,lon);
+ else coord.setLon(lon);
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java b/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java
index b54e79a..1da1204 100644
--- a/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java
+++ b/src/org/openstreetmap/gui/jmapviewer/MapMarkerDot.java
@@ -1,52 +1,52 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.awt.Color;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
-
-/**
- * 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 extends MapMarkerCircle {
-
- public static final int DOT_RADIUS = 5;
-
- public MapMarkerDot(Coordinate coord) {
- this(null, null, coord);
- }
- public MapMarkerDot(String name, Coordinate coord) {
- this(null, name, coord);
- }
- public MapMarkerDot(Layer layer, Coordinate coord) {
- this(layer, null, coord);
- }
- public MapMarkerDot(Layer layer, String name, Coordinate coord) {
- this(layer, name, coord, getDefaultStyle());
- }
- public MapMarkerDot(Color color, double lat, double lon) {
- this(null, null, lat, lon);
- setColor(color);
- }
- public MapMarkerDot(double lat, double lon) {
- this(null, null, lat, lon);
- }
- public MapMarkerDot(Layer layer, double lat, double lon) {
- this(layer, null, lat, lon);
- }
- public MapMarkerDot(Layer layer, String name, double lat, double lon) {
- this(layer, name, new Coordinate(lat, lon), getDefaultStyle());
- }
- public MapMarkerDot(Layer layer, String name, Coordinate coord, Style style) {
- super(layer, name, coord, DOT_RADIUS, STYLE.FIXED, style);
- }
-
- public static Style getDefaultStyle(){
- return new Style(Color.BLACK, Color.YELLOW, null, getDefaultFont());
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.awt.Color;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
+
+/**
+ * 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 extends MapMarkerCircle {
+
+ public static final int DOT_RADIUS = 5;
+
+ public MapMarkerDot(Coordinate coord) {
+ this(null, null, coord);
+ }
+ public MapMarkerDot(String name, Coordinate coord) {
+ this(null, name, coord);
+ }
+ public MapMarkerDot(Layer layer, Coordinate coord) {
+ this(layer, null, coord);
+ }
+ public MapMarkerDot(Layer layer, String name, Coordinate coord) {
+ this(layer, name, coord, getDefaultStyle());
+ }
+ public MapMarkerDot(Color color, double lat, double lon) {
+ this(null, null, lat, lon);
+ setColor(color);
+ }
+ public MapMarkerDot(double lat, double lon) {
+ this(null, null, lat, lon);
+ }
+ public MapMarkerDot(Layer layer, double lat, double lon) {
+ this(layer, null, lat, lon);
+ }
+ public MapMarkerDot(Layer layer, String name, double lat, double lon) {
+ this(layer, name, new Coordinate(lat, lon), getDefaultStyle());
+ }
+ public MapMarkerDot(Layer layer, String name, Coordinate coord, Style style) {
+ super(layer, name, coord, DOT_RADIUS, STYLE.FIXED, style);
+ }
+
+ public static Style getDefaultStyle(){
+ return new Style(Color.BLACK, Color.YELLOW, null, getDefaultFont());
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java b/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
index 220df59..9192029 100644
--- a/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
+++ b/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
@@ -1,226 +1,226 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.logging.Logger;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-
-/**
- * {@link TileCache} implementation that stores all {@link Tile} objects in
- * memory up to a certain limit ({@link #getCacheSize()}). If the limit is
- * exceeded the least recently used {@link Tile} objects will be deleted.
- *
- * @author Jan Peter Stotz
- */
-public class MemoryTileCache implements TileCache {
-
- protected static final Logger log = Logger.getLogger(MemoryTileCache.class.getName());
-
- /**
- * Default cache size
- */
- protected int cacheSize = 200;
-
- protected final Map<String, CacheEntry> hash;
-
- /**
- * List of all tiles in their last recently used order
- */
- protected final CacheLinkedListElement lruTiles;
-
- /**
- * Constructs a new {@code MemoryTileCache}.
- */
- public MemoryTileCache() {
- hash = new HashMap<>(cacheSize);
- lruTiles = new CacheLinkedListElement();
- }
-
- @Override
- public synchronized void addTile(Tile tile) {
- CacheEntry entry = createCacheEntry(tile);
- hash.put(tile.getKey(), entry);
- lruTiles.addFirst(entry);
- if (hash.size() > cacheSize) {
- removeOldEntries();
- }
- }
-
- @Override
- public synchronized Tile getTile(TileSource source, int x, int y, int z) {
- CacheEntry entry = hash.get(Tile.getTileKey(source, x, y, z));
- if (entry == null)
- return null;
- // We don't care about placeholder tiles and hourglass image tiles, the
- // important tiles are the loaded ones
- if (entry.tile.isLoaded())
- lruTiles.moveElementToFirstPos(entry);
- return entry.tile;
- }
-
- /**
- * Removes the least recently used tiles
- */
- protected synchronized void removeOldEntries() {
- try {
- while (lruTiles.getElementCount() > cacheSize) {
- removeEntry(lruTiles.getLastElement());
- }
- } catch (Exception e) {
- log.warning(e.getMessage());
- }
- }
-
- protected synchronized void removeEntry(CacheEntry entry) {
- hash.remove(entry.tile.getKey());
- lruTiles.removeEntry(entry);
- }
-
- protected CacheEntry createCacheEntry(Tile tile) {
- return new CacheEntry(tile);
- }
-
- @Override
- public synchronized void clear() {
- hash.clear();
- lruTiles.clear();
- }
-
- @Override
- public int getTileCount() {
- return hash.size();
- }
-
- public int getCacheSize() {
- return cacheSize;
- }
-
- /**
- * Changes the maximum number of {@link Tile} objects that this cache holds.
- *
- * @param cacheSize
- * new maximum number of tiles
- */
- public synchronized void setCacheSize(int cacheSize) {
- this.cacheSize = cacheSize;
- if (hash.size() > cacheSize)
- removeOldEntries();
- }
-
- /**
- * Linked list element holding the {@link Tile} and links to the
- * {@link #next} and {@link #prev} item in the list.
- */
- protected static class CacheEntry {
- Tile tile;
-
- CacheEntry next;
- CacheEntry prev;
-
- protected CacheEntry(Tile tile) {
- this.tile = tile;
- }
-
- public Tile getTile() {
- return tile;
- }
-
- public CacheEntry getNext() {
- return next;
- }
-
- public CacheEntry getPrev() {
- return prev;
- }
-
- }
-
- /**
- * Special implementation of a double linked list for {@link CacheEntry}
- * elements. It supports element removal in constant time - in difference to
- * the Java implementation which needs O(n).
- *
- * @author Jan Peter Stotz
- */
- protected static class CacheLinkedListElement {
- protected CacheEntry firstElement = null;
- protected CacheEntry lastElement;
- protected int elementCount;
-
- public CacheLinkedListElement() {
- clear();
- }
-
- public void clear() {
- elementCount = 0;
- firstElement = null;
- lastElement = null;
- }
-
- /**
- * Add the element to the head of the list.
- *
- * @param element new element to be added
- */
- public void addFirst(CacheEntry element) {
- if (element == null) return;
- if (elementCount == 0) {
- firstElement = element;
- lastElement = element;
- element.prev = null;
- element.next = null;
- } else {
- element.next = firstElement;
- firstElement.prev = element;
- element.prev = null;
- firstElement = element;
- }
- elementCount++;
- }
-
- /**
- * Removes the specified element from the list.
- *
- * @param element element to be removed
- */
- public void removeEntry(CacheEntry element) {
- if (element == null) return;
- if (element.next != null) {
- element.next.prev = element.prev;
- }
- if (element.prev != null) {
- element.prev.next = element.next;
- }
- if (element == firstElement)
- firstElement = element.next;
- if (element == lastElement)
- lastElement = element.prev;
- element.next = null;
- element.prev = null;
- elementCount--;
- }
-
- public void moveElementToFirstPos(CacheEntry entry) {
- if (firstElement == entry)
- return;
- removeEntry(entry);
- addFirst(entry);
- }
-
- public int getElementCount() {
- return elementCount;
- }
-
- public CacheEntry getLastElement() {
- return lastElement;
- }
-
- public CacheEntry getFirstElement() {
- return firstElement;
- }
-
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+/**
+ * {@link TileCache} implementation that stores all {@link Tile} objects in
+ * memory up to a certain limit ({@link #getCacheSize()}). If the limit is
+ * exceeded the least recently used {@link Tile} objects will be deleted.
+ *
+ * @author Jan Peter Stotz
+ */
+public class MemoryTileCache implements TileCache {
+
+ protected static final Logger log = Logger.getLogger(MemoryTileCache.class.getName());
+
+ /**
+ * Default cache size
+ */
+ protected int cacheSize = 200;
+
+ protected final Map<String, CacheEntry> hash;
+
+ /**
+ * List of all tiles in their last recently used order
+ */
+ protected final CacheLinkedListElement lruTiles;
+
+ /**
+ * Constructs a new {@code MemoryTileCache}.
+ */
+ public MemoryTileCache() {
+ hash = new HashMap<>(cacheSize);
+ lruTiles = new CacheLinkedListElement();
+ }
+
+ @Override
+ public synchronized void addTile(Tile tile) {
+ CacheEntry entry = createCacheEntry(tile);
+ hash.put(tile.getKey(), entry);
+ lruTiles.addFirst(entry);
+ if (hash.size() > cacheSize) {
+ removeOldEntries();
+ }
+ }
+
+ @Override
+ public synchronized Tile getTile(TileSource source, int x, int y, int z) {
+ CacheEntry entry = hash.get(Tile.getTileKey(source, x, y, z));
+ if (entry == null)
+ return null;
+ // We don't care about placeholder tiles and hourglass image tiles, the
+ // important tiles are the loaded ones
+ if (entry.tile.isLoaded())
+ lruTiles.moveElementToFirstPos(entry);
+ return entry.tile;
+ }
+
+ /**
+ * Removes the least recently used tiles
+ */
+ protected synchronized void removeOldEntries() {
+ try {
+ while (lruTiles.getElementCount() > cacheSize) {
+ removeEntry(lruTiles.getLastElement());
+ }
+ } catch (Exception e) {
+ log.warning(e.getMessage());
+ }
+ }
+
+ protected synchronized void removeEntry(CacheEntry entry) {
+ hash.remove(entry.tile.getKey());
+ lruTiles.removeEntry(entry);
+ }
+
+ protected CacheEntry createCacheEntry(Tile tile) {
+ return new CacheEntry(tile);
+ }
+
+ @Override
+ public synchronized void clear() {
+ hash.clear();
+ lruTiles.clear();
+ }
+
+ @Override
+ public int getTileCount() {
+ return hash.size();
+ }
+
+ public int getCacheSize() {
+ return cacheSize;
+ }
+
+ /**
+ * Changes the maximum number of {@link Tile} objects that this cache holds.
+ *
+ * @param cacheSize
+ * new maximum number of tiles
+ */
+ public synchronized void setCacheSize(int cacheSize) {
+ this.cacheSize = cacheSize;
+ if (hash.size() > cacheSize)
+ removeOldEntries();
+ }
+
+ /**
+ * Linked list element holding the {@link Tile} and links to the
+ * {@link #next} and {@link #prev} item in the list.
+ */
+ protected static class CacheEntry {
+ Tile tile;
+
+ CacheEntry next;
+ CacheEntry prev;
+
+ protected CacheEntry(Tile tile) {
+ this.tile = tile;
+ }
+
+ public Tile getTile() {
+ return tile;
+ }
+
+ public CacheEntry getNext() {
+ return next;
+ }
+
+ public CacheEntry getPrev() {
+ return prev;
+ }
+
+ }
+
+ /**
+ * Special implementation of a double linked list for {@link CacheEntry}
+ * elements. It supports element removal in constant time - in difference to
+ * the Java implementation which needs O(n).
+ *
+ * @author Jan Peter Stotz
+ */
+ protected static class CacheLinkedListElement {
+ protected CacheEntry firstElement = null;
+ protected CacheEntry lastElement;
+ protected int elementCount;
+
+ public CacheLinkedListElement() {
+ clear();
+ }
+
+ public void clear() {
+ elementCount = 0;
+ firstElement = null;
+ lastElement = null;
+ }
+
+ /**
+ * Add the element to the head of the list.
+ *
+ * @param element new element to be added
+ */
+ public void addFirst(CacheEntry element) {
+ if (element == null) return;
+ if (elementCount == 0) {
+ firstElement = element;
+ lastElement = element;
+ element.prev = null;
+ element.next = null;
+ } else {
+ element.next = firstElement;
+ firstElement.prev = element;
+ element.prev = null;
+ firstElement = element;
+ }
+ elementCount++;
+ }
+
+ /**
+ * Removes the specified element from the list.
+ *
+ * @param element element to be removed
+ */
+ public void removeEntry(CacheEntry element) {
+ if (element == null) return;
+ if (element.next != null) {
+ element.next.prev = element.prev;
+ }
+ if (element.prev != null) {
+ element.prev.next = element.next;
+ }
+ if (element == firstElement)
+ firstElement = element.next;
+ if (element == lastElement)
+ lastElement = element.prev;
+ element.next = null;
+ element.prev = null;
+ elementCount--;
+ }
+
+ public void moveElementToFirstPos(CacheEntry entry) {
+ if (firstElement == entry)
+ return;
+ removeEntry(entry);
+ addFirst(entry);
+ }
+
+ public int getElementCount() {
+ return elementCount;
+ }
+
+ public CacheEntry getLastElement() {
+ return lastElement;
+ }
+
+ public CacheEntry getFirstElement() {
+ return firstElement;
+ }
+
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java b/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
index 08212ef..8291522 100644
--- a/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
+++ b/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
@@ -1,503 +1,504 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.charset.Charset;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Random;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileClearController;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource.TileUpdate;
-
-/**
- * A {@link TileLoader} implementation that loads tiles from OSM via HTTP and
- * saves all loaded files in a directory located in the temporary directory.
- * If a tile is present in this file cache it will not be loaded from OSM again.
- *
- * @author Jan Peter Stotz
- * @author Stefan Zeller
- */
-public class OsmFileCacheTileLoader extends OsmTileLoader implements CachedTileLoader {
-
- private static final Logger log = FeatureAdapter.getLogger(OsmFileCacheTileLoader.class.getName());
-
- protected static final String TAGS_FILE_EXT = "tags";
-
- private static final Charset TAGS_CHARSET = Charset.forName("UTF-8");
-
- public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
- public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
-
- protected String cacheDirBase;
-
- protected final Map<TileSource, File> sourceCacheDirMap;
-
- protected long maxCacheFileAge = Long.MAX_VALUE; // max. age not limited
- protected long recheckAfter = FILE_AGE_ONE_WEEK;
-
- public static File getDefaultCacheDir() throws SecurityException {
- String tempDir = null;
- String userName = System.getProperty("user.name");
- try {
- tempDir = System.getProperty("java.io.tmpdir");
- } catch (SecurityException e) {
- log.log(Level.WARNING,
- "Failed to access system property ''java.io.tmpdir'' for security reasons. Exception was: "
- + e.toString());
- throw e; // rethrow
- }
- try {
- if (tempDir == null)
- throw new IOException("No temp directory set");
- String subDirName = "JMapViewerTiles";
- // On Linux/Unix systems we do not have a per user tmp directory.
- // Therefore we add the user name for getting a unique dir name.
- if (userName != null && userName.length() > 0) {
- subDirName += "_" + userName;
- }
- File cacheDir = new File(tempDir, subDirName);
- return cacheDir;
- } catch (Exception e) {
- }
- return null;
- }
-
- /**
- * Create a OSMFileCacheTileLoader with given cache directory.
- * If cacheDir is not set or invalid, IOException will be thrown.
- * @param map the listener checking for tile load events (usually the map for display)
- * @param cacheDir directory to store cached tiles
- */
- public OsmFileCacheTileLoader(TileLoaderListener map, File cacheDir) throws IOException {
- super(map);
- if (cacheDir == null || (!cacheDir.exists() && !cacheDir.mkdirs()))
- throw new IOException("Cannot access cache directory");
-
- log.finest("Tile cache directory: " + cacheDir);
- cacheDirBase = cacheDir.getAbsolutePath();
- sourceCacheDirMap = new HashMap<>();
- }
-
- /**
- * Create a OSMFileCacheTileLoader with system property temp dir.
- * If not set an IOException will be thrown.
- * @param map the listener checking for tile load events (usually the map for display)
- */
- public OsmFileCacheTileLoader(TileLoaderListener map) throws SecurityException, IOException {
- this(map, getDefaultCacheDir());
- }
-
- @Override
- public TileJob createTileLoaderJob(final Tile tile) {
- return new FileLoadJob(tile);
- }
-
- protected File getSourceCacheDir(TileSource source) {
- File dir = sourceCacheDirMap.get(source);
- if (dir == null) {
- dir = new File(cacheDirBase, source.getName().replaceAll("[\\\\/:*?\"<>|]", "_"));
- if (!dir.exists()) {
- dir.mkdirs();
- }
- }
- return dir;
- }
-
- protected class FileLoadJob implements TileJob {
- InputStream input = null;
-
- Tile tile;
- File tileCacheDir;
- File tileFile = null;
- Long fileMtime = null;
- Long now = null; // current time in milliseconds (keep consistent value for the whole run)
-
- public FileLoadJob(Tile tile) {
- this.tile = tile;
- }
-
- @Override
- public Tile getTile() {
- return tile;
- }
-
- @Override
- public void run() {
- synchronized (tile) {
- if ((tile.isLoaded() && !tile.hasError()) || tile.isLoading())
- return;
- tile.loaded = false;
- tile.error = false;
- tile.loading = true;
- }
- now = System.currentTimeMillis();
- tileCacheDir = getSourceCacheDir(tile.getSource());
-
- if (loadTileFromFile(recheckAfter)) {
- log.log(Level.FINE, "TMS - found in tile cache: {0}", tile);
- tile.setLoaded(true);
- listener.tileLoadingFinished(tile, true);
- return;
- }
- TileJob job = new TileJob() {
-
- @Override
- public void run() {
- if (loadOrUpdateTile()) {
- tile.setLoaded(true);
- listener.tileLoadingFinished(tile, true);
- } else {
- // failed to download - use old cache file if available
- if (loadTileFromFile(maxCacheFileAge)) {
- tile.setLoaded(true);
- tile.error = false;
- listener.tileLoadingFinished(tile, true);
- log.log(Level.FINE, "TMS - found stale tile in cache: {0}", tile);
- } else {
- // failed completely
- tile.setLoaded(true);
- listener.tileLoadingFinished(tile, false);
- }
- }
- }
- @Override
- public Tile getTile() {
- return tile;
- }
- };
- JobDispatcher.getInstance().addJob(job);
- }
-
- protected boolean loadOrUpdateTile() {
- try {
- URLConnection urlConn = loadTileFromOsm(tile);
- if (fileMtime != null && now - fileMtime <= maxCacheFileAge) {
- switch (tile.getSource().getTileUpdate()) {
- case IfModifiedSince:
- urlConn.setIfModifiedSince(fileMtime);
- break;
- case LastModified:
- if (!isOsmTileNewer(fileMtime)) {
- log.log(Level.FINE, "TMS - LastModified test: local version is up to date: {0}", tile);
- tileFile.setLastModified(now);
- return true;
- }
- break;
- default:
- break;
- }
- }
- if (tile.getSource().getTileUpdate() == TileUpdate.ETag || tile.getSource().getTileUpdate() == TileUpdate.IfNoneMatch) {
- String fileETag = tile.getValue("etag");
- if (fileETag != null) {
- switch (tile.getSource().getTileUpdate()) {
- case IfNoneMatch:
- urlConn.addRequestProperty("If-None-Match", fileETag);
- break;
- case ETag:
- if (hasOsmTileETag(fileETag)) {
- log.log(Level.FINE, "TMS - ETag test: local version is up to date: {0}", tile);
- tileFile.setLastModified(now);
- return true;
- }
- default:
- break;
- }
- }
- tile.putValue("etag", urlConn.getHeaderField("ETag"));
- }
- if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) {
- // If we are isModifiedSince or If-None-Match has been set
- // and the server answers with a HTTP 304 = "Not Modified"
- switch (tile.getSource().getTileUpdate()) {
- case IfModifiedSince:
- log.log(Level.FINE, "TMS - IfModifiedSince test: local version is up to date: {0}", tile);
- break;
- case IfNoneMatch:
- log.log(Level.FINE, "TMS - IfNoneMatch test: local version is up to date: {0}", tile);
- break;
- default:
- break;
- }
- if (loadTileFromFile(maxCacheFileAge)) {
- tileFile.setLastModified(now);
- return true;
- }
- }
-
- loadTileMetadata(tile, urlConn);
- saveTagsToFile();
-
- if ("no-tile".equals(tile.getValue("tile-info")))
- {
- log.log(Level.FINE, "TMS - No tile: tile-info=no-tile: {0}", tile);
- tile.setError("No tile at this zoom level");
- return true;
- } else {
- for (int i = 0; i < 5; ++i) {
- if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) {
- Thread.sleep(5000+(new Random()).nextInt(5000));
- continue;
- }
- byte[] buffer = loadTileInBuffer(urlConn);
- if (buffer != null) {
- tile.loadImage(new ByteArrayInputStream(buffer));
- saveTileToFile(buffer);
- log.log(Level.FINE, "TMS - downloaded tile from server: {0}", tile.getUrl());
- return true;
- }
- }
- }
- } catch (Exception e) {
- tile.setError(e.getMessage());
- if (input == null) {
- try {
- log.log(Level.WARNING, "TMS - Failed downloading {0}: {1}", new Object[]{tile.getUrl(), e.getMessage()});
- return false;
- } catch(IOException i) {
- }
- }
- }
- log.log(Level.WARNING, "TMS - Failed downloading tile: {0}", tile);
- return false;
- }
-
- protected boolean loadTileFromFile(long maxAge) {
- try {
- tileFile = getTileFile();
- if (!tileFile.exists())
- return false;
- loadTagsFromFile();
-
- fileMtime = tileFile.lastModified();
- if (now - fileMtime > maxAge)
- return false;
-
- if ("no-tile".equals(tile.getValue("tile-info"))) {
- tile.setError("No tile at this zoom level");
- if (tileFile.exists()) {
- tileFile.delete();
- }
- tileFile = null;
- } else {
- try (FileInputStream fin = new FileInputStream(tileFile)) {
- if (fin.available() == 0)
- throw new IOException("File empty");
- tile.loadImage(fin);
- }
- }
- return true;
-
- } catch (Exception e) {
- log.log(Level.WARNING, "TMS - Error while loading image from tile cache: {0}; {1}", new Object[]{e.getMessage(), tile});
- tileFile.delete();
- tileFile = null;
- fileMtime = null;
- }
- return false;
- }
-
- protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
- input = urlConn.getInputStream();
- try {
- ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available());
- byte[] buffer = new byte[2048];
- boolean finished = false;
- do {
- int read = input.read(buffer);
- if (read >= 0) {
- bout.write(buffer, 0, read);
- } else {
- finished = true;
- }
- } while (!finished);
- if (bout.size() == 0)
- return null;
- return bout.toByteArray();
- } finally {
- input.close();
- input = null;
- }
- }
-
- /**
- * Performs a <code>HEAD</code> request for retrieving the
- * <code>LastModified</code> header value.
- *
- * Note: This does only work with servers providing the
- * <code>LastModified</code> header:
- * <ul>
- * <li>{@link org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.CycleMap} - supported</li>
- * <li>{@link org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik} - not supported</li>
- * </ul>
- *
- * @param fileAge time of the
- * @return <code>true</code> if the tile on the server is newer than the
- * file
- * @throws IOException
- */
- protected boolean isOsmTileNewer(long fileAge) throws IOException {
- URL url;
- url = new URL(tile.getUrl());
- HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
- prepareHttpUrlConnection(urlConn);
- urlConn.setRequestMethod("HEAD");
- urlConn.setReadTimeout(30000); // 30 seconds read timeout
- // System.out.println("Tile age: " + new
- // Date(urlConn.getLastModified()) + " / "
- // + new Date(fileMtime));
- long lastModified = urlConn.getLastModified();
- if (lastModified == 0)
- return true; // no LastModified time returned
- return (lastModified > fileAge);
- }
-
- protected boolean hasOsmTileETag(String eTag) throws IOException {
- URL url;
- url = new URL(tile.getUrl());
- HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
- prepareHttpUrlConnection(urlConn);
- urlConn.setRequestMethod("HEAD");
- urlConn.setReadTimeout(30000); // 30 seconds read timeout
- // System.out.println("Tile age: " + new
- // Date(urlConn.getLastModified()) + " / "
- // + new Date(fileMtime));
- String osmETag = urlConn.getHeaderField("ETag");
- if (osmETag == null)
- return true;
- return (osmETag.equals(eTag));
- }
-
- protected File getTileFile() {
- return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
- + tile.getSource().getTileType());
- }
-
- protected File getTagsFile() {
- return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
- + TAGS_FILE_EXT);
- }
-
- protected void saveTileToFile(byte[] rawData) {
- File file = getTileFile();
- file.getParentFile().mkdirs();
- try (
- FileOutputStream f = new FileOutputStream(file)
- ) {
- f.write(rawData);
- } catch (Exception e) {
- log.log(Level.SEVERE, "Failed to save tile content: {0}", e.getLocalizedMessage());
- }
- }
-
- protected void saveTagsToFile() {
- File tagsFile = getTagsFile();
- if (tile.getMetadata() == null) {
- tagsFile.delete();
- return;
- }
- try (PrintWriter f = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tagsFile), TAGS_CHARSET))) {
- for (Entry<String, String> entry : tile.getMetadata().entrySet()) {
- f.println(entry.getKey() + "=" + entry.getValue());
- }
- } catch (Exception e) {
- System.err.println("Failed to save tile tags: " + e.getLocalizedMessage());
- }
- }
-
- protected void loadTagsFromFile() {
- File tagsFile = getTagsFile();
- try (BufferedReader f = new BufferedReader(new InputStreamReader(new FileInputStream(tagsFile), TAGS_CHARSET))) {
- for (String line = f.readLine(); line != null; line = f.readLine()) {
- final int i = line.indexOf('=');
- if (i == -1 || i == 0) {
- System.err.println("Malformed tile tag in file '" + tagsFile.getName() + "':" + line);
- continue;
- }
- tile.putValue(line.substring(0,i),line.substring(i+1));
- }
- } catch (FileNotFoundException e) {
- } catch (Exception e) {
- System.err.println("Failed to load tile tags: " + e.getLocalizedMessage());
- }
- }
- }
-
- public long getMaxFileAge() {
- return maxCacheFileAge;
- }
-
- /**
- * Sets the maximum age of the local cached tile in the file system. If a
- * local tile is older than the specified file age
- * {@link OsmFileCacheTileLoader} will connect to the tile server and check
- * if a newer tile is available using the mechanism specified for the
- * selected tile source/server.
- *
- * @param maxFileAge
- * maximum age in milliseconds
- * @see #FILE_AGE_ONE_DAY
- * @see #FILE_AGE_ONE_WEEK
- * @see TileSource#getTileUpdate()
- */
- public void setCacheMaxFileAge(long maxFileAge) {
- this.maxCacheFileAge = maxFileAge;
- }
-
- public String getCacheDirBase() {
- return cacheDirBase;
- }
-
- public void setTileCacheDir(String tileCacheDir) {
- File dir = new File(tileCacheDir);
- dir.mkdirs();
- this.cacheDirBase = dir.getAbsolutePath();
- }
-
- @Override
- public void clearCache(TileSource source) {
- clearCache(source, null);
- }
-
- @Override
- public void clearCache(TileSource source, TileClearController controller) {
- File dir = getSourceCacheDir(source);
- if (dir != null) {
- if (controller != null) controller.initClearDir(dir);
- if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (controller != null) controller.initClearFiles(files);
- for (File file : files) {
- if (controller != null && controller.cancel()) return;
- file.delete();
- if (controller != null) controller.fileDeleted(file);
- }
- }
- dir.delete();
- }
- if (controller != null) controller.clearFinished();
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileClearController;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource.TileUpdate;
+
+/**
+ * A {@link TileLoader} implementation that loads tiles from OSM via HTTP and
+ * saves all loaded files in a directory located in the temporary directory.
+ * If a tile is present in this file cache it will not be loaded from OSM again.
+ *
+ * @author Jan Peter Stotz
+ * @author Stefan Zeller
+ */
+public class OsmFileCacheTileLoader extends OsmTileLoader implements CachedTileLoader {
+
+ private static final Logger log = FeatureAdapter.getLogger(OsmFileCacheTileLoader.class.getName());
+
+ protected static final String TAGS_FILE_EXT = "tags";
+
+ private static final Charset TAGS_CHARSET = Charset.forName("UTF-8");
+
+ public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
+ public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
+
+ protected String cacheDirBase;
+
+ protected final Map<TileSource, File> sourceCacheDirMap;
+
+ protected long maxCacheFileAge = Long.MAX_VALUE; // max. age not limited
+ protected long recheckAfter = FILE_AGE_ONE_WEEK;
+
+ public static File getDefaultCacheDir() throws SecurityException {
+ String tempDir = null;
+ String userName = System.getProperty("user.name");
+ try {
+ tempDir = System.getProperty("java.io.tmpdir");
+ } catch (SecurityException e) {
+ log.log(Level.WARNING,
+ "Failed to access system property ''java.io.tmpdir'' for security reasons. Exception was: "
+ + e.toString());
+ throw e; // rethrow
+ }
+ try {
+ if (tempDir == null)
+ throw new IOException("No temp directory set");
+ String subDirName = "JMapViewerTiles";
+ // On Linux/Unix systems we do not have a per user tmp directory.
+ // Therefore we add the user name for getting a unique dir name.
+ if (userName != null && userName.length() > 0) {
+ subDirName += "_" + userName;
+ }
+ File cacheDir = new File(tempDir, subDirName);
+ return cacheDir;
+ } catch (Exception e) {
+ }
+ return null;
+ }
+
+ /**
+ * Create a OSMFileCacheTileLoader with given cache directory.
+ * If cacheDir is not set or invalid, IOException will be thrown.
+ * @param map the listener checking for tile load events (usually the map for display)
+ * @param cacheDir directory to store cached tiles
+ */
+ public OsmFileCacheTileLoader(TileLoaderListener map, File cacheDir) throws IOException {
+ super(map);
+ if (cacheDir == null || (!cacheDir.exists() && !cacheDir.mkdirs()))
+ throw new IOException("Cannot access cache directory");
+
+ log.finest("Tile cache directory: " + cacheDir);
+ cacheDirBase = cacheDir.getAbsolutePath();
+ sourceCacheDirMap = new HashMap<>();
+ }
+
+ /**
+ * Create a OSMFileCacheTileLoader with system property temp dir.
+ * If not set an IOException will be thrown.
+ * @param map the listener checking for tile load events (usually the map for display)
+ */
+ public OsmFileCacheTileLoader(TileLoaderListener map) throws SecurityException, IOException {
+ this(map, getDefaultCacheDir());
+ }
+
+ @Override
+ public TileJob createTileLoaderJob(final Tile tile) {
+ return new FileLoadJob(tile);
+ }
+
+ protected File getSourceCacheDir(TileSource source) {
+ File dir = sourceCacheDirMap.get(source);
+ if (dir == null) {
+ dir = new File(cacheDirBase, source.getName().replaceAll("[\\\\/:*?\"<>|]", "_"));
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ }
+ return dir;
+ }
+
+ protected class FileLoadJob implements TileJob {
+ InputStream input = null;
+
+ Tile tile;
+ File tileCacheDir;
+ File tileFile = null;
+ Long fileMtime = null;
+ Long now = null; // current time in milliseconds (keep consistent value for the whole run)
+
+ public FileLoadJob(Tile tile) {
+ this.tile = tile;
+ }
+
+ @Override
+ public Tile getTile() {
+ return tile;
+ }
+
+ @Override
+ public void run() {
+ synchronized (tile) {
+ if ((tile.isLoaded() && !tile.hasError()) || tile.isLoading())
+ return;
+ tile.loaded = false;
+ tile.error = false;
+ tile.loading = true;
+ }
+ now = System.currentTimeMillis();
+ tileCacheDir = getSourceCacheDir(tile.getSource());
+
+ if (loadTileFromFile(recheckAfter)) {
+ log.log(Level.FINE, "TMS - found in tile cache: {0}", tile);
+ tile.setLoaded(true);
+ listener.tileLoadingFinished(tile, true);
+ return;
+ }
+ TileJob job = new TileJob() {
+
+ @Override
+ public void run() {
+ if (loadOrUpdateTile()) {
+ tile.setLoaded(true);
+ listener.tileLoadingFinished(tile, true);
+ } else {
+ // failed to download - use old cache file if available
+ if (loadTileFromFile(maxCacheFileAge)) {
+ tile.setLoaded(true);
+ tile.error = false;
+ listener.tileLoadingFinished(tile, true);
+ log.log(Level.FINE, "TMS - found stale tile in cache: {0}", tile);
+ } else {
+ // failed completely
+ tile.setLoaded(true);
+ listener.tileLoadingFinished(tile, false);
+ }
+ }
+ }
+ @Override
+ public Tile getTile() {
+ return tile;
+ }
+ };
+ JobDispatcher.getInstance().addJob(job);
+ }
+
+ protected boolean loadOrUpdateTile() {
+ try {
+ URLConnection urlConn = loadTileFromOsm(tile);
+ if (fileMtime != null && now - fileMtime <= maxCacheFileAge) {
+ switch (tile.getSource().getTileUpdate()) {
+ case IfModifiedSince:
+ urlConn.setIfModifiedSince(fileMtime);
+ break;
+ case LastModified:
+ if (!isOsmTileNewer(fileMtime)) {
+ log.log(Level.FINE, "TMS - LastModified test: local version is up to date: {0}", tile);
+ tileFile.setLastModified(now);
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (tile.getSource().getTileUpdate() == TileUpdate.ETag || tile.getSource().getTileUpdate() == TileUpdate.IfNoneMatch) {
+ String fileETag = tile.getValue("etag");
+ if (fileETag != null) {
+ switch (tile.getSource().getTileUpdate()) {
+ case IfNoneMatch:
+ urlConn.addRequestProperty("If-None-Match", fileETag);
+ break;
+ case ETag:
+ if (hasOsmTileETag(fileETag)) {
+ log.log(Level.FINE, "TMS - ETag test: local version is up to date: {0}", tile);
+ tileFile.setLastModified(now);
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+ tile.putValue("etag", urlConn.getHeaderField("ETag"));
+ }
+ if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) {
+ // If we are isModifiedSince or If-None-Match has been set
+ // and the server answers with a HTTP 304 = "Not Modified"
+ switch (tile.getSource().getTileUpdate()) {
+ case IfModifiedSince:
+ log.log(Level.FINE, "TMS - IfModifiedSince test: local version is up to date: {0}", tile);
+ break;
+ case IfNoneMatch:
+ log.log(Level.FINE, "TMS - IfNoneMatch test: local version is up to date: {0}", tile);
+ break;
+ default:
+ break;
+ }
+ if (loadTileFromFile(maxCacheFileAge)) {
+ tileFile.setLastModified(now);
+ return true;
+ }
+ }
+
+ loadTileMetadata(tile, urlConn);
+ saveTagsToFile();
+
+ if ("no-tile".equals(tile.getValue("tile-info")))
+ {
+ log.log(Level.FINE, "TMS - No tile: tile-info=no-tile: {0}", tile);
+ tile.setError("No tile at this zoom level");
+ return true;
+ } else {
+ for (int i = 0; i < 5; ++i) {
+ if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) {
+ Thread.sleep(5000+(new Random()).nextInt(5000));
+ continue;
+ }
+ byte[] buffer = loadTileInBuffer(urlConn);
+ if (buffer != null) {
+ tile.loadImage(new ByteArrayInputStream(buffer));
+ saveTileToFile(buffer);
+ log.log(Level.FINE, "TMS - downloaded tile from server: {0}", tile.getUrl());
+ return true;
+ }
+ }
+ }
+ } catch (Exception e) {
+ tile.setError(e.getMessage());
+ if (input == null) {
+ try {
+ log.log(Level.WARNING, "TMS - Failed downloading {0}: {1}", new Object[]{tile.getUrl(), e.getMessage()});
+ return false;
+ } catch(IOException i) {
+ }
+ }
+ }
+ log.log(Level.WARNING, "TMS - Failed downloading tile: {0}", tile);
+ return false;
+ }
+
+ protected boolean loadTileFromFile(long maxAge) {
+ try {
+ tileFile = getTileFile();
+ if (!tileFile.exists())
+ return false;
+ loadTagsFromFile();
+
+ fileMtime = tileFile.lastModified();
+ if (now - fileMtime > maxAge)
+ return false;
+
+ if ("no-tile".equals(tile.getValue("tile-info"))) {
+ tile.setError("No tile at this zoom level");
+ if (tileFile.exists()) {
+ tileFile.delete();
+ }
+ tileFile = null;
+ } else {
+ try (FileInputStream fin = new FileInputStream(tileFile)) {
+ if (fin.available() == 0)
+ throw new IOException("File empty");
+ tile.loadImage(fin);
+ }
+ }
+ return true;
+
+ } catch (Exception e) {
+ log.log(Level.WARNING, "TMS - Error while loading image from tile cache: {0}; {1}", new Object[]{e.getMessage(), tile});
+ tileFile.delete();
+ tileFile = null;
+ fileMtime = null;
+ }
+ return false;
+ }
+
+ protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
+ input = urlConn.getInputStream();
+ try {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available());
+ byte[] buffer = new byte[2048];
+ boolean finished = false;
+ do {
+ int read = input.read(buffer);
+ if (read >= 0) {
+ bout.write(buffer, 0, read);
+ } else {
+ finished = true;
+ }
+ } while (!finished);
+ if (bout.size() == 0)
+ return null;
+ return bout.toByteArray();
+ } finally {
+ input.close();
+ input = null;
+ }
+ }
+
+ /**
+ * Performs a <code>HEAD</code> request for retrieving the
+ * <code>LastModified</code> header value.
+ *
+ * Note: This does only work with servers providing the
+ * <code>LastModified</code> header:
+ * <ul>
+ * <li>{@link org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.CycleMap} - supported</li>
+ * <li>{@link org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik} - not supported</li>
+ * </ul>
+ *
+ * @param fileAge time of the
+ * @return <code>true</code> if the tile on the server is newer than the
+ * file
+ * @throws IOException
+ */
+ protected boolean isOsmTileNewer(long fileAge) throws IOException {
+ URL url;
+ url = new URL(tile.getUrl());
+ HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+ prepareHttpUrlConnection(urlConn);
+ urlConn.setRequestMethod("HEAD");
+ urlConn.setReadTimeout(30000); // 30 seconds read timeout
+ // System.out.println("Tile age: " + new
+ // Date(urlConn.getLastModified()) + " / "
+ // + new Date(fileMtime));
+ long lastModified = urlConn.getLastModified();
+ if (lastModified == 0)
+ return true; // no LastModified time returned
+ return (lastModified > fileAge);
+ }
+
+ protected boolean hasOsmTileETag(String eTag) throws IOException {
+ URL url;
+ url = new URL(tile.getUrl());
+ HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+ prepareHttpUrlConnection(urlConn);
+ urlConn.setRequestMethod("HEAD");
+ urlConn.setReadTimeout(30000); // 30 seconds read timeout
+ // System.out.println("Tile age: " + new
+ // Date(urlConn.getLastModified()) + " / "
+ // + new Date(fileMtime));
+ String osmETag = urlConn.getHeaderField("ETag");
+ if (osmETag == null)
+ return true;
+ return (osmETag.equals(eTag));
+ }
+
+ protected File getTileFile() {
+ return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
+ + tile.getSource().getTileType());
+ }
+
+ protected File getTagsFile() {
+ return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
+ + TAGS_FILE_EXT);
+ }
+
+ protected void saveTileToFile(byte[] rawData) {
+ File file = getTileFile();
+ file.getParentFile().mkdirs();
+ try (
+ FileOutputStream f = new FileOutputStream(file)
+ ) {
+ f.write(rawData);
+ } catch (Exception e) {
+ log.log(Level.SEVERE, "Failed to save tile content: {0}", e.getLocalizedMessage());
+ }
+ }
+
+ protected void saveTagsToFile() {
+ File tagsFile = getTagsFile();
+ tagsFile.getParentFile().mkdirs();
+ if (tile.getMetadata() == null) {
+ tagsFile.delete();
+ return;
+ }
+ try (PrintWriter f = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tagsFile), TAGS_CHARSET))) {
+ for (Entry<String, String> entry : tile.getMetadata().entrySet()) {
+ f.println(entry.getKey() + "=" + entry.getValue());
+ }
+ } catch (Exception e) {
+ System.err.println("Failed to save tile tags: " + e.getLocalizedMessage());
+ }
+ }
+
+ protected void loadTagsFromFile() {
+ File tagsFile = getTagsFile();
+ try (BufferedReader f = new BufferedReader(new InputStreamReader(new FileInputStream(tagsFile), TAGS_CHARSET))) {
+ for (String line = f.readLine(); line != null; line = f.readLine()) {
+ final int i = line.indexOf('=');
+ if (i == -1 || i == 0) {
+ System.err.println("Malformed tile tag in file '" + tagsFile.getName() + "':" + line);
+ continue;
+ }
+ tile.putValue(line.substring(0,i),line.substring(i+1));
+ }
+ } catch (FileNotFoundException e) {
+ } catch (Exception e) {
+ System.err.println("Failed to load tile tags: " + e.getLocalizedMessage());
+ }
+ }
+ }
+
+ public long getMaxFileAge() {
+ return maxCacheFileAge;
+ }
+
+ /**
+ * Sets the maximum age of the local cached tile in the file system. If a
+ * local tile is older than the specified file age
+ * {@link OsmFileCacheTileLoader} will connect to the tile server and check
+ * if a newer tile is available using the mechanism specified for the
+ * selected tile source/server.
+ *
+ * @param maxFileAge
+ * maximum age in milliseconds
+ * @see #FILE_AGE_ONE_DAY
+ * @see #FILE_AGE_ONE_WEEK
+ * @see TileSource#getTileUpdate()
+ */
+ public void setCacheMaxFileAge(long maxFileAge) {
+ this.maxCacheFileAge = maxFileAge;
+ }
+
+ public String getCacheDirBase() {
+ return cacheDirBase;
+ }
+
+ public void setTileCacheDir(String tileCacheDir) {
+ File dir = new File(tileCacheDir);
+ dir.mkdirs();
+ this.cacheDirBase = dir.getAbsolutePath();
+ }
+
+ @Override
+ public void clearCache(TileSource source) {
+ clearCache(source, null);
+ }
+
+ @Override
+ public void clearCache(TileSource source, TileClearController controller) {
+ File dir = getSourceCacheDir(source);
+ if (dir != null) {
+ if (controller != null) controller.initClearDir(dir);
+ if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (controller != null) controller.initClearFiles(files);
+ for (File file : files) {
+ if (controller != null && controller.cancel()) return;
+ file.delete();
+ if (controller != null) controller.fileDeleted(file);
+ }
+ }
+ dir.delete();
+ }
+ if (controller != null) controller.clearFinished();
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java b/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java
index 903ee25..f0a7bcd 100644
--- a/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java
+++ b/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java
@@ -1,174 +1,174 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-/**
- * 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.
- */
-public class OsmMercator {
-
- public static int TILE_SIZE = 256;
- public static final double MAX_LAT = 85.05112877980659;
- public static final double MIN_LAT = -85.05112877980659;
- private static double EARTH_RADIUS = 6378137; // equatorial earth radius for EPSG:3857 (Mercator)
-
- public static double radius(int aZoomlevel) {
- return (TILE_SIZE * (1 << aZoomlevel)) / (2.0 * 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 zoom level to request pixel data
- * @return number of pixels
- */
- public static int getMaxPixels(int aZoomlevel) {
- return TILE_SIZE * (1 << aZoomlevel);
- }
-
- public static int falseEasting(int aZoomlevel) {
- return getMaxPixels(aZoomlevel) / 2;
- }
-
- public static int falseNorthing(int aZoomlevel) {
- return (-1 * getMaxPixels(aZoomlevel) / 2);
- }
-
- /**
- * Transform pixelspace to coordinates and get the distance.
- *
- * @param x1 the first x coordinate
- * @param y1 the first y coordinate
- * @param x2 the second x coordinate
- * @param y2 the second y coordinate
- *
- * @param zoomLevel the zoom level
- * @return the distance
- * @author Jason Huntley
- */
- public static double getDistance(int x1, int y1, int x2, int y2, int zoomLevel) {
- double la1 = YToLat(y1, zoomLevel);
- double lo1 = XToLon(x1, zoomLevel);
- double la2 = YToLat(y2, zoomLevel);
- double lo2 = XToLon(x2, zoomLevel);
-
- return getDistance(la1, lo1, la2, lo2);
- }
-
- /**
- * Gets the distance using Spherical law of cosines.
- *
- * @param la1 the Latitude in degrees
- * @param lo1 the Longitude in degrees
- * @param la2 the Latitude from 2nd coordinate in degrees
- * @param lo2 the Longitude from 2nd coordinate in degrees
- * @return the distance
- * @author Jason Huntley
- */
- public static double getDistance(double la1, double lo1, double la2, double lo2) {
- double aStartLat = Math.toRadians(la1);
- double aStartLong = Math.toRadians(lo1);
- double aEndLat =Math.toRadians(la2);
- double aEndLong = Math.toRadians(lo2);
-
- double distance = Math.acos(Math.sin(aStartLat) * Math.sin(aEndLat)
- + Math.cos(aStartLat) * Math.cos(aEndLat)
- * Math.cos(aEndLong - aStartLong));
-
- return (EARTH_RADIUS * distance);
- }
-
- /**
- * Transform longitude to pixelspace
- *
- * <p>
- * Mathematical optimization<br>
- * <code>
- * x = radius(aZoomlevel) * toRadians(aLongitude) + falseEasting(aZoomLevel)<br>
- * x = getMaxPixels(aZoomlevel) / (2 * PI) * (aLongitude * PI) / 180 + getMaxPixels(aZoomlevel) / 2<br>
- * x = getMaxPixels(aZoomlevel) * aLongitude / 360 + 180 * getMaxPixels(aZoomlevel) / 360<br>
- * x = getMaxPixels(aZoomlevel) * (aLongitude + 180) / 360<br>
- * </code>
- * </p>
- *
- * @param aLongitude
- * [-180..180]
- * @return [0..2^Zoomlevel*TILE_SIZE[
- * @author Jan Peter Stotz
- */
- public static double LonToX(double aLongitude, int aZoomlevel) {
- int mp = getMaxPixels(aZoomlevel);
- double x = (mp * (aLongitude + 180l)) / 360l;
- return Math.min(x, mp - 1);
- }
-
- /**
- * Transforms latitude to pixelspace
- * <p>
- * Mathematical optimization<br>
- * <code>
- * log(u) := log((1.0 + sin(toRadians(aLat))) / (1.0 - sin(toRadians(aLat))<br>
- *
- * y = -1 * (radius(aZoomlevel) / 2 * log(u)))) - falseNorthing(aZoomlevel))<br>
- * y = -1 * (getMaxPixel(aZoomlevel) / 2 * PI / 2 * log(u)) - -1 * getMaxPixel(aZoomLevel) / 2<br>
- * y = getMaxPixel(aZoomlevel) / (-4 * PI) * log(u)) + getMaxPixel(aZoomLevel) / 2<br>
- * y = getMaxPixel(aZoomlevel) * ((log(u) / (-4 * PI)) + 1/2)<br>
- * </code>
- * </p>
- * @param aLat
- * [-90...90]
- * @return [0..2^Zoomlevel*TILE_SIZE[
- * @author Jan Peter Stotz
- */
- public static double LatToY(double aLat, int aZoomlevel) {
- if (aLat < MIN_LAT)
- aLat = MIN_LAT;
- else if (aLat > MAX_LAT)
- aLat = MAX_LAT;
- double sinLat = Math.sin(Math.toRadians(aLat));
- double log = Math.log((1.0 + sinLat) / (1.0 - sinLat));
- int mp = getMaxPixels(aZoomlevel);
- double y = mp * (0.5 - (log / (4.0 * Math.PI)));
- return Math.min(y, mp - 1);
- }
-
- /**
- * Transforms pixel coordinate X to longitude
- *
- * <p>
- * Mathematical optimization<br>
- * <code>
- * lon = toDegree((aX - falseEasting(aZoomlevel)) / radius(aZoomlevel))<br>
- * lon = 180 / PI * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel) / (2 * PI)<br>
- * lon = 180 * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel))<br>
- * lon = 360 / getMaxPixels(aZoomlevel) * (aX - getMaxPixels(aZoomlevel) / 2)<br>
- * lon = 360 * aX / getMaxPixels(aZoomlevel) - 180<br>
- * </code>
- * </p>
- * @param aX
- * [0..2^Zoomlevel*TILE_WIDTH[
- * @return ]-180..180[
- * @author Jan Peter Stotz
- */
- public static double XToLon(int aX, int aZoomlevel) {
- return ((360d * aX) / getMaxPixels(aZoomlevel)) - 180.0;
- }
-
- /**
- * 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);
- }
-
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+/**
+ * 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.
+ */
+public class OsmMercator {
+
+ public static int TILE_SIZE = 256;
+ public static final double MAX_LAT = 85.05112877980659;
+ public static final double MIN_LAT = -85.05112877980659;
+ private static double EARTH_RADIUS = 6378137; // equatorial earth radius for EPSG:3857 (Mercator)
+
+ public static double radius(int aZoomlevel) {
+ return (TILE_SIZE * (1 << aZoomlevel)) / (2.0 * 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 zoom level to request pixel data
+ * @return number of pixels
+ */
+ public static int getMaxPixels(int aZoomlevel) {
+ return TILE_SIZE * (1 << aZoomlevel);
+ }
+
+ public static int falseEasting(int aZoomlevel) {
+ return getMaxPixels(aZoomlevel) / 2;
+ }
+
+ public static int falseNorthing(int aZoomlevel) {
+ return (-1 * getMaxPixels(aZoomlevel) / 2);
+ }
+
+ /**
+ * Transform pixelspace to coordinates and get the distance.
+ *
+ * @param x1 the first x coordinate
+ * @param y1 the first y coordinate
+ * @param x2 the second x coordinate
+ * @param y2 the second y coordinate
+ *
+ * @param zoomLevel the zoom level
+ * @return the distance
+ * @author Jason Huntley
+ */
+ public static double getDistance(int x1, int y1, int x2, int y2, int zoomLevel) {
+ double la1 = YToLat(y1, zoomLevel);
+ double lo1 = XToLon(x1, zoomLevel);
+ double la2 = YToLat(y2, zoomLevel);
+ double lo2 = XToLon(x2, zoomLevel);
+
+ return getDistance(la1, lo1, la2, lo2);
+ }
+
+ /**
+ * Gets the distance using Spherical law of cosines.
+ *
+ * @param la1 the Latitude in degrees
+ * @param lo1 the Longitude in degrees
+ * @param la2 the Latitude from 2nd coordinate in degrees
+ * @param lo2 the Longitude from 2nd coordinate in degrees
+ * @return the distance
+ * @author Jason Huntley
+ */
+ public static double getDistance(double la1, double lo1, double la2, double lo2) {
+ double aStartLat = Math.toRadians(la1);
+ double aStartLong = Math.toRadians(lo1);
+ double aEndLat =Math.toRadians(la2);
+ double aEndLong = Math.toRadians(lo2);
+
+ double distance = Math.acos(Math.sin(aStartLat) * Math.sin(aEndLat)
+ + Math.cos(aStartLat) * Math.cos(aEndLat)
+ * Math.cos(aEndLong - aStartLong));
+
+ return (EARTH_RADIUS * distance);
+ }
+
+ /**
+ * Transform longitude to pixelspace
+ *
+ * <p>
+ * Mathematical optimization<br>
+ * <code>
+ * x = radius(aZoomlevel) * toRadians(aLongitude) + falseEasting(aZoomLevel)<br>
+ * x = getMaxPixels(aZoomlevel) / (2 * PI) * (aLongitude * PI) / 180 + getMaxPixels(aZoomlevel) / 2<br>
+ * x = getMaxPixels(aZoomlevel) * aLongitude / 360 + 180 * getMaxPixels(aZoomlevel) / 360<br>
+ * x = getMaxPixels(aZoomlevel) * (aLongitude + 180) / 360<br>
+ * </code>
+ * </p>
+ *
+ * @param aLongitude
+ * [-180..180]
+ * @return [0..2^Zoomlevel*TILE_SIZE[
+ * @author Jan Peter Stotz
+ */
+ public static double LonToX(double aLongitude, int aZoomlevel) {
+ int mp = getMaxPixels(aZoomlevel);
+ double x = (mp * (aLongitude + 180l)) / 360l;
+ return Math.min(x, mp - 1);
+ }
+
+ /**
+ * Transforms latitude to pixelspace
+ * <p>
+ * Mathematical optimization<br>
+ * <code>
+ * log(u) := log((1.0 + sin(toRadians(aLat))) / (1.0 - sin(toRadians(aLat))<br>
+ *
+ * y = -1 * (radius(aZoomlevel) / 2 * log(u)))) - falseNorthing(aZoomlevel))<br>
+ * y = -1 * (getMaxPixel(aZoomlevel) / 2 * PI / 2 * log(u)) - -1 * getMaxPixel(aZoomLevel) / 2<br>
+ * y = getMaxPixel(aZoomlevel) / (-4 * PI) * log(u)) + getMaxPixel(aZoomLevel) / 2<br>
+ * y = getMaxPixel(aZoomlevel) * ((log(u) / (-4 * PI)) + 1/2)<br>
+ * </code>
+ * </p>
+ * @param aLat
+ * [-90...90]
+ * @return [0..2^Zoomlevel*TILE_SIZE[
+ * @author Jan Peter Stotz
+ */
+ public static double LatToY(double aLat, int aZoomlevel) {
+ if (aLat < MIN_LAT)
+ aLat = MIN_LAT;
+ else if (aLat > MAX_LAT)
+ aLat = MAX_LAT;
+ double sinLat = Math.sin(Math.toRadians(aLat));
+ double log = Math.log((1.0 + sinLat) / (1.0 - sinLat));
+ int mp = getMaxPixels(aZoomlevel);
+ double y = mp * (0.5 - (log / (4.0 * Math.PI)));
+ return Math.min(y, mp - 1);
+ }
+
+ /**
+ * Transforms pixel coordinate X to longitude
+ *
+ * <p>
+ * Mathematical optimization<br>
+ * <code>
+ * lon = toDegree((aX - falseEasting(aZoomlevel)) / radius(aZoomlevel))<br>
+ * lon = 180 / PI * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel) / (2 * PI)<br>
+ * lon = 180 * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel))<br>
+ * lon = 360 / getMaxPixels(aZoomlevel) * (aX - getMaxPixels(aZoomlevel) / 2)<br>
+ * lon = 360 * aX / getMaxPixels(aZoomlevel) - 180<br>
+ * </code>
+ * </p>
+ * @param aX
+ * [0..2^Zoomlevel*TILE_WIDTH[
+ * @return ]-180..180[
+ * @author Jan Peter Stotz
+ */
+ public static double XToLon(int aX, int aZoomlevel) {
+ return ((360d * aX) / getMaxPixels(aZoomlevel)) - 180.0;
+ }
+
+ /**
+ * 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);
+ }
+
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java b/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
index eee1fc8..fa0c170 100644
--- a/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
+++ b/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
@@ -1,128 +1,128 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
-
-/**
- * A {@link TileLoader} implementation that loads tiles from OSM.
- *
- * @author Jan Peter Stotz
- */
-public class OsmTileLoader implements TileLoader {
-
- /**
- * Holds the HTTP headers. Insert e.g. User-Agent here when default should not be used.
- */
- public Map<String, String> headers = new HashMap<>();
-
- public int timeoutConnect = 0;
- public int timeoutRead = 0;
-
- protected TileLoaderListener listener;
-
- public OsmTileLoader(TileLoaderListener listener) {
- headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*");
- this.listener = listener;
- }
-
- public TileJob createTileLoaderJob(final Tile tile) {
- return new TileJob() {
-
- InputStream input = null;
-
- public void run() {
- synchronized (tile) {
- if ((tile.isLoaded() && !tile.hasError()) || tile.isLoading())
- return;
- tile.loaded = false;
- tile.error = false;
- tile.loading = true;
- }
- try {
- URLConnection conn = loadTileFromOsm(tile);
- loadTileMetadata(tile, conn);
- if ("no-tile".equals(tile.getValue("tile-info"))) {
- tile.setError("No tile at this zoom level");
- } else {
- input = conn.getInputStream();
- try {
- tile.loadImage(input);
- } finally {
- input.close();
- input = null;
- }
- }
- tile.setLoaded(true);
- listener.tileLoadingFinished(tile, true);
- } catch (Exception e) {
- tile.setError(e.getMessage());
- listener.tileLoadingFinished(tile, false);
- if (input == null) {
- try {
- System.err.println("Failed loading " + tile.getUrl() +": "
- +e.getClass() + ": " + e.getMessage());
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- }
- } finally {
- tile.loading = false;
- tile.setLoaded(true);
- }
- }
-
- public Tile getTile() {
- return tile;
- }
- };
- }
-
- protected URLConnection loadTileFromOsm(Tile tile) throws IOException {
- URL url;
- url = new URL(tile.getUrl());
- URLConnection urlConn = url.openConnection();
- if (urlConn instanceof HttpURLConnection) {
- prepareHttpUrlConnection((HttpURLConnection)urlConn);
- }
- urlConn.setReadTimeout(30000); // 30 seconds read timeout
- return urlConn;
- }
-
- protected void loadTileMetadata(Tile tile, URLConnection urlConn) {
- String str = urlConn.getHeaderField("X-VE-TILEMETA-CaptureDatesRange");
- if (str != null) {
- tile.putValue("capture-date", str);
- }
- str = urlConn.getHeaderField("X-VE-Tile-Info");
- if (str != null) {
- tile.putValue("tile-info", str);
- }
- }
-
- protected void prepareHttpUrlConnection(HttpURLConnection urlConn) {
- for(Entry<String, String> e : headers.entrySet()) {
- urlConn.setRequestProperty(e.getKey(), e.getValue());
- }
- if(timeoutConnect != 0)
- urlConn.setConnectTimeout(timeoutConnect);
- if(timeoutRead != 0)
- urlConn.setReadTimeout(timeoutRead);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+
+/**
+ * A {@link TileLoader} implementation that loads tiles from OSM.
+ *
+ * @author Jan Peter Stotz
+ */
+public class OsmTileLoader implements TileLoader {
+
+ /**
+ * Holds the HTTP headers. Insert e.g. User-Agent here when default should not be used.
+ */
+ public Map<String, String> headers = new HashMap<>();
+
+ public int timeoutConnect = 0;
+ public int timeoutRead = 0;
+
+ protected TileLoaderListener listener;
+
+ public OsmTileLoader(TileLoaderListener listener) {
+ headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*");
+ this.listener = listener;
+ }
+
+ public TileJob createTileLoaderJob(final Tile tile) {
+ return new TileJob() {
+
+ InputStream input = null;
+
+ public void run() {
+ synchronized (tile) {
+ if ((tile.isLoaded() && !tile.hasError()) || tile.isLoading())
+ return;
+ tile.loaded = false;
+ tile.error = false;
+ tile.loading = true;
+ }
+ try {
+ URLConnection conn = loadTileFromOsm(tile);
+ loadTileMetadata(tile, conn);
+ if ("no-tile".equals(tile.getValue("tile-info"))) {
+ tile.setError("No tile at this zoom level");
+ } else {
+ input = conn.getInputStream();
+ try {
+ tile.loadImage(input);
+ } finally {
+ input.close();
+ input = null;
+ }
+ }
+ tile.setLoaded(true);
+ listener.tileLoadingFinished(tile, true);
+ } catch (Exception e) {
+ tile.setError(e.getMessage());
+ listener.tileLoadingFinished(tile, false);
+ if (input == null) {
+ try {
+ System.err.println("Failed loading " + tile.getUrl() +": "
+ +e.getClass() + ": " + e.getMessage());
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ } finally {
+ tile.loading = false;
+ tile.setLoaded(true);
+ }
+ }
+
+ public Tile getTile() {
+ return tile;
+ }
+ };
+ }
+
+ protected URLConnection loadTileFromOsm(Tile tile) throws IOException {
+ URL url;
+ url = new URL(tile.getUrl());
+ URLConnection urlConn = url.openConnection();
+ if (urlConn instanceof HttpURLConnection) {
+ prepareHttpUrlConnection((HttpURLConnection)urlConn);
+ }
+ urlConn.setReadTimeout(30000); // 30 seconds read timeout
+ return urlConn;
+ }
+
+ protected void loadTileMetadata(Tile tile, URLConnection urlConn) {
+ String str = urlConn.getHeaderField("X-VE-TILEMETA-CaptureDatesRange");
+ if (str != null) {
+ tile.putValue("capture-date", str);
+ }
+ str = urlConn.getHeaderField("X-VE-Tile-Info");
+ if (str != null) {
+ tile.putValue("tile-info", str);
+ }
+ }
+
+ protected void prepareHttpUrlConnection(HttpURLConnection urlConn) {
+ for(Entry<String, String> e : headers.entrySet()) {
+ urlConn.setRequestProperty(e.getKey(), e.getValue());
+ }
+ if(timeoutConnect != 0)
+ urlConn.setConnectTimeout(timeoutConnect);
+ if(timeoutRead != 0)
+ urlConn.setReadTimeout(timeoutRead);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/Tile.java b/src/org/openstreetmap/gui/jmapviewer/Tile.java
index 82ab19e..895b962 100644
--- a/src/org/openstreetmap/gui/jmapviewer/Tile.java
+++ b/src/org/openstreetmap/gui/jmapviewer/Tile.java
@@ -1,334 +1,334 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.geom.AffineTransform;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.imageio.ImageIO;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-
-/**
- * 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 {
-
- /**
- * Hourglass image that is displayed until a map tile has been loaded, except for overlay sources
- */
- public static BufferedImage LOADING_IMAGE;
-
- /**
- * Red cross image that is displayed after a loading error, except for overlay sources
- */
- public static BufferedImage ERROR_IMAGE;
-
- static {
- try {
- LOADING_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/hourglass.png"));
- ERROR_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/error.png"));
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
-
- protected TileSource source;
- protected int xtile;
- protected int ytile;
- protected int zoom;
- protected BufferedImage image;
- protected String key;
- protected boolean loaded = false;
- protected boolean loading = false;
- protected boolean error = false;
- protected String error_message;
-
- /** TileLoader-specific tile metadata */
- protected Map<String, String> metadata;
-
- /**
- * Creates a tile with empty image.
- *
- * @param source Tile source
- * @param xtile X coordinate
- * @param ytile Y coordinate
- * @param zoom Zoom level
- */
- public Tile(TileSource source, int xtile, int ytile, int zoom) {
- this(source, xtile, ytile, zoom, LOADING_IMAGE);
- }
-
- /**
- * Creates a tile with specified image.
- *
- * @param source Tile source
- * @param xtile X coordinate
- * @param ytile Y coordinate
- * @param zoom Zoom level
- * @param image Image content
- */
- public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
- this.source = source;
- this.xtile = xtile;
- this.ytile = ytile;
- this.zoom = zoom;
- this.image = image;
- this.key = getTileKey(source, xtile, ytile, zoom);
- }
-
- /**
- * Tries to get tiles of a lower or higher zoom level (one or two level
- * difference) from cache and use it as a placeholder until the tile has
- * been loaded.
- */
- public void loadPlaceholderFromCache(TileCache cache) {
- BufferedImage tmpImage = new BufferedImage(source.getTileSize(), source.getTileSize(), BufferedImage.TYPE_INT_RGB);
- Graphics2D g = (Graphics2D) tmpImage.getGraphics();
- // g.drawImage(image, 0, 0, null);
- for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
- // first we check if there are already the 2^x tiles
- // of a higher detail level
- int zoom_high = zoom + zoomDiff;
- if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
- int factor = 1 << zoomDiff;
- int xtile_high = xtile << zoomDiff;
- int ytile_high = ytile << zoomDiff;
- double scale = 1.0 / factor;
- g.setTransform(AffineTransform.getScaleInstance(scale, scale));
- int paintedTileCount = 0;
- for (int x = 0; x < factor; x++) {
- for (int y = 0; y < factor; y++) {
- Tile tile = cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
- if (tile != null && tile.isLoaded()) {
- paintedTileCount++;
- tile.paint(g, x * source.getTileSize(), y * source.getTileSize());
- }
- }
- }
- if (paintedTileCount == factor * factor) {
- image = tmpImage;
- return;
- }
- }
-
- int zoom_low = zoom - zoomDiff;
- if (zoom_low >= JMapViewer.MIN_ZOOM) {
- int xtile_low = xtile >> zoomDiff;
- int ytile_low = ytile >> zoomDiff;
- int factor = (1 << zoomDiff);
- double scale = factor;
- AffineTransform at = new AffineTransform();
- int translate_x = (xtile % factor) * source.getTileSize();
- int translate_y = (ytile % factor) * source.getTileSize();
- at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
- g.setTransform(at);
- Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
- if (tile != null && tile.isLoaded()) {
- tile.paint(g, 0, 0);
- image = tmpImage;
- return;
- }
- }
- }
- }
-
- public TileSource getSource() {
- return source;
- }
-
- /**
- * Returns the X coordinate.
- * @return tile number on the x axis of this tile
- */
- public int getXtile() {
- return xtile;
- }
-
- /**
- * Returns the Y coordinate.
- * @return tile number on the y axis of this tile
- */
- public int getYtile() {
- return ytile;
- }
-
- /**
- * Returns the zoom level.
- * @return zoom level of this tile
- */
- public int getZoom() {
- return zoom;
- }
-
- public BufferedImage getImage() {
- return image;
- }
-
- public void setImage(BufferedImage image) {
- this.image = image;
- }
-
- public void loadImage(InputStream input) throws IOException {
- image = ImageIO.read(input);
- }
-
- /**
- * @return key that identifies a tile
- */
- public String getKey() {
- return key;
- }
-
- public boolean isLoaded() {
- return loaded;
- }
-
- public boolean isLoading() {
- return loading;
- }
-
- public void setLoaded(boolean loaded) {
- this.loaded = loaded;
- }
-
- public String getUrl() throws IOException {
- return source.getTileUrl(zoom, xtile, ytile);
- }
-
- /**
- * Paints the tile-image on the {@link Graphics} <code>g</code> at the
- * position <code>x</code>/<code>y</code>.
- *
- * @param g the Graphics object
- * @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;
- g.drawImage(image, x, y, null);
- }
-
- @Override
- public String toString() {
- return "Tile " + key;
- }
-
- /**
- * Note that the hash code does not include the {@link #source}.
- * Therefore a hash based collection can only contain tiles
- * of one {@link #source}.
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + xtile;
- result = prime * result + ytile;
- result = prime * result + zoom;
- return result;
- }
-
- /**
- * Compares this object with <code>obj</code> based on
- * the fields {@link #xtile}, {@link #ytile} and
- * {@link #zoom}.
- * The {@link #source} field is ignored.
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Tile other = (Tile) obj;
- if (xtile != other.xtile)
- return false;
- if (ytile != other.ytile)
- return false;
- if (zoom != other.zoom)
- return false;
- return true;
- }
-
- public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
- return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
- }
-
- public String getStatus() {
- if (this.error)
- return "error";
- if (this.loaded)
- return "loaded";
- if (this.loading)
- return "loading";
- return "new";
- }
-
- public boolean hasError() {
- return error;
- }
-
- public String getErrorMessage() {
- return error_message;
- }
-
- public void setError(String message) {
- error = true;
- setImage(ERROR_IMAGE);
- error_message = message;
- }
-
- /**
- * Puts the given key/value pair to the metadata of the tile.
- * If value is null, the (possibly existing) key/value pair is removed from
- * the meta data.
- *
- * @param key Key
- * @param value Value
- */
- public void putValue(String key, String value) {
- if (value == null || value.isEmpty()) {
- if (metadata != null) {
- metadata.remove(key);
- }
- return;
- }
- if (metadata == null) {
- metadata = new HashMap<>();
- }
- metadata.put(key, value);
- }
-
- public String getValue(String key) {
- if (metadata == null) return null;
- return metadata.get(key);
- }
-
- public Map<String,String> getMetadata() {
- return metadata;
- }
-
- public void initLoading() {
- loaded = false;
- error = false;
- loading = true;
- }
-
- public void finishLoading() {
- loading = false;
- loaded = true;
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+/**
+ * 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 {
+
+ /**
+ * Hourglass image that is displayed until a map tile has been loaded, except for overlay sources
+ */
+ public static BufferedImage LOADING_IMAGE;
+
+ /**
+ * Red cross image that is displayed after a loading error, except for overlay sources
+ */
+ public static BufferedImage ERROR_IMAGE;
+
+ static {
+ try {
+ LOADING_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/hourglass.png"));
+ ERROR_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/error.png"));
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ protected TileSource source;
+ protected int xtile;
+ protected int ytile;
+ protected int zoom;
+ protected BufferedImage image;
+ protected String key;
+ protected boolean loaded = false;
+ protected boolean loading = false;
+ protected boolean error = false;
+ protected String error_message;
+
+ /** TileLoader-specific tile metadata */
+ protected Map<String, String> metadata;
+
+ /**
+ * Creates a tile with empty image.
+ *
+ * @param source Tile source
+ * @param xtile X coordinate
+ * @param ytile Y coordinate
+ * @param zoom Zoom level
+ */
+ public Tile(TileSource source, int xtile, int ytile, int zoom) {
+ this(source, xtile, ytile, zoom, LOADING_IMAGE);
+ }
+
+ /**
+ * Creates a tile with specified image.
+ *
+ * @param source Tile source
+ * @param xtile X coordinate
+ * @param ytile Y coordinate
+ * @param zoom Zoom level
+ * @param image Image content
+ */
+ public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
+ this.source = source;
+ this.xtile = xtile;
+ this.ytile = ytile;
+ this.zoom = zoom;
+ this.image = image;
+ this.key = getTileKey(source, xtile, ytile, zoom);
+ }
+
+ /**
+ * Tries to get tiles of a lower or higher zoom level (one or two level
+ * difference) from cache and use it as a placeholder until the tile has
+ * been loaded.
+ */
+ public void loadPlaceholderFromCache(TileCache cache) {
+ BufferedImage tmpImage = new BufferedImage(source.getTileSize(), source.getTileSize(), BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = (Graphics2D) tmpImage.getGraphics();
+ // g.drawImage(image, 0, 0, null);
+ for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
+ // first we check if there are already the 2^x tiles
+ // of a higher detail level
+ int zoom_high = zoom + zoomDiff;
+ if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
+ int factor = 1 << zoomDiff;
+ int xtile_high = xtile << zoomDiff;
+ int ytile_high = ytile << zoomDiff;
+ double scale = 1.0 / factor;
+ g.setTransform(AffineTransform.getScaleInstance(scale, scale));
+ int paintedTileCount = 0;
+ for (int x = 0; x < factor; x++) {
+ for (int y = 0; y < factor; y++) {
+ Tile tile = cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
+ if (tile != null && tile.isLoaded()) {
+ paintedTileCount++;
+ tile.paint(g, x * source.getTileSize(), y * source.getTileSize());
+ }
+ }
+ }
+ if (paintedTileCount == factor * factor) {
+ image = tmpImage;
+ return;
+ }
+ }
+
+ int zoom_low = zoom - zoomDiff;
+ if (zoom_low >= JMapViewer.MIN_ZOOM) {
+ int xtile_low = xtile >> zoomDiff;
+ int ytile_low = ytile >> zoomDiff;
+ int factor = (1 << zoomDiff);
+ double scale = factor;
+ AffineTransform at = new AffineTransform();
+ int translate_x = (xtile % factor) * source.getTileSize();
+ int translate_y = (ytile % factor) * source.getTileSize();
+ at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
+ g.setTransform(at);
+ Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
+ if (tile != null && tile.isLoaded()) {
+ tile.paint(g, 0, 0);
+ image = tmpImage;
+ return;
+ }
+ }
+ }
+ }
+
+ public TileSource getSource() {
+ return source;
+ }
+
+ /**
+ * Returns the X coordinate.
+ * @return tile number on the x axis of this tile
+ */
+ public int getXtile() {
+ return xtile;
+ }
+
+ /**
+ * Returns the Y coordinate.
+ * @return tile number on the y axis of this tile
+ */
+ public int getYtile() {
+ return ytile;
+ }
+
+ /**
+ * Returns the zoom level.
+ * @return zoom level of this tile
+ */
+ public int getZoom() {
+ return zoom;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+ public void setImage(BufferedImage image) {
+ this.image = image;
+ }
+
+ public void loadImage(InputStream input) throws IOException {
+ image = ImageIO.read(input);
+ }
+
+ /**
+ * @return key that identifies a tile
+ */
+ public String getKey() {
+ return key;
+ }
+
+ public boolean isLoaded() {
+ return loaded;
+ }
+
+ public boolean isLoading() {
+ return loading;
+ }
+
+ public void setLoaded(boolean loaded) {
+ this.loaded = loaded;
+ }
+
+ public String getUrl() throws IOException {
+ return source.getTileUrl(zoom, xtile, ytile);
+ }
+
+ /**
+ * Paints the tile-image on the {@link Graphics} <code>g</code> at the
+ * position <code>x</code>/<code>y</code>.
+ *
+ * @param g the Graphics object
+ * @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;
+ g.drawImage(image, x, y, null);
+ }
+
+ @Override
+ public String toString() {
+ return "Tile " + key;
+ }
+
+ /**
+ * Note that the hash code does not include the {@link #source}.
+ * Therefore a hash based collection can only contain tiles
+ * of one {@link #source}.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + xtile;
+ result = prime * result + ytile;
+ result = prime * result + zoom;
+ return result;
+ }
+
+ /**
+ * Compares this object with <code>obj</code> based on
+ * the fields {@link #xtile}, {@link #ytile} and
+ * {@link #zoom}.
+ * The {@link #source} field is ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Tile other = (Tile) obj;
+ if (xtile != other.xtile)
+ return false;
+ if (ytile != other.ytile)
+ return false;
+ if (zoom != other.zoom)
+ return false;
+ return true;
+ }
+
+ public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
+ return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
+ }
+
+ public String getStatus() {
+ if (this.error)
+ return "error";
+ if (this.loaded)
+ return "loaded";
+ if (this.loading)
+ return "loading";
+ return "new";
+ }
+
+ public boolean hasError() {
+ return error;
+ }
+
+ public String getErrorMessage() {
+ return error_message;
+ }
+
+ public void setError(String message) {
+ error = true;
+ setImage(ERROR_IMAGE);
+ error_message = message;
+ }
+
+ /**
+ * Puts the given key/value pair to the metadata of the tile.
+ * If value is null, the (possibly existing) key/value pair is removed from
+ * the meta data.
+ *
+ * @param key Key
+ * @param value Value
+ */
+ public void putValue(String key, String value) {
+ if (value == null || value.isEmpty()) {
+ if (metadata != null) {
+ metadata.remove(key);
+ }
+ return;
+ }
+ if (metadata == null) {
+ metadata = new HashMap<>();
+ }
+ metadata.put(key, value);
+ }
+
+ public String getValue(String key) {
+ if (metadata == null) return null;
+ return metadata.get(key);
+ }
+
+ public Map<String,String> getMetadata() {
+ return metadata;
+ }
+
+ public void initLoading() {
+ loaded = false;
+ error = false;
+ loading = true;
+ }
+
+ public void finishLoading() {
+ loading = false;
+ loaded = true;
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/TileController.java b/src/org/openstreetmap/gui/jmapviewer/TileController.java
index d159ee1..ffde8ef 100644
--- a/src/org/openstreetmap/gui/jmapviewer/TileController.java
+++ b/src/org/openstreetmap/gui/jmapviewer/TileController.java
@@ -1,87 +1,87 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer;
-
-import org.openstreetmap.gui.jmapviewer.JobDispatcher.JobThread;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-
-public class TileController {
- protected TileLoader tileLoader;
- protected TileCache tileCache;
- protected TileSource tileSource;
-
- JobDispatcher jobDispatcher;
-
- public TileController(TileSource source, TileCache tileCache, TileLoaderListener listener) {
- this.tileSource = source;
- this.tileLoader = new OsmTileLoader(listener);
- this.tileCache = tileCache;
- this.jobDispatcher = JobDispatcher.getInstance();
- }
-
- /**
- * 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 the X position of the tile
- * @param tiley the Y position of the tile
- * @param zoom the zoom level of the tile
- * @return specified tile from the cache or <code>null</code> if the tile
- * was not found in the cache.
- */
- public Tile getTile(int tilex, int tiley, int zoom) {
- int max = (1 << zoom);
- if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
- return null;
- Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
- if (tile == null) {
- tile = new Tile(tileSource, tilex, tiley, zoom);
- tileCache.addTile(tile);
- tile.loadPlaceholderFromCache(tileCache);
- }
- if (tile.error) {
- tile.loadPlaceholderFromCache(tileCache);
- }
- if (!tile.isLoaded()) {
- jobDispatcher.addJob(tileLoader.createTileLoaderJob(tile));
- }
- return tile;
- }
-
- public TileCache getTileCache() {
- return tileCache;
- }
-
- public void setTileCache(TileCache tileCache) {
- this.tileCache = tileCache;
- }
-
- public TileLoader getTileLoader() {
- return tileLoader;
- }
-
- public void setTileLoader(TileLoader tileLoader) {
- this.tileLoader = tileLoader;
- }
-
- public TileSource getTileLayerSource() {
- return tileSource;
- }
-
- public TileSource getTileSource() {
- return tileSource;
- }
-
- public void setTileSource(TileSource tileSource) {
- this.tileSource = tileSource;
- }
-
- /**
- * Removes all jobs from the queue that are currently not being processed.
- */
- public void cancelOutstandingJobs() {
- jobDispatcher.cancelOutstandingJobs();
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import org.openstreetmap.gui.jmapviewer.JobDispatcher.JobThread;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+public class TileController {
+ protected TileLoader tileLoader;
+ protected TileCache tileCache;
+ protected TileSource tileSource;
+
+ JobDispatcher jobDispatcher;
+
+ public TileController(TileSource source, TileCache tileCache, TileLoaderListener listener) {
+ this.tileSource = source;
+ this.tileLoader = new OsmTileLoader(listener);
+ this.tileCache = tileCache;
+ this.jobDispatcher = JobDispatcher.getInstance();
+ }
+
+ /**
+ * 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 the X position of the tile
+ * @param tiley the Y position of the tile
+ * @param zoom the zoom level of the tile
+ * @return specified tile from the cache or <code>null</code> if the tile
+ * was not found in the cache.
+ */
+ public Tile getTile(int tilex, int tiley, int zoom) {
+ int max = (1 << zoom);
+ if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
+ return null;
+ Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
+ if (tile == null) {
+ tile = new Tile(tileSource, tilex, tiley, zoom);
+ tileCache.addTile(tile);
+ tile.loadPlaceholderFromCache(tileCache);
+ }
+ if (tile.error) {
+ tile.loadPlaceholderFromCache(tileCache);
+ }
+ if (!tile.isLoaded()) {
+ jobDispatcher.addJob(tileLoader.createTileLoaderJob(tile));
+ }
+ return tile;
+ }
+
+ public TileCache getTileCache() {
+ return tileCache;
+ }
+
+ public void setTileCache(TileCache tileCache) {
+ this.tileCache = tileCache;
+ }
+
+ public TileLoader getTileLoader() {
+ return tileLoader;
+ }
+
+ public void setTileLoader(TileLoader tileLoader) {
+ this.tileLoader = tileLoader;
+ }
+
+ public TileSource getTileLayerSource() {
+ return tileSource;
+ }
+
+ public TileSource getTileSource() {
+ return tileSource;
+ }
+
+ public void setTileSource(TileSource tileSource) {
+ this.tileSource = tileSource;
+ }
+
+ /**
+ * Removes all jobs from the queue that are currently not being processed.
+ */
+ public void cancelOutstandingJobs() {
+ jobDispatcher.cancelOutstandingJobs();
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/MapMarker.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/MapMarker.java
index 29ae5c2..2a1968f 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/MapMarker.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/MapMarker.java
@@ -1,54 +1,54 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import java.awt.Graphics;
-import java.awt.Point;
-
-import org.openstreetmap.gui.jmapviewer.Coordinate;
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-
-/**
- * Interface to be implemented by all one dimensional elements that can be displayed on the map.
- *
- * @author Jan Peter Stotz
- * @see JMapViewer#addMapMarker(MapMarker)
- * @see JMapViewer#getMapMarkerList()
- */
-public interface MapMarker extends MapObject, ICoordinate{
-
- public static enum STYLE {FIXED, VARIABLE}
-
- /**
- * @return Latitude and Longitude of the map marker position
- */
- public Coordinate getCoordinate();
- /**
- * @return Latitude of the map marker position
- */
- public double getLat();
-
- /**
- * @return Longitude of the map marker position
- */
- public double getLon();
-
- /**
- * @return Radius of the map marker position
- */
- public double getRadius();
-
- /**
- * @return Style of the map marker
- */
- public STYLE getMarkerStyle();
-
- /**
- * Paints the map marker on the map. The <code>position</code> specifies the
- * coordinates within <code>g</code>
- *
- * @param g
- * @param position
- * @param radio
- */
- public void paint(Graphics g, Point position, int radio);
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+
+/**
+ * Interface to be implemented by all one dimensional elements that can be displayed on the map.
+ *
+ * @author Jan Peter Stotz
+ * @see JMapViewer#addMapMarker(MapMarker)
+ * @see JMapViewer#getMapMarkerList()
+ */
+public interface MapMarker extends MapObject, ICoordinate{
+
+ public static enum STYLE {FIXED, VARIABLE}
+
+ /**
+ * @return Latitude and Longitude of the map marker position
+ */
+ public Coordinate getCoordinate();
+ /**
+ * @return Latitude of the map marker position
+ */
+ public double getLat();
+
+ /**
+ * @return Longitude of the map marker position
+ */
+ public double getLon();
+
+ /**
+ * @return Radius of the map marker position
+ */
+ public double getRadius();
+
+ /**
+ * @return Style of the map marker
+ */
+ public STYLE getMarkerStyle();
+
+ /**
+ * Paints the map marker on the map. The <code>position</code> specifies the
+ * coordinates within <code>g</code>
+ *
+ * @param g
+ * @param position
+ * @param radio
+ */
+ public void paint(Graphics g, Point position, int radio);
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/MapRectangle.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/MapRectangle.java
index f76197b..0d11c11 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/MapRectangle.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/MapRectangle.java
@@ -1,38 +1,38 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import java.awt.Graphics;
-import java.awt.Point;
-
-import org.openstreetmap.gui.jmapviewer.Coordinate;
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-
-/**
- * Interface to be implemented by rectangles that can be displayed on the map.
- *
- * @author Stefan Zeller
- * @see JMapViewer#addMapRectangle(MapRectangle)
- * @see JMapViewer#getMapRectangleList()
- */
-public interface MapRectangle extends MapObject{
-
- /**
- * @return Latitude/Longitude of top left of rectangle
- */
- public Coordinate getTopLeft();
-
- /**
- * @return Latitude/Longitude of bottom right of rectangle
- */
- public Coordinate getBottomRight();
-
- /**
- * Paints the map rectangle on the map. The <code>topLeft</code> and
- * <code>bottomRight</code> are specifying the coordinates within <code>g</code>
- *
- * @param g graphics structure for painting
- * @param topLeft lop left edge of painting region
- * @param bottomRight bottom right edge of painting region
- */
- public void paint(Graphics g, Point topLeft, Point bottomRight);
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import java.awt.Graphics;
+import java.awt.Point;
+
+import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+
+/**
+ * Interface to be implemented by rectangles that can be displayed on the map.
+ *
+ * @author Stefan Zeller
+ * @see JMapViewer#addMapRectangle(MapRectangle)
+ * @see JMapViewer#getMapRectangleList()
+ */
+public interface MapRectangle extends MapObject{
+
+ /**
+ * @return Latitude/Longitude of top left of rectangle
+ */
+ public Coordinate getTopLeft();
+
+ /**
+ * @return Latitude/Longitude of bottom right of rectangle
+ */
+ public Coordinate getBottomRight();
+
+ /**
+ * Paints the map rectangle on the map. The <code>topLeft</code> and
+ * <code>bottomRight</code> are specifying the coordinates within <code>g</code>
+ *
+ * @param g graphics structure for painting
+ * @param topLeft lop left edge of painting region
+ * @param bottomRight bottom right edge of painting region
+ */
+ public void paint(Graphics g, Point topLeft, Point bottomRight);
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java
index cda5d67..209d2fa 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java
@@ -1,50 +1,50 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-import org.openstreetmap.gui.jmapviewer.Tile;
-
-/**
- * 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 source
- * the tile source
- * @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(TileSource source, int x, int y, int z);
-
- /**
- * Adds a tile to the cache. How long after adding a tile can be retrieved
- * via {@link #getTile(TileSource, int, int, int)} is unspecified and depends on the
- * implementation.
- *
- * @param tile the tile to be added
- */
- public void addTile(Tile tile);
-
- /**
- * @return the number of tiles hold by the cache
- */
- public int getTileCount();
-
- /**
- * Clears the cache deleting all tiles from memory.
- */
- public void clear();
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+import org.openstreetmap.gui.jmapviewer.Tile;
+
+/**
+ * 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 source
+ * the tile source
+ * @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(TileSource source, int x, int y, int z);
+
+ /**
+ * Adds a tile to the cache. How long after adding a tile can be retrieved
+ * via {@link #getTile(TileSource, int, int, int)} is unspecified and depends on the
+ * implementation.
+ *
+ * @param tile the tile to be added
+ */
+ public void addTile(Tile tile);
+
+ /**
+ * @return the number of tiles hold by the cache
+ */
+ public int getTileCount();
+
+ /**
+ * Clears the cache deleting all tiles from memory.
+ */
+ public void clear();
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileJob.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileJob.java
index 45fdc54..5a7cfb1 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileJob.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileJob.java
@@ -1,20 +1,20 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import org.openstreetmap.gui.jmapviewer.Tile;
-
-/**
- * Interface for implementing a tile loading job. Tiles are usually loaded via HTTP
- * or from a file.
- *
- * @author Dirk Stöcker
- */
-public interface TileJob extends Runnable {
-
- /**
- * Function to return the tile associated with the job
- *
- * @return {@link Tile} to be handled
- */
- public Tile getTile();
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import org.openstreetmap.gui.jmapviewer.Tile;
+
+/**
+ * Interface for implementing a tile loading job. Tiles are usually loaded via HTTP
+ * or from a file.
+ *
+ * @author Dirk Stöcker
+ */
+public interface TileJob extends Runnable {
+
+ /**
+ * Function to return the tile associated with the job
+ *
+ * @return {@link Tile} to be handled
+ */
+ public Tile getTile();
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java
index c07619a..723d707 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java
@@ -1,23 +1,23 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import org.openstreetmap.gui.jmapviewer.Tile;
-
-/**
- * Interface for implementing a tile loader. Tiles are usually loaded via HTTP
- * or from a file.
- *
- * @author Jan Peter Stotz
- */
-public interface TileLoader {
-
- /**
- * A typical implementation of this function should create and return a
- * new {@link TileJob} instance that performs the load action.
- *
- * @param tile the tile to be loaded
- * @return {@link TileJob} implementation that performs the desired load
- * action.
- */
- public TileJob createTileLoaderJob(Tile tile);
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import org.openstreetmap.gui.jmapviewer.Tile;
+
+/**
+ * Interface for implementing a tile loader. Tiles are usually loaded via HTTP
+ * or from a file.
+ *
+ * @author Jan Peter Stotz
+ */
+public interface TileLoader {
+
+ /**
+ * A typical implementation of this function should create and return a
+ * new {@link TileJob} instance that performs the load action.
+ *
+ * @param tile the tile to be loaded
+ * @return {@link TileJob} implementation that performs the desired load
+ * action.
+ */
+ public TileJob createTileLoaderJob(Tile tile);
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java
index ecd566e..133c27a 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java
@@ -1,16 +1,16 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import org.openstreetmap.gui.jmapviewer.Tile;
-
-public interface TileLoaderListener {
-
- /**
- * Will be called if a new {@link Tile} has been loaded successfully.
- * Loaded can mean downloaded or loaded from file cache.
- *
- * @param tile The tile
- * @param success {@code true} if the tile has been loaded successfully, {@code false} otherwise
- */
- public void tileLoadingFinished(Tile tile, boolean success);
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import org.openstreetmap.gui.jmapviewer.Tile;
+
+public interface TileLoaderListener {
+
+ /**
+ * Will be called if a new {@link Tile} has been loaded successfully.
+ * Loaded can mean downloaded or loaded from file cache.
+ *
+ * @param tile The tile
+ * @param success {@code true} if the tile has been loaded successfully, {@code false} otherwise
+ */
+ public void tileLoadingFinished(Tile tile, boolean success);
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
index b8680e8..ab003a6 100644
--- a/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
@@ -1,158 +1,158 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-import java.io.IOException;
-
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-
-/**
- *
- * @author Jan Peter Stotz
- */
-public interface TileSource extends Attributed {
-
- /**
- * Specifies the different mechanisms for detecting updated tiles
- * respectively only download newer tiles than those stored locally.
- *
- * <ul>
- * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
- * and <b>supports</b> conditional download via <code>If-None-Match</code>
- * header entry.</li>
- * <li>{@link #ETag} Server provides ETag header entry for all tiles but
- * <b>does not support</b> conditional download via
- * <code>If-None-Match</code> header entry.</li>
- * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
- * for all tiles and <b>supports</b> conditional download via
- * <code>If-Modified-Since</code> header entry.</li>
- * <li>{@link #LastModified} Server provides Last-Modified header entry for
- * all tiles but <b>does not support</b> conditional download via
- * <code>If-Modified-Since</code> header entry.</li>
- * <li>{@link #None} The server does not support any of the listed
- * mechanisms.</li>
- * </ul>
- *
- */
- public enum TileUpdate {
- IfNoneMatch, ETag, IfModifiedSince, LastModified, None
- }
-
- /**
- * Specifies the maximum zoom value. The number of zoom levels is [0..
- * {@link #getMaxZoom()}].
- *
- * @return maximum zoom value that has to be smaller or equal to
- * {@link JMapViewer#MAX_ZOOM}
- */
- int getMaxZoom();
-
- /**
- * Specifies the minimum zoom value. This value is usually 0.
- * Only for maps that cover a certain region up to a limited zoom level
- * this method should return a value different than 0.
- *
- * @return minimum zoom value - usually 0
- */
- int getMinZoom();
-
- /**
- * @return The supported tile update mechanism
- * @see TileUpdate
- */
- TileUpdate getTileUpdate();
-
- /**
- * A tile layer name as displayed to the user.
- *
- * @return Name of the tile layer
- */
- String getName();
-
- /**
- * A unique id for this tile source.
- *
- * Unlike the name it has to be unique and has to consist only of characters
- * valid for filenames.
- *
- * @return the id
- */
- String getId();
-
- /**
- * Constructs the tile url.
- *
- * @param zoom
- * @param tilex
- * @param tiley
- * @return fully qualified url for downloading the specified tile image
- */
- String getTileUrl(int zoom, int tilex, int tiley) throws IOException;
-
- /**
- * Specifies the tile image type. For tiles rendered by Mapnik or
- * Osmarenderer this is usually <code>"png"</code>.
- *
- * @return file extension of the tile image type
- */
- String getTileType();
-
- /**
- * Specifies how large each tile is.
- * @return The size of a single tile in pixels.
- */
- int getTileSize();
-
- /**
- * Gets the distance using Spherical law of cosines.
- * @return the distance, m.
- */
- double getDistance(double la1, double lo1, double la2, double lo2);
-
- /**
- * Transform longitude to pixelspace.
- * @return [0..2^Zoomlevel*TILE_SIZE[
- */
- int LonToX(double aLongitude, int aZoomlevel);
-
- /**
- * Transforms latitude to pixelspace.
- * @return [0..2^Zoomlevel*TILE_SIZE[
- */
- int LatToY(double aLat, int aZoomlevel);
-
- /**
- * Transforms pixel coordinate X to longitude
- * @return ]-180..180[
- */
- double XToLon(int aX, int aZoomlevel);
-
- /**
- * Transforms pixel coordinate Y to latitude.
- * @return [MIN_LAT..MAX_LAT]
- */
- double YToLat(int aY, int aZoomlevel);
-
- /**
- * Transforms longitude to X tile coordinate.
- * @return [0..2^Zoomlevel[
- */
- double lonToTileX(double lon, int zoom);
-
- /**
- * Transforms latitude to Y tile coordinate.
- * @return [0..2^Zoomlevel[
- */
- double latToTileY(double lat, int zoom);
-
- /**
- * Transforms tile X coordinate to longitude.
- * @return ]-180..180[
- */
- double tileXToLon(int x, int zoom);
-
- /**
- * Transforms tile Y coordinate to latitude.
- * @return [MIN_LAT..MAX_LAT]
- */
- double tileYToLat(int y, int zoom);
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import java.io.IOException;
+
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+
+/**
+ *
+ * @author Jan Peter Stotz
+ */
+public interface TileSource extends Attributed {
+
+ /**
+ * Specifies the different mechanisms for detecting updated tiles
+ * respectively only download newer tiles than those stored locally.
+ *
+ * <ul>
+ * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
+ * and <b>supports</b> conditional download via <code>If-None-Match</code>
+ * header entry.</li>
+ * <li>{@link #ETag} Server provides ETag header entry for all tiles but
+ * <b>does not support</b> conditional download via
+ * <code>If-None-Match</code> header entry.</li>
+ * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
+ * for all tiles and <b>supports</b> conditional download via
+ * <code>If-Modified-Since</code> header entry.</li>
+ * <li>{@link #LastModified} Server provides Last-Modified header entry for
+ * all tiles but <b>does not support</b> conditional download via
+ * <code>If-Modified-Since</code> header entry.</li>
+ * <li>{@link #None} The server does not support any of the listed
+ * mechanisms.</li>
+ * </ul>
+ *
+ */
+ public enum TileUpdate {
+ IfNoneMatch, ETag, IfModifiedSince, LastModified, None
+ }
+
+ /**
+ * Specifies the maximum zoom value. The number of zoom levels is [0..
+ * {@link #getMaxZoom()}].
+ *
+ * @return maximum zoom value that has to be smaller or equal to
+ * {@link JMapViewer#MAX_ZOOM}
+ */
+ int getMaxZoom();
+
+ /**
+ * Specifies the minimum zoom value. This value is usually 0.
+ * Only for maps that cover a certain region up to a limited zoom level
+ * this method should return a value different than 0.
+ *
+ * @return minimum zoom value - usually 0
+ */
+ int getMinZoom();
+
+ /**
+ * @return The supported tile update mechanism
+ * @see TileUpdate
+ */
+ TileUpdate getTileUpdate();
+
+ /**
+ * A tile layer name as displayed to the user.
+ *
+ * @return Name of the tile layer
+ */
+ String getName();
+
+ /**
+ * A unique id for this tile source.
+ *
+ * Unlike the name it has to be unique and has to consist only of characters
+ * valid for filenames.
+ *
+ * @return the id
+ */
+ String getId();
+
+ /**
+ * Constructs the tile url.
+ *
+ * @param zoom
+ * @param tilex
+ * @param tiley
+ * @return fully qualified url for downloading the specified tile image
+ */
+ String getTileUrl(int zoom, int tilex, int tiley) throws IOException;
+
+ /**
+ * Specifies the tile image type. For tiles rendered by Mapnik or
+ * Osmarenderer this is usually <code>"png"</code>.
+ *
+ * @return file extension of the tile image type
+ */
+ String getTileType();
+
+ /**
+ * Specifies how large each tile is.
+ * @return The size of a single tile in pixels.
+ */
+ int getTileSize();
+
+ /**
+ * Gets the distance using Spherical law of cosines.
+ * @return the distance, m.
+ */
+ double getDistance(double la1, double lo1, double la2, double lo2);
+
+ /**
+ * Transform longitude to pixelspace.
+ * @return [0..2^Zoomlevel*TILE_SIZE[
+ */
+ int LonToX(double aLongitude, int aZoomlevel);
+
+ /**
+ * Transforms latitude to pixelspace.
+ * @return [0..2^Zoomlevel*TILE_SIZE[
+ */
+ int LatToY(double aLat, int aZoomlevel);
+
+ /**
+ * Transforms pixel coordinate X to longitude
+ * @return ]-180..180[
+ */
+ double XToLon(int aX, int aZoomlevel);
+
+ /**
+ * Transforms pixel coordinate Y to latitude.
+ * @return [MIN_LAT..MAX_LAT]
+ */
+ double YToLat(int aY, int aZoomlevel);
+
+ /**
+ * Transforms longitude to X tile coordinate.
+ * @return [0..2^Zoomlevel[
+ */
+ double lonToTileX(double lon, int zoom);
+
+ /**
+ * Transforms latitude to Y tile coordinate.
+ * @return [0..2^Zoomlevel[
+ */
+ double latToTileY(double lat, int zoom);
+
+ /**
+ * Transforms tile X coordinate to longitude.
+ * @return ]-180..180[
+ */
+ double tileXToLon(int x, int zoom);
+
+ /**
+ * Transforms tile Y coordinate to latitude.
+ * @return [MIN_LAT..MAX_LAT]
+ */
+ double tileYToLat(int y, int zoom);
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
index e3cfd19..2ec140b 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
@@ -1,125 +1,125 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-import java.io.IOException;
-
-import org.openstreetmap.gui.jmapviewer.OsmMercator;
-
-public abstract class AbstractTMSTileSource extends AbstractTileSource {
-
- protected String name;
- protected String baseUrl;
- protected String id;
-
- public AbstractTMSTileSource(String name, String base_url, String id) {
- this.name = name;
- this.baseUrl = base_url;
- if(baseUrl.endsWith("/")) {
- baseUrl = baseUrl.substring(0,baseUrl.length()-1);
- }
- this.id = id;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- @Override
- public int getMaxZoom() {
- return 21;
- }
-
- @Override
- public int getMinZoom() {
- return 0;
- }
-
- public String getExtension() {
- return "png";
- }
-
- /**
- * @throws IOException when subclass cannot return the tile URL
- */
- public String getTilePath(int zoom, int tilex, int tiley) throws IOException {
- return "/" + zoom + "/" + tilex + "/" + tiley + "." + getExtension();
- }
-
- public String getBaseUrl() {
- return this.baseUrl;
- }
-
- @Override
- public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
- return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
- }
-
- @Override
- public String toString() {
- return getName();
- }
-
- @Override
- public String getTileType() {
- return "png";
- }
-
- /*
- * Most tilesources use OsmMercator projection.
- */
- @Override
- public int getTileSize() {
- return OsmMercator.TILE_SIZE;
- }
-
- @Override
- public double getDistance(double lat1, double lon1, double lat2, double lon2) {
- return OsmMercator.getDistance(lat1, lon1, lat2, lon2);
- }
-
- @Override
- public int LonToX(double lon, int zoom) {
- return (int )OsmMercator.LonToX(lon, zoom);
- }
-
- @Override
- public int LatToY(double lat, int zoom) {
- return (int )OsmMercator.LatToY(lat, zoom);
- }
-
- @Override
- public double XToLon(int x, int zoom) {
- return OsmMercator.XToLon(x, zoom);
- }
-
- @Override
- public double YToLat(int y, int zoom) {
- return OsmMercator.YToLat(y, zoom);
- }
-
- @Override
- public double latToTileY(double lat, int zoom) {
- return OsmMercator.LatToY(lat, zoom) / OsmMercator.TILE_SIZE;
- }
-
- @Override
- public double lonToTileX(double lon, int zoom) {
- return OsmMercator.LonToX(lon, zoom) / OsmMercator.TILE_SIZE;
- }
-
- @Override
- public double tileYToLat(int y, int zoom) {
- return OsmMercator.YToLat(y * OsmMercator.TILE_SIZE, zoom);
- }
-
- @Override
- public double tileXToLon(int x, int zoom) {
- return OsmMercator.XToLon(x * OsmMercator.TILE_SIZE, zoom);
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+import java.io.IOException;
+
+import org.openstreetmap.gui.jmapviewer.OsmMercator;
+
+public abstract class AbstractTMSTileSource extends AbstractTileSource {
+
+ protected String name;
+ protected String baseUrl;
+ protected String id;
+
+ public AbstractTMSTileSource(String name, String base_url, String id) {
+ this.name = name;
+ this.baseUrl = base_url;
+ if(baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0,baseUrl.length()-1);
+ }
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public int getMaxZoom() {
+ return 21;
+ }
+
+ @Override
+ public int getMinZoom() {
+ return 0;
+ }
+
+ public String getExtension() {
+ return "png";
+ }
+
+ /**
+ * @throws IOException when subclass cannot return the tile URL
+ */
+ public String getTilePath(int zoom, int tilex, int tiley) throws IOException {
+ return "/" + zoom + "/" + tilex + "/" + tiley + "." + getExtension();
+ }
+
+ public String getBaseUrl() {
+ return this.baseUrl;
+ }
+
+ @Override
+ public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
+ return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ @Override
+ public String getTileType() {
+ return "png";
+ }
+
+ /*
+ * Most tilesources use OsmMercator projection.
+ */
+ @Override
+ public int getTileSize() {
+ return OsmMercator.TILE_SIZE;
+ }
+
+ @Override
+ public double getDistance(double lat1, double lon1, double lat2, double lon2) {
+ return OsmMercator.getDistance(lat1, lon1, lat2, lon2);
+ }
+
+ @Override
+ public int LonToX(double lon, int zoom) {
+ return (int )OsmMercator.LonToX(lon, zoom);
+ }
+
+ @Override
+ public int LatToY(double lat, int zoom) {
+ return (int )OsmMercator.LatToY(lat, zoom);
+ }
+
+ @Override
+ public double XToLon(int x, int zoom) {
+ return OsmMercator.XToLon(x, zoom);
+ }
+
+ @Override
+ public double YToLat(int y, int zoom) {
+ return OsmMercator.YToLat(y, zoom);
+ }
+
+ @Override
+ public double latToTileY(double lat, int zoom) {
+ return OsmMercator.LatToY(lat, zoom) / OsmMercator.TILE_SIZE;
+ }
+
+ @Override
+ public double lonToTileX(double lon, int zoom) {
+ return OsmMercator.LonToX(lon, zoom) / OsmMercator.TILE_SIZE;
+ }
+
+ @Override
+ public double tileYToLat(int y, int zoom) {
+ return OsmMercator.YToLat(y * OsmMercator.TILE_SIZE, zoom);
+ }
+
+ @Override
+ public double tileXToLon(int x, int zoom) {
+ return OsmMercator.XToLon(x * OsmMercator.TILE_SIZE, zoom);
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
index f8723b6..e71d093 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
@@ -1,305 +1,305 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-import java.awt.Image;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.regex.Pattern;
-
-import javax.imageio.ImageIO;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpression;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-import org.openstreetmap.gui.jmapviewer.Coordinate;
-import org.openstreetmap.gui.jmapviewer.JMapViewer;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-public class BingAerialTileSource extends AbstractTMSTileSource {
-
- private static String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
- private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
- private static String imageUrlTemplate;
- private static Integer imageryZoomMax;
- private static String[] subdomains;
-
- private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain\\}");
- private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
- private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
- private String brandLogoUri = null;
-
- /**
- * Constructs a new {@code BingAerialTileSource}.
- */
- public BingAerialTileSource() {
- this("Bing");
- }
-
- /**
- * Constructs a new {@code BingAerialTileSource}.
- */
- public BingAerialTileSource(String id) {
- super("Bing Aerial Maps", "http://example.com/", id);
- }
-
- protected class Attribution {
- String attribution;
- int minZoom;
- int maxZoom;
- Coordinate min;
- Coordinate max;
- }
-
- @Override
- public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
- // make sure that attribution is loaded. otherwise subdomains is null.
- if (getAttribution() == null)
- throw new IOException("Attribution is not loaded yet");
-
- int t = (zoom + tilex + tiley) % subdomains.length;
- String subdomain = subdomains[t];
-
- String url = imageUrlTemplate;
- url = subdomainPattern.matcher(url).replaceAll(subdomain);
- url = quadkeyPattern.matcher(url).replaceAll(computeQuadTree(zoom, tilex, tiley));
-
- return url;
- }
-
- protected URL getAttributionUrl() throws MalformedURLException {
- return new URL("http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key="
- + API_KEY);
- }
-
- protected List<Attribution> parseAttributionText(InputSource xml) throws IOException {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(xml);
-
- XPathFactory xPathFactory = XPathFactory.newInstance();
- XPath xpath = xPathFactory.newXPath();
- imageUrlTemplate = xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document);
- imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).replaceAll(Locale.getDefault().toString());
- imageryZoomMax = Integer.parseInt(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document));
-
- NodeList subdomainTxt = (NodeList) xpath.compile("//ImageryMetadata/ImageUrlSubdomains/string/text()").evaluate(document, XPathConstants.NODESET);
- subdomains = new String[subdomainTxt.getLength()];
- for(int i = 0; i < subdomainTxt.getLength(); i++) {
- subdomains[i] = subdomainTxt.item(i).getNodeValue();
- }
-
- brandLogoUri = xpath.compile("/Response/BrandLogoUri/text()").evaluate(document);
-
- XPathExpression attributionXpath = xpath.compile("Attribution/text()");
- XPathExpression coverageAreaXpath = xpath.compile("CoverageArea");
- XPathExpression zoomMinXpath = xpath.compile("ZoomMin/text()");
- XPathExpression zoomMaxXpath = xpath.compile("ZoomMax/text()");
- XPathExpression southLatXpath = xpath.compile("BoundingBox/SouthLatitude/text()");
- XPathExpression westLonXpath = xpath.compile("BoundingBox/WestLongitude/text()");
- XPathExpression northLatXpath = xpath.compile("BoundingBox/NorthLatitude/text()");
- XPathExpression eastLonXpath = xpath.compile("BoundingBox/EastLongitude/text()");
-
- NodeList imageryProviderNodes = (NodeList) xpath.compile("//ImageryMetadata/ImageryProvider").evaluate(document, XPathConstants.NODESET);
- List<Attribution> attributions = new ArrayList<>(imageryProviderNodes.getLength());
- for (int i = 0; i < imageryProviderNodes.getLength(); i++) {
- Node providerNode = imageryProviderNodes.item(i);
-
- String attribution = attributionXpath.evaluate(providerNode);
-
- NodeList coverageAreaNodes = (NodeList) coverageAreaXpath.evaluate(providerNode, XPathConstants.NODESET);
- for(int j = 0; j < coverageAreaNodes.getLength(); j++) {
- Node areaNode = coverageAreaNodes.item(j);
- Attribution attr = new Attribution();
- attr.attribution = attribution;
-
- attr.maxZoom = Integer.parseInt(zoomMaxXpath.evaluate(areaNode));
- attr.minZoom = Integer.parseInt(zoomMinXpath.evaluate(areaNode));
-
- Double southLat = Double.parseDouble(southLatXpath.evaluate(areaNode));
- Double northLat = Double.parseDouble(northLatXpath.evaluate(areaNode));
- Double westLon = Double.parseDouble(westLonXpath.evaluate(areaNode));
- Double eastLon = Double.parseDouble(eastLonXpath.evaluate(areaNode));
- attr.min = new Coordinate(southLat, westLon);
- attr.max = new Coordinate(northLat, eastLon);
-
- attributions.add(attr);
- }
- }
-
- return attributions;
- } catch (SAXException e) {
- System.err.println("Could not parse Bing aerials attribution metadata.");
- e.printStackTrace();
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (XPathExpressionException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- @Override
- public int getMaxZoom() {
- if(imageryZoomMax != null)
- return imageryZoomMax;
- else
- return 22;
- }
-
- @Override
- public TileUpdate getTileUpdate() {
- return TileUpdate.IfNoneMatch;
- }
-
- @Override
- public boolean requiresAttribution() {
- return true;
- }
-
- @Override
- public String getAttributionLinkURL() {
- //return "http://bing.com/maps"
- // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
- // (the requirement is that we have such a link at the bottom of the window)
- return "http://go.microsoft.com/?linkid=9710837";
- }
-
- @Override
- public Image getAttributionImage() {
- try {
- final InputStream imageResource = JMapViewer.class.getResourceAsStream("images/bing_maps.png");
- if (imageResource != null) {
- return ImageIO.read(imageResource);
- } else {
- // Some Linux distributions (like Debian) will remove Bing logo from sources, so get it at runtime
- for (int i = 0; i < 5 && getAttribution() == null; i++) {
- // Makes sure attribution is loaded
- }
- if (brandLogoUri != null && !brandLogoUri.isEmpty()) {
- System.out.println("Reading Bing logo from "+brandLogoUri);
- return ImageIO.read(new URL(brandLogoUri));
- }
- }
- } catch (IOException e) {
- System.err.println("Error while retrieving Bing logo: "+e.getMessage());
- }
- return null;
- }
-
- @Override
- public String getAttributionImageURL() {
- return "http://opengeodata.org/microsoft-imagery-details";
- }
-
- @Override
- public String getTermsOfUseText() {
- return null;
- }
-
- @Override
- public String getTermsOfUseURL() {
- return "http://opengeodata.org/microsoft-imagery-details";
- }
-
- protected Callable<List<Attribution>> getAttributionLoaderCallable() {
- return new Callable<List<Attribution>>() {
-
- @Override
- public List<Attribution> call() throws Exception {
- int waitTimeSec = 1;
- while (true) {
- try {
- InputSource xml = new InputSource(getAttributionUrl().openStream());
- List<Attribution> r = parseAttributionText(xml);
- System.out.println("Successfully loaded Bing attribution data.");
- return r;
- } catch (IOException ex) {
- System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
- Thread.sleep(waitTimeSec * 1000L);
- waitTimeSec *= 2;
- }
- }
- }
- };
- }
-
- protected List<Attribution> getAttribution() {
- if (attributions == null) {
- // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
- synchronized (BingAerialTileSource.class) {
- if (attributions == null) {
- attributions = Executors.newSingleThreadExecutor().submit(getAttributionLoaderCallable());
- }
- }
- }
- try {
- return attributions.get(1000, TimeUnit.MILLISECONDS);
- } catch (TimeoutException ex) {
- System.err.println("Bing: attribution data is not yet loaded.");
- } catch (ExecutionException ex) {
- throw new RuntimeException(ex.getCause());
- } catch (InterruptedException ign) {
- }
- return null;
- }
-
- @Override
- public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
- try {
- final List<Attribution> data = getAttribution();
- if (data == null)
- return "Error loading Bing attribution data";
- StringBuilder a = new StringBuilder();
- for (Attribution attr : data) {
- if (zoom <= attr.maxZoom && zoom >= attr.minZoom) {
- if (topLeft.getLon() < attr.max.getLon() && botRight.getLon() > attr.min.getLon()
- && topLeft.getLat() > attr.min.getLat() && botRight.getLat() < attr.max.getLat()) {
- a.append(attr.attribution);
- a.append(" ");
- }
- }
- }
- return a.toString();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return "Error loading Bing attribution data";
- }
-
- static String computeQuadTree(int zoom, int tilex, int tiley) {
- StringBuilder k = new StringBuilder();
- for (int i = zoom; i > 0; i--) {
- char digit = 48;
- int mask = 1 << (i - 1);
- if ((tilex & mask) != 0) {
- digit += 1;
- }
- if ((tiley & mask) != 0) {
- digit += 2;
- }
- k.append(digit);
- }
- return k.toString();
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+import java.awt.Image;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Pattern;
+
+import javax.imageio.ImageIO;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class BingAerialTileSource extends AbstractTMSTileSource {
+
+ private static String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
+ private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
+ private static String imageUrlTemplate;
+ private static Integer imageryZoomMax;
+ private static String[] subdomains;
+
+ private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain\\}");
+ private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
+ private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
+ private String brandLogoUri = null;
+
+ /**
+ * Constructs a new {@code BingAerialTileSource}.
+ */
+ public BingAerialTileSource() {
+ this("Bing");
+ }
+
+ /**
+ * Constructs a new {@code BingAerialTileSource}.
+ */
+ public BingAerialTileSource(String id) {
+ super("Bing Aerial Maps", "http://example.com/", id);
+ }
+
+ protected class Attribution {
+ String attribution;
+ int minZoom;
+ int maxZoom;
+ Coordinate min;
+ Coordinate max;
+ }
+
+ @Override
+ public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
+ // make sure that attribution is loaded. otherwise subdomains is null.
+ if (getAttribution() == null)
+ throw new IOException("Attribution is not loaded yet");
+
+ int t = (zoom + tilex + tiley) % subdomains.length;
+ String subdomain = subdomains[t];
+
+ String url = imageUrlTemplate;
+ url = subdomainPattern.matcher(url).replaceAll(subdomain);
+ url = quadkeyPattern.matcher(url).replaceAll(computeQuadTree(zoom, tilex, tiley));
+
+ return url;
+ }
+
+ protected URL getAttributionUrl() throws MalformedURLException {
+ return new URL("http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key="
+ + API_KEY);
+ }
+
+ protected List<Attribution> parseAttributionText(InputSource xml) throws IOException {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.parse(xml);
+
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+ XPath xpath = xPathFactory.newXPath();
+ imageUrlTemplate = xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document);
+ imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).replaceAll(Locale.getDefault().toString());
+ imageryZoomMax = Integer.parseInt(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document));
+
+ NodeList subdomainTxt = (NodeList) xpath.compile("//ImageryMetadata/ImageUrlSubdomains/string/text()").evaluate(document, XPathConstants.NODESET);
+ subdomains = new String[subdomainTxt.getLength()];
+ for(int i = 0; i < subdomainTxt.getLength(); i++) {
+ subdomains[i] = subdomainTxt.item(i).getNodeValue();
+ }
+
+ brandLogoUri = xpath.compile("/Response/BrandLogoUri/text()").evaluate(document);
+
+ XPathExpression attributionXpath = xpath.compile("Attribution/text()");
+ XPathExpression coverageAreaXpath = xpath.compile("CoverageArea");
+ XPathExpression zoomMinXpath = xpath.compile("ZoomMin/text()");
+ XPathExpression zoomMaxXpath = xpath.compile("ZoomMax/text()");
+ XPathExpression southLatXpath = xpath.compile("BoundingBox/SouthLatitude/text()");
+ XPathExpression westLonXpath = xpath.compile("BoundingBox/WestLongitude/text()");
+ XPathExpression northLatXpath = xpath.compile("BoundingBox/NorthLatitude/text()");
+ XPathExpression eastLonXpath = xpath.compile("BoundingBox/EastLongitude/text()");
+
+ NodeList imageryProviderNodes = (NodeList) xpath.compile("//ImageryMetadata/ImageryProvider").evaluate(document, XPathConstants.NODESET);
+ List<Attribution> attributions = new ArrayList<>(imageryProviderNodes.getLength());
+ for (int i = 0; i < imageryProviderNodes.getLength(); i++) {
+ Node providerNode = imageryProviderNodes.item(i);
+
+ String attribution = attributionXpath.evaluate(providerNode);
+
+ NodeList coverageAreaNodes = (NodeList) coverageAreaXpath.evaluate(providerNode, XPathConstants.NODESET);
+ for(int j = 0; j < coverageAreaNodes.getLength(); j++) {
+ Node areaNode = coverageAreaNodes.item(j);
+ Attribution attr = new Attribution();
+ attr.attribution = attribution;
+
+ attr.maxZoom = Integer.parseInt(zoomMaxXpath.evaluate(areaNode));
+ attr.minZoom = Integer.parseInt(zoomMinXpath.evaluate(areaNode));
+
+ Double southLat = Double.parseDouble(southLatXpath.evaluate(areaNode));
+ Double northLat = Double.parseDouble(northLatXpath.evaluate(areaNode));
+ Double westLon = Double.parseDouble(westLonXpath.evaluate(areaNode));
+ Double eastLon = Double.parseDouble(eastLonXpath.evaluate(areaNode));
+ attr.min = new Coordinate(southLat, westLon);
+ attr.max = new Coordinate(northLat, eastLon);
+
+ attributions.add(attr);
+ }
+ }
+
+ return attributions;
+ } catch (SAXException e) {
+ System.err.println("Could not parse Bing aerials attribution metadata.");
+ e.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (XPathExpressionException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public int getMaxZoom() {
+ if(imageryZoomMax != null)
+ return imageryZoomMax;
+ else
+ return 22;
+ }
+
+ @Override
+ public TileUpdate getTileUpdate() {
+ return TileUpdate.IfNoneMatch;
+ }
+
+ @Override
+ public boolean requiresAttribution() {
+ return true;
+ }
+
+ @Override
+ public String getAttributionLinkURL() {
+ //return "http://bing.com/maps"
+ // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
+ // (the requirement is that we have such a link at the bottom of the window)
+ return "http://go.microsoft.com/?linkid=9710837";
+ }
+
+ @Override
+ public Image getAttributionImage() {
+ try {
+ final InputStream imageResource = JMapViewer.class.getResourceAsStream("images/bing_maps.png");
+ if (imageResource != null) {
+ return ImageIO.read(imageResource);
+ } else {
+ // Some Linux distributions (like Debian) will remove Bing logo from sources, so get it at runtime
+ for (int i = 0; i < 5 && getAttribution() == null; i++) {
+ // Makes sure attribution is loaded
+ }
+ if (brandLogoUri != null && !brandLogoUri.isEmpty()) {
+ System.out.println("Reading Bing logo from "+brandLogoUri);
+ return ImageIO.read(new URL(brandLogoUri));
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("Error while retrieving Bing logo: "+e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ public String getAttributionImageURL() {
+ return "http://opengeodata.org/microsoft-imagery-details";
+ }
+
+ @Override
+ public String getTermsOfUseText() {
+ return null;
+ }
+
+ @Override
+ public String getTermsOfUseURL() {
+ return "http://opengeodata.org/microsoft-imagery-details";
+ }
+
+ protected Callable<List<Attribution>> getAttributionLoaderCallable() {
+ return new Callable<List<Attribution>>() {
+
+ @Override
+ public List<Attribution> call() throws Exception {
+ int waitTimeSec = 1;
+ while (true) {
+ try {
+ InputSource xml = new InputSource(getAttributionUrl().openStream());
+ List<Attribution> r = parseAttributionText(xml);
+ System.out.println("Successfully loaded Bing attribution data.");
+ return r;
+ } catch (IOException ex) {
+ System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
+ Thread.sleep(waitTimeSec * 1000L);
+ waitTimeSec *= 2;
+ }
+ }
+ }
+ };
+ }
+
+ protected List<Attribution> getAttribution() {
+ if (attributions == null) {
+ // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
+ synchronized (BingAerialTileSource.class) {
+ if (attributions == null) {
+ attributions = Executors.newSingleThreadExecutor().submit(getAttributionLoaderCallable());
+ }
+ }
+ }
+ try {
+ return attributions.get(1000, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException ex) {
+ System.err.println("Bing: attribution data is not yet loaded.");
+ } catch (ExecutionException ex) {
+ throw new RuntimeException(ex.getCause());
+ } catch (InterruptedException ign) {
+ }
+ return null;
+ }
+
+ @Override
+ public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
+ try {
+ final List<Attribution> data = getAttribution();
+ if (data == null)
+ return "Error loading Bing attribution data";
+ StringBuilder a = new StringBuilder();
+ for (Attribution attr : data) {
+ if (zoom <= attr.maxZoom && zoom >= attr.minZoom) {
+ if (topLeft.getLon() < attr.max.getLon() && botRight.getLon() > attr.min.getLon()
+ && topLeft.getLat() > attr.min.getLat() && botRight.getLat() < attr.max.getLat()) {
+ a.append(attr.attribution);
+ a.append(" ");
+ }
+ }
+ }
+ return a.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "Error loading Bing attribution data";
+ }
+
+ static String computeQuadTree(int zoom, int tilex, int tiley) {
+ StringBuilder k = new StringBuilder();
+ for (int i = zoom; i > 0; i--) {
+ char digit = 48;
+ int mask = 1 << (i - 1);
+ if ((tilex & mask) != 0) {
+ digit += 1;
+ }
+ if ((tiley & mask) != 0) {
+ digit += 2;
+ }
+ k.append(digit);
+ }
+ return k.toString();
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java
index 1edaf5d..ab83fe3 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java
@@ -1,78 +1,78 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-/**
- * OSM Tile source.
- */
-public class OsmTileSource {
-
- /**
- * The default "Mapnik" OSM tile source.
- */
- public static class Mapnik extends AbstractOsmTileSource {
-
- private static final String PATTERN = "https://%s.tile.openstreetmap.org";
-
- private static final String[] SERVER = { "a", "b", "c" };
-
- private int SERVER_NUM = 0;
-
- /**
- * Constructs a new {@code "Mapnik"} tile source.
- */
- public Mapnik() {
- super("Mapnik", PATTERN, "MAPNIK");
- }
-
- @Override
- public String getBaseUrl() {
- String url = String.format(this.baseUrl, new Object[] { SERVER[SERVER_NUM] });
- SERVER_NUM = (SERVER_NUM + 1) % SERVER.length;
- return url;
- }
-
- @Override
- public int getMaxZoom() {
- return 19;
- }
-
- public TileUpdate getTileUpdate() {
- return TileUpdate.IfNoneMatch;
- }
- }
-
- /**
- * The "Cycle Map" OSM tile source.
- */
- public static class CycleMap extends AbstractOsmTileSource {
-
- private static final String PATTERN = "http://%s.tile.opencyclemap.org/cycle";
-
- private static final String[] SERVER = { "a", "b", "c" };
-
- private int SERVER_NUM = 0;
-
- /**
- * Constructs a new {@code CycleMap} tile source.
- */
- public CycleMap() {
- super("Cyclemap", PATTERN, "opencyclemap");
- }
-
- @Override
- public String getBaseUrl() {
- String url = String.format(this.baseUrl, new Object[] { SERVER[SERVER_NUM] });
- SERVER_NUM = (SERVER_NUM + 1) % SERVER.length;
- return url;
- }
-
- @Override
- public int getMaxZoom() {
- return 18;
- }
-
- public TileUpdate getTileUpdate() {
- return TileUpdate.LastModified;
- }
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+/**
+ * OSM Tile source.
+ */
+public class OsmTileSource {
+
+ /**
+ * The default "Mapnik" OSM tile source.
+ */
+ public static class Mapnik extends AbstractOsmTileSource {
+
+ private static final String PATTERN = "https://%s.tile.openstreetmap.org";
+
+ private static final String[] SERVER = { "a", "b", "c" };
+
+ private int SERVER_NUM = 0;
+
+ /**
+ * Constructs a new {@code "Mapnik"} tile source.
+ */
+ public Mapnik() {
+ super("Mapnik", PATTERN, "MAPNIK");
+ }
+
+ @Override
+ public String getBaseUrl() {
+ String url = String.format(this.baseUrl, new Object[] { SERVER[SERVER_NUM] });
+ SERVER_NUM = (SERVER_NUM + 1) % SERVER.length;
+ return url;
+ }
+
+ @Override
+ public int getMaxZoom() {
+ return 19;
+ }
+
+ public TileUpdate getTileUpdate() {
+ return TileUpdate.IfNoneMatch;
+ }
+ }
+
+ /**
+ * The "Cycle Map" OSM tile source.
+ */
+ public static class CycleMap extends AbstractOsmTileSource {
+
+ private static final String PATTERN = "http://%s.tile.opencyclemap.org/cycle";
+
+ private static final String[] SERVER = { "a", "b", "c" };
+
+ private int SERVER_NUM = 0;
+
+ /**
+ * Constructs a new {@code CycleMap} tile source.
+ */
+ public CycleMap() {
+ super("Cyclemap", PATTERN, "opencyclemap");
+ }
+
+ @Override
+ public String getBaseUrl() {
+ String url = String.format(this.baseUrl, new Object[] { SERVER[SERVER_NUM] });
+ SERVER_NUM = (SERVER_NUM + 1) % SERVER.length;
+ return url;
+ }
+
+ @Override
+ public int getMaxZoom() {
+ return 18;
+ }
+
+ public TileUpdate getTileUpdate() {
+ return TileUpdate.LastModified;
+ }
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
index be7128a..b12b98e 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
@@ -1,158 +1,158 @@
-// License: BSD or GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-import java.util.Random;
-
-import org.openstreetmap.gui.jmapviewer.OsmMercator;
-
-/*
- * This tilesource uses different to OsmMercator projection.
- *
- * Earth is assumed an ellipsoid in this projection, unlike
- * sphere in OsmMercator, so latitude calculation differs
- * a lot.
- *
- * The longitude calculation is the same as in OsmMercator,
- * we inherit it from AbstractTMSTileSource.
- *
- * TODO: correct getDistance() method.
- */
-
-public class ScanexTileSource extends TMSTileSource {
- private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru";
- private static final int DEFAULT_MAXZOOM = 14;
- private static String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7";
-
- private enum ScanexLayer {
- IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"),
- SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC");
-
- private String name;
- private String uri;
-
- ScanexLayer(String name, String uri) {
- this.name = name;
- this.uri = uri;
- }
- public String getName() {
- return name;
- }
- public String getUri() {
- return uri;
- }
- }
-
- /* IRS by default */
- private ScanexLayer Layer = ScanexLayer.IRS;
-
- public ScanexTileSource(String name, String url, String id, int maxZoom) {
- super(name, url, id, maxZoom);
-
- for (ScanexLayer layer : ScanexLayer.values()) {
- if (url.equalsIgnoreCase(layer.getName())) {
- this.Layer = layer;
- /*
- * Override baseUrl and maxZoom in base class.
- */
- this.baseUrl = DEFAULT_URL;
- if (maxZoom == 0)
- this.maxZoom = DEFAULT_MAXZOOM;
- break;
- }
- }
- }
-
- @Override
- public String getExtension() {
- return("jpeg");
- }
-
- @Override
- public String getTilePath(int zoom, int tilex, int tiley) {
- int tmp = (int)Math.pow(2.0, zoom - 1);
-
- tilex = tilex - tmp;
- tiley = tmp - tiley - 1;
-
- return this.Layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
- }
-
- public TileUpdate getTileUpdate() {
- return TileUpdate.IfNoneMatch;
- }
-
-
- /*
- * Latitude to Y and back calculations.
- */
- private static double RADIUS_E = 6378137; /* radius of Earth at equator, m */
- private static double EQUATOR = 40075016.68557849; /* equator length, m */
- private static double E = 0.0818191908426; /* eccentricity of Earth's ellipsoid */
-
- @Override
- public int LatToY(double lat, int zoom) {
- return (int )(latToTileY(lat, zoom) * OsmMercator.TILE_SIZE);
- }
-
- @Override
- public double YToLat(int y, int zoom) {
- return tileYToLat((double )y / OsmMercator.TILE_SIZE, zoom);
- }
-
- @Override
- public double latToTileY(double lat, int zoom) {
- double tmp = Math.tan(Math.PI/4 * (1 + lat/90));
- double pow = Math.pow(Math.tan(Math.PI/4 + Math.asin(E * Math.sin(Math.toRadians(lat)))/2), E);
-
- return (EQUATOR/2 - (RADIUS_E * Math.log(tmp/pow))) * Math.pow(2.0, zoom) / EQUATOR;
- }
-
- @Override
- public double tileYToLat(int y, int zoom) {
- return tileYToLat((double )y, zoom);
- }
-
- /*
- * To solve inverse formula latitude = f(y) we use
- * Newton's method. We cache previous calculated latitude,
- * because new one is usually close to the old one. In case
- * if solution gets out of bounds, we reset to a new random
- * value.
- */
- private double cached_lat = 0;
- private double tileYToLat(double y, int zoom) {
- double lat0, lat;
-
- lat = cached_lat;
- do {
- lat0 = lat;
- lat = lat - Math.toDegrees(NextTerm(Math.toRadians(lat), y, zoom));
- if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) {
- Random r = new Random();
- lat = OsmMercator.MIN_LAT +
- r.nextInt((int )(OsmMercator.MAX_LAT - OsmMercator.MIN_LAT));
- }
- } while ((Math.abs(lat0 - lat) > 0.000001));
-
- cached_lat = lat;
-
- return (lat);
- }
-
- /* Next term in Newton's polynomial */
- private double NextTerm(double lat, double y, int zoom) {
- double sinl=Math.sin(lat);
- double cosl=Math.cos(lat);
- double ec, f, df;
-
- zoom = (int )Math.pow(2.0, zoom - 1);
- ec = Math.exp((1 - y/zoom)*Math.PI);
-
- f = (Math.tan(Math.PI/4+lat/2) -
- ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E));
- df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) *
- (Math.sqrt (1 - E * E * sinl * sinl)));
-
- return (f/df);
- }
-}
+// License: BSD or GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+import java.util.Random;
+
+import org.openstreetmap.gui.jmapviewer.OsmMercator;
+
+/*
+ * This tilesource uses different to OsmMercator projection.
+ *
+ * Earth is assumed an ellipsoid in this projection, unlike
+ * sphere in OsmMercator, so latitude calculation differs
+ * a lot.
+ *
+ * The longitude calculation is the same as in OsmMercator,
+ * we inherit it from AbstractTMSTileSource.
+ *
+ * TODO: correct getDistance() method.
+ */
+
+public class ScanexTileSource extends TMSTileSource {
+ private static final String DEFAULT_URL = "http://maps.kosmosnimki.ru";
+ private static final int DEFAULT_MAXZOOM = 14;
+ private static String API_KEY = "4018C5A9AECAD8868ED5DEB2E41D09F7";
+
+ private enum ScanexLayer {
+ IRS("irs", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=BAC78D764F0443BD9AF93E7A998C9F5B"),
+ SPOT("spot", "/TileSender.ashx?ModeKey=tile&MapName=F7B8CF651682420FA1749D894C8AD0F6&LayerName=F51CE95441284AF6B2FC319B609C7DEC");
+
+ private String name;
+ private String uri;
+
+ ScanexLayer(String name, String uri) {
+ this.name = name;
+ this.uri = uri;
+ }
+ public String getName() {
+ return name;
+ }
+ public String getUri() {
+ return uri;
+ }
+ }
+
+ /* IRS by default */
+ private ScanexLayer Layer = ScanexLayer.IRS;
+
+ public ScanexTileSource(String name, String url, String id, int maxZoom) {
+ super(name, url, id, maxZoom);
+
+ for (ScanexLayer layer : ScanexLayer.values()) {
+ if (url.equalsIgnoreCase(layer.getName())) {
+ this.Layer = layer;
+ /*
+ * Override baseUrl and maxZoom in base class.
+ */
+ this.baseUrl = DEFAULT_URL;
+ if (maxZoom == 0)
+ this.maxZoom = DEFAULT_MAXZOOM;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String getExtension() {
+ return("jpeg");
+ }
+
+ @Override
+ public String getTilePath(int zoom, int tilex, int tiley) {
+ int tmp = (int)Math.pow(2.0, zoom - 1);
+
+ tilex = tilex - tmp;
+ tiley = tmp - tiley - 1;
+
+ return this.Layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
+ }
+
+ public TileUpdate getTileUpdate() {
+ return TileUpdate.IfNoneMatch;
+ }
+
+
+ /*
+ * Latitude to Y and back calculations.
+ */
+ private static double RADIUS_E = 6378137; /* radius of Earth at equator, m */
+ private static double EQUATOR = 40075016.68557849; /* equator length, m */
+ private static double E = 0.0818191908426; /* eccentricity of Earth's ellipsoid */
+
+ @Override
+ public int LatToY(double lat, int zoom) {
+ return (int )(latToTileY(lat, zoom) * OsmMercator.TILE_SIZE);
+ }
+
+ @Override
+ public double YToLat(int y, int zoom) {
+ return tileYToLat((double )y / OsmMercator.TILE_SIZE, zoom);
+ }
+
+ @Override
+ public double latToTileY(double lat, int zoom) {
+ double tmp = Math.tan(Math.PI/4 * (1 + lat/90));
+ double pow = Math.pow(Math.tan(Math.PI/4 + Math.asin(E * Math.sin(Math.toRadians(lat)))/2), E);
+
+ return (EQUATOR/2 - (RADIUS_E * Math.log(tmp/pow))) * Math.pow(2.0, zoom) / EQUATOR;
+ }
+
+ @Override
+ public double tileYToLat(int y, int zoom) {
+ return tileYToLat((double )y, zoom);
+ }
+
+ /*
+ * To solve inverse formula latitude = f(y) we use
+ * Newton's method. We cache previous calculated latitude,
+ * because new one is usually close to the old one. In case
+ * if solution gets out of bounds, we reset to a new random
+ * value.
+ */
+ private double cached_lat = 0;
+ private double tileYToLat(double y, int zoom) {
+ double lat0, lat;
+
+ lat = cached_lat;
+ do {
+ lat0 = lat;
+ lat = lat - Math.toDegrees(NextTerm(Math.toRadians(lat), y, zoom));
+ if (lat > OsmMercator.MAX_LAT || lat < OsmMercator.MIN_LAT) {
+ Random r = new Random();
+ lat = OsmMercator.MIN_LAT +
+ r.nextInt((int )(OsmMercator.MAX_LAT - OsmMercator.MIN_LAT));
+ }
+ } while ((Math.abs(lat0 - lat) > 0.000001));
+
+ cached_lat = lat;
+
+ return (lat);
+ }
+
+ /* Next term in Newton's polynomial */
+ private double NextTerm(double lat, double y, int zoom) {
+ double sinl=Math.sin(lat);
+ double cosl=Math.cos(lat);
+ double ec, f, df;
+
+ zoom = (int )Math.pow(2.0, zoom - 1);
+ ec = Math.exp((1 - y/zoom)*Math.PI);
+
+ f = (Math.tan(Math.PI/4+lat/2) -
+ ec * Math.pow(Math.tan(Math.PI/4 + Math.asin(E * sinl)/2), E));
+ df = 1/(1 - sinl) - ec * E * cosl/((1 - E * sinl) *
+ (Math.sqrt (1 - E * E * sinl * sinl)));
+
+ return (f/df);
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
index 23c792d..7f47c6a 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
@@ -1,34 +1,34 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-public class TMSTileSource extends AbstractTMSTileSource {
-
- protected int maxZoom;
- protected int minZoom = 0;
-
- public TMSTileSource(String name, String url, String id, int maxZoom) {
- super(name, url, id);
- this.maxZoom = maxZoom;
- }
-
- public TMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
- super(name, url, id);
- this.minZoom = minZoom;
- this.maxZoom = maxZoom;
- }
-
- @Override
- public int getMinZoom() {
- return (minZoom == 0) ? super.getMinZoom() : minZoom;
- }
-
- @Override
- public int getMaxZoom() {
- return (maxZoom == 0) ? super.getMaxZoom() : maxZoom;
- }
-
- @Override
- public TileUpdate getTileUpdate() {
- return TileUpdate.IfNoneMatch;
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+public class TMSTileSource extends AbstractTMSTileSource {
+
+ protected int maxZoom;
+ protected int minZoom = 0;
+
+ public TMSTileSource(String name, String url, String id, int maxZoom) {
+ super(name, url, id);
+ this.maxZoom = maxZoom;
+ }
+
+ public TMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
+ super(name, url, id);
+ this.minZoom = minZoom;
+ this.maxZoom = maxZoom;
+ }
+
+ @Override
+ public int getMinZoom() {
+ return (minZoom == 0) ? super.getMinZoom() : minZoom;
+ }
+
+ @Override
+ public int getMaxZoom() {
+ return (maxZoom == 0) ? super.getMaxZoom() : maxZoom;
+ }
+
+ @Override
+ public TileUpdate getTileUpdate() {
+ return TileUpdate.IfNoneMatch;
+ }
+}
diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
index a5d5c04..98417fb 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
@@ -1,87 +1,96 @@
-// License: GPL. For details, see Readme.txt file.
-package org.openstreetmap.gui.jmapviewer.tilesources;
-
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Random;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-public class TemplatedTMSTileSource extends TMSTileSource {
-
- private Random rand = null;
- private String[] randomParts = null;
- private Map<String, String> headers = new HashMap<>();
-
- public static final String PATTERN_ZOOM = "\\{(?:(\\d+)-)?z(?:oom)?([+-]\\d+)?\\}";
- public static final String PATTERN_X = "\\{x\\}";
- public static final String PATTERN_Y = "\\{y\\}";
- public static final String PATTERN_Y_YAHOO = "\\{!y\\}";
- public static final String PATTERN_NEG_Y = "\\{-y\\}";
- public static final String PATTERN_SWITCH = "\\{switch:([^}]+)\\}";
- public static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}";
-
- public static final String[] ALL_PATTERNS = {
- PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_NEG_Y,
- PATTERN_SWITCH
- };
-
- public TemplatedTMSTileSource(String name, String url, String id, int maxZoom) {
- super(name, url, id, maxZoom);
- handleTemplate();
- }
-
- public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
- super(name, url, id, minZoom, maxZoom);
- handleTemplate();
- }
-
- private void handleTemplate() {
- // Capturing group pattern on switch values
- Matcher m = Pattern.compile(".*"+PATTERN_SWITCH+".*").matcher(baseUrl);
- if (m.matches()) {
- rand = new Random();
- randomParts = m.group(1).split(",");
- }
- Pattern pattern = Pattern.compile(PATTERN_HEADER);
- StringBuffer output = new StringBuffer();
- Matcher matcher = pattern.matcher(baseUrl);
- while (matcher.find()) {
- headers.put(matcher.group(1),matcher.group(2));
- matcher.appendReplacement(output, "");
- }
- matcher.appendTail(output);
- baseUrl = output.toString();
- }
-
- public Map<String, String> getHeaders() {
- return headers;
- }
-
- @Override
- public String getTileUrl(int zoom, int tilex, int tiley) {
- int finalZoom = zoom;
- Matcher m = Pattern.compile(".*"+PATTERN_ZOOM+".*").matcher(this.baseUrl);
- if (m.matches()) {
- if(m.group(1) != null) {
- finalZoom = Integer.valueOf(m.group(1))-zoom;
- }
- if(m.group(2) != null) {
- String ofs = m.group(2);
- if(ofs.startsWith("+"))
- ofs = ofs.substring(1);
- finalZoom += Integer.valueOf(ofs);
- }
- }
- String r = this.baseUrl
- .replaceAll(PATTERN_ZOOM, Integer.toString(finalZoom))
- .replaceAll(PATTERN_X, Integer.toString(tilex))
- .replaceAll(PATTERN_Y, Integer.toString(tiley))
- .replaceAll(PATTERN_Y_YAHOO, Integer.toString((int)Math.pow(2, zoom-1)-1-tiley))
- .replaceAll(PATTERN_NEG_Y, Integer.toString((int)Math.pow(2, zoom)-1-tiley));
- if (rand != null) {
- r = r.replaceAll(PATTERN_SWITCH, randomParts[rand.nextInt(randomParts.length)]);
- }
- return r;
- }
-}
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class TemplatedTMSTileSource extends TMSTileSource {
+
+ private Random rand = null;
+ private String[] randomParts = null;
+ private Map<String, String> headers = new HashMap<>();
+
+ public static final String COOKIE_HEADER = "Cookie";
+ public static final String PATTERN_ZOOM = "\\{(?:(\\d+)-)?z(?:oom)?([+-]\\d+)?\\}";
+ public static final String PATTERN_X = "\\{x\\}";
+ public static final String PATTERN_Y = "\\{y\\}";
+ public static final String PATTERN_Y_YAHOO = "\\{!y\\}";
+ public static final String PATTERN_NEG_Y = "\\{-y\\}";
+ public static final String PATTERN_SWITCH = "\\{switch:([^}]+)\\}";
+ public static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}";
+
+ public static final String[] ALL_PATTERNS = {
+ PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_NEG_Y,
+ PATTERN_SWITCH
+ };
+
+ public TemplatedTMSTileSource(String name, String url, String id, int maxZoom) {
+ super(name, url, id, maxZoom);
+ handleTemplate();
+ }
+
+ public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
+ super(name, url, id, minZoom, maxZoom);
+ handleTemplate();
+ }
+
+ public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom, String cookies) {
+ super(name, url, id, minZoom, maxZoom);
+ if (cookies != null) {
+ headers.put(COOKIE_HEADER, cookies);
+ }
+ handleTemplate();
+ }
+
+ private void handleTemplate() {
+ // Capturing group pattern on switch values
+ Matcher m = Pattern.compile(".*"+PATTERN_SWITCH+".*").matcher(baseUrl);
+ if (m.matches()) {
+ rand = new Random();
+ randomParts = m.group(1).split(",");
+ }
+ Pattern pattern = Pattern.compile(PATTERN_HEADER);
+ StringBuffer output = new StringBuffer();
+ Matcher matcher = pattern.matcher(baseUrl);
+ while (matcher.find()) {
+ headers.put(matcher.group(1),matcher.group(2));
+ matcher.appendReplacement(output, "");
+ }
+ matcher.appendTail(output);
+ baseUrl = output.toString();
+ }
+
+ public Map<String, String> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public String getTileUrl(int zoom, int tilex, int tiley) {
+ int finalZoom = zoom;
+ Matcher m = Pattern.compile(".*"+PATTERN_ZOOM+".*").matcher(this.baseUrl);
+ if (m.matches()) {
+ if(m.group(1) != null) {
+ finalZoom = Integer.valueOf(m.group(1))-zoom;
+ }
+ if(m.group(2) != null) {
+ String ofs = m.group(2);
+ if(ofs.startsWith("+"))
+ ofs = ofs.substring(1);
+ finalZoom += Integer.valueOf(ofs);
+ }
+ }
+ String r = this.baseUrl
+ .replaceAll(PATTERN_ZOOM, Integer.toString(finalZoom))
+ .replaceAll(PATTERN_X, Integer.toString(tilex))
+ .replaceAll(PATTERN_Y, Integer.toString(tiley))
+ .replaceAll(PATTERN_Y_YAHOO, Integer.toString((int)Math.pow(2, zoom-1)-1-tiley))
+ .replaceAll(PATTERN_NEG_Y, Integer.toString((int)Math.pow(2, zoom)-1-tiley));
+ if (rand != null) {
+ r = r.replaceAll(PATTERN_SWITCH, randomParts[rand.nextInt(randomParts.length)]);
+ }
+ return r;
+ }
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/jmapviewer.git
More information about the Pkg-grass-devel
mailing list