Ticket #7237: LatLonToolPatch.diff

File LatLonToolPatch.diff, 21.5 KB (added by lysgaard, 12 years ago)

Patch with the code

  • src/utilsplugin2/UtilsPlugin2.java

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: images/latlon.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
     
    3232    JMenuItem undoSelection;
    3333    JMenuItem extractPoint;
    3434    JMenuItem wiki;
     35    JMenuItem latlon;
    3536   
    3637    JMenuItem replaceGeometry;
    3738    JMenuItem tagBuffer;
     
    6465        extractPoint = MainMenu.add(toolsMenu, new ExtractPointAction());
    6566        symmetry = MainMenu.add(toolsMenu, new SymmetryAction());
    6667        wiki = MainMenu.add(toolsMenu, new OpenPageAction());
     68        latlon = MainMenu.add(toolsMenu, new LatLonAction());
    6769
    6870        JMenu selectionMenu = Main.main.menu.addMenu(marktr("Selection"), KeyEvent.VK_N, Main.main.menu.defaultMenuPos, "help");
    6971        selectWayNodes = MainMenu.add(selectionMenu, new SelectWayNodesAction());
  • src/utilsplugin2/LatLonAction.java

     
     1// License: GPL. Copyright 2007 by Immanuel Scholz and others
     2package utilsplugin2;
     3
     4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6
     7import java.awt.event.ActionEvent;
     8import java.awt.event.KeyEvent;
     9
     10import java.util.Collection;
     11import java.util.LinkedList;
     12
     13import org.openstreetmap.josm.Main;
     14import org.openstreetmap.josm.command.AddCommand;
     15import org.openstreetmap.josm.command.SequenceCommand;
     16import org.openstreetmap.josm.command.Command;
     17import org.openstreetmap.josm.data.coor.LatLon;
     18import org.openstreetmap.josm.data.osm.Node;
     19import org.openstreetmap.josm.data.osm.Way;
     20import org.openstreetmap.josm.tools.Shortcut;
     21import org.openstreetmap.josm.actions.JosmAction;
     22
     23/**
     24 * This action displays a dialog where the user can enter a latitude and longitude,
     25 * and when ok is pressed, a new node is created at the specified position.
     26 */
     27public final class LatLonAction extends JosmAction {
     28    // remember input from last time
     29    private String textLatLon;
     30
     31    public LatLonAction() {
     32        super(tr("Lat Lon tool"), "latlon", tr("Create geometry by entering lat lon coordinates for it."),
     33                Shortcut.registerShortcut("addnode", tr("Edit: {0}", tr("Add Node...")), KeyEvent.VK_D, Shortcut.GROUP_EDIT,
     34                        Shortcut.SHIFT_DEFAULT), true);
     35        putValue("help", ht("/Action/AddNode"));
     36    }
     37
     38    public void actionPerformed(ActionEvent e) {
     39        if (!isEnabled())
     40            return;
     41
     42        LatLonDialog dialog = new LatLonDialog(Main.parent, tr("Add Node..."), ht("/Action/AddNode"));
     43
     44        if (textLatLon != null) {
     45            dialog.setLatLonText(textLatLon);
     46        }
     47
     48        dialog.showDialog();
     49       
     50        if (dialog.getValue() != 1)
     51            return;
     52
     53        LatLon[] coordinates = dialog.getCoordinates();
     54        String type = dialog.getGeomType();
     55        if (coordinates == null)
     56            return;
     57
     58        textLatLon = dialog.getLatLonText();
     59
     60        // we create a list of commands that will modify the map in the way we want.
     61        Collection<Command> cmds = new LinkedList<Command>();
     62        // first we create all the nodes, then we do extra stuff based on what geometry type we need.
     63        LinkedList<Node> nodes = new LinkedList<Node>();
     64
     65        for (LatLon ll : coordinates) {
     66            Node nnew = new Node(ll);
     67            nodes.add(nnew);
     68            cmds.add(new AddCommand(nnew));
     69        }
     70
     71        if (type == "Nodes") {
     72            //we dont need to do anything, we already have all the nodes
     73        }
     74        if (type == "Line") {
     75            Way wnew = new Way();
     76            wnew.setNodes(nodes);
     77            cmds.add(new AddCommand(wnew));
     78        }
     79        if (type == "Area") {
     80            nodes.add(nodes.get(0)); // this is needed to close the way.
     81            Way wnew = new Way();
     82            wnew.setNodes(nodes);
     83            cmds.add(new AddCommand(wnew));
     84        }
     85        Main.main.undoRedo.add(new SequenceCommand(tr("Lat Lon tool"), cmds));
     86        Main.map.mapView.repaint();
     87    }
     88
     89    @Override
     90    protected void updateEnabledState() {
     91        setEnabled(getEditLayer() != null);
     92    }
     93}
  • src/utilsplugin2/LatLonDialog.java

     
     1// License: GPL. For details, see LICENSE file.
     2package utilsplugin2;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Color;
     7import java.awt.Component;
     8import java.awt.GridBagLayout;
     9import java.awt.event.ActionEvent;
     10import java.awt.event.FocusEvent;
     11import java.awt.event.FocusListener;
     12import java.awt.event.WindowAdapter;
     13import java.awt.event.WindowEvent;
     14import java.text.NumberFormat;
     15import java.text.ParsePosition;
     16import java.util.ArrayList;
     17import java.util.List;
     18import java.util.Locale;
     19import java.util.regex.Matcher;
     20import java.util.regex.Pattern;
     21
     22import javax.swing.AbstractAction;
     23import javax.swing.BorderFactory;
     24import javax.swing.JLabel;
     25import javax.swing.JPanel;
     26import javax.swing.JSeparator;
     27import javax.swing.JTabbedPane;
     28
     29import javax.swing.JTextArea;
     30import javax.swing.JComboBox;
     31
     32import javax.swing.UIManager;
     33import javax.swing.event.ChangeEvent;
     34import javax.swing.event.ChangeListener;
     35import javax.swing.event.DocumentEvent;
     36import javax.swing.event.DocumentListener;
     37
     38import org.openstreetmap.josm.Main;
     39import org.openstreetmap.josm.data.coor.CoordinateFormat;
     40import org.openstreetmap.josm.data.coor.LatLon;
     41import org.openstreetmap.josm.gui.ExtendedDialog;
     42import org.openstreetmap.josm.gui.widgets.HtmlPanel;
     43import org.openstreetmap.josm.tools.GBC;
     44import org.openstreetmap.josm.tools.ImageProvider;
     45import org.openstreetmap.josm.tools.WindowGeometry;
     46
     47public class LatLonDialog extends ExtendedDialog {
     48    private static final Color BG_COLOR_ERROR = new Color(255,224,224);
     49
     50    public JTabbedPane tabs;
     51    private JTextArea taLatLon;
     52    private JComboBox cbGeomType;
     53    private LatLon[] latLonCoordinates;
     54
     55    private static final double ZERO = 0.0;
     56    private static final String DEG = "\u00B0";
     57    private static final String MIN = "\u2032";
     58    private static final String SEC = "\u2033";
     59
     60    private static final char N_TR = LatLon.NORTH.charAt(0);
     61    private static final char S_TR = LatLon.SOUTH.charAt(0);
     62    private static final char E_TR = LatLon.EAST.charAt(0);
     63    private static final char W_TR = LatLon.WEST.charAt(0);
     64
     65    private static final Pattern p = Pattern.compile(
     66            "([+|-]?\\d+[.,]\\d+)|"             // (1)
     67            + "([+|-]?\\d+)|"                   // (2)
     68            + "("+DEG+"|o|deg)|"                // (3)
     69            + "('|"+MIN+"|min)|"                // (4)
     70            + "(\"|"+SEC+"|sec)|"               // (5)
     71            + "(,|;)|"                          // (6)
     72            + "([NSEW"+N_TR+S_TR+E_TR+W_TR+"])|"// (7)
     73            + "\\s+|"
     74            + "(.+)");
     75
     76    protected JPanel buildLatLon() {
     77        JPanel pnl = new JPanel(new GridBagLayout());
     78        pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
     79
     80        pnl.add(new JLabel(tr("Coordinates:")), GBC.std().insets(0,10,5,0));
     81        taLatLon = new JTextArea(24,24);
     82        pnl.add(taLatLon, GBC.eol().insets(0,10,0,0).fill(GBC.HORIZONTAL).weight(2.0, 0.0));
     83
     84        cbGeomType = new JComboBox(new Object[] {"Nodes", "Line", "Area"});
     85        pnl.add(cbGeomType, GBC.eol().insets(0,10,0,0).fill(GBC.HORIZONTAL).weight(2.0, 0.0));
     86
     87        pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
     88
     89        pnl.add(new HtmlPanel(
     90                tr("Enter the coordinates for the new nodes, one for each line.<br/>If you enter two lines with the same coordinates there will be generated duplicate nodes.<br/>You can separate longitude and latitude with space, comma or semicolon.<br/>" +
     91                    "Use positive numbers or N, E characters to indicate North or East cardinal direction.<br/>" +
     92                    "For South and West cardinal directions you can use either negative numbers or S, W characters.<br/>" +
     93                    "Coordinate value can be in one of three formats:<ul>" +
     94                    "<li><i>degrees</i><tt>&deg;</tt></li>" +
     95                    "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt></li>" +
     96                    "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt> <i>seconds</i><tt>&quot</tt></li>" +
     97                    "</ul>" +
     98                    "Symbols <tt>&deg;</tt>, <tt>&#39;</tt>, <tt>&prime;</tt>, <tt>&quot;</tt>, <tt>&Prime;</tt> are optional.<br/><br/>" +
     99                    "Some examples:<ul>" +
     100                    "<li>49.29918&deg; 19.24788&deg;</li>" +
     101                    "<li>N 49.29918 E 19.24788</li>" +
     102                    "<li>W 49&deg;29.918&#39; S 19&deg;24.788&#39;</li>" +
     103                    "<li>N 49&deg;29&#39;04&quot; E 19&deg;24&#39;43&quot;</li>" +
     104                    "<li>49.29918 N, 19.24788 E</li>" +
     105                    "<li>49&deg;29&#39;21&quot; N 19&deg;24&#39;38&quot; E</li>" +
     106                    "<li>49 29 51, 19 24 18</li>" +
     107                    "<li>49 29, 19 24</li>" +
     108                    "<li>E 49 29, N 19 24</li>" +
     109                    "<li>49&deg; 29; 19&deg; 24</li>" +
     110                    "<li>N 49&deg; 29, W 19&deg; 24</li>" +
     111                    "<li>49&deg; 29.5 S, 19&deg; 24.6 E</li>" +
     112                    "<li>N 49 29.918 E 19 15.88</li>" +
     113                    "<li>49 29.4 19 24.5</li>" +
     114                    "<li>-49 29.4 N -19 24.5 W</li></ul>" +
     115                    "<li>48 deg 42&#39; 52.13\" N, 21 deg 11&#39; 47.60\" E</li></ul>"
     116                )),
     117                GBC.eol().fill().weight(1.0, 1.0));
     118
     119        // parse and verify input on the fly
     120        //
     121        LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
     122        taLatLon.getDocument().addDocumentListener(inputVerifier);
     123
     124        // select the text in the field on focus
     125        //
     126        TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
     127        taLatLon.addFocusListener(focusHandler);
     128        return pnl;
     129    }
     130
     131    protected void build() {
     132        tabs = new JTabbedPane();
     133        tabs.addTab(tr("Lat/Lon"), buildLatLon());
     134        tabs.getModel().addChangeListener(new ChangeListener() {
     135            @Override
     136            public void stateChanged(ChangeEvent e) {
     137                switch (tabs.getModel().getSelectedIndex()) {
     138                    case 0: parseLatLonUserInput(); break;
     139                    default: throw new AssertionError();
     140                }
     141            }
     142        });
     143        setContent(tabs, false);
     144    }
     145
     146    public LatLonDialog(Component parent, String title, String help) {
     147        super(Main.parent, tr("Add Node..."), new String[] { tr("Ok"), tr("Cancel") });
     148        setButtonIcons(new String[] { "ok", "cancel" });
     149        configureContextsensitiveHelp("/Action/AddNode", true);
     150
     151        build();
     152        setCoordinates(null);
     153    }
     154
     155    public void setCoordinates(LatLon[] ll) {
     156        if (ll == null) {
     157            ll = new LatLon[] {};
     158        }
     159        this.latLonCoordinates = ll;
     160    String text = "";
     161    for (LatLon latlon : ll) {
     162            text = text + latlon.latToString(CoordinateFormat.getDefaultFormat()) + " " + latlon.lonToString(CoordinateFormat.getDefaultFormat()) + "\n";
     163        }
     164        taLatLon.setText(text);
     165        setOkEnabled(true);
     166    }
     167
     168    public LatLon[] getCoordinates() {
     169        return latLonCoordinates;
     170    }
     171
     172    public LatLon[] getLatLonCoordinates() {
     173        return latLonCoordinates;
     174    }
     175
     176    public String getGeomType() {
     177        return cbGeomType.getSelectedItem().toString();
     178    }
     179
     180    protected void setErrorFeedback(JTextArea tf, String message) {
     181        tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
     182        tf.setToolTipText(message);
     183        tf.setBackground(BG_COLOR_ERROR);
     184    }
     185
     186    protected void clearErrorFeedback(JTextArea tf, String message) {
     187        tf.setBorder(UIManager.getBorder("TextField.border"));
     188        tf.setToolTipText(message);
     189        tf.setBackground(UIManager.getColor("TextField.background"));
     190    }
     191
     192    protected Double parseDoubleFromUserInput(String input) {
     193        if (input == null) return null;
     194        // remove white space and an optional degree symbol
     195        //
     196        input = input.trim();
     197        input = input.replaceAll(DEG, "");
     198
     199        // try to parse using the current locale
     200        //
     201        NumberFormat f = NumberFormat.getNumberInstance();
     202        Number n=null;
     203        ParsePosition pp = new ParsePosition(0);
     204        n = f.parse(input,pp);
     205        if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length()) {
     206            // fall back - try to parse with the english locale
     207            //
     208            pp = new ParsePosition(0);
     209            f = NumberFormat.getNumberInstance(Locale.ENGLISH);
     210            n = f.parse(input, pp);
     211            if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length())
     212                return null;
     213        }
     214        return n== null ? null : n.doubleValue();
     215    }
     216
     217    protected void parseLatLonUserInput() {
     218        LatLon[] latLons;
     219        try {
     220            latLons = parseLatLons(taLatLon.getText());
     221        Boolean working = true;
     222        int i=0;
     223        while (working && i < latLons.length) {
     224        if (!LatLon.isValidLat(latLons[i].lat()) || !LatLon.isValidLon(latLons[i].lon())) {
     225                    latLons = null;
     226            working = false;
     227                }
     228        i++;
     229        }
     230        } catch (IllegalArgumentException e) {
     231            latLons = null;
     232        }
     233        if (latLons == null) {
     234            setErrorFeedback(taLatLon, tr("Please enter a GPS coordinates"));
     235            latLonCoordinates = null;
     236            setOkEnabled(false);
     237        } else {
     238            clearErrorFeedback(taLatLon,tr("Please enter a GPS coordinates"));
     239            latLonCoordinates = latLons;
     240            setOkEnabled(true);
     241        }
     242    }
     243
     244    private void setOkEnabled(boolean b) {
     245        if (buttons != null && buttons.size() > 0) {
     246            buttons.get(0).setEnabled(b);
     247        }
     248    }
     249
     250    @Override
     251    public void setVisible(boolean visible) {
     252        if (visible) {
     253            WindowGeometry.centerInWindow(Main.parent, getSize()).applySafe(this);
     254        }
     255        super.setVisible(visible);
     256    }
     257
     258    class LatLonInputVerifier implements DocumentListener {
     259        public void changedUpdate(DocumentEvent e) {
     260            parseLatLonUserInput();
     261        }
     262
     263        public void insertUpdate(DocumentEvent e) {
     264            parseLatLonUserInput();
     265        }
     266
     267        public void removeUpdate(DocumentEvent e) {
     268            parseLatLonUserInput();
     269        }
     270    }
     271
     272    static class TextFieldFocusHandler implements FocusListener {
     273        public void focusGained(FocusEvent e) {
     274            Component c = e.getComponent();
     275            if (c instanceof JTextArea) {
     276                JTextArea tf = (JTextArea)c;
     277                tf.selectAll();
     278            }
     279        }
     280        public void focusLost(FocusEvent e) {}
     281    }
     282
     283    private static LatLon[] parseLatLons(final String text) {
     284        String lines[] = text.split("\\r?\\n");
     285        List<LatLon> latLons = new ArrayList<LatLon>();
     286        for (String line : lines) {
     287            latLons.add(parseLatLon(line));
     288        }
     289        return latLons.toArray(new LatLon[]{});
     290    }
     291
     292    private static LatLon parseLatLon(final String coord) {
     293        final Matcher m = p.matcher(coord);
     294
     295        final StringBuilder sb = new StringBuilder();
     296        final List<Object> list = new ArrayList<Object>();
     297
     298        while (m.find()) {
     299            if (m.group(1) != null) {
     300                sb.append('R');     // floating point number
     301                list.add(Double.parseDouble(m.group(1).replace(',', '.')));
     302            } else if (m.group(2) != null) {
     303                sb.append('Z');     // integer number
     304                list.add(Double.parseDouble(m.group(2)));
     305            } else if (m.group(3) != null) {
     306                sb.append('o');     // degree sign
     307            } else if (m.group(4) != null) {
     308                sb.append('\'');    // seconds sign
     309            } else if (m.group(5) != null) {
     310                sb.append('"');     // minutes sign
     311            } else if (m.group(6) != null) {
     312                sb.append(',');     // separator
     313            } else if (m.group(7) != null) {
     314                sb.append("x");     // cardinal direction
     315                String c = m.group(7).toUpperCase();
     316                if (c.equals("N") || c.equals("S") || c.equals("E") || c.equals("W")) {
     317                    list.add(c);
     318                } else {
     319                    list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
     320                            .replace(E_TR, 'E').replace(W_TR, 'W'));
     321                }
     322            } else if (m.group(8) != null) {
     323                throw new IllegalArgumentException("invalid token: " + m.group(8));
     324            }
     325        }
     326
     327        final String pattern = sb.toString();
     328
     329        final Object[] params = list.toArray();
     330        final LatLonHolder latLon = new LatLonHolder();
     331
     332        if (pattern.matches("Ro?,?Ro?")) {
     333            setLatLonObj(latLon,
     334                    params[0], ZERO, ZERO, "N",
     335                    params[1], ZERO, ZERO, "E");
     336        } else if (pattern.matches("xRo?,?xRo?")) {
     337            setLatLonObj(latLon,
     338                    params[1], ZERO, ZERO, params[0],
     339                    params[3], ZERO, ZERO, params[2]);
     340        } else if (pattern.matches("Ro?x,?Ro?x")) {
     341            setLatLonObj(latLon,
     342                    params[0], ZERO, ZERO, params[1],
     343                    params[2], ZERO, ZERO, params[3]);
     344        } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
     345            setLatLonObj(latLon,
     346                    params[0], params[1], ZERO, "N",
     347                    params[2], params[3], ZERO, "E");
     348        } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
     349            setLatLonObj(latLon,
     350                    params[1], params[2], ZERO, params[0],
     351                    params[4], params[5], ZERO, params[3]);
     352        } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
     353            setLatLonObj(latLon,
     354                    params[0], params[1], ZERO, params[2],
     355                    params[3], params[4], ZERO, params[5]);
     356        } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
     357            setLatLonObj(latLon,
     358                    params[0], params[1], params[2], params[3],
     359                    params[4], params[5], params[6], params[7]);
     360        } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
     361            setLatLonObj(latLon,
     362                    params[1], params[2], params[3], params[0],
     363                    params[5], params[6], params[7], params[4]);
     364        } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
     365            setLatLonObj(latLon,
     366                    params[0], params[1], params[2], "N",
     367                    params[3], params[4], params[5], "E");
     368        } else {
     369            throw new IllegalArgumentException("invalid format: " + pattern);
     370        }
     371
     372        return new LatLon(latLon.lat, latLon.lon);
     373    }
     374
     375    private static class LatLonHolder {
     376        double lat, lon;
     377    }
     378
     379    private static void setLatLonObj(final LatLonHolder latLon,
     380            final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
     381            final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
     382
     383        setLatLon(latLon,
     384                (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
     385                (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
     386    }
     387
     388    private static void setLatLon(final LatLonHolder latLon,
     389            final double coord1deg, final double coord1min, final double coord1sec, final String card1,
     390            final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
     391
     392        setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
     393        setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
     394    }
     395
     396    private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec, final String card) {
     397        if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
     398            throw new IllegalArgumentException("out of range");
     399        }
     400
     401        double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
     402        coord = card.equals("N") || card.equals("E") ? coord : -coord;
     403        if (card.equals("N") || card.equals("S")) {
     404            latLon.lat = coord;
     405        } else {
     406            latLon.lon = coord;
     407        }
     408    }
     409
     410    public String getLatLonText() {
     411        return taLatLon.getText();
     412    }
     413
     414    public void setLatLonText(String text) {
     415        taLatLon.setText(text);
     416    }
     417
     418}