package tim.prune.gui.map; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.MediaTracker; import java.awt.image.BufferedImage; import java.net.MalformedURLException; import java.net.URL; import javax.swing.ImageIcon; import javax.swing.JOptionPane; import javax.swing.JPanel; import tim.prune.I18nManager; import tim.prune.data.DoubleRange; import tim.prune.data.Track; /** * Class for the map canvas, to display a background map and draw on it */ public class MapCanvas extends JPanel { private BufferedImage _mapImage = null; private Track _track = null; private DoubleRange _latRange = null, _lonRange = null; private DoubleRange _xRange = null, _yRange = null; private boolean _gettingTiles = false; /** Current zoom level */ private int _currZoom = 0; /** Maximum zoom level (to avoid panning) */ private int _maxZoom = 0; /** * Constructor * @param inTrack track object */ public MapCanvas(Track inTrack) { _track = inTrack; _latRange = inTrack.getLatRange(); _lonRange = inTrack.getLonRange(); _xRange = new DoubleRange(transformX(_lonRange.getMinimum()), transformX(_lonRange.getMaximum())); _yRange = new DoubleRange(transformY(_latRange.getMinimum()), transformY(_latRange.getMaximum())); } /** * Paint method * @see java.awt.Canvas#paint(java.awt.Graphics) */ public void paint(Graphics g) { super.paint(g); if (_mapImage == null && !_gettingTiles) { _gettingTiles = true; new Thread(new Runnable() { public void run() { getMapTiles(); } }).start(); } if (_mapImage != null) { g.drawImage(_mapImage, 0, 0, 512, 512, null); } } /** * Get the map tiles for the specified track range */ private void getMapTiles() { _mapImage = new BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB); // zoom out until mins and maxes all on same group of four tiles for (int zoom=15; zoom>1; zoom--) { int tx1 = (int) Math.floor(_xRange.getMinimum() * (1<= (ty2-1)) { _currZoom = zoom; _maxZoom = zoom; getMapTiles(tx1, ty1); break; } } _gettingTiles = false; repaint(); } /** * Get the map tiles for the current zoom level and given tile parameters * @param inTileX x index of leftmost tile * @param inTileY y index of lower tile */ private void getMapTiles(int inTileX, int inTileY) { // Check if tile parameters were given if (inTileX == -1 || inTileY == -1) { double tileX = _xRange.getMinimum() * (1<<_currZoom); double tileY = _yRange.getMinimum() * (1<<_currZoom); inTileX = (int) Math.floor(tileX); inTileY = (int) Math.floor(tileY); // see if should be shifted by 1 to make more central if (_currZoom != _maxZoom) { if ((tileX - inTileX) < 0.5) inTileX--; // don't squash to left if ((tileY - inTileY) < 0.5) inTileY--; // don't squash too high } } try { ImageIcon[] icons = new ImageIcon[4]; boolean loadingFailed = false; // Clear map Graphics g = _mapImage.getGraphics(); g.clearRect(0, 0, 512, 512); for (int i=0; i<4 && !loadingFailed; i++) { String url = "http://tile.openstreetmap.org/" + _currZoom + "/" + (inTileX + i%2) + "/" + (inTileY + i/2) + ".png"; icons[i] = new ImageIcon(new URL(url)); if (icons[i] == null || icons[i].getImage() == null || icons[i].getImageLoadStatus() == MediaTracker.ERRORED) { loadingFailed = true; } g.drawImage(icons[i].getImage(), 256*(i%2), 256*(i/2), 256, 256, null); } // show message if loading failed if (loadingFailed) { JOptionPane.showMessageDialog(this, I18nManager.getText("error.osmimage.failed"), I18nManager.getText("error.osmimage.dialogtitle"), JOptionPane.ERROR_MESSAGE); } // red rectangle int rectX1 = (int) (256 * ((_xRange.getMinimum() * (1<<_currZoom)) - inTileX)); int rectX2 = (int) (256 * ((_xRange.getMaximum() * (1<<_currZoom)) - inTileX)); int rectY1 = (int) (256 * ((_yRange.getMinimum() * (1<<_currZoom)) - inTileY)); int rectY2 = (int) (256 * ((_yRange.getMaximum() * (1<<_currZoom)) - inTileY)); g.setColor(Color.RED); g.drawRect(rectX1, rectY1, rectX2-rectX1, rectY2-rectY1); // draw points g.setColor(Color.BLUE); for (int i=0; i<_track.getNumPoints(); i++) { int px = (int) (256 * ((transformX(_track.getPoint(i).getLongitude().getDouble()) * (1<<_currZoom)) - inTileX)); int py = (int) (256 * ((transformY(_track.getPoint(i).getLatitude().getDouble()) * (1<<_currZoom)) - inTileY)); g.drawRect(px, py, 2, 2); } } catch (MalformedURLException urle) { _mapImage = null; } } /** * Zoom out, if not already at minimum zoom */ public void zoomOut() { if (_currZoom >= 2) { _currZoom--; getMapTiles(-1, -1); repaint(); } } /** * Zoom in, if not already at maximum zoom */ public void zoomIn() { if (_currZoom < _maxZoom) { _currZoom++; getMapTiles(-1, -1); repaint(); } } /** * Transform a longitude into an x coordinate * @param inLon longitude in degrees * @return scaled X value from 0 to 1 */ private static double transformX(double inLon) { return (inLon + 180.0) / 360.0; } /** * Transform a latitude into a y coordinate * @param inLat latitude in degrees * @return scaled Y value from 0 to 1 */ private static double transformY(double inLat) { return (1 - Math.log(Math.tan(inLat * Math.PI / 180) + 1 / Math.cos(inLat * Math.PI / 180)) / Math.PI) / 2; } /** * @see javax.swing.JComponent#getMinimumSize() */ public Dimension getMinimumSize() { final Dimension minSize = new Dimension(512, 512); return minSize; } /** * @see javax.swing.JComponent#getPreferredSize() */ public Dimension getPreferredSize() { return getMinimumSize(); } }