Premier Depot
This commit is contained in:
commit
b7d843338f
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/bin/
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExempleJPanelOSM</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
|
|
@ -0,0 +1,8 @@
|
|||
public class ClassePrincipale {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new MaJFrameDemo().setVisible(true);
|
||||
new MaJFrameSimple().setVisible(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.JMapViewer;
|
||||
import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
|
||||
import org.openstreetmap.gui.jmapviewer.MapMarkerImage;
|
||||
import org.openstreetmap.gui.jmapviewer.MapMarkerLabel;
|
||||
import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
|
||||
|
||||
/**
|
||||
*
|
||||
* Demonstrates the usage of {@link JMapViewer}
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*
|
||||
*/
|
||||
public class MaJFrameDemo extends JFrame {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final JMapViewer map;
|
||||
|
||||
static {
|
||||
System.setProperty("http.agent", "Gluon Mobile/1.0.3");
|
||||
}
|
||||
|
||||
public MaJFrameDemo() {
|
||||
super("OSM Demo");
|
||||
setSize(400, 400);
|
||||
map = new JMapViewer();
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
JPanel panel = new JPanel();
|
||||
JPanel helpPanel = new JPanel();
|
||||
getContentPane().add(panel, BorderLayout.NORTH);
|
||||
getContentPane().add(helpPanel, BorderLayout.SOUTH);
|
||||
JLabel helpLabel = new JLabel("Utilisez le bouton droit de la souris pour vous déplacer,\n"
|
||||
+"Double-clic gauche ou molette de la souris pour zoomer.");
|
||||
helpPanel.add(helpLabel);
|
||||
panel.setLayout(new GridLayout(0, 1, 0, 0));
|
||||
|
||||
JPanel panel_1 = new JPanel();
|
||||
panel.add(panel_1);
|
||||
|
||||
JCheckBox showMapMarker = new JCheckBox("Marqueurs de carte visibles");
|
||||
showMapMarker.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
map.setMapMarkerVisible(showMapMarker.isSelected());
|
||||
}
|
||||
});
|
||||
showMapMarker.setSelected(true);
|
||||
panel_1.add(showMapMarker);
|
||||
|
||||
JCheckBox showTileGrid = new JCheckBox("Grille de tuiles visible");
|
||||
showTileGrid.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
map.setTileGridVisible(showTileGrid.isSelected());
|
||||
}
|
||||
});
|
||||
showTileGrid.setSelected(false);
|
||||
panel_1.add(showTileGrid);
|
||||
|
||||
JCheckBox showZoomControls = new JCheckBox("Afficher les contrôles de zoom");
|
||||
showZoomControls.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
map.setZoomContolsVisible(showZoomControls.isSelected());
|
||||
}
|
||||
});
|
||||
showZoomControls.setSelected(true);
|
||||
panel_1.add(showZoomControls);
|
||||
|
||||
JPanel panel_2 = new JPanel();
|
||||
panel.add(panel_2);
|
||||
|
||||
JButton buttonFit = new JButton("Adapter la carte aux marqueurs");
|
||||
buttonFit.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
map.setDisplayToFitMapMarkers();
|
||||
}
|
||||
});
|
||||
panel_2.add(buttonFit);
|
||||
|
||||
JButton buttonMarkerPoint = new JButton("Marqueurs points");
|
||||
buttonMarkerPoint.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
addMarkerDot(100);
|
||||
}
|
||||
});
|
||||
panel_2.add(buttonMarkerPoint);
|
||||
|
||||
JButton buttonMarkerImg = new JButton("Marqueurs images");
|
||||
buttonMarkerImg.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
addMarkerImg(50);
|
||||
}
|
||||
});
|
||||
panel_2.add(buttonMarkerImg);
|
||||
|
||||
JButton buttonMarkerLabel = new JButton("Marqueurs labels");
|
||||
buttonMarkerLabel.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
addMarkerLabel(50);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
panel_2.add(buttonMarkerLabel);
|
||||
|
||||
JButton buttonCleanMarkers = new JButton("Supprimer tous les marqueurs");
|
||||
buttonCleanMarkers.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
cleanMarkers();
|
||||
}
|
||||
});
|
||||
panel_2.add(buttonCleanMarkers);
|
||||
getContentPane().add(map, BorderLayout.CENTER);
|
||||
|
||||
//
|
||||
addMarkerDot(10);
|
||||
map.addPropertyChangeListener(getName(), new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
// TODO Auto-generated method stub
|
||||
System.out.println("on property change");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// map.setDisplayPositionByLatLon(49.807, 8.6, 11);
|
||||
// map.setTileGridVisible(true);
|
||||
}
|
||||
|
||||
private void cleanMarkers() {
|
||||
List<MapMarker> list = new ArrayList<>(map.getMapMarkerList());
|
||||
Iterator<MapMarker> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
map.removeMapMarker(it.next());
|
||||
}
|
||||
}
|
||||
|
||||
private void addMarkerLabel(int nbMarkers) {
|
||||
Random r = new Random();
|
||||
for (int i=0;i<nbMarkers;i++) {
|
||||
double latitude = (r.nextDouble() * -180.0) + 90.0;
|
||||
double longitude = (r.nextDouble() * -360.0) + 180.0;
|
||||
Color color = new Color((int)(Math.random() * 0x1000000));
|
||||
|
||||
map.addMapMarker(new MapMarkerLabel(color, "label" + i, 16, latitude, longitude));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void addMarkerImg(int nbMarkers) {
|
||||
Random r = new Random();
|
||||
for (int i=0;i<nbMarkers;i++) {
|
||||
double latitude = (r.nextDouble() * -180.0) + 90.0;
|
||||
double longitude = (r.nextDouble() * -360.0) + 180.0;
|
||||
map.addMapMarker(new MapMarkerImage("region-auvergne-rhone-alpes_logo.png", 10, latitude, longitude));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void addMarkerDot(int nbMarkers) {
|
||||
Random r = new Random();
|
||||
for (int i=0;i<nbMarkers;i++) {
|
||||
double latitude = (r.nextDouble() * -180.0) + 90.0;
|
||||
double longitude = (r.nextDouble() * -360.0) + 180.0;
|
||||
Color color = new Color((int)(Math.random() * 0x1000000));
|
||||
map.addMapMarker(new MapMarkerDot(color, 10, latitude, longitude));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import java.awt.BorderLayout;
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.JMapViewer;
|
||||
|
||||
import javax.swing.JButton;
|
||||
|
||||
public class MaJFrameSimple extends JFrame {
|
||||
|
||||
private JPanel contentPane;
|
||||
private JMapViewer panel_1;
|
||||
static {
|
||||
System.setProperty("http.agent", "Gluon Mobile/1.0.3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the frame.
|
||||
*/
|
||||
public MaJFrameSimple() {
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setBounds(100, 100, 450, 300);
|
||||
contentPane = new JPanel();
|
||||
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
contentPane.setLayout(new BorderLayout(0, 0));
|
||||
setContentPane(contentPane);
|
||||
|
||||
panel_1 = new JMapViewer();
|
||||
contentPane.add(panel_1, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2009 by Stefan Zeller
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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 + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
|
||||
/**
|
||||
* Default map controller which implements map moving by pressing the right
|
||||
* mouse button and zooming by double click or by mouse wheel.
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*
|
||||
*/
|
||||
public class DefaultMapController extends JMapController implements MouseListener, MouseMotionListener,
|
||||
MouseWheelListener {
|
||||
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
|
||||
/**
|
||||
* Abstract base class for all mouse controller implementations. For
|
||||
* implementing your own controller create a class that derives from this one
|
||||
* and implements one or more of the following interfaces:
|
||||
* <ul>
|
||||
* <li>{@link MouseListener}</li>
|
||||
* <li>{@link MouseMotionListener}</li>
|
||||
* <li>{@link MouseWheelListener}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*/
|
||||
public abstract class JMapController {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,790 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
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.awt.font.TextAttribute;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashMap;
|
||||
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 org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
|
||||
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 {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 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 boolean mapMarkersVisible;
|
||||
protected boolean mapRectanglesVisible;
|
||||
|
||||
protected boolean tileGridVisible;
|
||||
|
||||
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;
|
||||
|
||||
private TileSource tileSource;
|
||||
|
||||
// Attribution
|
||||
private Image attrImage;
|
||||
private String attrTermsUrl;
|
||||
public static final Font ATTR_FONT = new Font("Arial", Font.PLAIN, 10);
|
||||
public static final Font ATTR_LINK_FONT;
|
||||
|
||||
static {
|
||||
HashMap<TextAttribute, Integer> aUnderline = new HashMap<TextAttribute, Integer>();
|
||||
aUnderline.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
||||
ATTR_LINK_FONT = ATTR_FONT.deriveFont(aUnderline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standard {@link JMapViewer} instance that can be controlled via
|
||||
* mouse: hold right mouse button for moving, double click left mouse button
|
||||
* or use mouse wheel for zooming. Loaded tiles are stored the
|
||||
* {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
|
||||
* retrieving the tiles.
|
||||
*/
|
||||
public JMapViewer() {
|
||||
this(new MemoryTileCache(), 4);
|
||||
new DefaultMapController(this);
|
||||
}
|
||||
|
||||
public JMapViewer(TileCache tileCache, int downloadThreadCount) {
|
||||
super();
|
||||
tileSource = new OsmTileSource.Mapnik();
|
||||
tileController = new TileController(tileSource, tileCache, this);
|
||||
mapMarkerList = new LinkedList<MapMarker>();
|
||||
mapRectangleList = new LinkedList<MapRectangle>();
|
||||
mapMarkersVisible = true;
|
||||
mapRectanglesVisible = true;
|
||||
tileGridVisible = false;
|
||||
setLayout(null);
|
||||
initializeZoomSlider();
|
||||
setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
|
||||
setPreferredSize(new Dimension(400, 400));
|
||||
setDisplayPositionByLatLon(50, 9, 3);
|
||||
//setToolTipText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
// Point screenPoint = event.getLocationOnScreen();
|
||||
// Coordinate c = getPosition(screenPoint);
|
||||
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());
|
||||
}
|
||||
});
|
||||
add(zoomSlider);
|
||||
int size = 18;
|
||||
try {
|
||||
ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
|
||||
zoomInButton = new JButton(icon);
|
||||
} catch (Exception e) {
|
||||
zoomInButton = new JButton("+");
|
||||
zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
|
||||
zoomInButton.setMargin(new Insets(0, 0, 0, 0));
|
||||
}
|
||||
zoomInButton.setBounds(4, 155, size, size);
|
||||
zoomInButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
zoomIn();
|
||||
}
|
||||
});
|
||||
add(zoomInButton);
|
||||
try {
|
||||
ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
|
||||
zoomOutButton = new JButton(icon);
|
||||
} catch (Exception e) {
|
||||
zoomOutButton = new JButton("-");
|
||||
zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
|
||||
zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
|
||||
}
|
||||
zoomOutButton.setBounds(8 + size, 155, size, size);
|
||||
zoomOutButton.addActionListener(new ActionListener() {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
zoomOut();
|
||||
}
|
||||
});
|
||||
add(zoomOutButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the map pane so that it is centered on the specified coordinate
|
||||
* at the given zoom level.
|
||||
*
|
||||
* @param lat
|
||||
* latitude of the specified coordinate
|
||||
* @param lon
|
||||
* longitude of the specified coordinate
|
||||
* @param zoom
|
||||
* {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
|
||||
*/
|
||||
public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
|
||||
setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the map pane so that the specified coordinate at the given zoom
|
||||
* level is displayed on the map at the screen coordinate
|
||||
* <code>mapPoint</code>.
|
||||
*
|
||||
* @param mapPoint
|
||||
* point on the map denoted in pixels where the coordinate should
|
||||
* be set
|
||||
* @param lat
|
||||
* latitude of the specified coordinate
|
||||
* @param lon
|
||||
* longitude of the specified coordinate
|
||||
* @param zoom
|
||||
* {@link #MIN_ZOOM} <= zoom level <=
|
||||
* {@link TileSource#getMaxZoom()}
|
||||
*/
|
||||
public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
|
||||
int x = OsmMercator.LonToX(lon, zoom);
|
||||
int y = OsmMercator.LatToY(lat, zoom);
|
||||
setDisplayPosition(mapPoint, x, y, zoom);
|
||||
}
|
||||
|
||||
public void setDisplayPosition(int x, int y, int zoom) {
|
||||
setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
|
||||
}
|
||||
|
||||
public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
|
||||
if (zoom > 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 map markers are
|
||||
* visible.
|
||||
*/
|
||||
public void setDisplayToFitMapMarkers() {
|
||||
if (mapMarkerList == null || mapMarkerList.size() == 0)
|
||||
return;
|
||||
int x_min = Integer.MAX_VALUE;
|
||||
int y_min = Integer.MAX_VALUE;
|
||||
int x_max = Integer.MIN_VALUE;
|
||||
int y_max = Integer.MIN_VALUE;
|
||||
int mapZoomMax = tileController.getTileSource().getMaxZoom();
|
||||
for (MapMarker marker : mapMarkerList) {
|
||||
int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
|
||||
int y = OsmMercator.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);
|
||||
}
|
||||
int height = Math.max(0, getHeight());
|
||||
int width = Math.max(0, getWidth());
|
||||
// System.out.println(x_min + " < x < " + x_max);
|
||||
// System.out.println(y_min + " < y < " + y_max);
|
||||
// System.out.println("tiles: " + width + " " + height);
|
||||
int newZoom = mapZoomMax;
|
||||
int x = x_max - x_min;
|
||||
int y = y_max - y_min;
|
||||
while (x > width || y > height) {
|
||||
// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
|
||||
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 setDisplayToFitMapRectangle() {
|
||||
if (mapRectangleList == null || mapRectangleList.size() == 0)
|
||||
return;
|
||||
int x_min = Integer.MAX_VALUE;
|
||||
int y_min = Integer.MAX_VALUE;
|
||||
int x_max = Integer.MIN_VALUE;
|
||||
int y_max = Integer.MIN_VALUE;
|
||||
int mapZoomMax = tileController.getTileSource().getMaxZoom();
|
||||
for (MapRectangle rectangle : mapRectangleList) {
|
||||
x_max = Math.max(x_max, OsmMercator.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
|
||||
y_max = Math.max(y_max, OsmMercator.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
|
||||
x_min = Math.min(x_min, OsmMercator.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
|
||||
y_min = Math.min(y_min, OsmMercator.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
|
||||
}
|
||||
int height = Math.max(0, getHeight());
|
||||
int width = Math.max(0, getWidth());
|
||||
// System.out.println(x_min + " < x < " + x_max);
|
||||
// System.out.println(y_min + " < y < " + y_max);
|
||||
// System.out.println("tiles: " + width + " " + height);
|
||||
int newZoom = mapZoomMax;
|
||||
int x = x_max - x_min;
|
||||
int y = y_max - y_min;
|
||||
while (x > width || y > height) {
|
||||
// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the latitude/longitude coordinate of the center of the
|
||||
* currently displayed map area.
|
||||
*
|
||||
* @return latitude / longitude
|
||||
*/
|
||||
public Coordinate getPosition() {
|
||||
double lon = OsmMercator.XToLon(center.x, zoom);
|
||||
double lat = OsmMercator.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
|
||||
*/
|
||||
public Coordinate getPosition(int mapPointX, int mapPointY) {
|
||||
int x = center.x + mapPointX - getWidth() / 2;
|
||||
int y = center.y + mapPointY - getHeight() / 2;
|
||||
double lon = OsmMercator.XToLon(x, zoom);
|
||||
double lat = OsmMercator.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 = OsmMercator.LonToX(lon, zoom);
|
||||
int y = OsmMercator.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
|
||||
* @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 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(Coordinate coord, boolean checkOutside) {
|
||||
if (coord != null)
|
||||
return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// 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 = tileController.getTile(tilex, tiley, zoom);
|
||||
if (tile != null) {
|
||||
painted = true;
|
||||
tile.paint(g, posx, posy);
|
||||
if (tileGridVisible) {
|
||||
g.drawRect(posx, posy, tilesize, tilesize);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
|
||||
|
||||
// g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
|
||||
|
||||
if (mapRectanglesVisible && mapRectangleList != null) {
|
||||
for (MapRectangle rectangle : mapRectangleList) {
|
||||
Coordinate topLeft = rectangle.getTopLeft();
|
||||
Coordinate bottomRight = rectangle.getBottomRight();
|
||||
if (topLeft != null && bottomRight != null) {
|
||||
Point pTopLeft = getMapPosition(topLeft.getLat(), topLeft.getLon(), false);
|
||||
Point pBottomRight = getMapPosition(bottomRight.getLat(), bottomRight.getLon(), false);
|
||||
if (pTopLeft != null && pBottomRight != null) {
|
||||
rectangle.paint(g, pTopLeft, pBottomRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapMarkersVisible && mapMarkerList != null) {
|
||||
for (MapMarker marker : mapMarkerList) {
|
||||
paintMarker(g, marker);
|
||||
}
|
||||
}
|
||||
paintAttribution(g);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint a single marker.
|
||||
*/
|
||||
protected void paintMarker(Graphics g, MapMarker marker) {
|
||||
Point p = getMapPosition(marker.getLat(), marker.getLon());
|
||||
if (p != null) {
|
||||
marker.paint(g, p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the visible map pane.
|
||||
*
|
||||
* @param x
|
||||
* horizontal movement in pixel.
|
||||
* @param y
|
||||
* vertical movement in pixel
|
||||
*/
|
||||
public void moveMap(int x, int y) {
|
||||
center.x += x;
|
||||
center.y += y;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current zoom level
|
||||
*/
|
||||
public int getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the current zoom level by one
|
||||
*/
|
||||
public void zoomIn() {
|
||||
setZoom(zoom + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the current zoom level by one
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public void zoomOut(Point mapPoint) {
|
||||
setZoom(zoom - 1, mapPoint);
|
||||
}
|
||||
|
||||
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
|
||||
setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom);
|
||||
}
|
||||
|
||||
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 addMapMarker(MapMarker marker) {
|
||||
mapMarkerList.add(marker);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void removeMapMarker(MapMarker marker) {
|
||||
mapMarkerList.remove(marker);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void addMapRectangle(MapRectangle rectangle) {
|
||||
mapRectangleList.add(rectangle);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void removeMapRectangle(MapRectangle rectangle) {
|
||||
mapRectangleList.remove(rectangle);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setZoomContolsVisible(boolean visible) {
|
||||
zoomSlider.setVisible(visible);
|
||||
zoomInButton.setVisible(visible);
|
||||
zoomOutButton.setVisible(visible);
|
||||
}
|
||||
|
||||
public boolean getZoomContolsVisible() {
|
||||
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("Minumim zoom level too low");
|
||||
this.tileSource = tileSource;
|
||||
tileController.setTileSource(tileSource);
|
||||
zoomSlider.setMinimum(tileSource.getMinZoom());
|
||||
zoomSlider.setMaximum(tileSource.getMaxZoom());
|
||||
tileController.cancelOutstandingJobs();
|
||||
if (zoom > tileSource.getMaxZoom()) {
|
||||
setZoom(tileSource.getMaxZoom());
|
||||
}
|
||||
boolean requireAttr = tileSource.requiresAttribution();
|
||||
if (requireAttr) {
|
||||
attrImage = tileSource.getAttributionImage();
|
||||
attrTermsUrl = tileSource.getTermsOfUseURL();
|
||||
} else {
|
||||
attrImage = null;
|
||||
attrTermsUrl = null;
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void tileLoadingFinished(Tile tile, boolean success) {
|
||||
repaint();
|
||||
}
|
||||
|
||||
public boolean isMapRectanglesVisible() {
|
||||
return mapRectanglesVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables painting of the {@link MapRectangle}
|
||||
*
|
||||
* @param mapMarkersVisible
|
||||
* @see #addMapRectangle(MapRectangle)
|
||||
* @see #getMapRectangleList()
|
||||
*/
|
||||
public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
|
||||
this.mapRectanglesVisible = mapRectanglesVisible;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener#getTileCache
|
||||
* ()
|
||||
*/
|
||||
public TileCache getTileCache() {
|
||||
return tileController.getTileCache();
|
||||
}
|
||||
|
||||
public void setTileLoader(TileLoader loader) {
|
||||
tileController.setTileLoader(loader);
|
||||
}
|
||||
|
||||
private void paintAttribution(Graphics g) {
|
||||
if (!tileSource.requiresAttribution())
|
||||
return;
|
||||
// Draw attribution
|
||||
Font font = g.getFont();
|
||||
g.setFont(ATTR_LINK_FONT);
|
||||
|
||||
Rectangle2D termsStringBounds = g.getFontMetrics().getStringBounds("Background Terms of Use", g);
|
||||
int textHeight = (int) termsStringBounds.getHeight() - 5;
|
||||
int termsTextY = getHeight() - textHeight;
|
||||
if (attrTermsUrl != null) {
|
||||
int x = 2;
|
||||
int y = getHeight() - textHeight;
|
||||
g.setColor(Color.black);
|
||||
g.drawString("Background Terms of Use", x + 1, y + 1);
|
||||
g.setColor(Color.white);
|
||||
g.drawString("Background Terms of Use", x, y);
|
||||
}
|
||||
|
||||
// Draw attribution logo
|
||||
if (attrImage != null) {
|
||||
int x = 2;
|
||||
int height = attrImage.getHeight(null);
|
||||
int y = termsTextY - height - textHeight - 5;
|
||||
g.drawImage(attrImage, x, y, null);
|
||||
}
|
||||
|
||||
g.setFont(ATTR_FONT);
|
||||
Coordinate topLeft = getPosition(0, 0);
|
||||
Coordinate bottomRight = getPosition(getWidth(), getHeight());
|
||||
String attributionText = tileSource.getAttributionText(zoom, topLeft, bottomRight);
|
||||
if (attributionText != null) {
|
||||
Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(attributionText, g);
|
||||
int x = getWidth() - (int) stringBounds.getWidth();
|
||||
int y = getHeight() - textHeight;
|
||||
g.setColor(Color.black);
|
||||
g.drawString(attributionText, x + 1, y + 1);
|
||||
g.setColor(Color.white);
|
||||
g.drawString(attributionText, x, y);
|
||||
}
|
||||
|
||||
g.setFont(font);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 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 #WORKER_THREAD_MAX_COUNT} (default: 8). If an instance is idle
|
||||
* more than {@link #WORKER_THREAD_TIMEOUT} 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 BlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
|
||||
|
||||
public static int WORKER_THREAD_MAX_COUNT = 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.
|
||||
*/
|
||||
public static int WORKER_THREAD_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
public void addJob(Runnable job) {
|
||||
try {
|
||||
jobQueue.put(job);
|
||||
if (workerThreadIdleCount == 0 && workerThreadCount < WORKER_THREAD_MAX_COUNT)
|
||||
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 (firstThread)
|
||||
job = jobQueue.take();
|
||||
else
|
||||
job = jobQueue.poll(WORKER_THREAD_TIMEOUT, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e1) {
|
||||
return;
|
||||
} finally {
|
||||
synchronized (instance) {
|
||||
workerThreadIdleCount--;
|
||||
}
|
||||
}
|
||||
if (job == null)
|
||||
return;
|
||||
try {
|
||||
job.run();
|
||||
job = null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
|
||||
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 implements MapMarker {
|
||||
|
||||
private double lat;
|
||||
private double lon;
|
||||
private Color color;
|
||||
private int size;
|
||||
|
||||
public MapMarkerDot(double lat, double lon) {
|
||||
this(Color.YELLOW, 10, lat, lon);
|
||||
}
|
||||
|
||||
public MapMarkerDot(Color color, int size, double lat, double lon) {
|
||||
super();
|
||||
this.color = color;
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public void paint(Graphics g, Point position) {
|
||||
g.setColor(color);
|
||||
g.fillOval(position.x - size/2, position.y - size/2, size, size);
|
||||
g.setColor(Color.BLACK);
|
||||
g.drawOval(position.x - size/2, position.y - size/2, size, size);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MapMarker at " + lat + " " + lon;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
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 MapMarkerImage implements MapMarker {
|
||||
|
||||
private double lat;
|
||||
private double lon;
|
||||
private BufferedImage image;
|
||||
private int size;
|
||||
|
||||
public MapMarkerImage(double lat, double lon) {
|
||||
this("region-auvergne-rhone-alpes_logo.png", 10, lat, lon);
|
||||
}
|
||||
|
||||
public MapMarkerImage(String img, int size, double lat, double lon) {
|
||||
super();
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.size = size;
|
||||
|
||||
try {
|
||||
image = ImageIO.read(new File(img));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public void paint(Graphics g, Point position) {
|
||||
g.drawImage(image, position.x - size/2, position.y - size/2, size, size, null);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MapMarker at " + lat + " " + lon;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
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 MapMarkerLabel implements MapMarker {
|
||||
|
||||
private double lat;
|
||||
private double lon;
|
||||
private Color color;
|
||||
private String label;
|
||||
private int sizeFont;
|
||||
|
||||
public MapMarkerLabel(double lat, double lon) {
|
||||
this(Color.YELLOW, "Label", 16, lat, lon);
|
||||
}
|
||||
|
||||
public MapMarkerLabel(Color color, String label, int sizeFont, double lat, double lon) {
|
||||
super();
|
||||
this.color = color;
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.label = label;
|
||||
this.sizeFont = sizeFont;
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public void paint(Graphics g, Point position) {
|
||||
g.setColor(color);
|
||||
g.setFont(new Font("default", Font.BOLD, sizeFont));
|
||||
g.drawString(label, position.x , position.y );
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MapMarker at " + lat + " " + lon;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.util.Hashtable;
|
||||
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 Hashtable<String, CacheEntry> hashtable;
|
||||
|
||||
/**
|
||||
* List of all tiles in their last recently used order
|
||||
*/
|
||||
protected CacheLinkedListElement lruTiles;
|
||||
|
||||
public MemoryTileCache() {
|
||||
hashtable = new Hashtable<String, CacheEntry>(cacheSize);
|
||||
lruTiles = new CacheLinkedListElement();
|
||||
}
|
||||
|
||||
public void addTile(Tile tile) {
|
||||
CacheEntry entry = createCacheEntry(tile);
|
||||
hashtable.put(tile.getKey(), entry);
|
||||
lruTiles.addFirst(entry);
|
||||
if (hashtable.size() > cacheSize)
|
||||
removeOldEntries();
|
||||
}
|
||||
|
||||
public Tile getTile(TileSource source, int x, int y, int z) {
|
||||
CacheEntry entry = hashtable.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 void removeOldEntries() {
|
||||
synchronized (lruTiles) {
|
||||
try {
|
||||
while (lruTiles.getElementCount() > cacheSize) {
|
||||
removeEntry(lruTiles.getLastElement());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeEntry(CacheEntry entry) {
|
||||
hashtable.remove(entry.tile.getKey());
|
||||
lruTiles.removeEntry(entry);
|
||||
}
|
||||
|
||||
protected CacheEntry createCacheEntry(Tile tile) {
|
||||
return new CacheEntry(tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache deleting all tiles from memory
|
||||
*/
|
||||
public void clear() {
|
||||
synchronized (lruTiles) {
|
||||
hashtable.clear();
|
||||
lruTiles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTileCount() {
|
||||
return hashtable.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 void setCacheSize(int cacheSize) {
|
||||
this.cacheSize = cacheSize;
|
||||
if (hashtable.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 synchronized void clear() {
|
||||
elementCount = 0;
|
||||
firstElement = null;
|
||||
lastElement = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the element to the head of the list.
|
||||
*
|
||||
* @param new element to be added
|
||||
*/
|
||||
public synchronized void addFirst(CacheEntry element) {
|
||||
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 elemntent form the list.
|
||||
*
|
||||
* @param element
|
||||
* to be removed
|
||||
*/
|
||||
public synchronized void removeEntry(CacheEntry element) {
|
||||
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 synchronized 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
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.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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.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 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 {
|
||||
|
||||
private static final Logger log = Logger.getLogger(OsmFileCacheTileLoader.class.getName());
|
||||
|
||||
private static final String ETAG_FILE_EXT = ".etag";
|
||||
private 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 long maxCacheFileAge = FILE_AGE_ONE_WEEK;
|
||||
protected long recheckAfter = FILE_AGE_ONE_DAY;
|
||||
|
||||
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
|
||||
* @param cacheDir
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a OSMFileCacheTileLoader with system property temp dir.
|
||||
* If not set an IOException will be thrown.
|
||||
* @param map
|
||||
*/
|
||||
public OsmFileCacheTileLoader(TileLoaderListener map) throws SecurityException, IOException {
|
||||
this(map, getDefaultCacheDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
|
||||
return new FileLoadJob(source, tilex, tiley, zoom);
|
||||
}
|
||||
|
||||
protected class FileLoadJob implements Runnable {
|
||||
InputStream input = null;
|
||||
|
||||
int tilex, tiley, zoom;
|
||||
Tile tile;
|
||||
TileSource source;
|
||||
File tileCacheDir;
|
||||
File tileFile = null;
|
||||
long fileAge = 0;
|
||||
boolean fileTilePainted = false;
|
||||
|
||||
public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
|
||||
this.source = source;
|
||||
this.tilex = tilex;
|
||||
this.tiley = tiley;
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
TileCache cache = listener.getTileCache();
|
||||
synchronized (cache) {
|
||||
tile = cache.getTile(source, tilex, tiley, zoom);
|
||||
if (tile == null || tile.isLoaded() || tile.loading)
|
||||
return;
|
||||
tile.loading = true;
|
||||
}
|
||||
tileCacheDir = new File(cacheDirBase, source.getName().replaceAll("[\\\\/:*?\"<>|]", "_"));
|
||||
if (!tileCacheDir.exists()) {
|
||||
tileCacheDir.mkdirs();
|
||||
}
|
||||
if (loadTileFromFile())
|
||||
return;
|
||||
if (fileTilePainted) {
|
||||
Runnable job = new Runnable() {
|
||||
|
||||
public void run() {
|
||||
loadOrUpdateTile();
|
||||
}
|
||||
};
|
||||
JobDispatcher.getInstance().addJob(job);
|
||||
} else {
|
||||
loadOrUpdateTile();
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadOrUpdateTile() {
|
||||
|
||||
try {
|
||||
// log.finest("Loading tile from OSM: " + tile);
|
||||
URLConnection urlConn = loadTileFromOsm(tile);
|
||||
if (tileFile != null) {
|
||||
switch (source.getTileUpdate()) {
|
||||
case IfModifiedSince:
|
||||
urlConn.setIfModifiedSince(fileAge);
|
||||
break;
|
||||
case LastModified:
|
||||
if (!isOsmTileNewer(fileAge)) {
|
||||
log.finest("LastModified test: local version is up to date: " + tile);
|
||||
tile.setLoaded(true);
|
||||
tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (source.getTileUpdate() == TileUpdate.ETag || source.getTileUpdate() == TileUpdate.IfNoneMatch) {
|
||||
String fileETag = tile.getValue("etag");
|
||||
if (fileETag != null) {
|
||||
switch (source.getTileUpdate()) {
|
||||
case IfNoneMatch:
|
||||
urlConn.addRequestProperty("If-None-Match", fileETag);
|
||||
break;
|
||||
case ETag:
|
||||
if (hasOsmTileETag(fileETag)) {
|
||||
tile.setLoaded(true);
|
||||
tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
|
||||
+ recheckAfter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
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"
|
||||
log.finest("ETag test: local version is up to date: " + tile);
|
||||
tile.setLoaded(true);
|
||||
tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
|
||||
return;
|
||||
}
|
||||
|
||||
loadTileMetadata(tile, urlConn);
|
||||
saveTagsToFile();
|
||||
|
||||
if ("no-tile".equals(tile.getValue("tile-info")))
|
||||
{
|
||||
tile.setError("No tile at this zoom level");
|
||||
listener.tileLoadingFinished(tile, true);
|
||||
} else {
|
||||
byte[] buffer = loadTileInBuffer(urlConn);
|
||||
if (buffer != null) {
|
||||
tile.loadImage(new ByteArrayInputStream(buffer));
|
||||
tile.setLoaded(true);
|
||||
listener.tileLoadingFinished(tile, true);
|
||||
saveTileToFile(buffer);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
tile.setError(e.getMessage());
|
||||
listener.tileLoadingFinished(tile, false);
|
||||
if (input == null) {
|
||||
System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
tile.loading = false;
|
||||
tile.setLoaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean loadTileFromFile() {
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
tileFile = getTileFile();
|
||||
loadTagsFromFile();
|
||||
if ("no-tile".equals(tile.getValue("tile-info")))
|
||||
{
|
||||
tile.setError("No tile at this zoom level");
|
||||
if (tileFile.exists()) {
|
||||
tileFile.delete();
|
||||
}
|
||||
tileFile = getTagsFile();
|
||||
} else {
|
||||
fin = new FileInputStream(tileFile);
|
||||
if (fin.available() == 0)
|
||||
throw new IOException("File empty");
|
||||
tile.loadImage(fin);
|
||||
fin.close();
|
||||
}
|
||||
|
||||
fileAge = tileFile.lastModified();
|
||||
boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
|
||||
// System.out.println("Loaded from file: " + tile);
|
||||
if (!oldTile) {
|
||||
tile.setLoaded(true);
|
||||
listener.tileLoadingFinished(tile, true);
|
||||
fileTilePainted = true;
|
||||
return true;
|
||||
}
|
||||
listener.tileLoadingFinished(tile, true);
|
||||
fileTilePainted = true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
if (fin != null) {
|
||||
fin.close();
|
||||
tileFile.delete();
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
}
|
||||
tileFile = null;
|
||||
fileAge = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
|
||||
input = urlConn.getInputStream();
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OsmTileLoader#MAP_OSMA} - supported</li>
|
||||
* <li>{@link OsmTileLoader#MAP_MAPNIK} - not supported</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param fileAge
|
||||
* @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(fileAge));
|
||||
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(fileAge));
|
||||
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() + "."
|
||||
+ source.getTileType());
|
||||
}
|
||||
|
||||
protected File getTagsFile() {
|
||||
return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile()
|
||||
+ TAGS_FILE_EXT);
|
||||
}
|
||||
|
||||
protected void saveTileToFile(byte[] rawData) {
|
||||
try {
|
||||
FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
|
||||
+ "_" + tile.getYtile() + "." + source.getTileType());
|
||||
f.write(rawData);
|
||||
f.close();
|
||||
// System.out.println("Saved tile to file: " + tile);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to save tile content: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected void saveTagsToFile() {
|
||||
File tagsFile = getTagsFile();
|
||||
if (tile.getMetadata() == null) {
|
||||
tagsFile.delete();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final 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());
|
||||
}
|
||||
f.close();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to save tile tags: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Load backward-compatiblity .etag file and if it exists move it to new .tags file*/
|
||||
private void loadOldETagfromFile() {
|
||||
File etagFile = new File(tileCacheDir, tile.getZoom() + "_"
|
||||
+ tile.getXtile() + "_" + tile.getYtile() + ETAG_FILE_EXT);
|
||||
if (!etagFile.exists()) return;
|
||||
try {
|
||||
FileInputStream f = new FileInputStream(etagFile);
|
||||
byte[] buf = new byte[f.available()];
|
||||
f.read(buf);
|
||||
f.close();
|
||||
String etag = new String(buf, TAGS_CHARSET.name());
|
||||
tile.putValue("etag", etag);
|
||||
if (etagFile.delete()) {
|
||||
saveTagsToFile();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to load compatiblity etag: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadTagsFromFile() {
|
||||
loadOldETagfromFile();
|
||||
File tagsFile = getTagsFile();
|
||||
try {
|
||||
final 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));
|
||||
}
|
||||
f.close();
|
||||
} 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
// License: GPL. Copyright 2007 by Tim Haussmann
|
||||
|
||||
/**
|
||||
* This class implements the Mercator Projection as it is used by Openstreetmap
|
||||
* (and google). It provides methods to translate coordinates from 'map space'
|
||||
* into latitude and longitude (on the WGS84 ellipsoid) and vice versa. Map
|
||||
* space is measured in pixels. The origin of the map space is the top left
|
||||
* corner. The map space origin (0,0) has latitude ~85 and longitude -180
|
||||
*
|
||||
* @author Tim Haussmann
|
||||
*
|
||||
*/
|
||||
|
||||
public class OsmMercator {
|
||||
|
||||
private static int TILE_SIZE = 256;
|
||||
public static final double MAX_LAT = 85.05112877980659;
|
||||
public static final double MIN_LAT = -85.05112877980659;
|
||||
|
||||
public static double radius(int aZoomlevel) {
|
||||
return (TILE_SIZE * (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
|
||||
* @return
|
||||
*/
|
||||
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 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 int LonToX(double aLongitude, int aZoomlevel) {
|
||||
int mp = getMaxPixels(aZoomlevel);
|
||||
int x = (int) ((mp * (aLongitude + 180l)) / 360l);
|
||||
x = Math.min(x, mp - 1);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 int 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);
|
||||
int y = (int) (mp * (0.5 - (log / (4.0 * Math.PI))));
|
||||
y = Math.min(y, mp - 1);
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A {@link TileLoader} implementation that loads tiles from OSM.
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*/
|
||||
public class OsmTileLoader implements TileLoader {
|
||||
|
||||
/**
|
||||
* Holds the used user agent used for HTTP requests. If this field is
|
||||
* <code>null</code>, the default Java user agent is used.
|
||||
*/
|
||||
public static String USER_AGENT = null;
|
||||
public static String ACCEPT = "text/html, image/png, image/jpeg, image/gif, */*";
|
||||
|
||||
protected TileLoaderListener listener;
|
||||
|
||||
public OsmTileLoader(TileLoaderListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
|
||||
return new Runnable() {
|
||||
|
||||
InputStream input = null;
|
||||
|
||||
public void run() {
|
||||
TileCache cache = listener.getTileCache();
|
||||
Tile tile;
|
||||
synchronized (cache) {
|
||||
tile = cache.getTile(source, tilex, tiley, zoom);
|
||||
if (tile == null || tile.isLoaded() || tile.loading)
|
||||
return;
|
||||
tile.loading = true;
|
||||
}
|
||||
try {
|
||||
// Thread.sleep(500);
|
||||
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();
|
||||
tile.loadImage(input);
|
||||
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) {
|
||||
System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
tile.loading = false;
|
||||
tile.setLoaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
if (USER_AGENT != null) {
|
||||
urlConn.setRequestProperty("User-agent", USER_AGENT);
|
||||
}
|
||||
urlConn.setRequestProperty("Accept", ACCEPT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
package org.openstreetmap.gui.jmapviewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
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
|
||||
*/
|
||||
public static BufferedImage LOADING_IMAGE;
|
||||
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 e1) {
|
||||
LOADING_IMAGE = null;
|
||||
ERROR_IMAGE = null;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @param xtile
|
||||
* @param ytile
|
||||
* @param zoom
|
||||
*/
|
||||
public Tile(TileSource source, int xtile, int ytile, int zoom) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.xtile = xtile;
|
||||
this.ytile = ytile;
|
||||
this.zoom = zoom;
|
||||
this.image = LOADING_IMAGE;
|
||||
this.key = getTileKey(source, xtile, ytile, zoom);
|
||||
}
|
||||
|
||||
public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
|
||||
this(source, xtile, ytile, zoom);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return tile number on the x axis of this tile
|
||||
*/
|
||||
public int getXtile() {
|
||||
return xtile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return tile number on the y axis of this tile
|
||||
*/
|
||||
public int getYtile() {
|
||||
return ytile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return zoom level of this tile
|
||||
*/
|
||||
public int getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
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
|
||||
* @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;
|
||||
}
|
||||
|
||||
public void putValue(String key, String value) {
|
||||
if (value == null || "".equals(value)) {
|
||||
if (metadata != null) {
|
||||
metadata.remove(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (metadata == null) {
|
||||
metadata = new HashMap<String,String>();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
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;
|
||||
import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
|
||||
|
||||
public class TileController {
|
||||
protected TileLoader tileLoader;
|
||||
protected TileCache tileCache;
|
||||
protected TileSource tileSource;
|
||||
|
||||
JobDispatcher jobDispatcher;
|
||||
|
||||
public TileController(TileSource source, TileCache tileCache, TileLoaderListener listener) {
|
||||
tileSource = new OsmTileSource.Mapnik();
|
||||
tileLoader = new OsmTileLoader(listener);
|
||||
this.tileCache = tileCache;
|
||||
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
|
||||
* @param tiley
|
||||
* @param zoom
|
||||
* @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.isLoaded()) {
|
||||
jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void cancelOutstandingJobs() {
|
||||
jobDispatcher.cancelOutstandingJobs();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 171 B |
Binary file not shown.
|
After Width: | Height: | Size: 225 B |
|
|
@ -0,0 +1,37 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* @return Latitude of the map marker position
|
||||
*/
|
||||
public double getLat();
|
||||
|
||||
/**
|
||||
* @return Longitude of the map marker position
|
||||
*/
|
||||
public double getLon();
|
||||
|
||||
/**
|
||||
* Paints the map marker on the map. The <code>position</code> specifies the
|
||||
* coordinates within <code>g</code>
|
||||
*
|
||||
* @param g
|
||||
* @param position
|
||||
*/
|
||||
public void paint(Graphics g, Point position);
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
//License: GPL. Copyright 2009 by Stefan Zeller
|
||||
|
||||
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()
|
||||
* @date 21.06.2009S
|
||||
*/
|
||||
public interface MapRectangle {
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param position
|
||||
*/
|
||||
public void paint(Graphics g, Point topLeft, Point bottomRight);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.JMapViewer;
|
||||
import org.openstreetmap.gui.jmapviewer.Tile;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
/**
|
||||
* Implement this interface for creating your custom tile cache for
|
||||
* {@link JMapViewer}.
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*/
|
||||
public interface TileCache {
|
||||
|
||||
/**
|
||||
* Retrieves a tile from the cache if present, otherwise <code>null</code>
|
||||
* will be returned.
|
||||
*
|
||||
* @param 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(int, int, int)} is unspecified and depends on the
|
||||
* implementation.
|
||||
*
|
||||
* @param tile
|
||||
*/
|
||||
public void addTile(Tile tile);
|
||||
|
||||
/**
|
||||
* @return the number of tiles hold by the cache
|
||||
*/
|
||||
public int getTileCount();
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
/**
|
||||
* 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 {@link #createTileLoaderJob(int, int, int)} implementation
|
||||
* should create and return a new {@link Job} instance that performs the
|
||||
* load action.
|
||||
*
|
||||
* @param tileLayerSource
|
||||
* @param tilex
|
||||
* @param tiley
|
||||
* @param zoom
|
||||
* @returns {@link Runnable} implementation that performs the desired load
|
||||
* action.
|
||||
*/
|
||||
public Runnable createTileLoaderJob(TileSource tileLayerSource, int tilex, int tiley, int zoom);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.Tile;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
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
|
||||
*/
|
||||
public void tileLoadingFinished(Tile tile, boolean success);
|
||||
|
||||
public TileCache getTileCache();
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package org.openstreetmap.gui.jmapviewer.interfaces;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.Coordinate;
|
||||
import org.openstreetmap.gui.jmapviewer.JMapViewer;
|
||||
|
||||
//License: GPL. Copyright 2008 by Jan Peter Stotz
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jan Peter Stotz
|
||||
*/
|
||||
public interface TileSource {
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public int getMinZoom();
|
||||
|
||||
/**
|
||||
* @return The supported tile update mechanism
|
||||
* @see TileUpdate
|
||||
*/
|
||||
public TileUpdate getTileUpdate();
|
||||
|
||||
/**
|
||||
* A tile layer name has to be unique and has to consist only of characters
|
||||
* valid for filenames.
|
||||
*
|
||||
* @return Name of the tile layer
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Constructs the tile url.
|
||||
*
|
||||
* @param zoom
|
||||
* @param tilex
|
||||
* @param tiley
|
||||
* @return fully qualified url for downloading the specified tile image
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public String getTileType();
|
||||
|
||||
/**
|
||||
* Specifies how large each tile is.
|
||||
* @return The size of a single tile in pixels.
|
||||
*/
|
||||
public int getTileSize();
|
||||
|
||||
/**
|
||||
* @return True if the tile source requires attribution in text or image form.
|
||||
*/
|
||||
public boolean requiresAttribution();
|
||||
|
||||
/**
|
||||
* @param zoom The optional zoom level for the view.
|
||||
* @param botRight The bottom right of the bounding box for attribution.
|
||||
* @param topLeft The top left of the bounding box for attribution.
|
||||
* @return Attribution text for the image source.
|
||||
*/
|
||||
public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight);
|
||||
|
||||
/**
|
||||
* @return The URL for the attribution image. Null if no image should be displayed.
|
||||
*/
|
||||
public Image getAttributionImage();
|
||||
|
||||
/**
|
||||
* @return The URL to open when the user clicks the attribution image.
|
||||
*/
|
||||
public String getAttributionLinkURL();
|
||||
|
||||
/**
|
||||
* @return The URL to open when the user clicks the attribution "Terms of Use" text.
|
||||
*/
|
||||
public String getTermsOfUseURL();
|
||||
|
||||
public double latToTileY(double lat, int zoom);
|
||||
|
||||
public double lonToTileX(double lon, int zoom);
|
||||
|
||||
public double tileYToLat(int y, int zoom);
|
||||
|
||||
public double tileXToLon(int x, int zoom);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>org.openstreetmap.gui.jmapviewer</h1>
|
||||
<p>This package and all sub-packages are belonging to the Java
|
||||
component <a href="http://wiki.openstreetmap.org/wiki/JMapViewer">JMapViewer</a>
|
||||
</p>
|
||||
<p>JMapViewer is designed to run as stand-alone component without
|
||||
any further requirements. Therefore <b>please do not add any code that
|
||||
depends on other libraries or applications</b>. Only functions and methods
|
||||
provided by the runtime library of Java 5 should be used.</p>
|
||||
<h2>In particular, this concerns the developer of JOSM!</h2>
|
||||
<p>2009-08-10 Jan Peter Stotz jpstotz@gmx.de (maintainer of JMapViewer)</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.openstreetmap.gui.jmapviewer.tilesources;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.Coordinate;
|
||||
import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
|
||||
|
||||
public abstract class AbstractOsmTileSource implements TileSource {
|
||||
|
||||
protected String name;
|
||||
protected String baseUrl;
|
||||
protected String attrImgUrl;
|
||||
|
||||
public AbstractOsmTileSource(String name, String base_url) {
|
||||
this(name, base_url, null);
|
||||
}
|
||||
|
||||
public AbstractOsmTileSource(String name, String base_url, String attr_img_url) {
|
||||
this.name = name;
|
||||
this.baseUrl = base_url;
|
||||
attrImgUrl = attr_img_url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getMaxZoom() {
|
||||
return 18;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
|
||||
return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public String getTileType() {
|
||||
return "png";
|
||||
}
|
||||
|
||||
public int getTileSize() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
public Image getAttributionImage() {
|
||||
if (attrImgUrl != null)
|
||||
return new ImageIcon(attrImgUrl).getImage();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean requiresAttribution() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
|
||||
return "© OpenStreetMap contributors, CC-BY-SA ";
|
||||
}
|
||||
|
||||
public String getAttributionLinkURL() {
|
||||
return "http://openstreetmap.org/";
|
||||
}
|
||||
|
||||
public String getTermsOfUseURL() {
|
||||
return "http://www.openstreetmap.org/copyright";
|
||||
}
|
||||
|
||||
public double latToTileY(double lat, int zoom) {
|
||||
double l = lat / 180 * Math.PI;
|
||||
double pf = Math.log(Math.tan(l) + (1 / Math.cos(l)));
|
||||
return Math.pow(2.0, zoom - 1) * (Math.PI - pf) / Math.PI;
|
||||
}
|
||||
|
||||
public double lonToTileX(double lon, int zoom) {
|
||||
return Math.pow(2.0, zoom - 3) * (lon + 180.0) / 45.0;
|
||||
}
|
||||
|
||||
public double tileYToLat(int y, int zoom) {
|
||||
return Math.atan(Math.sinh(Math.PI - (Math.PI * y / Math.pow(2.0, zoom - 1)))) * 180 / Math.PI;
|
||||
}
|
||||
|
||||
public double tileXToLon(int x, int zoom) {
|
||||
return x * 45.0 / Math.pow(2.0, zoom - 3) - 180.0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package org.openstreetmap.gui.jmapviewer.tilesources;
|
||||
|
||||
public class OfflineOsmTileSource extends AbstractOsmTileSource {
|
||||
|
||||
private final int minZoom;
|
||||
private final int maxZoom;
|
||||
|
||||
public OfflineOsmTileSource(String path, int minZoom, int maxZoom) {
|
||||
super("Offline from "+path, path);
|
||||
this.minZoom = minZoom;
|
||||
this.maxZoom = maxZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxZoom() {
|
||||
return maxZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinZoom() {
|
||||
return minZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.None;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package org.openstreetmap.gui.jmapviewer.tilesources;
|
||||
|
||||
public class OsmTileSource {
|
||||
|
||||
public static final String MAP_MAPNIK = "http://tile.openstreetmap.org";
|
||||
public static final String MAP_OSMA = "http://tah.openstreetmap.org/Tiles";
|
||||
|
||||
public static class Mapnik extends AbstractOsmTileSource {
|
||||
public Mapnik() {
|
||||
super("Mapnik", MAP_MAPNIK);
|
||||
}
|
||||
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.IfNoneMatch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public CycleMap() {
|
||||
super("OSM Cycle Map", PATTERN);
|
||||
}
|
||||
|
||||
@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 17;
|
||||
}
|
||||
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.LastModified;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class OsmaSource extends AbstractOsmTileSource {
|
||||
String osmaSuffix;
|
||||
|
||||
public OsmaSource(String name, String osmaSuffix) {
|
||||
super(name, MAP_OSMA);
|
||||
this.osmaSuffix = osmaSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxZoom() {
|
||||
return 17;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return MAP_OSMA + "/" + osmaSuffix;
|
||||
}
|
||||
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.IfModifiedSince;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TilesAtHome extends OsmaSource {
|
||||
public TilesAtHome() {
|
||||
super("TilesAtHome", "tile");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Maplint extends OsmaSource {
|
||||
public Maplint() {
|
||||
super("Maplint", "maplint");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package org.openstreetmap.gui.jmapviewer.tilesources;
|
||||
|
||||
|
||||
public class TMSTileSource extends AbstractOsmTileSource {
|
||||
private int maxZoom;
|
||||
|
||||
public TMSTileSource(String name, String url, int maxZoom) {
|
||||
super(name, url);
|
||||
this.maxZoom = maxZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxZoom() {
|
||||
return (maxZoom == 0) ? super.getMaxZoom() : maxZoom;
|
||||
}
|
||||
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.IfNoneMatch;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package org.openstreetmap.gui.jmapviewer.tilesources;
|
||||
|
||||
|
||||
public class TemplatedTMSTileSource extends AbstractOsmTileSource {
|
||||
private int maxZoom;
|
||||
|
||||
public TemplatedTMSTileSource(String name, String url, int maxZoom) {
|
||||
super(name, url);
|
||||
this.maxZoom = maxZoom;
|
||||
}
|
||||
|
||||
public String getTileUrl(int zoom, int tilex, int tiley) {
|
||||
return this.baseUrl
|
||||
.replaceAll("\\{zoom\\}", Integer.toString(zoom))
|
||||
.replaceAll("\\{x\\}", Integer.toString(tilex))
|
||||
.replaceAll("\\{y\\}", Integer.toString(tiley));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxZoom() {
|
||||
return (maxZoom == 0) ? super.getMaxZoom() : maxZoom;
|
||||
}
|
||||
|
||||
public TileUpdate getTileUpdate() {
|
||||
return TileUpdate.IfNoneMatch;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue