001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.GridBagLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.KeyEvent; 011 012import javax.swing.JLabel; 013import javax.swing.JOptionPane; 014import javax.swing.JPanel; 015import javax.swing.event.DocumentEvent; 016import javax.swing.event.DocumentListener; 017 018import org.openstreetmap.josm.Main; 019import org.openstreetmap.josm.data.Bounds; 020import org.openstreetmap.josm.data.coor.LatLon; 021import org.openstreetmap.josm.gui.MapFrame; 022import org.openstreetmap.josm.gui.MapFrameListener; 023import org.openstreetmap.josm.gui.MapView; 024import org.openstreetmap.josm.gui.widgets.JosmTextField; 025import org.openstreetmap.josm.tools.GBC; 026import org.openstreetmap.josm.tools.ImageProvider; 027import org.openstreetmap.josm.tools.OsmUrlToBounds; 028import org.openstreetmap.josm.tools.Shortcut; 029 030/** 031 * Allows to jump to a specific location. 032 * @since 2575 033 */ 034public class JumpToAction extends JosmAction { 035 036 /** 037 * Constructs a new {@code JumpToAction}. 038 */ 039 public JumpToAction() { 040 super(tr("Jump To Position"), (ImageProvider) null, tr("Opens a dialog that allows to jump to a specific location"), 041 Shortcut.registerShortcut("tools:jumpto", tr("Tool: {0}", tr("Jump To Position")), 042 KeyEvent.VK_J, Shortcut.CTRL), true, "action/jumpto", true); 043 putValue("help", ht("/Action/JumpToPosition")); 044 } 045 046 private final JosmTextField url = new JosmTextField(); 047 private final JosmTextField lat = new JosmTextField(); 048 private final JosmTextField lon = new JosmTextField(); 049 private final JosmTextField zm = new JosmTextField(); 050 051 class OsmURLListener implements DocumentListener { 052 @Override public void changedUpdate(DocumentEvent e) { parseURL(); } 053 @Override public void insertUpdate(DocumentEvent e) { parseURL(); } 054 @Override public void removeUpdate(DocumentEvent e) { parseURL(); } 055 } 056 057 class OsmLonLatListener implements DocumentListener { 058 @Override public void changedUpdate(DocumentEvent e) { updateUrl(false); } 059 @Override public void insertUpdate(DocumentEvent e) { updateUrl(false); } 060 @Override public void removeUpdate(DocumentEvent e) { updateUrl(false); } 061 } 062 063 /** 064 * Displays the "Jump to" dialog. 065 */ 066 public void showJumpToDialog() { 067 if (!Main.isDisplayingMapView()) { 068 return; 069 } 070 MapView mv = Main.map.mapView; 071 LatLon curPos = mv.getProjection().eastNorth2latlon(mv.getCenter()); 072 lat.setText(Double.toString(curPos.lat())); 073 lon.setText(Double.toString(curPos.lon())); 074 075 double dist = mv.getDist100Pixel(); 076 double zoomFactor = 1/dist; 077 078 zm.setText(Long.toString(Math.round(dist*100)/100)); 079 updateUrl(true); 080 081 JPanel panel = new JPanel(new BorderLayout()); 082 panel.add(new JLabel("<html>" 083 + tr("Enter Lat/Lon to jump to position.") 084 + "<br>" 085 + tr("You can also paste an URL from www.openstreetmap.org") 086 + "<br>" 087 + "</html>"), 088 BorderLayout.NORTH); 089 090 OsmLonLatListener x = new OsmLonLatListener(); 091 lat.getDocument().addDocumentListener(x); 092 lon.getDocument().addDocumentListener(x); 093 zm.getDocument().addDocumentListener(x); 094 url.getDocument().addDocumentListener(new OsmURLListener()); 095 096 JPanel p = new JPanel(new GridBagLayout()); 097 panel.add(p, BorderLayout.NORTH); 098 099 p.add(new JLabel(tr("Latitude")), GBC.eol()); 100 p.add(lat, GBC.eol().fill(GBC.HORIZONTAL)); 101 102 p.add(new JLabel(tr("Longitude")), GBC.eol()); 103 p.add(lon, GBC.eol().fill(GBC.HORIZONTAL)); 104 105 p.add(new JLabel(tr("Zoom (in metres)")), GBC.eol()); 106 p.add(zm, GBC.eol().fill(GBC.HORIZONTAL)); 107 108 p.add(new JLabel(tr("URL")), GBC.eol()); 109 p.add(url, GBC.eol().fill(GBC.HORIZONTAL)); 110 111 Object[] buttons = { tr("Jump there"), tr("Cancel") }; 112 LatLon ll = null; 113 double zoomLvl = 100; 114 while(ll == null) { 115 int option = JOptionPane.showOptionDialog( 116 Main.parent, 117 panel, 118 tr("Jump to Position"), 119 JOptionPane.OK_CANCEL_OPTION, 120 JOptionPane.PLAIN_MESSAGE, 121 null, 122 buttons, 123 buttons[0]); 124 125 if (option != JOptionPane.OK_OPTION) return; 126 try { 127 zoomLvl = Double.parseDouble(zm.getText()); 128 ll = new LatLon(Double.parseDouble(lat.getText()), Double.parseDouble(lon.getText())); 129 } catch (NumberFormatException ex) { 130 JOptionPane.showMessageDialog(Main.parent, tr("Could not parse Latitude, Longitude or Zoom. Please check."), tr("Unable to parse Lon/Lat"), JOptionPane.ERROR_MESSAGE); 131 } 132 } 133 134 mv.zoomToFactor(mv.getProjection().latlon2eastNorth(ll), zoomFactor * zoomLvl); 135 } 136 137 private void parseURL() { 138 if (!url.hasFocus()) return; 139 String urlText = url.getText(); 140 Bounds b = OsmUrlToBounds.parse(urlText); 141 if (b != null) { 142 lat.setText(Double.toString((b.getMinLat() + b.getMaxLat())/2)); 143 lon.setText(Double.toString((b.getMinLon() + b.getMaxLon())/2)); 144 145 int zoomLvl = 16; 146 int hashIndex = urlText.indexOf("#map"); 147 if (hashIndex >= 0) { 148 zoomLvl = Integer.parseInt(urlText.substring(hashIndex+5, urlText.indexOf('/', hashIndex))); 149 } else { 150 String[] args = urlText.substring(urlText.indexOf('?')+1).split("&"); 151 for (String arg : args) { 152 int eq = arg.indexOf('='); 153 if (eq == -1 || !"zoom".equalsIgnoreCase(arg.substring(0, eq))) continue; 154 155 zoomLvl = Integer.parseInt(arg.substring(eq + 1)); 156 break; 157 } 158 } 159 160 // 10 000 000 = 10 000 * 1000 = World * (km -> m) 161 zm.setText(Double.toString(Math.round(10000000 * Math.pow(2, (-1) * zoomLvl)))); 162 } 163 } 164 165 private void updateUrl(boolean force) { 166 if(!lat.hasFocus() && !lon.hasFocus() && !zm.hasFocus() && !force) return; 167 try { 168 double dlat = Double.parseDouble(lat.getText()); 169 double dlon = Double.parseDouble(lon.getText()); 170 double m = Double.parseDouble(zm.getText()); 171 // Inverse function to the one above. 18 is the current maximum zoom 172 // available on standard renderers, so choose this is in case m 173 // should be zero 174 int zoomLvl = 18; 175 if(m > 0) 176 zoomLvl = (int)Math.round((-1) * Math.log(m/10000000)/Math.log(2)); 177 178 url.setText(OsmUrlToBounds.getURL(dlat, dlon, zoomLvl)); 179 } catch (NumberFormatException x) { 180 Main.debug(x.getMessage()); 181 } 182 } 183 184 @Override 185 public void actionPerformed(ActionEvent e) { 186 showJumpToDialog(); 187 } 188 189 @Override 190 protected void updateEnabledState() { 191 setEnabled(Main.isDisplayingMapView()); 192 } 193 194 @Override 195 protected void installAdapters() { 196 super.installAdapters(); 197 // make this action listen to mapframe change events 198 Main.addMapFrameListener(new MapFrameListener() { 199 @Override 200 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { 201 updateEnabledState(); 202 } 203 }); 204 } 205}