Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/UtilsPlugin2.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/UtilsPlugin2.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/UtilsPlugin2.java	(revision 28028)
@@ -0,0 +1,309 @@
+// License: GPL v2 or later. See LICENSE file for details.
+package org.openstreetmap.josm.plugins.utilsplugin2;
+
+import java.awt.event.KeyEvent;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.search.PushbackTokenizer;
+import org.openstreetmap.josm.actions.search.SearchCompiler;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
+import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
+import org.openstreetmap.josm.actions.search.SearchCompiler.UnaryMatch;
+import org.openstreetmap.josm.actions.search.SearchCompiler.UnaryMatchFactory;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.utilsplugin2.actions.*;
+import org.openstreetmap.josm.plugins.utilsplugin2.curves.CurveAction;
+import org.openstreetmap.josm.plugins.utilsplugin2.customurl.ChooseURLAction;
+import org.openstreetmap.josm.plugins.utilsplugin2.customurl.OpenPageAction;
+import org.openstreetmap.josm.plugins.utilsplugin2.customurl.UtilsPluginPreferences;
+import org.openstreetmap.josm.plugins.utilsplugin2.latlon.LatLonAction;
+import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryAction;
+import org.openstreetmap.josm.plugins.utilsplugin2.selection.*;
+import static org.openstreetmap.josm.tools.I18n.marktr;
+
+public class UtilsPlugin2 extends Plugin {
+    JMenuItem unglueRelation;
+    JMenuItem symmetry;
+    JMenuItem addIntersections;
+    JMenuItem splitObject;
+    JMenuItem selectWayNodes;
+    JMenuItem adjNodes;
+    JMenuItem unsNodes;
+    JMenuItem midNodes;
+    JMenuItem adjWays;
+    JMenuItem adjWaysAll;
+    JMenuItem intWays;
+    JMenuItem intWaysR;
+    JMenuItem allInside;
+    JMenuItem undoSelection;
+    JMenuItem extractPoint;
+    JMenuItem wiki;
+    JMenuItem latlon;
+    
+    JMenuItem replaceGeometry;
+    JMenuItem tagBuffer;
+    JMenuItem sourceTag;
+    JMenuItem pasteRelations;
+    JMenuItem alignWayNodes;
+    JMenuItem splitOnIntersections;
+    JMenuItem selModifiedNodes;
+    JMenuItem selModifiedWays;
+    JMenuItem selectHighway;
+    JMenuItem selectAreaBoundary;
+    
+    JMenuItem selectURL;
+
+    JMenuItem drawArc;
+    
+    public UtilsPlugin2(PluginInformation info) {
+        super(info);
+
+        JMenu toolsMenu = Main.main.menu.addMenu(marktr("More tools"), KeyEvent.VK_Q, 4, "help");
+        unglueRelation = MainMenu.add(toolsMenu, new UnGlueRelationAction());
+        addIntersections = MainMenu.add(toolsMenu, new AddIntersectionsAction());
+        splitObject = MainMenu.add(toolsMenu, new SplitObjectAction());
+        
+        toolsMenu.addSeparator();
+        replaceGeometry = MainMenu.add(toolsMenu, new ReplaceGeometryAction());
+        tagBuffer = MainMenu.add(toolsMenu, new TagBufferAction());
+        sourceTag = MainMenu.add(toolsMenu, new TagSourceAction());
+        pasteRelations = MainMenu.add(toolsMenu, new PasteRelationsAction());
+        alignWayNodes = MainMenu.add(toolsMenu, new AlignWayNodesAction());
+        splitOnIntersections = MainMenu.add(toolsMenu, new SplitOnIntersectionsAction());
+        extractPoint = MainMenu.add(toolsMenu, new ExtractPointAction());
+        symmetry = MainMenu.add(toolsMenu, new SymmetryAction());
+        wiki = MainMenu.add(toolsMenu, new OpenPageAction());
+        latlon = MainMenu.add(toolsMenu, new LatLonAction());
+
+        JMenu selectionMenu = Main.main.menu.addMenu(marktr("Selection"), KeyEvent.VK_N, Main.main.menu.defaultMenuPos, "help");
+        selectWayNodes = MainMenu.add(selectionMenu, new SelectWayNodesAction());
+        adjNodes = MainMenu.add(selectionMenu, new AdjacentNodesAction());
+        unsNodes = MainMenu.add(selectionMenu, new UnselectNodesAction());
+        midNodes = MainMenu.add(selectionMenu, new MiddleNodesAction());
+        adjWays = MainMenu.add(selectionMenu, new AdjacentWaysAction());
+        adjWaysAll = MainMenu.add(selectionMenu, new ConnectedWaysAction());
+        intWays = MainMenu.add(selectionMenu, new IntersectedWaysAction());
+        intWaysR = MainMenu.add(selectionMenu, new IntersectedWaysRecursiveAction());
+        allInside = MainMenu.add(selectionMenu, new SelectAllInsideAction());
+        selModifiedNodes = MainMenu.add(selectionMenu, new SelectModNodesAction());
+        selModifiedWays = MainMenu.add(selectionMenu, new SelectModWaysAction());
+        undoSelection = MainMenu.add(selectionMenu, new UndoSelectionAction());
+        selectHighway = MainMenu.add(selectionMenu, new SelectHighwayAction());
+        selectAreaBoundary = MainMenu.add(selectionMenu, new SelectBoundaryAction());
+        
+        selectURL = MainMenu.add(toolsMenu, new ChooseURLAction());
+	drawArc = MainMenu.add(toolsMenu, new CurveAction());
+
+        // register search operators
+        SearchCompiler.addMatchFactory(new UtilsUnaryMatchFactory());
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        boolean enabled = newFrame != null;
+        enabled = false;
+        unglueRelation.setEnabled(enabled);
+        addIntersections.setEnabled(enabled);
+        splitObject.setEnabled(enabled);
+
+        replaceGeometry.setEnabled(enabled);
+        tagBuffer.setEnabled(enabled);
+        sourceTag.setEnabled(enabled);
+        pasteRelations.setEnabled(enabled);
+        alignWayNodes.setEnabled(enabled);
+        splitOnIntersections.setEnabled(enabled);
+        wiki.setEnabled(enabled);
+
+        selectWayNodes.setEnabled(enabled);
+        adjNodes.setEnabled(enabled);
+        unsNodes.setEnabled(enabled);
+        midNodes.setEnabled(enabled);
+        adjWays.setEnabled(enabled);
+        adjWaysAll.setEnabled(enabled);
+        intWays.setEnabled(enabled);
+        intWaysR.setEnabled(enabled);
+        selModifiedNodes.setEnabled(enabled);
+        selModifiedWays.setEnabled(enabled);
+        undoSelection.setEnabled(enabled);
+        selectURL.setEnabled(enabled);
+        allInside.setEnabled(enabled);
+
+        drawArc.setEnabled(enabled);
+    }
+    
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new UtilsPluginPreferences();
+    }
+    
+    public static class UtilsUnaryMatchFactory implements UnaryMatchFactory {
+        private static Collection<String> keywords = Arrays.asList("inside",
+                "intersecting", "allintersecting", "adjacent", "connected");
+        
+        @Override
+        public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError {
+            if ("inside".equals(keyword))
+                return new InsideMatch(matchOperand);
+            else if ("adjacent".equals(keyword))
+                return new ConnectedMatch(matchOperand, false);
+            else if ("connected".equals(keyword))
+                return new ConnectedMatch(matchOperand, true);
+            else if ("intersecting".equals(keyword))
+                return new IntersectingMatch(matchOperand, false);
+            else if ("allintersecting".equals(keyword))
+                return new IntersectingMatch(matchOperand, true);
+            return null;
+        }
+
+        @Override
+        public Collection<String> getKeywords() {
+            return keywords;
+        }
+    }
+
+    /**
+     * Matches all objects contained within the match expression.
+     */
+    public static class InsideMatch extends UnaryMatch {
+        private Collection<OsmPrimitive> inside = null;
+        
+        public InsideMatch(Match match) {
+            super(match);
+        }
+        
+        /**
+         * Find all objects inside areas which match the expression
+         */
+        private void init() {
+            Collection<OsmPrimitive> matchedAreas = new HashSet<OsmPrimitive>();
+
+            // find all ways that match the expression
+            Collection<Way> ways = Main.main.getCurrentDataSet().getWays();
+            for (Way way : ways) {
+                if (match.match(way))
+                    matchedAreas.add(way);
+            }
+            
+            // find all relations that match the expression
+            Collection<Relation> rels = Main.main.getCurrentDataSet().getRelations();
+            for (Relation rel : rels) {
+                if (match.match(rel))
+                    matchedAreas.add(rel);
+            }
+            
+            inside = NodeWayUtils.selectAllInside(matchedAreas, Main.main.getCurrentDataSet());
+        }
+
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            if (inside == null)
+                init(); // lazy initialization
+
+            return inside.contains(osm);
+        }
+    }
+    
+    public static class IntersectingMatch extends UnaryMatch {
+        private Collection<Way> intersecting = null;
+        boolean all;
+        
+        public IntersectingMatch(Match match, boolean all) {
+            super(match);
+            this.all=all;
+            //init(all);
+        }   
+        
+        /**
+         * Find (all) ways intersecting ways which match the expression.
+         */
+        private void init(boolean all) {
+            Collection<Way> matchedWays = new HashSet<Way>();
+            
+            // find all ways that match the expression
+            Collection<Way> allWays = Main.main.getCurrentDataSet().getWays();
+            for (Way way : allWays) {
+                if (match.match(way))
+                    matchedWays.add(way);
+            }
+            
+            Set<Way> newWays = new HashSet<Way>();
+            if (all)
+                NodeWayUtils.addWaysIntersectingWaysRecursively(allWays, matchedWays, newWays);
+            else
+                NodeWayUtils.addWaysIntersectingWays(allWays, matchedWays, newWays);
+            intersecting = newWays;
+        }
+        
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            if (intersecting==null) init(all); // lazy initialization
+            if (osm instanceof Way)
+                return intersecting.contains((Way)osm);
+            return false;
+        }
+    }
+    
+    public static class ConnectedMatch extends UnaryMatch {
+        private Collection<Way> connected = null;
+        boolean all;
+        
+        public ConnectedMatch(Match match, boolean all) {
+            super(match);
+            this.all=all;
+        }   
+        
+        /**
+         * Find (all) ways intersecting ways which match the expression.
+         */
+        private void init(boolean all) {
+            Collection<Way> matchedWays = new HashSet<Way>();
+            Set<Node> matchedNodes = new HashSet<Node>();
+            
+            // find all ways that match the expression
+            Collection<Way> allWays = Main.main.getCurrentDataSet().getWays();
+            for (Way way : allWays) {
+                if (match.match(way))
+                    matchedWays.add(way);
+            }
+            
+            // find all nodes that match the expression
+            Collection<Node> allNodes = Main.main.getCurrentDataSet().getNodes();
+            for (Node node: allNodes) {
+                if (match.match(node))
+                    matchedNodes.add(node);
+            }
+            
+            Set<Way> newWays = new HashSet<Way>();
+            if (all) {
+                NodeWayUtils.addWaysConnectedToNodes(matchedNodes, newWays);
+                NodeWayUtils.addWaysConnectedToWaysRecursively(matchedWays, newWays);
+            } else {
+                NodeWayUtils.addWaysConnectedToNodes(matchedNodes, newWays);
+                NodeWayUtils.addWaysConnectedToWays(matchedWays, newWays);
+            }
+            connected = newWays;
+        }
+        
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            if (connected==null) init(all); // lazy initialization
+            if (osm instanceof Way)
+                return connected.contains((Way)osm);
+            return false;
+        }
+    }   
+    
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AddIntersectionsAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AddIntersectionsAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AddIntersectionsAction.java	(revision 28028)
@@ -0,0 +1,81 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Geometry;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class AddIntersectionsAction extends JosmAction {
+    public AddIntersectionsAction() {
+        super(tr("Add nodes at intersections"), "addintersect", tr("Add missing nodes at intersections of selected ways."),
+                Shortcut.registerShortcut("tools:addintersect", tr("Tool: {0}", tr("Add nodes at intersections")), KeyEvent.VK_I, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/AddIntersections"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent arg0) {
+        if (!isEnabled())
+            return;
+        List<Way> ways = OsmPrimitive.getFilteredList(getCurrentDataSet().getSelected(), Way.class);
+        if (ways.isEmpty()) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("Please select one or more ways with intersections of segments."),
+                    tr("Information"),
+                    JOptionPane.INFORMATION_MESSAGE
+            );
+            return;
+        }
+
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        Geometry.addIntersections(ways, false, cmds);
+        if (!cmds.isEmpty()) {
+            Main.main.undoRedo.add(new SequenceCommand(tr("Add nodes at intersections"),cmds));
+            Set<Node> nodes = new HashSet<Node>(10);
+            // find and select newly added nodes
+            for (Command cmd: cmds) if (cmd instanceof AddCommand){
+                Collection<? extends OsmPrimitive> pp = cmd.getParticipatingPrimitives();
+                for ( OsmPrimitive p : pp) { // find all affected nodes
+                    if (p instanceof Node && p.isNew()) nodes.add((Node)p);
+                }
+                if (!nodes.isEmpty()) {
+                    getCurrentDataSet().setSelected(nodes);
+                    }
+                }
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AlignWayNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AlignWayNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/AlignWayNodesAction.java	(revision 28028)
@@ -0,0 +1,189 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.*;
+import java.util.*;
+import java.awt.event.ActionEvent;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.actions.JosmAction;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Pastes relation membership from objects in the paste buffer onto selected object(s).
+ *
+ * @author Zverik
+ */
+public class AlignWayNodesAction extends JosmAction {
+    private static final String TITLE = tr("Align Way Nodes");
+    private static final double MOVE_THRESHOLD = 1e-9;
+
+    public AlignWayNodesAction() {
+        super(TITLE, "dumbutils/alignwaynodes", tr("Align nodes in a way"),
+                Shortcut.registerShortcut("tools:alignwaynodes", tr("Tool: {0}", tr("Align Way Nodes")), KeyEvent.VK_L, Shortcut.SHIFT)
+                , true);
+    }
+
+    public void actionPerformed( ActionEvent e ) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = filterNodes(selection);
+        int selectedNodesCount = selectedNodes.size();
+        Set<Way> ways = findCommonWays(selectedNodes);
+        if( ways == null || ways.size() != 1 || selectedNodesCount == 0 )
+            return;
+        Way way = ways.iterator().next();
+        if( way.getNodesCount() < (way.isClosed() ? 4 : 3) ) {
+            JOptionPane.showMessageDialog(Main.parent, tr("The way with selected nodes can not be straightened."), TITLE, JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        // Prepare a list of nodes to align
+        int firstNodePos = findFirstNode(way, selectedNodes);
+        int lastNodePos = way.isClosed() ? firstNodePos : way.getNodesCount();
+        List<Node> nodes = new ArrayList<Node>();
+        int i = firstNodePos;
+        boolean iterated = false;
+        while( !iterated || i != lastNodePos ) {
+            Node node = way.getNode(i);
+            if( selectedNodes.contains(node) ) {
+                nodes.add(node);
+                selectedNodes.remove(node);
+                if( selectedNodesCount == 1 ) {
+                    nodes.add(0, way.getNode( i > 0 ? i - 1 : way.isClosed() ? way.getNodesCount() - 2 : i + 2 ));
+                    nodes.add(way.getNode( i + 1 < way.getNodesCount() ? i + 1 : way.isClosed() ? 1 : i - 2 ));
+                }
+                if( selectedNodes.isEmpty() )
+                    break;
+            } else if( selectedNodesCount == 2 && selectedNodes.size() == 1 )
+                nodes.add(node);
+            i++;
+            if( i >= way.getNodesCount() && way.isClosed() )
+                i = 0;
+            iterated = true;
+        }
+
+        if( nodes.size() < 3 ) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Internal error: number of nodes is {0}.", nodes.size()),
+                    TITLE, JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        // Now, we have an ordered list of nodes, of which idx 0 and N-1 serve as guides
+        // and 1..N-2 should be aligned with them
+        List<Command> commands = new ArrayList<Command>();
+        double ax = nodes.get(0).getEastNorth().east();
+        double ay = nodes.get(0).getEastNorth().north();
+        double bx = nodes.get(nodes.size() - 1).getEastNorth().east();
+        double by = nodes.get(nodes.size() - 1).getEastNorth().north();
+        
+        for( i = 1; i + 1 < nodes.size(); i++ ) {
+            Node n = nodes.get(i);
+            
+            // Algorithm is copied from org.openstreetmap.josm.actions.AlignInLineAction
+            double nx = n.getEastNorth().east();
+            double ny = n.getEastNorth().north();
+
+            if( ax == bx ) {
+                // Special case if AB is vertical...
+                nx = ax;
+            } else if( ay == by ) {
+                // ...or horizontal
+                ny = ay;
+            } else {
+                // Otherwise calculate position by solving y=mx+c (simplified)
+                double m1 = (by - ay) / (bx - ax);
+                double c1 = ay - (ax * m1);
+                double m2 = (-1) / m1;
+                double c2 = ny - (nx * m2);
+
+                nx = (c2 - c1) / (m1 - m2);
+                ny = (m1 * nx) + c1;
+            }
+
+            // Add the command to move the node to its new position.
+            if( Math.abs(nx - n.getEastNorth().east()) > MOVE_THRESHOLD && Math.abs(ny - n.getEastNorth().north()) > MOVE_THRESHOLD )
+                commands.add(new MoveCommand(n, nx - n.getEastNorth().east(), ny - n.getEastNorth().north()));
+        }
+
+        if( !commands.isEmpty() )
+            Main.main.undoRedo.add(new SequenceCommand(TITLE, commands));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        Set<Node> nodes = filterNodes(selection);
+        Set<Way> ways = findCommonWays(nodes);
+        setEnabled(ways != null && ways.size() == 1 && !nodes.isEmpty());
+    }
+
+    private Set<Way> findCommonWays( Set<Node> nodes ) {
+        Set<Way> ways = null;
+        for( Node n : nodes ) {
+            List<Way> referrers = OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
+            if( ways == null )
+                ways = new HashSet<Way>(referrers);
+            else {
+                ways.retainAll(referrers);
+            }
+        }
+        return ways;
+    }
+
+    private Set<Node> filterNodes( Collection<? extends OsmPrimitive> selection ) {
+        Set<Node> result = new HashSet<Node>();
+        if( selection != null ) {
+            for( OsmPrimitive p : selection )
+                if( p instanceof Node )
+                    result.add((Node)p);
+        }
+        return result;
+    }
+
+    /**
+     * Find the largest empty span between nodes and returns the index of the node right after it.
+     *
+     * TODO: not the maximum node count, but maximum distance!
+     */
+    private int findFirstNode( Way way, Set<Node> nodes ) {
+        int pos = 0;
+        while( pos < way.getNodesCount() && !nodes.contains(way.getNode(pos)) )
+            pos++;
+        if( pos >= way.getNodesCount() )
+            return 0;
+        if( !way.isClosed() || nodes.size() <= 1 )
+            return pos;
+
+        // now, way is closed
+        boolean fullCircle = false;
+        int maxLength = 0;
+        int lastPos = 0;
+        while( !fullCircle ) {
+            int length = 0;
+            boolean skippedFirst = false;
+            while( !(skippedFirst && nodes.contains(way.getNode(pos))) ) {
+                skippedFirst = true;
+                length++;
+                pos++;
+                if( pos >= way.getNodesCount() ) {
+                    pos = 0;
+                    fullCircle = true;
+                }
+            }
+            if( length > maxLength ) {
+                maxLength = length;
+                lastPos = pos;
+            }
+        }
+        return lastPos;
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/ExtractPointAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/ExtractPointAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/ExtractPointAction.java	(revision 28028)
@@ -0,0 +1,87 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and Martin Ždila
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import java.awt.MouseInfo;
+import java.awt.Point;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeNodesCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Unselects all nodes
+ */
+public class ExtractPointAction extends JosmAction {
+
+    
+    public ExtractPointAction() {
+        super(tr("Extract node"), "extnode",
+                tr("Extracts node from a way"),
+                Shortcut.registerShortcut("tools:extnode", tr("Tool: {0}","Extract node"),
+                KeyEvent.VK_J, Shortcut.ALT), true);
+        putValue("help", ht("/Action/ExtractNode"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
+        if (selectedNodes.size()!=1) {
+             JOptionPane.showMessageDialog(Main.parent,
+                    tr("This tool extracts node from its ways and requires single node to be selected."),
+                    tr("Extract node"), JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+        Node nd = selectedNodes.get(0);
+        Node ndCopy = new Node(nd.getCoor());
+        List<Command> cmds = new LinkedList<Command>();
+        
+        Point p = Main.map.mapView.getMousePosition();
+        if (p!=null) cmds.add(new MoveCommand(nd,Main.map.mapView.getLatLon(p.x, p.y)));
+        List<OsmPrimitive> refs = nd.getReferrers();
+        cmds.add(new AddCommand(ndCopy));
+        
+        for (OsmPrimitive pr: refs) {
+            if (pr instanceof Way) {
+                Way w=(Way)pr;
+                List<Node> nodes = w.getNodes();
+                int idx=nodes.indexOf(nd);
+                nodes.set(idx, ndCopy); // replace node with its copy
+                cmds.add(new ChangeNodesCommand(w, nodes));
+            }
+        }
+        if (cmds.size()>1) Main.main.undoRedo.add(new SequenceCommand(tr("Extract node from line"),cmds));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(selection.size()==1);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/PasteRelationsAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/PasteRelationsAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/PasteRelationsAction.java	(revision 28028)
@@ -0,0 +1,81 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.*;
+import java.util.*;
+import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.Shortcut;
+import java.awt.event.ActionEvent;
+import org.openstreetmap.josm.actions.JosmAction;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Pastes relation membership from objects in the paste buffer onto selected object(s).
+ *
+ * @author Zverik
+ */
+public class PasteRelationsAction extends JosmAction {
+    private static final String TITLE = tr("Paste Relations");
+
+    public PasteRelationsAction() {
+        super(TITLE, "dumbutils/pasterelations", tr("Paste relation membership from objects in the buffer onto selected object(s)"),
+                Shortcut.registerShortcut("tools:pasterelations", tr("Tool: {0}",  tr("Paste Relations")), KeyEvent.VK_V, Shortcut.ALT_CTRL), true);
+    }
+
+    public void actionPerformed( ActionEvent e ) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        if( selection.isEmpty() )
+            return;
+
+        Map<Relation, String> relations = new HashMap<Relation, String>();
+        for( PrimitiveData pdata : Main.pasteBuffer.getDirectlyAdded() ) {
+            OsmPrimitive p = getCurrentDataSet().getPrimitiveById(pdata.getUniqueId(), pdata.getType());
+            for( Relation r : OsmPrimitive.getFilteredList(p.getReferrers(), Relation.class)) {
+                String role = relations.get(r);
+                for( RelationMember m : r.getMembers() ) {
+                    if( m.getMember().equals(p) ) {
+                        String newRole = m.getRole();
+                        if( newRole != null && role == null )
+                            role = newRole;
+                        else if( newRole != null ? !newRole.equals(role) : role != null ) {
+                            role = "";
+                            break;
+                        }
+                    }
+                }
+                relations.put(r, role);
+            }
+        }
+
+        List<Command> commands = new ArrayList<Command>();
+        for( Relation rel : relations.keySet() ) {
+            Relation r = new Relation(rel);
+            boolean changed = false;
+            for( OsmPrimitive p : selection ) {
+                if( !r.getMemberPrimitives().contains(p) && !r.equals(p) ) {
+                    r.addMember(new RelationMember(relations.get(rel), p));
+                    changed = true;
+                }
+            }
+            if( changed )
+                commands.add(new ChangeCommand(rel, r));
+        }
+
+        if( !commands.isEmpty() )
+            Main.main.undoRedo.add(new SequenceCommand(TITLE, commands));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        setEnabled(selection != null && !selection.isEmpty() && !Main.pasteBuffer.isEmpty() );
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitObjectAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitObjectAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitObjectAction.java	(revision 28028)
@@ -0,0 +1,239 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.SplitWayAction;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Splits a closed way (polygon) into two closed ways.
+ *
+ * The closed ways are just split at the selected nodes (which must be exactly two).
+ * The nodes remain in their original order.
+ * 
+ * This is similar to SplitWayAction with the addition that the split ways are closed
+ * immediately.
+ */
+
+public class SplitObjectAction extends JosmAction {
+    /**
+     * Create a new SplitObjectAction.
+     */
+    public SplitObjectAction() {
+        super(tr("Split Object"), "splitobject", tr("Split an object at the selected nodes."),
+                Shortcut.registerShortcut("tools:splitobject", tr("Tool: {0}", tr("Split Object")),
+                KeyEvent.VK_X,  Shortcut.ALT)
+                , true);
+        putValue("help", ht("/Action/SplitObject"));
+    }
+
+    /**
+     * Called when the action is executed.
+     *
+     * This method performs an expensive check whether the selection clearly defines one
+     * of the split actions outlined above, and if yes, calls the splitObject method.
+     */
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+
+        List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
+        List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class);
+
+        if (!checkSelection(selection)) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("The current selection cannot be used for splitting."),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE
+            );
+            return;
+        }
+
+
+        Way selectedWay = null;
+        if (!selectedWays.isEmpty()){
+            selectedWay = selectedWays.get(0);
+        }
+
+        // If only nodes are selected, try to guess which way to split. This works if there
+        // is exactly one way that all nodes are part of.
+        if (selectedWay == null && !selectedNodes.isEmpty()) {
+            Map<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
+            for (Node n : selectedNodes) {
+                for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
+                    if (!w.isUsable()) {
+                        continue;
+                    }
+                    int last = w.getNodesCount() - 1;
+                    // Only closed ways with at least four nodes
+                    // (i.e. five members since the first/last is listed twice)
+                    // can be split into two objects
+                    if (last <= 4 || !w.isClosed()) {
+                        continue;
+                    }
+                    for (Node wn : w.getNodes()) {
+                        if (n.equals(wn)) {
+                            Integer old = wayOccurenceCounter.get(w);
+                            wayOccurenceCounter.put(w, (old == null) ? 1 : old + 1);
+                            break;
+                        }
+                    }
+                }
+            }
+            if (wayOccurenceCounter.isEmpty()) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        trn("The selected node is not in the middle of any way.",
+                                "The selected nodes are not in the middle of any way.",
+                                selectedNodes.size()),
+                                tr("Warning"),
+                                JOptionPane.WARNING_MESSAGE);
+                return;
+            }
+
+            for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
+                if (entry.getValue().equals(selectedNodes.size())) {
+                    if (selectedWay != null) {
+                        JOptionPane.showMessageDialog(Main.parent,
+                                trn("There is more than one way using the node you selected. Please select the way also.",
+                                        "There is more than one way using the nodes you selected. Please select the way also.",
+                                        selectedNodes.size()),
+                                        tr("Warning"),
+                                        JOptionPane.WARNING_MESSAGE);
+                        return;
+                    }
+                    selectedWay = entry.getKey();
+                }
+            }
+
+            if (selectedWay == null) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("The selected nodes do not share the same way."),
+                        tr("Warning"),
+                        JOptionPane.WARNING_MESSAGE);
+                return;
+            }
+
+            // If a way and nodes are selected, verify that the nodes
+            // are part of the way and that the way is closed.
+        } else if (selectedWay != null && !selectedNodes.isEmpty()) {
+            if (!selectedWay.isClosed()) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        tr("The selected way is not closed."),
+                        tr("Warning"),
+                        JOptionPane.WARNING_MESSAGE);
+                return;
+            }
+            HashSet<Node> nds = new HashSet<Node>(selectedNodes);
+            nds.removeAll(selectedWay.getNodes());
+            if (!nds.isEmpty()) {
+                JOptionPane.showMessageDialog(Main.parent,
+                        trn("The selected way does not contain the selected node.",
+                                "The selected way does not contain all the selected nodes.",
+                                selectedNodes.size()),
+                                tr("Warning"),
+                                JOptionPane.WARNING_MESSAGE);
+                return;
+            }
+        }
+
+        // we're guaranteed to have two nodes
+        Node node1 = selectedNodes.get(0);
+        int nodeIndex1 = -1;
+        Node node2 = selectedNodes.get(1);
+        int nodeIndex2 = -1;
+        int i = 0;
+        for (Node wn : selectedWay.getNodes()) {
+            if (nodeIndex1 == -1 && wn.equals(node1)) {
+                nodeIndex1 = i;
+            } else if (nodeIndex2 == -1 && wn.equals(node2)) {
+                nodeIndex2 = i;
+            }
+            i++;
+        }
+        // both nodes aren't allowed to be consecutive
+        if (nodeIndex1 == nodeIndex2 + 1 ||
+                nodeIndex2 == nodeIndex1 + 1 ||
+                // minus 2 because we've a circular way where
+                // the penultimate node is the last unique one
+                (nodeIndex1 == 0 && nodeIndex2 == selectedWay.getNodesCount() - 2) ||
+                (nodeIndex2 == 0 && nodeIndex1 == selectedWay.getNodesCount() - 2)) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("The selected nodes can not be consecutive nodes in the object."),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        List<List<Node>> wayChunks = SplitWayAction.buildSplitChunks(selectedWay, selectedNodes);
+        //        List<List<Node>> wayChunks = buildSplitChunks(selectedWay, selectedNodes);
+        if (wayChunks != null) {
+            // close the chunks
+            for (List<Node> wayChunk : wayChunks) {
+                wayChunk.add(wayChunk.get(0));
+            }
+            SplitWayAction.SplitWayResult result = SplitWayAction.splitWay(getEditLayer(), selectedWay, wayChunks, Collections.<OsmPrimitive>emptyList());
+            //            SplitObjectResult result = splitObject(getEditLayer(),selectedWay, wayChunks);
+            Main.main.undoRedo.add(result.getCommand());
+            getCurrentDataSet().setSelected(result.getNewSelection());
+        }
+    }
+
+    /**
+     * Checks if the selection consists of something we can work with.
+     * Checks only if the number and type of items selected looks good;
+     * does not check whether the selected items are really a valid
+     * input for splitting (this would be too expensive to be carried
+     * out from the selectionChanged listener).
+     */
+    private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
+        boolean way = false;
+        int node = 0;
+        for (OsmPrimitive p : selection) {
+            if (p instanceof Way && !way) {
+                way = true;
+            } else if (p instanceof Node) {
+                node++;
+            } else
+                return false;
+        }
+        return node == 2;
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(checkSelection(selection));
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitOnIntersectionsAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitOnIntersectionsAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitOnIntersectionsAction.java	(revision 28028)
@@ -0,0 +1,123 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.actions.SplitWayAction;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.*;
+import java.util.*;
+import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.Shortcut;
+import java.awt.event.ActionEvent;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Splits selected ways on T-intersections. Target points should belong only to one way (except selected one),
+ * or two selected ways.
+ *
+ * @author Zverik
+ */
+public class SplitOnIntersectionsAction extends JosmAction {
+    private static final String TITLE = tr("Split adjacent ways");
+
+    public SplitOnIntersectionsAction() {
+        super(TITLE, "dumbutils/splitonintersections", tr("Split adjacent ways on T-intersections"),
+                Shortcut.registerShortcut("tools:splitonintersections", tr("Tool: {0}", tr("Split adjacent ways")),
+                KeyEvent.VK_P, Shortcut.ALT_CTRL_SHIFT), true);
+    }
+
+    public void actionPerformed( ActionEvent e ) {
+        List<Command> list = new ArrayList<Command>();
+        List<Way> selectedWays = OsmPrimitive.getFilteredList(getCurrentDataSet().getSelected(), Way.class);
+        Map<Way, List<Node>> splitWays = new HashMap<Way, List<Node>>();
+
+        for( Way way : selectedWays ) {
+            if( way.getNodesCount() > 1 && !way.hasIncompleteNodes() && !way.isClosed() )
+            for( Node node : new Node[] {way.getNode(0), way.getNode(way.getNodesCount() - 1)} ) {
+                List<Way> refs = OsmPrimitive.getFilteredList(node.getReferrers(), Way.class);
+                refs.remove(way);
+                if( selectedWays.size() > 1 ) {
+                    // When several ways are selected, split only those among selected
+                    Iterator<Way> it = refs.iterator();
+                    while (it.hasNext()) {
+                        if(!selectedWays.contains(it.next()))
+                            it.remove();
+                    }
+                }
+
+                Iterator<Way> it = refs.iterator();
+                while (it.hasNext()) {
+                    Way w = it.next();
+                    if( w.isDeleted() || w.isIncomplete() || !w.isInnerNode(node) )
+                        it.remove();
+                }
+                if( refs.size() == 1 ) {
+                    if( splitWays.containsKey(refs.get(0)) )
+                        splitWays.get(refs.get(0)).add(node);
+                    else {
+                        List<Node> nodes = new ArrayList<Node>(1);
+                        nodes.add(node);
+                        splitWays.put(refs.get(0), nodes);
+                    }
+                } else if( refs.size() > 1 ) {
+                    JOptionPane.showMessageDialog(
+                            Main.parent,
+                            tr("There are several ways containing one of the splitting nodes. Select ways participating in this operation."),
+                            tr("Warning"),
+                            JOptionPane.WARNING_MESSAGE);
+                    return;
+                }
+            }
+        }
+
+        for( Way splitWay : splitWays.keySet() ) {
+            List<List<Node>> wayChunks = SplitWayAction.buildSplitChunks(splitWay, splitWays.get(splitWay));
+            if( wayChunks != null ) {
+                List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(selectedWays.size());
+                sel.addAll(selectedWays);
+                SplitWayAction.SplitWayResult result = SplitWayAction.splitWay(getEditLayer(), splitWay, wayChunks, sel);
+                list.add(result.getCommand());
+            }
+        }
+
+        if( !list.isEmpty() ) {
+            Main.main.undoRedo.add(list.size() == 1 ? list.get(0) : new SequenceCommand(TITLE, list));
+            getCurrentDataSet().clearSelection();
+        }
+    }
+    
+    private void filterInnerNodes( Node n, List<Way> ways ) {
+        Iterator<Way> it = ways.iterator();
+        while (it.hasNext()) {
+            Way w = it.next();
+            if(!w.isInnerNode(n))
+                it.remove();
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        boolean ok = false;
+        if( selection != null )
+        for( OsmPrimitive p : selection ) {
+            if( p instanceof Way )
+                ok = true;
+            else {
+                ok = false;
+                break;
+            }
+        }
+        setEnabled(ok);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SymmetryAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SymmetryAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SymmetryAction.java	(revision 28028)
@@ -0,0 +1,102 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Mirror the selected ways nodes or ways along line given by two first selected points
+ *
+ * Note: If a ways are selected, their nodes are mirrored
+ *
+ * @author Alexei Kasatkin, based on much copy&Paste from other MirrorAction :)
+ */ 
+public final class SymmetryAction extends JosmAction {
+
+    public SymmetryAction() {
+        super(tr("Symmetry"), "symmetry", tr("Mirror selected nodes and ways."),
+                Shortcut.registerShortcut("tools:symmetry", tr("Tool: {0}", tr("Symmetry")),
+                        KeyEvent.VK_S, Shortcut.ALT_SHIFT), true);
+        putValue("help", ht("/Action/Symmetry"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
+        HashSet<Node> nodes = new HashSet<Node>();
+        EastNorth p1=null,p2=null;
+        
+        for (OsmPrimitive osm : sel) {
+            if (osm instanceof Node) {
+                if (p1==null) p1=((Node)osm).getEastNorth(); else
+                if (p2==null) p2=((Node)osm).getEastNorth(); else
+                nodes.add((Node)osm);
+            }
+        }
+        for (OsmPrimitive osm : sel) {
+            if (osm instanceof Way) {
+                nodes.addAll(((Way)osm).getNodes());
+            }
+        }
+        
+        if (p1==null || p2==null || nodes.size() < 1) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("Please select at least two nodes for symmetry axis and something else to mirror."),
+                    tr("Information"),
+                    JOptionPane.INFORMATION_MESSAGE
+            );
+            return;
+        }
+
+        double ne,nn,l,e0,n0;
+        e0=p1.east();        n0=p1.north();
+        ne =  -(p2.north()-p1.north());      nn =  (p2.east()-p1.east());
+        l = Math.hypot(ne,nn);
+        ne /= l; nn /= l; // normal unit vector
+        
+        Collection<Command> cmds = new LinkedList<Command>();
+
+        for (Node n : nodes) {
+            EastNorth c = n.getEastNorth();
+            double pr = (c.east()-e0)*ne + (c.north()-n0)*nn;
+            //pr=10;
+            cmds.add(new MoveCommand(n, -2*ne*pr, -2*nn*pr ));
+        }
+
+        Main.main.undoRedo.add(new SequenceCommand(tr("Symmetry"), cmds));
+        Main.map.repaint();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagBufferAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagBufferAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagBufferAction.java	(revision 28028)
@@ -0,0 +1,111 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.*;
+import java.util.*;
+import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.Shortcut;
+import java.awt.event.ActionEvent;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Remembers tags of selected object(s) and when clicked, pastes them onto selection.
+ *
+ * @author Zverik
+ */
+public class TagBufferAction extends JosmAction {
+    private static final String TITLE = tr("Copy tags from previous selection");
+    private Map<String, String> tags = new HashMap<String, String>();
+    private Map<String, String> currentTags = new HashMap<String, String>();
+    private Set<OsmPrimitive> selectionBuf = new HashSet<OsmPrimitive>();
+
+    public TagBufferAction() {
+        super(TITLE, "dumbutils/tagbuffer", tr("Pastes tags of previously selected object(s)"),
+                Shortcut.registerShortcut("tools:tagbuffer", tr("Tool: {0}", tr("Copy tags from previous selection")), KeyEvent.VK_R, Shortcut.SHIFT)
+		, true);
+    }
+
+    public void actionPerformed( ActionEvent e ) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        if( selection.isEmpty() )
+            return;
+
+        List<Command> commands = new ArrayList<Command>();
+        for( String key : tags.keySet() ) {
+            String value = tags.get(key);
+            boolean foundNew = false;
+            for( OsmPrimitive p : selection ) {
+                if( !p.hasKey(key) || !p.get(key).equals(value) ) {
+                    foundNew = true;
+                    break;
+                }
+            }
+            if( foundNew )
+                commands.add(new ChangePropertyCommand(selection, key, value));
+        }
+        
+        if( !commands.isEmpty() )
+            Main.main.undoRedo.add(new SequenceCommand(TITLE, commands));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+            if( selectionBuf != null )
+                selectionBuf.clear();
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        // selection changed => check if selection is completely different from before
+        boolean foundOld = false;
+        if( selection != null ) {
+            for( OsmPrimitive p : selectionBuf ) {
+                if( selection.contains(p) ) {
+                    foundOld = true;
+                    break;
+                }
+            }
+            selectionBuf.clear();
+            selectionBuf.addAll(selection);
+        } else {
+            foundOld = selectionBuf.isEmpty();
+            selectionBuf.clear();
+        }
+        if( !foundOld ) {
+            // selection has completely changed, remember tags
+            tags.clear();
+            tags.putAll(currentTags);
+        }
+        rememberSelectionTags();
+
+        setEnabled(selection != null && !selection.isEmpty() && !tags.isEmpty());
+    }
+
+    private void rememberSelectionTags() {
+        if( getCurrentDataSet() != null && !getCurrentDataSet().getSelected().isEmpty() ) {
+            currentTags.clear();
+            Set<String> bad = new HashSet<String>();
+            for( OsmPrimitive p : getCurrentDataSet().getSelected() ) {
+                if( currentTags.isEmpty() ) {
+                    for( String key : p.keySet() )
+                        currentTags.put(key, p.get(key));
+                } else {
+                    for( String key : p.keySet() )
+                        if( !currentTags.containsKey(key) || !currentTags.get(key).equals(p.get(key)) )
+                            bad.add(key);
+                    for( String key : currentTags.keySet() )
+                        if( !p.hasKey(key) )
+                            bad.add(key);
+                }
+            }
+            for( String key : bad )
+                currentTags.remove(key);
+        }
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagSourceAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagSourceAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/TagSourceAction.java	(revision 28028)
@@ -0,0 +1,84 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.*;
+import java.util.*;
+import java.awt.event.KeyEvent;
+import org.openstreetmap.josm.tools.Shortcut;
+import java.awt.event.ActionEvent;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Remembers last source value and put it on selected object(s).
+ *
+ * @author Zverik
+ */
+public class TagSourceAction extends JosmAction {
+    private static final String TITLE = tr("Add Source Tag");
+    private String source;
+    private Set<OsmPrimitive> selectionBuf = new HashSet<OsmPrimitive>();
+    private boolean clickedTwice = false;
+
+    public TagSourceAction() {
+        super(TITLE, "dumbutils/sourcetag", tr("Add remembered source tag"),
+                Shortcut.registerShortcut("tools:sourcetag", tr("Tool: {0}", tr("Add Source Tag")), KeyEvent.VK_S, Shortcut.ALT_CTRL)
+                , true);
+        source = Main.pref.get("sourcetag.value");
+    }
+
+    public void actionPerformed( ActionEvent e ) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        if( selection.isEmpty() || source == null || source.length() == 0 )
+            return;
+
+        Main.main.undoRedo.add(new ChangePropertyCommand(selection, "source", source));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+            if( selectionBuf != null )
+                selectionBuf.clear();
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        if( selection == null || selection.isEmpty() ) {
+            selectionBuf.clear();
+            clickedTwice = false;
+            setEnabled(false);
+            return;
+        }
+
+        if( selectionBuf.size() == selection.size() && selectionBuf.containsAll(selection) ) {
+            if( !clickedTwice )
+                clickedTwice = true;
+            else {
+                // tags may have been changed, get the source
+                String newSource = null;
+                for( OsmPrimitive p : selection ) {
+                    String value = p.get("source");
+                    if( value != null && newSource == null )
+                        newSource = value;
+                    else if( value != null ? !value.equals(newSource) : newSource != null ) {
+                        newSource = "";
+                        break;
+                    }
+                }
+                if( newSource != null && newSource.length() > 0 && !newSource.equals(source) ) {
+                    source = newSource;
+                    Main.pref.put("sourcetag.value", source);
+                }
+            }
+        } else
+            clickedTwice = false;
+        selectionBuf.clear();
+        selectionBuf.addAll(selection);
+        setEnabled(source != null && source.length() > 0);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/UnGlueRelationAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/UnGlueRelationAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/UnGlueRelationAction.java	(revision 28028)
@@ -0,0 +1,128 @@
+// License: GPL v2 or later. Copyright 2010 by Kalle Lampila and others
+// See LICENSE file for details.
+package org.openstreetmap.josm.plugins.utilsplugin2.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.ActionEvent;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+
+/**
+ * Duplicate nodes, ways and relations that are used by multiple relations.
+ *
+ * Resulting nodes, ways and relations are identical as the orginals.
+ *
+ * @author Kalle Lampila
+ *
+ */
+public class UnGlueRelationAction extends JosmAction {
+
+    /**
+     * Create a new UnGlueRelationAction.
+     */
+    public UnGlueRelationAction() {
+        super(tr("UnGlue Relation"), "ungluerelations", tr("Duplicate nodes, ways and relations that are used by multiple relations."),
+              Shortcut.registerShortcut("tools:ungluerelation", tr("Tool: {0}", tr("UnGlue Relations")), KeyEvent.VK_G, Shortcut.ALT_SHIFT ), true);
+        putValue("help", ht("/Action/UnGlueRelation"));
+    }
+
+    /**
+     * Called when the action is executed.
+     */
+    public void actionPerformed(ActionEvent e) {
+
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        List<OsmPrimitive> newPrimitives = new LinkedList<OsmPrimitive>();
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+
+        for (OsmPrimitive p : selection) {
+            boolean first = true;
+            for (Relation relation : OsmPrimitive.getFilteredList(p.getReferrers(), Relation.class)) {
+                if (relation.isDeleted()) {
+                    continue;
+                }
+                if (!first) {
+                    OsmPrimitive newp;
+                    switch(p.getType()) {
+                    case NODE: newp = new Node((Node)p, true); break;
+                    case WAY: newp = new Way((Way)p, true); break;
+                    case RELATION: newp = new Relation((Relation)p, true); break;
+                    default: throw new AssertionError();
+                    }                    
+                    newPrimitives.add(newp);
+                    cmds.add(new AddCommand(newp));
+                    cmds.add(new ChangeCommand(relation, changeRelationMember(relation, p, newp)));
+                } else {
+                    first = false;
+                }
+            }
+        }
+
+        if (newPrimitives.isEmpty() ) {
+            // error message nothing to do
+        }
+        else {
+            Main.main.undoRedo.add(new SequenceCommand(tr("Unglued Relations"), cmds));
+            //Set selection all primiteves (new and old)
+            newPrimitives.addAll(selection);
+            getCurrentDataSet().setSelected(newPrimitives);
+            Main.map.mapView.repaint();
+        }
+    }
+
+    /**
+     * Change member in relation to another one
+     * @param relation
+     * @param orginalMember member to change
+     * @param newMember
+     * @return new relation were change is made
+     */
+    private Relation changeRelationMember(Relation relation, OsmPrimitive orginalMember, OsmPrimitive newMember) {
+        LinkedList<RelationMember> newrms = new LinkedList<RelationMember>();
+        for (RelationMember rm : relation.getMembers()) {
+            if (rm.getMember() == orginalMember) {
+                newrms.add(new RelationMember(rm.getRole(),newMember));
+            } else {
+                newrms.add(rm);
+            }
+        }
+        Relation newRelation  = new Relation(relation);
+        newRelation.setMembers(newrms);
+        return newRelation;
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CircleArcMaker.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CircleArcMaker.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CircleArcMaker.java	(revision 28028)
@@ -0,0 +1,347 @@
+// License: GPL. Copyright 2011 by Ole Jørgen Brønner
+package org.openstreetmap.josm.plugins.utilsplugin2.curves;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+
+public class CircleArcMaker {
+    public static Collection<Command> doCircleArc(List<Node> selectedNodes, List<Way> selectedWays, int angleSeparation) {
+        Collection<Command> cmds = new LinkedList<Command>();
+
+        //// Decides which nodes to use as anchors based on selection
+        /*
+         * Rules goes like this:
+         * If there are selected ways, at least one of these are used as target ways for the arc.
+         * Selected ways override selected nodes. If nodes not part of the ways are selected they're ignored.
+         *
+         * When existing ways are reused for the arc, all ways overlapping these are transformed too.
+         *
+         * 1. Exactly 3 nodes selected:
+         *      Use these nodes.
+         *      - No way selected: create a new way.
+         * 2. Exactly 1 node selected, node part of exactly 1 way with 3 or more nodes:
+         *      Node selected used as first node, consequent nodes in the way's direction used as the rest.
+         *      - Reversed if not enough nodes in forward direction
+         *      - Selected node used as middle node its the middle node in a 3 node way
+         *      - Parent way used
+         */
+
+        //// Anchor nodes
+        Node n1 = null, n2 = null, n3 = null;
+
+        if (false) {
+            int nodeCount = selectedNodes.size();
+            int wayCount = selectedWays.size();
+
+            // TODO: filter garbage nodes based on selected ways
+
+            // Never interested in more than 3 nodes. Nodes prioritized by reverse selection order, but keep their order.
+            // TODO: replace by helper function (eg. getPostFixList(int count))
+            Node[] nodesOfInterest = new Node[3];
+            int nodesOfInterestCount = Math.min(nodeCount, 3);
+            for (int i = nodesOfInterestCount - 1; i >= 0; i--) {
+                nodesOfInterest[i] = selectedNodes.get(nodeCount - 1 - i);
+            }
+        }
+
+        Set<Way> targetWays = new HashSet<Way>();
+
+        boolean nodesHasBeenChoosen = false;
+        if (selectedNodes.size() == 3) {
+            Iterator<Node> nodeIter = selectedNodes.iterator();
+            n1 = nodeIter.next();
+            n2 = nodeIter.next();
+            n3 = nodeIter.next();
+            nodesHasBeenChoosen = true;
+            if (selectedWays.isEmpty()) { // Create a brand new way
+                Way newWay = new Way();
+                targetWays.add(newWay);
+                cmds.add(new AddCommand(newWay));
+                newWay.addNode(n1);
+                newWay.addNode(n2);
+                newWay.addNode(n3);
+            }
+        }
+        if (selectedWays.isEmpty() == false) {
+            // TODO: use only two nodes inferring the orientation from the parent way.
+
+            if (nodesHasBeenChoosen == false) {
+                // Use the three last nodes in the way as anchors. This is intended to be used with the
+                // built in draw mode
+                Way w = selectedWays.iterator().next(); //TODO: select last selected way instead
+                int nodeCount = w.getNodesCount();
+                if (nodeCount < 3)
+                    return null;
+                n3 = w.getNode(nodeCount - 1);
+                n2 = w.getNode(nodeCount - 2);
+                n1 = w.getNode(nodeCount - 3);
+                nodesHasBeenChoosen = true;
+            }
+            targetWays.addAll(OsmPrimitive.getFilteredList(n1.getReferrers(), Way.class));
+            targetWays.addAll(OsmPrimitive.getFilteredList(n2.getReferrers(), Way.class));
+            targetWays.addAll(OsmPrimitive.getFilteredList(n3.getReferrers(), Way.class));
+            //            for(Way w : selectedWays) {
+            //                targetWays.add(w);
+            //            }
+
+        }
+        if (nodesHasBeenChoosen == false) {
+            return null;
+        }
+
+
+        EastNorth p1 = n1.getEastNorth();
+        EastNorth p2 = n2.getEastNorth();
+        EastNorth p3 = n3.getEastNorth();
+        // TODO: Check that the points are distinct
+
+        // // Calculate the new points in the arc
+        ReturnValue<Integer> p2Index = new ReturnValue<Integer>();
+        List<EastNorth> points = circleArcPoints(p1, p2, p3, angleSeparation, false, p2Index);
+
+        //// Create the new arc nodes. Insert anchor nodes at correct positions.
+        List<Node> arcNodes = new ArrayList<Node>(points.size());
+        arcNodes.add(n1);
+        {
+            int i = 1;
+            for (EastNorth p : slice(points, 1, -2)) {
+                //            if (p == p2) {
+                if (i == p2Index.value) {
+                    Node n2new = new Node(n2);
+                    n2new.setEastNorth(p);
+                    arcNodes.add(n2); // add the original n2, or else we can't find it in the target ways
+                    cmds.add(new ChangeCommand(n2, n2new));
+                } else {
+                    Node n = new Node(p);
+                    arcNodes.add(n);
+                    cmds.add(new AddCommand(n));
+                }
+                i++;
+            }
+        }
+        arcNodes.add(n3);
+
+        Node[] anchorNodes = { n1, n2, n3 };
+        //// "Fuse" the arc with all target ways
+        fuseArc(anchorNodes, arcNodes, targetWays, cmds);
+
+        return cmds;
+    }
+
+    private static void fuseArc(Node[] anchorNodes, List<Node> arcNodes, Set<Way> targetWays, Collection<Command> cmds) {
+
+        for (Way originalTw : targetWays) {
+            Way tw = new Way(originalTw);
+            boolean didChangeTw = false;
+            /// Do one segment at the time (so ways only sharing one segment is fused too)
+            for (int a = 0; a < 2; a++) {
+                int anchorBi = arcNodes.indexOf(anchorNodes[a]); // TODO: optimize away
+                int anchorEi = arcNodes.indexOf(anchorNodes[a + 1]);
+                /// Find the anchor node indices in current target way
+                int bi = -1, ei = -1;
+                int i = -1;
+                // Caution: nodes might appear multiple times. For now only handle simple closed ways
+                for (Node n : tw.getNodes()) {
+                    i++;
+                    // We look for the first anchor node. The next should be directly to the left or right.
+                    // Exception when the way is closed
+                    if (n == anchorNodes[a]) {
+                        bi = i;
+                        Node otherAnchor = anchorNodes[a + 1];
+                        if (i > 0 && tw.getNode(i - 1) == otherAnchor) {
+                            ei = i - 1;
+                        } else if (i < (tw.getNodesCount() - 1) && tw.getNode(i + 1) == otherAnchor) {
+                            ei = i + 1;
+                        } else {
+                            continue; // this can happen with closed ways. Continue searching for the correct index
+                        }
+                        break;
+                    }
+                }
+                if (bi == -1 || ei == -1) {
+                    continue; // this segment is not part of the target way
+                }
+                didChangeTw = true;
+
+                /// Insert the nodes of this segment
+                // Direction of target way relative to the arc node order
+                int twDirection = ei > bi ? 1 : 0;
+                int anchorI = anchorBi + 1; // don't insert the anchor nodes again
+                int twI = bi + (twDirection == 1 ? 1 : 0); // TODO: explain
+                while (anchorI < anchorEi) {
+                    tw.addNode(twI, arcNodes.get(anchorI));
+                    anchorI++;
+                    twI += twDirection;
+                }
+            }
+            if (didChangeTw)
+                cmds.add(new ChangeCommand(originalTw, tw));
+        }
+    }
+
+    /**
+     * Return a list of coordinates lying an the circle arc determined by n1, n2 and n3.
+     * The order of the list and which of the 3 possible arcs to construct are given by the order of n1, n2, n3
+     *
+     * @param includeAnchors include the anchorpoints in the list. The original objects will be used, not copies.
+     *                       If {@code false}, p2 will be replaced by the closest arcpoint.
+     * @param anchor2Index if non-null, it's value will be set to p2's index in the returned list.
+     * @param angleSparation maximum angle separation between the arc points
+     */
+    private static List<EastNorth> circleArcPoints(EastNorth p1, EastNorth p2, EastNorth p3,
+            int angleSeparation, boolean includeAnchors, ReturnValue<Integer> anchor2Index) {
+
+        // triangle: three single nodes needed or a way with three nodes
+
+        // let's get some shorter names
+        double x1 = p1.east();
+        double y1 = p1.north();
+        double x2 = p2.east();
+        double y2 = p2.north();
+        double x3 = p3.east();
+        double y3 = p3.north();
+
+        // calculate the center (xc,yc)
+        double s = 0.5 * ((x2 - x3) * (x1 - x3) - (y2 - y3) * (y3 - y1));
+        double sUnder = (x1 - x2) * (y3 - y1) - (y2 - y1) * (x1 - x3);
+
+        assert (sUnder == 0);
+
+        s /= sUnder;
+
+        double xc = 0.5 * (x1 + x2) + s * (y2 - y1);
+        double yc = 0.5 * (y1 + y2) + s * (x1 - x2);
+
+        // calculate the radius (r)
+        double r = Math.sqrt(Math.pow(xc - x1, 2) + Math.pow(yc - y1, 2));
+
+        // The angles of the anchor points relative to the center
+        double realA1 = calcang(xc, yc, x1, y1);
+        double realA2 = calcang(xc, yc, x2, y2);
+        double realA3 = calcang(xc, yc, x3, y3);
+
+        double startAngle = realA1;
+        // Transform the angles to get a consistent starting point
+        double a1 = 0;
+        double a2 = normalizeAngle(realA2 - startAngle);
+        double a3 = normalizeAngle(realA3 - startAngle);
+        int direction = a3 > a2 ? 1 : -1;
+
+        double radialLength = 0;
+        if (direction == 1) { // counter clockwise
+            radialLength = a3;
+        } else { // clockwise
+            radialLength = Math.PI * 2 - a3;
+            // make the angles consistent with the direction.
+            a2 = (Math.PI * 2 - a2);
+            a3 = (Math.PI * 2 - a3);
+        }
+        int numberOfNodesInArc = Math.max((int) Math.ceil((radialLength / Math.PI) * 180 / angleSeparation)+1,
+                3);
+        List<EastNorth> points = new ArrayList<EastNorth>(numberOfNodesInArc);
+
+        // Calculate the circle points in order
+        double stepLength = radialLength / (numberOfNodesInArc-1);
+        // Determine closest index to p2
+
+        int indexJustBeforeP2 = (int) Math.floor(a2 / stepLength);
+        int closestIndexToP2 = indexJustBeforeP2;
+        if ((a2 - indexJustBeforeP2 * stepLength) > ((indexJustBeforeP2 + 1) * stepLength - a2)) {
+            closestIndexToP2 = indexJustBeforeP2 + 1;
+        }
+        // can't merge with end node
+        if (closestIndexToP2 == numberOfNodesInArc - 1) {
+            closestIndexToP2--;
+        } else if (closestIndexToP2 == 0) {
+            closestIndexToP2++;
+        }
+        assert (closestIndexToP2 != 0);
+
+        double a = direction * (stepLength);
+        points.add(p1);
+        if (indexJustBeforeP2 == 0 && includeAnchors) {
+            points.add(p2);
+        }
+        // i is ahead of the real index by one, since we need to be ahead in the angle calculation
+        for (int i = 2; i < numberOfNodesInArc; i++) {
+            double nextA = direction * (i * stepLength);
+            double realAngle = a + startAngle;
+            double x = xc + r * Math.cos(realAngle);
+            double y = yc + r * Math.sin(realAngle);
+
+            points.add(new EastNorth(x, y));
+            if (i - 1 == indexJustBeforeP2 && includeAnchors) {
+                points.add(p2);
+            }
+            a = nextA;
+        }
+        points.add(p3);
+        if (anchor2Index != null) {
+            anchor2Index.value = closestIndexToP2;
+        }
+        return points;
+    }
+
+    // gah... why can't java support "reverse indices"?
+    private static <T> List<T> slice(List<T> list, int from, int to) {
+        if (to < 0)
+            to += list.size() + 1;
+        return list.subList(from, to);
+    }
+
+    /**
+     * Normalizes {@code a} so it is between 0 and 2 PI
+     */
+    private static double normalizeAngle(double angle) {
+        double PI2 = Math.PI * 2;
+        if (angle < 0) {
+            angle = angle + (Math.floor(-angle / PI2) + 1) * PI2;
+        } else if (angle >= PI2) {
+            angle = angle - Math.floor(angle / PI2) * PI2;
+        }
+        return angle;
+    }
+
+    private static double calcang(double xc, double yc, double x, double y) {
+        // calculate the angle from xc|yc to x|y
+        if (xc == x && yc == y)
+            return 0; // actually invalid, but we won't have this case in this context
+        double yd = Math.abs(y - yc);
+        if (yd == 0 && xc < x)
+            return 0;
+        if (yd == 0 && xc > x)
+            return Math.PI;
+        double xd = Math.abs(x - xc);
+        double a = Math.atan2(xd, yd);
+        if (y > yc) {
+            a = Math.PI - a;
+        }
+        if (x < xc) {
+            a = -a;
+        }
+        a = 1.5 * Math.PI + a;
+        if (a < 0) {
+            a += 2 * Math.PI;
+        }
+        if (a >= 2 * Math.PI) {
+            a -= 2 * Math.PI;
+        }
+        return a;
+    }
+    public static class ReturnValue<T> {
+        public T value;
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CurveAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CurveAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CurveAction.java	(revision 28028)
@@ -0,0 +1,81 @@
+// License: GPL. Copyright 2011 by Ole Jørgen Brønner
+package org.openstreetmap.josm.plugins.utilsplugin2.curves;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+// TODO: investigate splines
+
+public class CurveAction extends JosmAction {
+
+    private static final long serialVersionUID = 1L;
+
+    private int angleSeparation = -1;
+
+    public CurveAction() {
+        super(tr("Circle arc"), "circlearc", tr("Create a circle arc"),
+                Shortcut.registerShortcut("tools:createcurve", tr("Tool: {0}", tr("Create a circle arc")), KeyEvent.VK_C,
+                        Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/CreateCircleArc"));
+        updatePreferences();
+    }
+
+    private void updatePreferences() {
+        // @formatter:off
+        angleSeparation = Main.pref.getInteger(prefKey("circlearc.angle-separation"), 20);
+        // @formatter:on
+    }
+
+    private String prefKey(String subKey) {
+        return "curves." + subKey;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (!isEnabled())
+            return;
+
+        updatePreferences();
+
+        List<Node> selectedNodes = new ArrayList<Node>(getCurrentDataSet().getSelectedNodes());
+        List<Way> selectedWays = new ArrayList<Way>(getCurrentDataSet().getSelectedWays());
+
+        // Collection<Command> cmds = doSpline(selectedNodes, selectedWays);
+        Collection<Command> cmds = CircleArcMaker.doCircleArc(selectedNodes, selectedWays, angleSeparation);
+        if (cmds != null)
+            Main.main.undoRedo.add(new SequenceCommand("Create a curve", cmds));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+
+    public static void main(String[] args) {
+    }
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/ChooseURLAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/ChooseURLAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/ChooseURLAction.java	(revision 28028)
@@ -0,0 +1,104 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.customurl;
+
+import java.awt.GridBagLayout;
+import java.awt.event.ItemEvent;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.event.ListSelectionEvent;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.Main;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionListener;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.SelectionManager;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class ChooseURLAction extends JosmAction {
+
+    public ChooseURLAction() {
+         super(tr("Select custom URL"), "selecturl", tr("Select custom URL"),null,true,true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+       showConfigDialog(false);
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            setEnabled(true);
+        }
+    }
+        
+    public static void showConfigDialog(final boolean fast) {
+        JPanel all = new JPanel();
+        GridBagLayout layout = new GridBagLayout();
+        all.setLayout(layout);
+        
+        List<String> items = URLList.getURLList();
+        String addr = URLList.getSelectedURL();
+        int n=items.size()/2 , idxToSelect=-1;
+        final String names[] =new String[n];
+        final String vals[] =new String[n];
+        for (int i=0;i<n;i++) {
+            names[i]=items.get(i*2);
+            vals[i]=items.get(i*2+1);
+            if (vals[i].equals(addr)) idxToSelect=i; 
+        }
+        final JLabel label1=new JLabel(tr("Please select one of custom URLs (configured in Preferences)"));
+        final JList list1=new JList(names);
+        final JTextField editField=new JTextField();
+        final JCheckBox check1=new JCheckBox(tr("Ask every time"));
+        
+        final ExtendedDialog dialog = new ExtendedDialog(Main.parent,
+                tr("Configure custom URL"),
+                new String[] {tr("OK"),tr("Cancel"),}
+        );
+        list1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        list1.addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                int idx=list1.getSelectedIndex();
+                if (idx>=0) editField.setText(vals[idx]);
+            }
+        });
+        list1.setSelectedIndex(idxToSelect);
+        check1.setSelected(Main.pref.getBoolean("utilsplugin2.askurl",false));
+        
+        editField.setEditable(false);
+        
+        all.add(label1,GBC.eop().fill(GBC.HORIZONTAL).insets(15,5,15,0));
+        all.add(list1,GBC.eop().fill(GBC.HORIZONTAL).insets(5,5,0,0));
+        all.add(editField,GBC.eop().fill(GBC.HORIZONTAL).insets(5,5,0,0));
+        all.add(check1,GBC.eop().fill(GBC.HORIZONTAL).insets(5,5,0,0));
+        
+        
+        dialog.setContent(all, false);
+        dialog.setButtonIcons(new String[] {"ok.png","cancel.png",});
+        dialog.setDefaultButton(1);
+        dialog.showDialog();
+        
+        int idx = list1.getSelectedIndex();
+        if (dialog.getValue() ==1 && idx>=0) {
+           URLList.select(vals[idx]);
+           Main.pref.put("utilsplugin2.askurl", check1.isSelected());
+        }
+    }
+
+    
+    
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/OpenPageAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/OpenPageAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/OpenPageAction.java	(revision 28028)
@@ -0,0 +1,113 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.plugins.utilsplugin2.customurl;
+
+import java.io.UnsupportedEncodingException;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.net.URLEncoder;
+import java.util.Collection;
+
+import java.util.regex.Matcher;
+
+import java.util.regex.Pattern;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.tools.OpenBrowser;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Mirror the selected ways nodes or ways along line given by two first selected points
+ *
+ * Note: If a ways are selected, their nodes are mirrored
+ *
+ * @author Alexei Kasatkin, based on much copy&Paste from other MirrorAction :)
+ */ 
+public final class OpenPageAction extends JosmAction {
+    
+    public OpenPageAction() {
+        super(tr("Open custom URL"), "openurl",
+                tr("Opens specified URL browser"),
+                Shortcut.registerShortcut("tools:openurl", tr("Tool: {0}", tr("Open custom URL")),
+                KeyEvent.VK_H, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/OpenPage"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
+        OsmPrimitive p=null;
+        if (sel.size()>=1) {
+            p=sel.iterator().next();
+        }
+        
+        if (Main.pref.getBoolean("utilsplugin2.askurl",false)==true) 
+            ChooseURLAction.showConfigDialog(true);
+        
+        //lat = p.getBBox().getTopLeft().lat();
+        //lon = p.getBBox().getTopLeft().lon();
+        LatLon center = Main.map.mapView.getLatLon(Main.map.mapView.getWidth()/2, Main.map.mapView.getHeight()/2);
+                
+        String addr =  URLList.getSelectedURL();
+        Pattern pat = Pattern.compile("\\{([^\\}]*)\\}");
+        Matcher m = pat.matcher(addr);
+        String val,key;
+        String keys[]=new String[100],vals[]=new String[100];
+        int i=0;
+        try {
+        while (m.find()) {
+                key=m.group(1); val=null;                
+                if (key.equals("#id")) {
+                    if (p!=null) val=Long.toString(p.getId()); ;
+                } else if (key.equals("#type")) {
+                    if (p!=null) val = OsmPrimitiveType.from(p).getAPIName(); ;
+                } else if (key.equals("#lat")) {
+                    val = Double.toString(center.lat());
+                } else if (key.equals("#lon")) {
+                    val = Double.toString(center.lon());
+                }
+                else {
+                    if (p!=null) {
+                        val =p.get(key);
+                        if (val!=null) val =URLEncoder.encode(p.get(key), "UTF-8"); else return;
+                    }
+                }
+                keys[i]=m.group();
+                if  (val!=null) vals[i]=val;
+                else vals[i]="";
+                i++;
+        }
+        } catch (UnsupportedEncodingException ex) {
+            System.err.println("Encoding error");
+            return;
+        }
+        for (int j=0;j<i;j++){
+            addr = addr.replace(keys[j],vals[j]);
+        }
+        try {
+            OpenBrowser.displayUrl(addr);
+        } catch (Exception ex) {
+            System.err.println("Can not open URL"+addr);
+        }
+        //Collection<Command> cmds = new LinkedList<Command>();
+
+          //  cmds.add(new MoveCommand(n, -2*ne*pr, -2*nn*pr ));
+        //Main.main.undoRedo.add(new SequenceCommand(tr("Symmetry"), cmds));
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            setEnabled(true);
+        }
+    }
+
+    
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/URLList.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/URLList.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/URLList.java	(revision 28028)
@@ -0,0 +1,97 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.customurl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.openstreetmap.josm.Main;
+
+public class URLList {
+    public static final String defaultURL = "http://osm.mapki.com/history/{#type}.php?id={#id}";
+
+    public static String getSelectedURL() {
+        getURLList();
+        return Main.pref.get("utilsplugin2.customurl", defaultURL);
+    }
+    public static void select(String url) {
+        Main.pref.put("utilsplugin2.customurl",url);
+    }    
+    public static List<String> resetURLList() {
+        List<String> items=new ArrayList<String>();
+        items.add("Wikipedia");
+        items.add("http://en.wikipedia.org/w/index.php?search={name}&fulltext=Search");
+        items.add("Wikipedia RU");
+        items.add(defaultURL);
+        items.add("LatLon buildings");
+        items.add("http://latlon.org/buildings?zoom=17&lat={#lat}&lon={#lon}&layers=B");
+        items.add("AMDMi3 Russian streets");
+        items.add("http://addresses.amdmi3.ru/?zoom=11&lat={#lat}&lon={#lon}&layers=B00");
+        items.add("Mapki - More  History with CT");
+        items.add("http://osm.mapki.com/history/{#type}.php?id={#id}");
+        items.add("Element history [demo, =Ctrl-Shift-H]");
+        items.add("http://www.openstreetmap.org/browse/{#type}/{#id}/history");
+        items.add("Browse element [demo, =Ctrl-Shift-I]");
+        items.add("http://www.openstreetmap.org/browse/{#type}/{#id}");
+        Main.pref.putCollection("utilsplugin2.urlHistory",items);
+        Main.pref.put("utilsplugin2.customurl",items.get(9));
+        return items;
+    }
+    
+    public static List<String> getURLList() {
+        List<String> items = (List<String>) Main.pref.getCollection("utilsplugin2.urlHistory");
+        if (items==null || items.isEmpty()) {
+            resetURLList();
+            items=(List<String>) Main.pref.getCollection("utilsplugin2.urlHistory");
+        }
+        return items;
+    }
+    
+    public static void updateURLList(List<String> lst) {
+        Main.pref.putCollection("utilsplugin2.urlHistory",lst);
+        try {
+            Main.pref.save();
+        } catch (IOException ex) {
+            Logger.getLogger(UtilsPluginPreferences.class.getName()).log(Level.SEVERE, null, ex);
+        }
+    }
+    
+    public static  List<String> loadURLList() {
+        ArrayList<String> items=new ArrayList<String>();
+        BufferedReader fr=null;
+        try {
+        File f = new File (Main.pref.getPreferencesDir(),"customurl.txt");
+        fr = new BufferedReader(new FileReader(f));
+        String s;
+        while ((s = fr.readLine()) !=null ) items.add(s);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try { if (fr!=null) fr.close(); } catch (Exception e) {}
+        }
+        return items;
+        
+    }
+    
+    public static  void saveURLList(List<String> items) {
+        File f = new File (Main.pref.getPreferencesDir(),"customurl.txt");
+        PrintWriter fw=null;
+        try {
+        fw=new PrintWriter(f);
+        for (String s : items) {
+            fw.println(s);
+        }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try { if (fw!=null) fw.close(); } catch (Exception e) {}
+        }
+    }
+
+
+}
+
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/UtilsPluginPreferences.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/UtilsPluginPreferences.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/customurl/UtilsPluginPreferences.java	(revision 28028)
@@ -0,0 +1,159 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.utilsplugin2.customurl;
+
+import java.awt.event.ActionEvent;
+import javax.swing.JButton;
+import java.util.List;
+import javax.swing.table.TableModel;
+import javax.swing.JTable;
+import javax.swing.JLabel;
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import javax.swing.event.TableModelEvent;
+import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import java.awt.GridBagConstraints;
+import java.awt.event.ActionListener;
+import javax.swing.JPanel;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.tools.GBC;
+
+import static org.openstreetmap.josm.tools.I18n.*;
+
+public class UtilsPluginPreferences extends DefaultTabPreferenceSetting {
+    HistoryComboBox combo1=new HistoryComboBox();
+    JTable table;
+    JButton resetButton;
+    JButton loadButton;
+    JButton saveButton;
+
+    public UtilsPluginPreferences() {
+        super("utils", tr("Utilsplugin2 settings [TESTING]"), tr("Here you can change some preferences of Utilsplugin2 functions"));
+    }
+
+    @Override
+    public void addGui(PreferenceTabbedPane gui) {
+        JPanel pp = gui.createPreferenceTab(this);
+        JPanel all = new JPanel();
+        GridBagLayout layout = new GridBagLayout();
+        all.setLayout(layout);
+
+        // FIXME: get rid of hardcoded URLS
+
+        String addr =  URLList.getSelectedURL();
+        table=new JTable(new DefaultTableModel(null,new String[]{"Title","URL"}));
+
+        List<String> items = URLList.getURLList();
+
+        resetButton = new JButton(tr("Reset"));
+        resetButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                fillRows(URLList.resetURLList());
+            }
+        });
+
+        saveButton = new JButton(tr("Save to file"));
+        saveButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                URLList.saveURLList(readItemsFromTable());
+            }
+        });
+
+        loadButton = new JButton(tr("Load from file"));
+        loadButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                fillRows(URLList.loadURLList());
+            }
+        });
+
+        fillRows(items);
+
+        HtmlPanel help = new HtmlPanel(tr("Please edit custom URLs and select one row to use with the tool<br/>"
+                + " <b>&#123;key&#125;</b> is replaced with the tag value<br/>"
+                + " <b>&#123;#id&#125;</b> is replaced with the element ID<br/>"
+                + " <b>&#123;#type&#125;</b> is replaced with \"node\",\"way\" or \"relation\" <br/>"
+                + " <b>&#123;#lat&#125; , &#123;#lon&#125;</b> is replaced with map center latitude/longitude <br/>"
+                + " Your can manually load settings from file <b>customurl.txt</b> in JOSM folder"));
+
+        all.add(new JLabel(tr("Custom URL configuration")),GBC.std().insets(5,10,0,0));
+        all.add(resetButton,GBC.std().insets(25,10,0,0));
+        all.add(loadButton,GBC.std().insets(25,10,0,0));
+        all.add(saveButton,GBC.eol().insets(25,10,0,0));
+        all.add(help,GBC.eop().insets(5,10,0,0));
+
+        table.getColumnModel().getColumn(0).setPreferredWidth(150);
+        table.getColumnModel().getColumn(0).setMaxWidth(300);
+        table.getColumnModel().getColumn(1).setPreferredWidth(300);
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        table.getModel().addTableModelListener(new TableModelListener() {
+            @Override
+            public void tableChanged(TableModelEvent e) {
+                int row = e.getFirstRow();
+                int column = e.getColumn();
+                DefaultTableModel model = (DefaultTableModel)(e.getSource());
+                if (row<0  || column<0) return;
+                String data = (String)model.getValueAt(row, column);
+                if (data!=null && data.length()>0 && row==model.getRowCount()-1)
+                    model.addRow(new String[]{"",""});
+            }
+        });
+        all.add(table,GBC.eop().fill());
+
+        pp.add(all, GBC.eol().fill(GridBagConstraints.BOTH));
+    }
+
+    private void fillRows(List<String> items) {
+        if (items==null) return;
+        int p=0,row=0;
+        String name, url;
+        DefaultTableModel model = (DefaultTableModel) table.getModel();
+        model.setRowCount(0);
+        int n=items.size();
+        while (true) {
+            if (p>=n) break;
+            name = items.get(p);
+            //System.out.println("name="+name);
+            p++;
+            if (p>=n) break;
+            url = items.get(p);
+            //System.out.println("url="+url);
+            p++;
+            model.addRow(new String[]{name,url});
+            row++;
+        }
+        model.addRow(new String[]{"",""});
+    }
+
+    @Override
+    public boolean ok() {
+        String addr=combo1.getText();
+        List<String> lst = readItemsFromTable();
+        URLList.updateURLList(lst);
+
+        return false;
+    }
+
+    private List<String> readItemsFromTable() {
+        TableModel model = (table.getModel());
+        String v;
+        ArrayList<String> lst=new ArrayList<String>();
+        int n=model.getRowCount();
+        for (int i=0;i<n;i++) {
+            v=(String) model.getValueAt(i, 0);
+            if (v.length()==0) continue;
+            lst.add(v);
+            v=(String) model.getValueAt(i, 1);
+            lst.add(v);
+        }
+        int row=table.getSelectedRow();
+        if (row!=-1) {
+            v=(String) model.getValueAt(row, 1);
+            URLList.select(v);
+        }
+        return lst;
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonAction.java	(revision 28028)
@@ -0,0 +1,92 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.plugins.utilsplugin2.latlon;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.actions.JosmAction;
+
+/**
+ * This action displays a dialog where the user can enter a latitude and longitude,
+ * and when ok is pressed, a new node is created at the specified position.
+ */
+public final class LatLonAction extends JosmAction {
+    // remember input from last time
+    private String textLatLon;
+
+    public LatLonAction() {
+        super(tr("Lat Lon tool"), "latlon", tr("Create geometry by entering lat lon coordinates for it."),
+                Shortcut.registerShortcut("latlon", tr("Edit: {0}", tr("Lat Lon tool")), KeyEvent.VK_L, Shortcut.CTRL_SHIFT), true);
+        putValue("help", ht("/Action/AddNode"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        if (!isEnabled())
+            return;
+
+        LatLonDialog dialog = new LatLonDialog(Main.parent, tr("Add Node..."), ht("/Action/AddNode"));
+
+        if (textLatLon != null) {
+            dialog.setLatLonText(textLatLon);
+        }
+
+        dialog.showDialog();
+        
+        if (dialog.getValue() != 1)
+            return;
+
+        LatLon[] coordinates = dialog.getCoordinates();
+        String type = dialog.getGeomType();
+        if (coordinates == null)
+            return;
+
+        textLatLon = dialog.getLatLonText();
+
+        // we create a list of commands that will modify the map in the way we want.
+        Collection<Command> cmds = new LinkedList<Command>();
+        // first we create all the nodes, then we do extra stuff based on what geometry type we need.
+        LinkedList<Node> nodes = new LinkedList<Node>();
+
+        for (LatLon ll : coordinates) {
+            Node nnew = new Node(ll);
+            nodes.add(nnew);
+            cmds.add(new AddCommand(nnew));
+        }
+
+        if (type == "nodes") {
+            //we dont need to do anything, we already have all the nodes
+        }
+        if (type == "way") {
+            Way wnew = new Way();
+            wnew.setNodes(nodes);
+            cmds.add(new AddCommand(wnew));
+        }
+        if (type == "area") {
+            nodes.add(nodes.get(0)); // this is needed to close the way.
+            Way wnew = new Way();
+            wnew.setNodes(nodes);
+            cmds.add(new AddCommand(wnew));
+        }
+        Main.main.undoRedo.add(new SequenceCommand(tr("Lat Lon tool"), cmds));
+        Main.map.mapView.repaint();
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(getEditLayer() != null);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonDialog.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonDialog.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/latlon/LatLonDialog.java	(revision 28028)
@@ -0,0 +1,475 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.utilsplugin2.latlon;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.JTabbedPane;
+
+import javax.swing.text.Document;
+import javax.swing.JTextArea;
+import javax.swing.JScrollPane;
+import javax.swing.ButtonGroup;
+import javax.swing.JRadioButton;
+
+import javax.swing.UIManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.CoordinateFormat;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.widgets.HtmlPanel;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class LatLonDialog extends ExtendedDialog {
+    private static final Color BG_COLOR_ERROR = new Color(255,224,224);
+
+    public JTabbedPane tabs;
+    private JTextArea taLatLon;
+    private JScrollPane spScroll;
+    private JRadioButton rbNodes;
+    private JRadioButton rbWay;
+    private JRadioButton rbClosedWay;
+    private ButtonGroup bgType;
+    private String geomType;
+
+    private LatLon[] latLonCoordinates;
+
+    private static final double ZERO = 0.0;
+    private static final String DEG = "\u00B0";
+    private static final String MIN = "\u2032";
+    private static final String SEC = "\u2033";
+
+    private static final char N_TR = LatLon.NORTH.charAt(0);
+    private static final char S_TR = LatLon.SOUTH.charAt(0);
+    private static final char E_TR = LatLon.EAST.charAt(0);
+    private static final char W_TR = LatLon.WEST.charAt(0);
+
+    private static final Pattern p = Pattern.compile(
+            "([+|-]?\\d+[.,]\\d+)|"             // (1)
+            + "([+|-]?\\d+)|"                   // (2)
+            + "("+DEG+"|o|deg)|"                // (3)
+            + "('|"+MIN+"|min)|"                // (4)
+            + "(\"|"+SEC+"|sec)|"               // (5)
+            + "(,|;)|"                          // (6)
+            + "([NSEW"+N_TR+S_TR+E_TR+W_TR+"])|"// (7)
+            + "\\s+|"
+            + "(.+)");
+
+    protected JPanel buildLatLon() {
+        JPanel pnl = new JPanel(new GridBagLayout());
+        pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+        pnl.add(new JLabel(tr("Coordinates:")), GBC.std().insets(0,10,5,0));
+
+        taLatLon = new JTextArea(5,24);
+        taLatLon.getDocument().addDocumentListener(new CoordinateListener());
+	spScroll = new JScrollPane(taLatLon);
+        pnl.add(spScroll, GBC.eol().insets(0,10,0,0).fill().weight(2.0, 2.0));
+
+	//Radio button setup
+	bgType = new ButtonGroup();
+
+	rbNodes = new JRadioButton("Nodes", true);
+	rbNodes.setActionCommand("nodes");
+	bgType.add(rbNodes);
+	pnl.add(rbNodes);
+
+	rbWay = new JRadioButton("Way");
+	rbWay.setActionCommand("way");
+	bgType.add(rbWay);
+	pnl.add(rbWay);
+
+	rbClosedWay = new JRadioButton("Closed Way (Area)");
+	rbClosedWay.setActionCommand("area");
+	bgType.add(rbClosedWay);
+	pnl.add(rbClosedWay, GBC.eol());
+
+        //pnl.add(bgType, GBC.eol().insets(0,10,0,0).fill(GBC.HORIZONTAL).weight(2.0, 0.0));
+	//pnl.add(new JRadioButton("test"));
+	//pnl.add(bgType);
+
+        pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
+
+        pnl.add(new HtmlPanel(
+                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/>" +
+                    "Use positive numbers or N, E characters to indicate North or East cardinal direction.<br/>" +
+                    "For South and West cardinal directions you can use either negative numbers or S, W characters.<br/>" +
+                    "Coordinate value can be in one of three formats:<ul>" +
+                    "<li><i>degrees</i><tt>&deg;</tt></li>" +
+                    "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt></li>" +
+                    "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt> <i>seconds</i><tt>&quot</tt></li>" +
+                    "</ul>" +
+                    "Symbols <tt>&deg;</tt>, <tt>&#39;</tt>, <tt>&prime;</tt>, <tt>&quot;</tt>, <tt>&Prime;</tt> are optional.<br/><br/>" +
+                    "Some examples:<ul>" +
+                    "<li>49.29918&deg; 19.24788&deg;</li>" +
+                    "<li>N 49.29918 E 19.24788</li>" +
+                    "<li>W 49&deg;29.918&#39; S 19&deg;24.788&#39;</li>" +
+                    "<li>N 49&deg;29&#39;04&quot; E 19&deg;24&#39;43&quot;</li>" +
+                    "<li>49.29918 N, 19.24788 E</li>" +
+                    "<li>49&deg;29&#39;21&quot; N 19&deg;24&#39;38&quot; E</li>" +
+                    "<li>49 29 51, 19 24 18</li>" +
+                    "<li>49 29, 19 24</li>" +
+                    "<li>E 49 29, N 19 24</li>" +
+                    "<li>49&deg; 29; 19&deg; 24</li>" +
+                    "<li>N 49&deg; 29, W 19&deg; 24</li>" +
+                    "<li>49&deg; 29.5 S, 19&deg; 24.6 E</li>" +
+                    "<li>N 49 29.918 E 19 15.88</li>" +
+                    "<li>49 29.4 19 24.5</li>" +
+                    "<li>-49 29.4 N -19 24.5 W</li></ul>" +
+                    "<li>48 deg 42&#39; 52.13\" N, 21 deg 11&#39; 47.60\" E</li></ul>"
+                )),
+                GBC.eol().fill().weight(1.0, 1.0));
+
+        // parse and verify input on the fly
+        //
+        LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
+        taLatLon.getDocument().addDocumentListener(inputVerifier);
+
+        // select the text in the field on focus
+        //
+        TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
+        taLatLon.addFocusListener(focusHandler);
+        return pnl;
+    }
+
+    protected void build() {
+        tabs = new JTabbedPane();
+        tabs.addTab(tr("Lat/Lon"), buildLatLon());
+        tabs.getModel().addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                switch (tabs.getModel().getSelectedIndex()) {
+                    case 0: parseLatLonUserInput(); break;
+                    default: throw new AssertionError();
+                }
+            }
+        });
+        setContent(tabs, false);
+    }
+
+    public LatLonDialog(Component parent, String title, String help) {
+        super(Main.parent, tr("Add Node..."), new String[] { tr("Ok"), tr("Cancel") });
+        setButtonIcons(new String[] { "ok", "cancel" });
+        configureContextsensitiveHelp("/Action/AddNode", true);
+
+        build();
+        setCoordinates(null);
+    }
+
+    public void setCoordinates(LatLon[] ll) {
+        if (ll == null) {
+            ll = new LatLon[] {};
+        }
+        this.latLonCoordinates = ll;
+    String text = "";
+    for (LatLon latlon : ll) {
+            text = text + latlon.latToString(CoordinateFormat.getDefaultFormat()) + " " + latlon.lonToString(CoordinateFormat.getDefaultFormat()) + "\n";
+        }
+        taLatLon.setText(text);
+        setOkEnabled(true);
+    }
+
+    public LatLon[] getCoordinates() {
+        return latLonCoordinates;
+    }
+
+    public LatLon[] getLatLonCoordinates() {
+        return latLonCoordinates;
+    }
+
+    public String getGeomType() {
+        return bgType.getSelection().getActionCommand();
+    }
+
+    protected void setErrorFeedback(JTextArea tf, String message) {
+        tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
+        tf.setToolTipText(message);
+        tf.setBackground(BG_COLOR_ERROR);
+    }
+
+    protected void clearErrorFeedback(JTextArea tf, String message) {
+        tf.setBorder(UIManager.getBorder("TextField.border"));
+        tf.setToolTipText(message);
+        tf.setBackground(UIManager.getColor("TextField.background"));
+    }
+
+    protected Double parseDoubleFromUserInput(String input) {
+        if (input == null) return null;
+        // remove white space and an optional degree symbol
+        //
+        input = input.trim();
+        input = input.replaceAll(DEG, "");
+
+        // try to parse using the current locale
+        //
+        NumberFormat f = NumberFormat.getNumberInstance();
+        Number n=null;
+        ParsePosition pp = new ParsePosition(0);
+        n = f.parse(input,pp);
+        if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length()) {
+            // fall back - try to parse with the english locale
+            //
+            pp = new ParsePosition(0);
+            f = NumberFormat.getNumberInstance(Locale.ENGLISH);
+            n = f.parse(input, pp);
+            if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length())
+                return null;
+        }
+        return n== null ? null : n.doubleValue();
+    }
+
+    protected void parseLatLonUserInput() {
+        LatLon[] latLons;
+        try {
+            latLons = parseLatLons(taLatLon.getText());
+        Boolean working = true;
+        int i=0;
+        while (working && i < latLons.length) {
+        if (!LatLon.isValidLat(latLons[i].lat()) || !LatLon.isValidLon(latLons[i].lon())) {
+                    latLons = null;
+            working = false;
+                }
+        i++;
+        }
+        } catch (IllegalArgumentException e) {
+            latLons = null;
+        }
+        if (latLons == null) {
+            setErrorFeedback(taLatLon, tr("Please enter a GPS coordinates"));
+            latLonCoordinates = null;
+            setOkEnabled(false);
+        } else {
+            clearErrorFeedback(taLatLon,tr("Please enter a GPS coordinates"));
+            latLonCoordinates = latLons;
+            setOkEnabled(true);
+        }
+    }
+
+    private void setOkEnabled(boolean b) {
+        if (buttons != null && buttons.size() > 0) {
+            buttons.get(0).setEnabled(b);
+        }
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            WindowGeometry.centerInWindow(Main.parent, getSize()).applySafe(this);
+        }
+        super.setVisible(visible);
+    }
+
+    class LatLonInputVerifier implements DocumentListener {
+        public void changedUpdate(DocumentEvent e) {
+            parseLatLonUserInput();
+        }
+
+        public void insertUpdate(DocumentEvent e) {
+            parseLatLonUserInput();
+        }
+
+        public void removeUpdate(DocumentEvent e) {
+            parseLatLonUserInput();
+        }
+    }
+
+    static class TextFieldFocusHandler implements FocusListener {
+        public void focusGained(FocusEvent e) {
+            Component c = e.getComponent();
+            if (c instanceof JTextArea) {
+                JTextArea tf = (JTextArea)c;
+                tf.selectAll();
+            }
+        }
+        public void focusLost(FocusEvent e) {}
+    }
+
+    private static LatLon[] parseLatLons(final String text) {
+        String lines[] = text.split("\\r?\\n");
+        List<LatLon> latLons = new ArrayList<LatLon>();
+        for (String line : lines) {
+            latLons.add(parseLatLon(line));
+        }
+        return latLons.toArray(new LatLon[]{});
+    }
+
+    private static LatLon parseLatLon(final String coord) {
+        final Matcher m = p.matcher(coord);
+
+        final StringBuilder sb = new StringBuilder();
+        final List<Object> list = new ArrayList<Object>();
+
+        while (m.find()) {
+            if (m.group(1) != null) {
+                sb.append('R');     // floating point number
+                list.add(Double.parseDouble(m.group(1).replace(',', '.')));
+            } else if (m.group(2) != null) {
+                sb.append('Z');     // integer number
+                list.add(Double.parseDouble(m.group(2)));
+            } else if (m.group(3) != null) {
+                sb.append('o');     // degree sign
+            } else if (m.group(4) != null) {
+                sb.append('\'');    // seconds sign
+            } else if (m.group(5) != null) {
+                sb.append('"');     // minutes sign
+            } else if (m.group(6) != null) {
+                sb.append(',');     // separator
+            } else if (m.group(7) != null) {
+                sb.append("x");     // cardinal direction
+                String c = m.group(7).toUpperCase();
+                if (c.equals("N") || c.equals("S") || c.equals("E") || c.equals("W")) {
+                    list.add(c);
+                } else {
+                    list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
+                            .replace(E_TR, 'E').replace(W_TR, 'W'));
+                }
+            } else if (m.group(8) != null) {
+                throw new IllegalArgumentException("invalid token: " + m.group(8));
+            }
+        }
+
+        final String pattern = sb.toString();
+
+        final Object[] params = list.toArray();
+        final LatLonHolder latLon = new LatLonHolder();
+
+        if (pattern.matches("Ro?,?Ro?")) {
+            setLatLonObj(latLon,
+                    params[0], ZERO, ZERO, "N",
+                    params[1], ZERO, ZERO, "E");
+        } else if (pattern.matches("xRo?,?xRo?")) {
+            setLatLonObj(latLon,
+                    params[1], ZERO, ZERO, params[0],
+                    params[3], ZERO, ZERO, params[2]);
+        } else if (pattern.matches("Ro?x,?Ro?x")) {
+            setLatLonObj(latLon,
+                    params[0], ZERO, ZERO, params[1],
+                    params[2], ZERO, ZERO, params[3]);
+        } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
+            setLatLonObj(latLon,
+                    params[0], params[1], ZERO, "N",
+                    params[2], params[3], ZERO, "E");
+        } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
+            setLatLonObj(latLon,
+                    params[1], params[2], ZERO, params[0],
+                    params[4], params[5], ZERO, params[3]);
+        } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
+            setLatLonObj(latLon,
+                    params[0], params[1], ZERO, params[2],
+                    params[3], params[4], ZERO, params[5]);
+        } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
+            setLatLonObj(latLon,
+                    params[0], params[1], params[2], params[3],
+                    params[4], params[5], params[6], params[7]);
+        } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
+            setLatLonObj(latLon,
+                    params[1], params[2], params[3], params[0],
+                    params[5], params[6], params[7], params[4]);
+        } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
+            setLatLonObj(latLon,
+                    params[0], params[1], params[2], "N",
+                    params[3], params[4], params[5], "E");
+        } else {
+            throw new IllegalArgumentException("invalid format: " + pattern);
+        }
+
+        return new LatLon(latLon.lat, latLon.lon);
+    }
+
+    private static class LatLonHolder {
+        double lat, lon;
+    }
+
+    private static void setLatLonObj(final LatLonHolder latLon,
+            final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
+            final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
+
+        setLatLon(latLon,
+                (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
+                (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
+    }
+
+    private static void setLatLon(final LatLonHolder latLon,
+            final double coord1deg, final double coord1min, final double coord1sec, final String card1,
+            final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
+
+        setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
+        setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
+    }
+
+    private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec, final String card) {
+        if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
+            throw new IllegalArgumentException("out of range");
+        }
+
+        double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
+        coord = card.equals("N") || card.equals("E") ? coord : -coord;
+        if (card.equals("N") || card.equals("S")) {
+            latLon.lat = coord;
+        } else {
+            latLon.lon = coord;
+        }
+    }
+
+    public String getLatLonText() {
+        return taLatLon.getText();
+    }
+
+    public void setLatLonText(String text) {
+        taLatLon.setText(text);
+    }
+
+    private class CoordinateListener implements DocumentListener {
+        public void changedUpdate(DocumentEvent e) {
+            //not fired
+        }
+        public void insertUpdate(DocumentEvent e) {
+            updateButtons();
+        }
+        public void removeUpdate(DocumentEvent e) {
+            updateButtons();
+        }
+        private void updateButtons() {
+            String text = taLatLon.getText();
+            String[] lines = text.split("\r\n|\r|\n");
+            rbNodes.setEnabled(true);
+            rbWay.setEnabled(true);
+            rbClosedWay.setEnabled(true);
+            if (lines.length < 3) {
+                rbClosedWay.setEnabled(false);
+                bgType.setSelected(rbNodes.getModel(), true);
+            }
+            if (lines.length < 2) {
+                rbWay.setEnabled(false);
+                bgType.setSelected(rbNodes.getModel(), true);
+            }
+        }
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/HungarianAlgorithm.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/HungarianAlgorithm.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/HungarianAlgorithm.java	(revision 28028)
@@ -0,0 +1,605 @@
+/*
+ * Created on Apr 25, 2005
+ * 
+ * Munkres-Kuhn (Hungarian) Algorithm Clean Version: 0.11
+ * 
+ * Konstantinos A. Nedas                     
+ * Department of Spatial Information Science & Engineering
+ * University of Maine, Orono, ME 04469-5711, USA
+ * kostas@spatial.maine.edu
+ * http://www.spatial.maine.edu/~kostas       
+ *
+ * This Java class implements the Hungarian algorithm [a.k.a Munkres' algorithm,
+ * a.k.a. Kuhn algorithm, a.k.a. Assignment problem, a.k.a. Marriage problem,
+ * a.k.a. Maximum Weighted Maximum Cardinality Bipartite Matching].
+ *
+ * [It can be used as a method call from within any main (or other function).]
+ * It takes 2 arguments:
+ * a. A 2-D array (could be rectangular or square).
+ * b. A string ("min" or "max") specifying whether you want the min or max assignment.
+ * [It returns an assignment matrix[array.length][2] that contains the row and col of
+ * the elements (in the original inputted array) that make up the optimum assignment.]
+ *  
+ * [This version contains only scarce comments. If you want to understand the 
+ * inner workings of the algorithm, get the tutorial version of the algorithm
+ * from the same website you got this one (http://www.spatial.maine.edu/~kostas/dev/soft/munkres.htm)]
+ * 
+ * Any comments, corrections, or additions would be much appreciated. 
+ * Credit due to professor Bob Pilgrim for providing an online copy of the
+ * pseudocode for this algorithm (http://216.249.163.93/bob.pilgrim/445/munkres.html)
+ * 
+ * Feel free to redistribute this source code, as long as this header--with
+ * the exception of sections in brackets--remains as part of the file.
+ * 
+ * Requirements: JDK 1.5.0_01 or better.
+ * [Created in Eclipse 3.1M6 (www.eclipse.org).]
+ * 
+ */
+
+package org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry;
+
+import static java.lang.Math.*;
+import java.util.*;
+
+public class HungarianAlgorithm {
+
+	//********************************//
+	//METHODS FOR CONSOLE INPUT-OUTPUT//
+	//********************************//
+	
+	public static int readInput(String prompt)	//Reads input,returns double.
+	{
+		Scanner in = new Scanner(System.in);
+		System.out.print(prompt);
+		int input = in.nextInt();
+		return input;
+	}
+	public static void printTime(double time)	//Formats time output.
+	{
+		String timeElapsed = "";
+		int days = (int)floor(time)/(24 * 3600);
+		int hours = (int)floor(time%(24*3600))/(3600);
+		int minutes = (int)floor((time%3600)/60);
+		int seconds = (int)round(time%60);
+		
+		if (days > 0)
+			timeElapsed = Integer.toString(days) + "d:";
+		if (hours > 0)
+			timeElapsed = timeElapsed + Integer.toString(hours) + "h:";
+		if (minutes > 0)
+			timeElapsed = timeElapsed + Integer.toString(minutes) + "m:";
+		
+		timeElapsed = timeElapsed + Integer.toString(seconds) + "s";
+		System.out.print("\nTotal time required: " + timeElapsed + "\n\n");
+	}
+	
+	//*******************************************//
+	//METHODS THAT PERFORM ARRAY-PROCESSING TASKS//
+	//*******************************************//
+	
+	public static void generateRandomArray	//Generates random 2-D array.
+	(double[][] array, String randomMethod)	
+	{
+		Random generator = new Random();
+		for (int i=0; i<array.length; i++)
+		{
+			for (int j=0; j<array[i].length; j++)
+			{
+				if (randomMethod.equals("random"))
+					{array[i][j] = generator.nextDouble();}
+				if (randomMethod.equals("gaussian"))
+				{
+						array[i][j] = generator.nextGaussian()/4;		//range length to 1.
+						if (array[i][j] > 0.5) {array[i][j] = 0.5;}		//eliminate outliers.
+						if (array[i][j] < -0.5) {array[i][j] = -0.5;}	//eliminate outliers.
+						array[i][j] = array[i][j] + 0.5;				//make elements positive.
+				}
+			}
+		}			
+	}
+	public static double findLargest		//Finds the largest element in a positive array.
+	(double[][] array)
+	//works for arrays where all values are >= 0.
+	{
+		double largest = 0;
+		for (int i=0; i<array.length; i++)
+		{
+			for (int j=0; j<array[i].length; j++)
+			{
+				if (array[i][j] > largest)
+				{
+					largest = array[i][j];
+				}
+			}
+		}
+			
+		return largest;
+	}
+	public static double[][] transpose		//Transposes a double[][] array.
+	(double[][] array)	
+	{
+		double[][] transposedArray = new double[array[0].length][array.length];
+		for (int i=0; i<transposedArray.length; i++)
+		{
+			for (int j=0; j<transposedArray[i].length; j++)
+			{transposedArray[i][j] = array[j][i];}
+		}
+		return transposedArray;
+	}
+	public static double[][] copyOf			//Copies all elements of an array to a new array.
+	(double[][] original)	
+	{
+		double[][] copy = new double[original.length][original[0].length];
+		for (int i=0; i<original.length; i++)
+		{
+			//Need to do it this way, otherwise it copies only memory location
+			System.arraycopy(original[i], 0, copy[i], 0, original[i].length);
+		}
+		
+		return copy;
+	}
+	
+	//**********************************//
+	//METHODS OF THE HUNGARIAN ALGORITHM//
+	//**********************************//
+	
+	public static int[][] hgAlgorithm (double[][] array, String sumType)
+	{
+		double[][] cost = copyOf(array);	//Create the cost matrix
+		
+		if (sumType.equalsIgnoreCase("max"))	//Then array is weight array. Must change to cost.
+		{
+			double maxWeight = findLargest(cost);
+			for (int i=0; i<cost.length; i++)		//Generate cost by subtracting.
+			{
+				for (int j=0; j<cost[i].length; j++)
+				{
+					cost [i][j] = (maxWeight - cost [i][j]);
+				}
+			}
+		}
+		double maxCost = findLargest(cost);		//Find largest cost matrix element (needed for step 6).
+		
+		int[][] mask = new int[cost.length][cost[0].length];	//The mask array.
+		int[] rowCover = new int[cost.length];					//The row covering vector.
+		int[] colCover = new int[cost[0].length];				//The column covering vector.
+		int[] zero_RC = new int[2];								//Position of last zero from Step 4.
+		int step = 1;											
+		boolean done = false;
+		while (done == false)	//main execution loop
+		{ 
+			switch (step)
+		    {
+				case 1:
+					step = hg_step1(step, cost);     
+		    	    break;
+		    	case 2:
+		    	    step = hg_step2(step, cost, mask, rowCover, colCover);
+					break;
+		    	case 3:
+		    	    step = hg_step3(step, mask, colCover);
+					break;
+		    	case 4:
+		    	    step = hg_step4(step, cost, mask, rowCover, colCover, zero_RC);
+					break;
+		    	case 5:
+					step = hg_step5(step, mask, rowCover, colCover, zero_RC);
+					break;
+		    	case 6:
+		    	   	step = hg_step6(step, cost, rowCover, colCover, maxCost);
+					break;
+		  	    case 7:
+		    	    done=true;
+		    	    break;
+		    }
+		}//end while
+		
+		int[][] assignment = new int[array.length][2];	//Create the returned array.
+		for (int i=0; i<mask.length; i++)
+		{
+			for (int j=0; j<mask[i].length; j++)
+			{
+				if (mask[i][j] == 1)
+				{
+					assignment[i][0] = i;
+					assignment[i][1] = j;
+				}
+			}
+		}
+		
+		//If you want to return the min or max sum, in your own main method
+		//instead of the assignment array, then use the following code:
+		/*
+		double sum = 0; 
+		for (int i=0; i<assignment.length; i++)
+		{
+			sum = sum + array[assignment[i][0]][assignment[i][1]];
+		}
+		return sum;
+		*/
+		//Of course you must also change the header of the method to:
+		//public static double hgAlgorithm (double[][] array, String sumType)
+		
+		return assignment;
+	}
+	public static int hg_step1(int step, double[][] cost)
+	{
+		//What STEP 1 does:
+		//For each row of the cost matrix, find the smallest element
+		//and subtract it from from every other element in its row. 
+	    
+	   	double minval;
+	   	
+		for (int i=0; i<cost.length; i++)	
+	   	{									
+	   	    minval=cost[i][0];
+	   	    for (int j=0; j<cost[i].length; j++)	//1st inner loop finds min val in row.
+	   	    {
+	   	        if (minval>cost[i][j])
+	   	        {
+	   	            minval=cost[i][j];
+	   	        }
+			}
+			for (int j=0; j<cost[i].length; j++)	//2nd inner loop subtracts it.
+	   	    {
+	   	        cost[i][j]=cost[i][j]-minval;
+	   	    }
+		}
+	   			    
+		step=2;
+		return step;
+	}
+	public static int hg_step2(int step, double[][] cost, int[][] mask, int[]rowCover, int[] colCover)
+	{
+		//What STEP 2 does:
+		//Marks uncovered zeros as starred and covers their row and column.
+		
+		for (int i=0; i<cost.length; i++)
+	    {
+	        for (int j=0; j<cost[i].length; j++)
+	        {
+	            if ((cost[i][j]==0) && (colCover[j]==0) && (rowCover[i]==0))
+	            {
+	                mask[i][j]=1;
+					colCover[j]=1;
+	                rowCover[i]=1;
+				}
+	        }
+	    }
+							
+		clearCovers(rowCover, colCover);	//Reset cover vectors.
+			    
+		step=3;
+		return step;
+	}
+	public static int hg_step3(int step, int[][] mask, int[] colCover)
+	{
+		//What STEP 3 does:
+		//Cover columns of starred zeros. Check if all columns are covered.
+		
+		for (int i=0; i<mask.length; i++)	//Cover columns of starred zeros.
+	    {
+	        for (int j=0; j<mask[i].length; j++)
+	        {
+	            if (mask[i][j] == 1)
+	            {
+	                colCover[j]=1;
+				}
+	        }
+	    }
+	    
+		int count=0;						
+		for (int j=0; j<colCover.length; j++)	//Check if all columns are covered.
+	    {
+	        count=count+colCover[j];
+	    }
+		
+		if (count>=mask.length)	//Should be cost.length but ok, because mask has same dimensions.	
+	    {
+			step=7;
+		}
+	    else
+		{
+			step=4;
+		}
+	    	
+		return step;
+	}
+	public static int hg_step4(int step, double[][] cost, int[][] mask, int[] rowCover, int[] colCover, int[] zero_RC)
+	{
+		//What STEP 4 does:
+		//Find an uncovered zero in cost and prime it (if none go to step 6). Check for star in same row:
+		//if yes, cover the row and uncover the star's column. Repeat until no uncovered zeros are left
+		//and go to step 6. If not, save location of primed zero and go to step 5.
+		
+		int[] row_col = new int[2];	//Holds row and col of uncovered zero.
+		boolean done = false;
+		while (done == false)
+		{
+			row_col = findUncoveredZero(row_col, cost, rowCover, colCover);
+			if (row_col[0] == -1)
+			{
+				done = true;
+				step = 6;
+			}
+			else
+			{
+				mask[row_col[0]][row_col[1]] = 2;	//Prime the found uncovered zero.
+				
+				boolean starInRow = false;
+				for (int j=0; j<mask[row_col[0]].length; j++)
+				{
+					if (mask[row_col[0]][j]==1)		//If there is a star in the same row...
+					{
+						starInRow = true;
+						row_col[1] = j;		//remember its column.
+					}
+				}
+							
+				if (starInRow==true)	
+				{
+					rowCover[row_col[0]] = 1;	//Cover the star's row.
+					colCover[row_col[1]] = 0;	//Uncover its column.
+				}
+				else
+				{
+					zero_RC[0] = row_col[0];	//Save row of primed zero.
+					zero_RC[1] = row_col[1];	//Save column of primed zero.
+					done = true;
+					step = 5;
+				}
+			}
+		}
+		
+		return step;
+	}
+	public static int[] findUncoveredZero	//Aux 1 for hg_step4.
+	(int[] row_col, double[][] cost, int[] rowCover, int[] colCover)
+	{
+		row_col[0] = -1;	//Just a check value. Not a real index.
+		row_col[1] = 0;
+		
+		int i = 0; boolean done = false;
+		while (done == false)
+		{
+			int j = 0;
+			while (j < cost[i].length)
+			{
+				if (cost[i][j]==0 && rowCover[i]==0 && colCover[j]==0)
+				{
+					row_col[0] = i;
+					row_col[1] = j;
+					done = true;
+				}
+				j = j+1;
+			}//end inner while
+			i=i+1;
+			if (i >= cost.length)
+			{
+				done = true;
+			}
+		}//end outer while
+		
+		return row_col;
+	}
+	public static int hg_step5(int step, int[][] mask, int[] rowCover, int[] colCover, int[] zero_RC)
+	{
+		//What STEP 5 does:	
+		//Construct series of alternating primes and stars. Start with prime from step 4.
+		//Take star in the same column. Next take prime in the same row as the star. Finish
+		//at a prime with no star in its column. Unstar all stars and star the primes of the
+		//series. Erasy any other primes. Reset covers. Go to step 3.
+		
+		int count = 0;												//Counts rows of the path matrix.
+		int[][] path = new int[(mask[0].length*mask.length)][2];	//Path matrix (stores row and col).
+		path[count][0] = zero_RC[0];								//Row of last prime.
+		path[count][1] = zero_RC[1];								//Column of last prime.
+		
+		boolean done = false;
+		while (done == false)
+		{ 
+			int r = findStarInCol(mask, path[count][1]);
+			if (r>=0)
+			{
+				count = count+1;
+				path[count][0] = r;					//Row of starred zero.
+				path[count][1] = path[count-1][1];	//Column of starred zero.
+			}
+			else
+			{
+				done = true;
+			}
+			
+			if (done == false)
+			{
+				int c = findPrimeInRow(mask, path[count][0]);
+				count = count+1;
+				path[count][0] = path [count-1][0];	//Row of primed zero.
+				path[count][1] = c;					//Col of primed zero.
+			}
+		}//end while
+		
+		convertPath(mask, path, count);
+		clearCovers(rowCover, colCover);
+		erasePrimes(mask);
+		
+		step = 3;
+		return step;
+		
+	}
+	public static int findStarInCol			//Aux 1 for hg_step5.
+	(int[][] mask, int col)
+	{
+		int r=-1;	//Again this is a check value.
+		for (int i=0; i<mask.length; i++)
+		{
+			if (mask[i][col]==1)
+			{
+				r = i;
+			}
+		}
+				
+		return r;
+	}
+	public static int findPrimeInRow		//Aux 2 for hg_step5.
+	(int[][] mask, int row)
+	{
+		int c = -1;
+		for (int j=0; j<mask[row].length; j++)
+		{
+			if (mask[row][j]==2)
+			{
+				c = j;
+			}
+		}
+		
+		return c;
+	}
+	public static void convertPath			//Aux 3 for hg_step5.
+	(int[][] mask, int[][] path, int count)
+	{
+		for (int i=0; i<=count; i++)
+		{
+			if (mask[(path[i][0])][(path[i][1])]==1)
+			{
+				mask[(path[i][0])][(path[i][1])] = 0;
+			}
+			else
+			{
+				mask[(path[i][0])][(path[i][1])] = 1;
+			}
+		}
+	}
+	public static void erasePrimes			//Aux 4 for hg_step5.
+	(int[][] mask)
+	{
+		for (int i=0; i<mask.length; i++)
+		{
+			for (int j=0; j<mask[i].length; j++)
+			{
+				if (mask[i][j]==2)
+				{
+					mask[i][j] = 0;
+				}
+			}
+		}
+	}
+	public static void clearCovers			//Aux 5 for hg_step5 (and not only).
+	(int[] rowCover, int[] colCover)
+	{
+		for (int i=0; i<rowCover.length; i++)
+		{
+			rowCover[i] = 0;
+		}
+		for (int j=0; j<colCover.length; j++)
+		{
+			colCover[j] = 0;
+		}
+	}
+	public static int hg_step6(int step, double[][] cost, int[] rowCover, int[] colCover, double maxCost)
+	{
+		//What STEP 6 does:
+		//Find smallest uncovered value in cost: a. Add it to every element of covered rows
+		//b. Subtract it from every element of uncovered columns. Go to step 4.
+		
+		double minval = findSmallest(cost, rowCover, colCover, maxCost);
+		
+		for (int i=0; i<rowCover.length; i++)
+		{
+			for (int j=0; j<colCover.length; j++)
+			{
+				if (rowCover[i]==1)
+				{
+					cost[i][j] = cost[i][j] + minval;
+				}
+				if (colCover[j]==0)
+				{
+					cost[i][j] = cost[i][j] - minval;
+				}
+			}
+		}
+			
+		step = 4;
+		return step;
+	}
+	public static double findSmallest		//Aux 1 for hg_step6.
+	(double[][] cost, int[] rowCover, int[] colCover, double maxCost)
+	{
+		double minval = maxCost;				//There cannot be a larger cost than this.
+		for (int i=0; i<cost.length; i++)		//Now find the smallest uncovered value.
+		{
+			for (int j=0; j<cost[i].length; j++)
+			{
+				if (rowCover[i]==0 && colCover[j]==0 && (minval > cost[i][j]))
+				{
+					minval = cost[i][j];
+				}
+			}
+		}
+		
+		return minval;
+	}
+	
+	//***********//
+	//MAIN METHOD//
+	//***********//
+	
+	public static void main(String[] args) 
+	{
+		//Below enter "max" or "min" to find maximum sum or minimum sum assignment.
+		String sumType = "max";		
+				
+		//Hard-coded example.
+		//double[][] array =
+		//{
+		//		{1, 2, 3},
+		//		{2, 4, 6},
+		//		{3, 6, 9}
+		//};
+		
+		//<UNCOMMENT> BELOW AND COMMENT BLOCK ABOVE TO USE A RANDOMLY GENERATED MATRIX
+		int numOfRows = readInput("How many rows for the matrix? ");
+		int numOfCols = readInput("How many columns for the matrix? ");
+		double[][] array = new double[numOfRows][numOfCols];
+		generateRandomArray(array, "random");	//All elements within [0,1].
+		//</UNCOMMENT>
+		
+		if (array.length > array[0].length)
+		{
+			System.out.println("Array transposed (because rows>columns).\n");	//Cols must be >= Rows.
+			array = transpose(array);
+		}
+				
+		//<COMMENT> TO AVOID PRINTING THE MATRIX FOR WHICH THE ASSIGNMENT IS CALCULATED
+		System.out.println("\n(Printing out only 2 decimals)\n");
+		System.out.println("The matrix is:");
+		for (int i=0; i<array.length; i++)
+		{
+			for (int j=0; j<array[i].length; j++)
+				{System.out.printf("%.2f\t", array[i][j]);}
+			System.out.println();
+		}
+		System.out.println();
+		//</COMMENT>*/
+		
+		double startTime = System.nanoTime();	
+		int[][] assignment = new int[array.length][2];
+		assignment = hgAlgorithm(array, sumType);	//Call Hungarian algorithm.
+		double endTime = System.nanoTime();
+						
+		System.out.println("The winning assignment (" + sumType + " sum) is:\n");	
+		double sum = 0;
+		for (int i=0; i<assignment.length; i++)
+		{
+			//<COMMENT> to avoid printing the elements that make up the assignment
+			System.out.printf("array(%d,%d) = %.2f\n", (assignment[i][0]+1), (assignment[i][1]+1),
+					array[assignment[i][0]][assignment[i][1]]);
+			sum = sum + array[assignment[i][0]][assignment[i][1]];
+			//</COMMENT>
+		}
+		
+		System.out.printf("\nThe %s is: %.2f\n", sumType, sum);
+		printTime((endTime - startTime)/1000000000.0);
+		
+	}
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryAction.java	(revision 28028)
@@ -0,0 +1,69 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Replaces already existing object (id>0) with a new object (id<0).
+ *
+ * @author Zverik
+ */
+public class ReplaceGeometryAction extends JosmAction {
+    private static final String TITLE = tr("Replace Geometry");
+
+    public ReplaceGeometryAction() {
+        super(TITLE, "dumbutils/replacegeometry", tr("Replace geometry of selected object with a new one"),
+                Shortcut.registerShortcut("tools:replacegeometry", tr("Tool: {0}", tr("Replace Geometry")), KeyEvent.VK_G, Shortcut.CTRL_SHIFT)
+                , true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (getCurrentDataSet() == null) {
+            return;
+        }
+
+        // There must be two ways selected: one with id > 0 and one new.
+        List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(getCurrentDataSet().getSelected());
+        if (selection.size() != 2) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("This tool replaces geometry of one object with another, and so requires exactly two objects to be selected."),
+                    TITLE, JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+
+        OsmPrimitive firstObject = selection.get(0);
+        OsmPrimitive secondObject = selection.get(1);
+        
+        try {
+            ReplaceGeometryUtils.replaceWithNew(firstObject, secondObject);
+        } catch (IllegalArgumentException ex) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    ex.getMessage(), TITLE, JOptionPane.INFORMATION_MESSAGE);
+        }
+         
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if( getCurrentDataSet() == null ) {
+            setEnabled(false);
+        }  else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) {
+        setEnabled(selection != null && selection.size() >= 2 );
+    }
+}
+
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java	(revision 28028)
@@ -0,0 +1,469 @@
+package org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry;
+
+import java.awt.geom.Area;
+import java.awt.geom.Point2D;
+import java.util.*;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.MergeNodesAction;
+import org.openstreetmap.josm.command.*;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
+import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ *
+ * @author Josh
+ */
+public final class ReplaceGeometryUtils {
+    private static final String TITLE = tr("Replace Geometry");
+    
+    /**
+     * Replace new or uploaded object with new object
+     * 
+     * @param firstObject
+     * @param secondObject
+     * @return 
+     */
+    public static boolean replaceWithNew(OsmPrimitive firstObject, OsmPrimitive secondObject) {
+        if (firstObject instanceof Node && secondObject instanceof Node) {
+            return replaceNodeWithNew((Node) firstObject, (Node) secondObject);
+        } else if (firstObject instanceof Way && secondObject instanceof Way) {
+            return replaceWayWithNew(Arrays.asList((Way) firstObject, (Way) secondObject));
+        } else if (firstObject instanceof Node) {
+            return upgradeNode((Node) firstObject, secondObject);
+        } else if (secondObject instanceof Node) {
+            return upgradeNode((Node) secondObject, firstObject);
+        } else {
+            throw new IllegalArgumentException(tr("This tool can only replace a node, upgrade a node to a way or a multipolygon, or replace a way with a way."));
+        }
+    }
+    
+    /**
+     * Replace subjectObject geometry with referenceObject geometry and merge tags
+     * and relation memberships.
+     * 
+     * @param subjectObject
+     * @param referenceSubject
+     * @return 
+     */
+    public static boolean replace(OsmPrimitive subjectObject, OsmPrimitive referenceSubject) {
+        if (subjectObject instanceof Node && referenceSubject instanceof Node) {
+            return replaceNode((Node) subjectObject, (Node) referenceSubject);
+        } else if (subjectObject instanceof Way && referenceSubject instanceof Way) {
+            return replaceWay((Way) subjectObject, (Way) referenceSubject);
+        } else if (subjectObject instanceof Node) {
+            return upgradeNode((Node) subjectObject, referenceSubject);
+        } else if (referenceSubject instanceof Node) {
+            // TODO: fix this illogical reversal?
+            return upgradeNode((Node) referenceSubject, subjectObject);
+        } else {
+            throw new IllegalArgumentException(tr("This tool can only replace a node, upgrade a node to a way or a multipolygon, or replace a way with a way."));
+        }
+    }
+
+    /**
+     * Replace a new or uploaded node with a new node
+     * @param firstNode
+     * @param secondNode
+     * @return 
+     */
+    public static boolean replaceNodeWithNew(Node firstNode, Node secondNode) {
+        if (firstNode.isNew() && !secondNode.isNew())
+            return replaceNode(secondNode, firstNode);
+        else if (!firstNode.isNew() && secondNode.isNew())
+            return replaceNode(firstNode, secondNode);
+        else
+            // both nodes are new OR uploaded, act like MergeNodes, moving first
+            // node to second
+            return replaceNode(firstNode, secondNode);
+    }
+    
+    /**
+     * Replace a node with another node (similar to MergeNodesAction)
+     *
+     * @param subjectNode
+     * @param referenceNode
+     * @return
+     */
+    public static boolean replaceNode(Node subjectNode, Node referenceNode) {
+        if (!OsmPrimitive.getFilteredList(subjectNode.getReferrers(), Way.class).isEmpty()) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Node belongs to way(s), cannot replace."),
+                    TITLE, JOptionPane.INFORMATION_MESSAGE);
+            return false;
+        }
+        // FIXME: handle different layers
+        List<Command> commands = new ArrayList<Command>();
+        commands.add(MergeNodesAction.mergeNodes(Main.main.getEditLayer(), Arrays.asList(subjectNode, referenceNode), referenceNode));
+
+        Main.main.undoRedo.add(new SequenceCommand(
+                tr("Replace geometry for node {0}", subjectNode.getDisplayName(DefaultNameFormatter.getInstance())),
+                commands));
+        return true;
+    }
+    
+    /**
+     * Upgrade a node to a way or multipolygon
+     *
+     * @param subjectNode node to be replaced
+     * @param referenceObject object with greater spatial quality
+     */
+    public static boolean upgradeNode(Node subjectNode, OsmPrimitive referenceObject) {
+        if (!OsmPrimitive.getFilteredList(subjectNode.getReferrers(), Way.class).isEmpty()) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Node belongs to way(s), cannot replace."),
+                    TITLE, JOptionPane.INFORMATION_MESSAGE);
+            return false;
+        }
+
+        if (referenceObject instanceof Relation && !((Relation) referenceObject).isMultipolygon()) {
+            JOptionPane.showMessageDialog(Main.parent, tr("Relation is not a multipolygon, cannot be used as a replacement."),
+                    TITLE, JOptionPane.INFORMATION_MESSAGE);
+            return false;
+        }
+
+        Node nodeToReplace = null;
+        // see if we need to replace a node in the replacement way to preserve connection in history
+        if (!subjectNode.isNew()) {
+            // Prepare a list of nodes that are not important
+            Collection<Node> nodePool = new HashSet<Node>();
+            if (referenceObject instanceof Way) {
+                nodePool.addAll(getUnimportantNodes((Way) referenceObject));
+            } else if (referenceObject instanceof Relation) {
+                for (RelationMember member : ((Relation) referenceObject).getMembers()) {
+                    if ((member.getRole().equals("outer") || member.getRole().equals("inner"))
+                            && member.isWay()) {
+                        // TODO: could consider more nodes, such as nodes that are members of other ways,
+                        // just need to replace occurences in all referrers
+                        nodePool.addAll(getUnimportantNodes(member.getWay()));
+                    }
+                }
+            } else {
+                assert false;
+            }
+            nodeToReplace = findNearestNode(subjectNode, nodePool);
+        }
+
+        List<Command> commands = new ArrayList<Command>();
+        AbstractMap<String, String> nodeTags = (AbstractMap<String, String>) subjectNode.getKeys();
+
+        // merge tags
+        Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(subjectNode, referenceObject);
+        if (tagResolutionCommands == null) {
+            // user canceled tag merge dialog
+            return false;
+        }
+        commands.addAll(tagResolutionCommands);
+
+        // replace sacrificial node in way with node that is being upgraded
+        if (nodeToReplace != null) {
+            // node should only have one parent, a way
+            Way parentWay = (Way) nodeToReplace.getReferrers().get(0);
+            List<Node> wayNodes = parentWay.getNodes();
+            int idx = wayNodes.indexOf(nodeToReplace);
+            wayNodes.set(idx, subjectNode);
+            if (idx == 0 && parentWay.isClosed()) {
+                // node is at start/end of way
+                wayNodes.set(wayNodes.size() - 1, subjectNode);
+            }
+            commands.add(new ChangeNodesCommand(parentWay, wayNodes));
+            commands.add(new MoveCommand(subjectNode, nodeToReplace.getCoor()));
+            commands.add(new DeleteCommand(nodeToReplace));
+
+            // delete tags from node
+            if (!nodeTags.isEmpty()) {
+                for (String key : nodeTags.keySet()) {
+                    commands.add(new ChangePropertyCommand(subjectNode, key, null));
+                }
+
+            }
+        } else {
+            // no node to replace, so just delete the original node
+            commands.add(new DeleteCommand(subjectNode));
+        }
+
+        Main.main.getCurrentDataSet().setSelected(referenceObject);
+
+        Main.main.undoRedo.add(new SequenceCommand(
+                tr("Replace geometry for node {0}", subjectNode.getDisplayName(DefaultNameFormatter.getInstance())),
+                commands));
+        return true;
+    }
+    
+    public static boolean replaceWayWithNew(List<Way> selection) {
+        // determine which way will be replaced and which will provide the geometry
+        boolean overrideNewCheck = false;
+        int idxNew = selection.get(0).isNew() ? 0 : 1;
+        if( selection.get(1-idxNew).isNew() ) {
+            // if both are new, select the one with all the DB nodes
+            boolean areNewNodes = false;
+            for (Node n : selection.get(0).getNodes()) {
+                if (n.isNew()) {
+                    areNewNodes = true;
+                }
+            }
+            idxNew = areNewNodes ? 0 : 1;
+            overrideNewCheck = true;
+            for (Node n : selection.get(1 - idxNew).getNodes()) {
+                if (n.isNew()) {
+                    overrideNewCheck = false;
+                }
+            }
+        }
+        Way referenceWay = selection.get(idxNew);
+        Way subjectWay = selection.get(1 - idxNew);
+        
+        if( !overrideNewCheck && (subjectWay.isNew() || !referenceWay.isNew()) ) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Please select one way that exists in the database and one new way with correct geometry."),
+                    TITLE, JOptionPane.WARNING_MESSAGE);
+            return false;
+        }
+        return replaceWay(subjectWay, referenceWay);
+    }
+    
+    public static boolean replaceWay(Way subjectWay, Way referenceWay) {
+
+        Area a = Main.main.getCurrentDataSet().getDataSourceArea();
+        if (!isInArea(subjectWay, a) || !isInArea(referenceWay, a)) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("The ways must be entirely within the downloaded area."),
+                    TITLE, JOptionPane.WARNING_MESSAGE);
+            return false;
+        }
+        
+        if (hasImportantNode(referenceWay, subjectWay)) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("The way to be replaced cannot have any nodes with properties or relation memberships unless they belong to both ways."),
+                    TITLE, JOptionPane.WARNING_MESSAGE);
+            return false;
+        }
+
+        List<Command> commands = new ArrayList<Command>();
+                
+        // merge tags
+        Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(referenceWay, subjectWay);
+        if (tagResolutionCommands == null) {
+            // user canceled tag merge dialog
+            return false;
+        }
+        commands.addAll(tagResolutionCommands);
+        
+        // Prepare a list of nodes that are not used anywhere except in the way
+        List<Node> nodePool = getUnimportantNodes(subjectWay);
+
+        // And the same for geometry, list nodes that can be freely deleted
+        List<Node> geometryPool = new LinkedList<Node>();
+        for( Node node : referenceWay.getNodes() ) {
+            List<OsmPrimitive> referrers = node.getReferrers();
+            if( node.isNew() && !node.isDeleted() && referrers.size() == 1
+                    && referrers.get(0).equals(referenceWay) && !subjectWay.containsNode(node)
+                    && !hasInterestingKey(node) && !geometryPool.contains(node))
+                geometryPool.add(node);
+        }
+
+        // Find new nodes that are closest to the old ones, remove matching old ones from the pool
+        // Assign node moves with least overall distance moved
+        Map<Node, Node> nodeAssoc = new HashMap<Node, Node>();
+        if (geometryPool.size() > 0 && nodePool.size() > 0) {
+            int gLen = geometryPool.size();
+            int nLen = nodePool.size();
+            double cost[][] = new double[nLen][gLen];
+
+            double maxDistance = Double.parseDouble(Main.pref.get("utilsplugin2.replace-geometry.max-distance", "1"));
+            for (int i = 0; i < nLen; i++) {
+                for (int j = 0; j < gLen; j++) {
+                    double d = nodePool.get(i).getCoor().distance(geometryPool.get(j).getCoor());
+                    if (d > maxDistance)
+                        cost[i][j] = Double.MAX_VALUE;
+                    else
+                        cost[i][j] = d;
+                }
+            }
+            int[][] assignment = HungarianAlgorithm.hgAlgorithm(cost, "min");
+            for (int i = 0; i < nLen; i++) {
+                int nIdx = assignment[i][0];
+                int gIdx = assignment[i][1];
+                if (cost[nIdx][gIdx] != Double.MAX_VALUE) {
+                    nodeAssoc.put(geometryPool.get(gIdx), nodePool.get(nIdx));
+                }
+            }
+        }
+        
+        // node will be moved, remove from pool
+        for (Node n : nodeAssoc.values()) {
+            nodePool.remove(n);
+        }
+
+        // Now that we have replacement list, move all unused new nodes to nodePool (and delete them afterwards)
+        for( Node n : geometryPool )
+            if( nodeAssoc.containsKey(n) )
+                nodePool.add(n);
+
+        // And prepare a list of nodes with all the replacements
+        List<Node> geometryNodes = referenceWay.getNodes();
+        for( int i = 0; i < geometryNodes.size(); i++ )
+            if( nodeAssoc.containsKey(geometryNodes.get(i)) )
+                geometryNodes.set(i, nodeAssoc.get(geometryNodes.get(i)));
+
+        // Now do the replacement
+        commands.add(new ChangeNodesCommand(subjectWay, geometryNodes));
+
+        // Move old nodes to new positions
+        for( Node node : nodeAssoc.keySet() )
+            commands.add(new MoveCommand(nodeAssoc.get(node), node.getCoor()));
+
+        // Remove geometry way from selection
+        Main.main.getCurrentDataSet().clearSelection(referenceWay);
+
+        // And delete old geometry way
+        commands.add(new DeleteCommand(referenceWay));
+
+        // Delete nodes that are not used anymore
+        if( !nodePool.isEmpty() )
+            commands.add(new DeleteCommand(nodePool));
+
+        // Two items in undo stack: change original way and delete geometry way
+        Main.main.undoRedo.add(new SequenceCommand(
+                tr("Replace geometry for way {0}", subjectWay.getDisplayName(DefaultNameFormatter.getInstance())),
+                commands));
+        return true;
+    }
+
+    /**
+     * Create a list of nodes that are not used anywhere except in the way.
+     *
+     * @param way
+     * @return 
+     */
+    protected static List<Node> getUnimportantNodes(Way way) {
+        List<Node> nodePool = new LinkedList<Node>();
+        for (Node n : way.getNodes()) {
+            List<OsmPrimitive> referrers = n.getReferrers();
+            if (!n.isDeleted() && referrers.size() == 1 && referrers.get(0).equals(way)
+                    && !hasInterestingKey(n) && !nodePool.contains(n)) {
+                nodePool.add(n);
+            }
+        }
+        return nodePool;
+    }
+    
+    /**
+     * Checks if a way has at least one important node (e.g. interesting tag,
+     * role membership), and thus cannot be safely modified.
+     * 
+     * @param way
+     * @return 
+     */
+    protected static boolean hasImportantNode(Way geometry, Way way) {
+        for (Node n : way.getNodes()) {
+            // if original and replacement way share a node, it's safe to replace
+            if (geometry.containsNode(n)) {
+                continue;
+            }
+            //TODO: if way is connected to other ways, warn or disallow?
+            for (OsmPrimitive o : n.getReferrers()) {
+                if (o instanceof Relation) {
+                    return true;
+                }
+            }
+            if (hasInterestingKey(n)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    protected static boolean hasInterestingKey(OsmPrimitive object) {
+        for (String key : object.getKeys().keySet()) {
+            if (!OsmPrimitive.isUninterestingKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static boolean isInArea(Node node, Area area) {
+        if (node.isNewOrUndeleted() || area == null || area.contains(node.getCoor())) {
+            return true;
+        }
+        return false;
+    }
+    
+    protected static boolean isInArea(Way way, Area area) {
+        if (area == null) {
+            return true;
+        }
+
+        for (Node n : way.getNodes()) {
+            if (!isInArea(n, area)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+    
+     /**
+     * Merge tags from source to target object, showing resolution dialog if
+     * needed.
+     *
+     * @param source object tags are merged from
+     * @param target object tags are merged to
+     * @return
+     */
+    protected static List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target) {
+        Collection<OsmPrimitive> primitives = Arrays.asList(source, target);
+        
+        Set<RelationToChildReference> relationToNodeReferences = RelationToChildReference.getRelationToChildReferences(primitives);
+
+        // build the tag collection
+        TagCollection tags = TagCollection.unionOfAllPrimitives(primitives);
+        TagConflictResolutionUtil.combineTigerTags(tags);
+        TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, primitives);
+        TagCollection tagsToEdit = new TagCollection(tags);
+        TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
+
+        // launch a conflict resolution dialog, if necessary
+        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
+        dialog.getTagConflictResolverModel().populate(tagsToEdit, tags.getKeysWithMultipleValues());
+        dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences);
+        dialog.setTargetPrimitive(target);
+        dialog.prepareDefaultDecisions();
+
+        // conflict resolution is necessary if there are conflicts in the merged tags
+        // or if at least one of the merged nodes is referred to by a relation
+        if (!tags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) {
+            dialog.setVisible(true);
+            if (dialog.isCanceled()) {
+                return null;
+            }
+        }
+        return dialog.buildResolutionCommands();
+    }
+
+    
+    /**
+     * Find node from the collection which is nearest to <tt>node</tt>. Max distance is taken in consideration.
+     * @return null if there is no such node.
+     */
+    protected static Node findNearestNode( Node node, Collection<Node> nodes ) {
+        if( nodes.contains(node) )
+            return node;
+        
+        Node nearest = null;
+        // TODO: use meters instead of degrees, but do it fast
+        double distance = Double.parseDouble(Main.pref.get("utilsplugin2.replace-geometry.max-distance", "1"));
+        Point2D coor = node.getCoor();
+
+        for( Node n : nodes ) {
+            double d = n.getCoor().distance(coor);
+            if( d < distance ) {
+                distance = d;
+                nearest = n;
+            }
+        }
+        return nearest;
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentNodesAction.java	(revision 28028)
@@ -0,0 +1,114 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and others
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection
+ */
+public class AdjacentNodesAction extends JosmAction {
+
+    public static final boolean treeMode = false;
+
+    public AdjacentNodesAction() {
+        super(tr("Adjacent nodes"), "adjnodes", tr("Select adjacent nodes"),
+                Shortcut.registerShortcut("tools:adjnodes", tr("Tool: {0}","Adjacent nodes"),
+                KeyEvent.VK_E, Shortcut.DIRECT), true);
+        putValue("help", ht("/Action/AdjacentNodes"));
+    }
+
+    private  Set<Way> activeWays = new HashSet<Way>();
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+        
+        // if no nodes and no ways are selected, do nothing
+        if (selectedNodes.isEmpty() && selectedWays.isEmpty()) return;
+
+        if (selectedWays.isEmpty()) {
+            // if one node is selected, used ways connected to it to extend selecteons
+            // activeWays are remembered for next extend action (!!!)
+
+            // FIXME: some strange behaviour is possible if user delete some of these way
+            // how to clear activeWays during such user actions? Do not know
+            if (selectedNodes.size() == 1) {
+                activeWays.clear();
+//                System.out.println("Cleared active ways");
+            }
+        } else {
+            // use only ways that were selected for adding nodes
+            activeWays = selectedWays;
+        }
+
+        // selecting nodes of selected ways
+        if(selectedNodes.isEmpty()) {
+            HashSet<Node> newNodes = new HashSet<Node>();
+            NodeWayUtils.addNodesConnectedToWays(selectedWays, newNodes);
+            activeWays.clear();
+            getCurrentDataSet().setSelected(newNodes);
+            return;
+        }
+
+        if (activeWays.isEmpty()) {
+                NodeWayUtils.addWaysConnectedToNodes(selectedNodes, activeWays);
+        }
+
+        Set<Node> newNodes = new HashSet <Node>();
+        for (Node node: selectedNodes) {
+            for (Way w: activeWays) {
+                NodeWayUtils.addNeighbours(w, node, newNodes);
+            }
+        }
+        
+        // select only newly found nodes
+         newNodes.removeAll(selectedNodes);
+
+//         System.out.printf("Found %d new nodes\n",newNodes.size());
+         
+         // enable branching on next call of this function
+         // if no new nodes were found, next search will include all touched ways
+         if (newNodes.isEmpty()) {
+             activeWays.clear();
+//             System.out.println("No more points found, activeways cleared");
+         }
+
+         getCurrentDataSet().addSelected(newNodes);
+         newNodes = null;
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentWaysAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentWaysAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/AdjacentWaysAction.java	(revision 28028)
@@ -0,0 +1,72 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection
+ */
+public class AdjacentWaysAction extends JosmAction {
+
+    public static final boolean treeMode = false;
+
+    public AdjacentWaysAction() {
+        super(tr("Adjacent ways"), "adjways",
+                tr("Adjacent ways will be selected. Nodes will be deselected."),
+                Shortcut.registerShortcut("tools:adjways", tr("Tool: {0}","Adjacent ways"),
+                KeyEvent.VK_E, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/AdjacentWays"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+
+        // select ways attached to already selected ways
+        Set<Way> newWays = new HashSet<Way>();
+        NodeWayUtils.addWaysConnectedToWays(selectedWays, newWays);
+        newWays.addAll(selectedWays);
+
+        // selecting ways attached to selected nodes
+        if(!selectedNodes.isEmpty()) {
+            NodeWayUtils.addWaysConnectedToNodes(selectedNodes, newWays);
+        }
+
+//        System.out.printf("%d ways added to selection\n",newWays.size()-selectedWays.size());
+        getCurrentDataSet().setSelected(newWays);
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/ConnectedWaysAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/ConnectedWaysAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/ConnectedWaysAction.java	(revision 28028)
@@ -0,0 +1,69 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection by selecting nodes on all touched ways
+ */
+public class ConnectedWaysAction extends JosmAction {
+
+    public ConnectedWaysAction() {
+        super(tr("All connected ways"), "adjwaysall", tr("Select all connected ways"),
+                Shortcut.registerShortcut("tools:adjwaysall", tr("Tool: {0}","All connected ways"),
+                KeyEvent.VK_E, Shortcut.CTRL_SHIFT), true);
+        putValue("help", ht("/Action/SelectConnectedWays"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+
+        Set<Way> newWays = new HashSet<Way>();
+
+        // selecting ways attached to selected nodes
+        if(!selectedNodes.isEmpty()) {
+            NodeWayUtils.addWaysConnectedToNodes(selectedNodes, newWays);
+        }
+
+        // select ways attached to already selected ways
+        newWays.addAll(selectedWays);
+        NodeWayUtils.addWaysConnectedToWaysRecursively(selectedWays, newWays);
+        
+//        System.out.printf("%d ways added to selection\n",newWays.size()-selectedWays.size());
+        getCurrentDataSet().setSelected(newWays);
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysAction.java	(revision 28028)
@@ -0,0 +1,69 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection by selecting nodes on all touched ways
+ */
+public class IntersectedWaysAction extends JosmAction {
+
+    public IntersectedWaysAction() {
+        super(tr("Intersecting ways"), "intway", tr("Select intersecting ways"),
+                Shortcut.registerShortcut("tools:intway", tr("Tool: {0}","Intersecting ways"),
+                KeyEvent.VK_I, Shortcut.DIRECT), true);
+        putValue("help", ht("/Action/SelectIntersectingWays"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+
+        // select ways attached to already selected ways
+        if (!selectedWays.isEmpty()) {
+            Set<Way> newWays = new HashSet<Way>();
+            NodeWayUtils.addWaysIntersectingWays(
+                    getCurrentDataSet().getWays(),
+                    selectedWays, newWays);
+            getCurrentDataSet().addSelected(newWays);
+            return;
+        } else {
+             JOptionPane.showMessageDialog(Main.parent,
+               tr("Please select some ways to find connected and intersecting ways!"),
+               tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysRecursiveAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysRecursiveAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/IntersectedWaysRecursiveAction.java	(revision 28028)
@@ -0,0 +1,72 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin ond others
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection by selecting nodes on all touched ways
+ */
+public class IntersectedWaysRecursiveAction extends JosmAction {
+    
+    public IntersectedWaysRecursiveAction() {
+        super(tr("All intersecting ways"), "intwayall", tr("Select all intersecting ways"),
+                Shortcut.registerShortcut("tools:intwayall", tr("Tool: {0}","All intersecting ways"),
+                KeyEvent.VK_MULTIPLY, Shortcut.CTRL), true);
+        putValue("help", ht("/Action/SelectAllIntersectingWays"));
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+
+        if (!selectedWays.isEmpty()) {
+            Set<Way> newWays = new HashSet<Way>();
+            NodeWayUtils.addWaysIntersectingWaysRecursively(
+                    getCurrentDataSet().getWays(),
+                    selectedWays, newWays);
+            getCurrentDataSet().addSelected(newWays);
+            return;
+        } else {
+             JOptionPane.showMessageDialog(Main.parent,
+               tr("Please select some ways to find all connected and intersecting ways!"),
+               tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+
+    }
+
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/MiddleNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/MiddleNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/MiddleNodesAction.java	(revision 28028)
@@ -0,0 +1,80 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and others
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Selects nodes between two selected
+ */
+public class MiddleNodesAction extends JosmAction {
+
+    public static final boolean treeMode = false;
+
+    public MiddleNodesAction() {
+        super(tr("Middle nodes"), "midnodes", tr("Select middle nodes"),
+                Shortcut.registerShortcut("tools:midnodes", tr("Tool: {0}","Middle nodes"),
+                KeyEvent.VK_E,  Shortcut.ALT_SHIFT), true);
+        putValue("help", ht("/Action/MiddleNodes"));
+    }
+
+    private  Set<Way> activeWays = new HashSet<Way>();
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+        
+        // if no 2 nodes and no ways are selected, do nothing
+        if (selectedNodes.size() != 2) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    tr("Please select two nodes connected by way!"),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        Set<Node> newNodes = new HashSet <Node>();
+        NodeWayUtils.addMiddle(selectedNodes, newNodes);
+        
+        // select only newly found nodes
+        newNodes.removeAll(selectedNodes);
+        getCurrentDataSet().addSelected(newNodes);
+        newNodes = null;
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/NodeWayUtils.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/NodeWayUtils.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/NodeWayUtils.java	(revision 28028)
@@ -0,0 +1,504 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import org.openstreetmap.josm.data.osm.Relation;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Geometry;
+import org.openstreetmap.josm.tools.Pair;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+
+/**
+ * Class with some useful functions that are reused in extend selection actions
+ *
+ */
+public final class NodeWayUtils {
+
+    static final int maxLevel = Main.pref.getInteger("selection.maxrecursion", 15);
+    static final int maxWays = Main.pref.getInteger("selection.maxfoundways", 2000);
+    static final int maxWays1 = Main.pref.getInteger("selection.maxfoundways.intersection", 500);
+
+    /**
+     * Find the neighbours of node n on the way w and put them in given collection
+     * @param w way on which the search goes
+     * @param n node to find its neighbours
+     * @param nodes collection to place the nodes we found
+     */
+    static void addNeighbours(Way w, Node n, Collection<Node> nodes) {
+        List<Node> nodeList = w.getNodes();
+        
+        int idx = nodeList.indexOf(n);
+        if (idx == -1) return;
+
+        // add previous element
+        if (idx > 0) {
+            nodes.add(nodeList.get(idx - 1));
+        }
+        // add next element
+        if (idx < nodeList.size() - 1) {
+            nodes.add(nodeList.get(idx + 1));
+        }
+        if (w.isClosed()) {
+            // cyclic neighbours detection
+            if (idx == 0) {
+                nodes.add(nodeList.get(nodeList.size() - 2));
+            }
+            if (idx == nodeList.size() - 1) {
+                nodes.add(nodeList.get(1));
+            }
+        }
+     }
+
+    /**
+     * Adds all ways attached to way to specified collection
+     * @param w way to find attached ways
+     * @param ways  collection to place the ways we found
+     */
+    static int addWaysConnectedToWay(Way w, Set<Way> ways) {
+         int s = ways.size();
+        List<Node> nodes = w.getNodes();
+        boolean flag = ways.contains(w);
+        for (Node n: nodes) {
+            ways.addAll(OsmPrimitive.getFilteredList(n.getReferrers(), Way.class));
+        }
+        if (!flag) ways.remove(w);
+        return ways.size() - s;
+    }
+
+    /**
+     * Adds all ways attached to node to specified collection
+     * @param n Node to find attached ways
+     * @param ways  collection to place the ways we found
+     */
+    static int addWaysConnectedToNode(Node n, Set<Way> ways) {
+        int s = ways.size();
+        ways.addAll(OsmPrimitive.getFilteredList(n.getReferrers(), Way.class));
+        return ways.size() - s;
+    }
+
+    /**
+     * Adds all ways intersecting one way to specified set
+     * @param ways collection of ways to search
+     * @param w way to check intersections
+     * @param newWays set to place the ways we found
+     */
+    static int addWaysIntersectingWay(Collection<Way> ways, Way w, Set<Way> newWays,Set<Way> excludeWays) {
+        List<Pair<Node, Node>> nodePairs = w.getNodePairs(false);
+        int count=0;
+        for (Way anyway: ways) {
+            if (anyway == w) continue;
+            if (newWays.contains(anyway) || excludeWays.contains(anyway) ) continue;
+
+            List<Pair<Node, Node>> nodePairs2 = anyway.getNodePairs(false);
+            loop: for (Pair<Node,Node> p1 : nodePairs) {
+                for (Pair<Node,Node> p2 : nodePairs2) {
+                    if (null!=Geometry.getSegmentSegmentIntersection(
+                            p1.a.getEastNorth(),p1.b.getEastNorth(),
+                            p2.a.getEastNorth(),p2.b.getEastNorth())) {
+                            newWays.add(anyway);
+                            count++;
+                            break loop;
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+
+        static int addWaysIntersectingWay(Collection<Way> ways, Way w, Set<Way> newWays) {
+        List<Pair<Node, Node>> nodePairs = w.getNodePairs(false);
+        int count=0;
+        for (Way anyway: ways) {
+            if (anyway == w) continue;
+            if (newWays.contains(anyway)) continue;
+            List<Pair<Node, Node>> nodePairs2 = anyway.getNodePairs(false);
+            loop: for (Pair<Node,Node> p1 : nodePairs) {
+                for (Pair<Node,Node> p2 : nodePairs2) {
+                    if (null!=Geometry.getSegmentSegmentIntersection(
+                            p1.a.getEastNorth(),p1.b.getEastNorth(),
+                            p2.a.getEastNorth(),p2.b.getEastNorth())) {
+                            newWays.add(anyway);
+                            count++;
+                            break loop;
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Adds all ways from allWays intersecting initWays way to specified set newWays
+     * @param allWays collection of ways to search
+     * @param initWays ways to check intersections
+     * @param newWays set to place the ways we found
+     */
+    public static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) {
+        int count=0;
+        for (Way w : initWays){
+            count+=addWaysIntersectingWay(allWays, w, newWays);
+        }
+        return count;
+    }
+    
+    public static void addWaysConnectedToWays(Collection<Way> ways, Set<Way> newWays) {
+        for (Way w : ways){
+            NodeWayUtils.addWaysConnectedToWay(w, newWays);
+        }
+    }
+
+    public static int addWaysConnectedToNodes(Set<Node> selectedNodes, Set<Way> newWays) {
+        int s = newWays.size();
+        for (Node node: selectedNodes) {
+            addWaysConnectedToNode(node, newWays);
+        }
+        return newWays.size() - s;
+    }
+
+    public static int addNodesConnectedToWays(Set<Way> initWays, Set<Node> newNodes) {
+        int s = newNodes.size();
+        for (Way w: initWays) {
+                newNodes.addAll(w.getNodes());
+        }
+        return newNodes.size()-s;
+    }
+
+    public static void addWaysIntersectingWaysRecursively
+            (Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays)
+    {
+            Set<Way> foundWays = new HashSet<Way>();
+            foundWays.addAll(initWays);
+            newWays.addAll(initWays);
+            Set<Way> newFoundWays = new HashSet<Way>();
+
+            int level=0,c;
+            do {
+                 c=0;
+                 newFoundWays = new HashSet<Way>();
+                 for (Way w : foundWays){
+                      c+=addWaysIntersectingWay(allWays, w, newFoundWays,newWays);
+                 }
+                 foundWays = newFoundWays;
+                 newWays.addAll(newFoundWays);
+                 level++;
+//                 System.out.printf("%d: %d ways added to selection intersectiong\n",level,c);
+                 if (c>maxWays1) {
+                       JOptionPane.showMessageDialog(Main.parent,
+                                tr("Too many ways are added: {0}!",c),
+                                        tr("Warning"),
+                                        JOptionPane.WARNING_MESSAGE);
+                       return;
+                 }
+            } while ( c >0 && level < maxLevel );
+            return;
+    }
+
+    public static void addWaysConnectedToWaysRecursively
+            (Collection<Way> initWays, Set<Way> newWays)
+    {
+            //long t = System.currentTimeMillis();
+            int level=0,c;
+            newWays.addAll(initWays);
+            do {
+                 c=0;
+                 Set<Way> foundWays = new HashSet<Way>();
+                 foundWays.addAll(newWays);
+                 for (Way w : foundWays){
+                      c+=addWaysConnectedToWay(w, newWays);
+                 }
+                 level++;
+//                 System.out.printf("%d: %d ways added to selection\n",level,c);
+                 if (c>maxWays) {
+                       JOptionPane.showMessageDialog(Main.parent,
+                                tr("Too many ways are added: {0}!",c),
+                                        tr("Warning"),
+                                        JOptionPane.WARNING_MESSAGE);
+                       return;
+                 }
+            } while ( c >0 && level < maxLevel );
+           // System.out.println("time = "+(System.currentTimeMillis()-t)+" ways = "+newWays.size());
+            return;
+    }
+
+    static void addMiddle(Set<Node> selectedNodes, Set<Node> newNodes) {
+        Iterator<Node> it=selectedNodes.iterator();
+        Node n1 = it.next();
+        Node n2 = it.next();
+        Set<Way> ways=new HashSet<Way>();
+        ways.addAll(OsmPrimitive.getFilteredList(n1.getReferrers(), Way.class));
+        for (Way w: ways) {
+
+            if (w.isUsable() && w.containsNode(n2) && w.containsNode(n1)) {
+                // Way w goes from n1 to n2
+                List <Node> nodes= w.getNodes();
+                int i1 = nodes.indexOf(n1);
+                int i2 = nodes.indexOf(n2);
+                int n = nodes.size();
+                if (i1>i2) { int p=i2; i2=i1; i1=p; } // now i1<i2
+                if (w.isClosed()) {
+                        if ((i2-i1)*2 <= n ) { // i1 ... i2
+                            for (int i=i1+1;i!=i2; i++) {
+                                newNodes.add(nodes.get(i));
+                            }
+                        } else { // i2 ... n-1 0 1 ... i1
+                            for (int i=i2+1;i!=i1; i=(i+1)%n) {
+                                newNodes.add(nodes.get(i));
+                            }
+                        }
+                    } else {
+                        for (int i=i1+1;i<i2;i++) {
+                            newNodes.add(nodes.get(i));
+                        }
+                    }
+            }
+        }
+        if (newNodes.size()==0) {
+                JOptionPane.showMessageDialog(Main.parent,
+                    tr("Please select two nodes connected by way!"),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            }
+    }
+    
+    static boolean addAreaBoundary(Way firstWay, Set<Way> newWays, boolean goingLeft) {
+        Way w=firstWay;
+        Node curNode = w.lastNode();
+        Node prevNode = w.getNode(w.getNodes().size()-2);
+        Set<Way> newestWays = new HashSet<Way>();
+        while(true) {
+
+            Node nextNode, endNode, otherEnd, preLast;
+            Way nextWay;
+
+            EastNorth en;
+            double startHeading,bestAngle;
+
+            en = curNode.getEastNorth();
+            startHeading = prevNode.getEastNorth().heading( en );
+
+            bestAngle = goingLeft ? -1e5 : 1e5 ;
+            otherEnd=null; nextWay=null;
+            
+            for (OsmPrimitive ref : curNode.getReferrers()) {
+                if (ref instanceof Way && ref!=w && ref.isSelectable()) {
+                    //
+                    Way w2 = (Way) ref;
+                    //  -----[prevNode]-(curNode)-[nextNode]------[preLast]-(endNode)
+                    //          w           |              w2
+                    if (w2.getNodesCount()<2 || w2.isClosed()) continue;
+
+
+                    if (curNode == w2.firstNode()) {
+                        nextNode = w2.getNode(1);
+                        preLast = w2.getNode(w2.getNodesCount()-2);
+                        endNode = w2.lastNode();
+                    } // forward direction
+                    else if (curNode == w2.lastNode()) {
+                        nextNode = w2.getNode(w2.getNodesCount()-2);
+                        preLast = w2.getNode(1);
+                        endNode = w2.firstNode();
+                    } // backward direction
+                        else continue; // we came to some way middle node
+
+                    double angle = startHeading -Math.PI - en.heading(nextNode.getEastNorth());
+                    while (angle<0) angle+=2*Math.PI;
+                    
+                    if (angle < bestAngle ^ goingLeft) {
+                        bestAngle = angle;
+                        otherEnd = endNode;
+                        prevNode = preLast;
+                        nextWay = w2;
+                    }
+                }
+            }
+            if (firstWay == nextWay ) {
+                //we came to starting way, but not not the right end 
+                if (otherEnd==firstWay.firstNode()) return false;
+                newWays.addAll(newestWays);
+                return true; // correct loop found 
+            }
+            if (newestWays.contains(nextWay)) {
+                // P - like loop found
+                return false;
+            }
+            if (nextWay != null) { 
+                newestWays.add(nextWay);
+                curNode = otherEnd;
+                w = nextWay;
+                // next way found, continuing
+            } else {
+                // no closed loop found
+                return false;
+            } 
+        }
+    }
+
+    static void addAllInsideMultipolygon(DataSet data, Relation rel, Set<Way> newWays, Set<Node> newNodes) {
+        if (!rel.isMultipolygon()) return;
+        BBox box = rel.getBBox();
+        Set<Way> usedWays = OsmPrimitive.getFilteredSet(rel.getMemberPrimitives(), Way.class);
+        List<EastNorth> polyPoints = new ArrayList<EastNorth>(10000);
+        
+        for (Way way: usedWays) {
+            List<Node> polyNodes = way.getNodes();
+            // converts all points to EastNorth
+            for (Node n: polyNodes) polyPoints.add(n.getEastNorth());  
+        }
+        
+        
+        List<Node> searchNodes = data.searchNodes(box);
+        Set<Node> newestNodes = new HashSet<Node>();
+        Set<Way> newestWays = new HashSet<Way>();
+        for (Node n : searchNodes) {
+            //if (Geometry.nodeInsidePolygon(n, polyNodes)) {
+            if (NodeWayUtils.isPointInsidePolygon(n.getEastNorth(), polyPoints)>0) {
+                newestNodes.add(n);
+            }
+        }
+        
+        List<Way> searchWays = data.searchWays(box);
+        for (Way w : searchWays) {
+            if (newestNodes.containsAll(w.getNodes())) {
+                newestWays.add(w);
+            }
+        }
+        for (Way w : newestWays) {
+            newestNodes.removeAll(w.getNodes());
+            // do not select nodes of already selected ways
+        }
+        
+        newNodes.addAll(newestNodes);
+        newWays.addAll(newestWays);
+    }
+
+    static void addAllInsideWay(DataSet data, Way way, Set<Way> newWays, Set<Node> newNodes) {
+        if (!way.isClosed()) return;
+        BBox box = way.getBBox();
+        List<Node> polyNodes = way.getNodes();
+        List<EastNorth> polyPoints = new ArrayList<EastNorth>(polyNodes.size());
+        
+        // converts all points to EastNorth
+        for (Node n: polyNodes) polyPoints.add(n.getEastNorth());  
+        
+        List<Node> searchNodes = data.searchNodes(box);
+        Set<Node> newestNodes = new HashSet<Node>();
+        Set<Way> newestWays = new HashSet<Way>();
+        for (Node n : searchNodes) {
+            //if (Geometry.nodeInsidePolygon(n, polyNodes)) {
+            if (NodeWayUtils.isPointInsidePolygon(n.getEastNorth(), polyPoints)>0) {
+                newestNodes.add(n);
+            }
+        }
+        
+        List<Way> searchWays = data.searchWays(box);
+        for (Way w : searchWays) {
+            if (newestNodes.containsAll(w.getNodes())) {
+                newestWays.add(w);
+            }
+        }
+        for (Way w : newestWays) {
+            newestNodes.removeAll(w.getNodes());
+            // do not select nodes of already selected ways
+        }
+        
+        newNodes.addAll(newestNodes);
+        newWays.addAll(newestWays);
+    }
+    
+    /**
+     * @return 0 =  not inside polygon, 1 = strictly inside, 2 = near edge, 3 = near vertex
+     */
+    public static int isPointInsidePolygon(EastNorth point, List<EastNorth> polygonPoints) {
+        int n=polygonPoints.size();
+        EastNorth oldPoint = polygonPoints.get(n-1);
+        double n1,n2,n3,e1,e2,e3,d;
+        int interCount=0;
+        
+        for (EastNorth curPoint : polygonPoints) {
+            n1 = curPoint.north(); n2 = oldPoint.north();  n3 =  point.north();
+            e1 = curPoint.east(); e2 = oldPoint.east();  e3 =  point.east();
+            
+            if (Math.abs(n1-n3)<1e-5 && Math.abs(e1-e3)<1e-5) return 3; // vertex
+            if (Math.abs(n2-n3)<1e-5 && Math.abs(e2-e3)<1e-5) return 3; // vertex
+            
+            // looking at oldPoint-curPoint segment
+            if ( n1 > n2) {
+                if (n1 > n3 && n3 >= n2) {
+                    n1-=n3; n2-=n3; e1-=e3; e2-=e3;
+                    d = e1*n2 - n1*e2;
+                    if (d<-1e-5) {
+                        interCount++; // there is OX intersecthion at e = (e1n2-e2n1)/(n2-n1) >=0
+                    } else if (d<=1e-5) return 2; // boundary detected
+                }
+            } else if (n1 == n2) {
+                if (n1 == n3) {
+                    e1-=e3; e2-=e3;
+                    if ((e1 <=0 && e2 >= 0) || (e1 >=0 && e2 <= 0)) return 2;// boundary detected
+                }
+            } else {
+                if (n1 <= n3 && n3 < n2) {
+                    n1-=n3; n2-=n3; e1-=e3; e2-=e3;
+                    d = e1*n2 - n1*e2;
+                    if (d>1e-5) {
+                        interCount++; // there is OX intersecthion at e = (e1n2-e2n1)/(n2-n1) >=0
+                    } else if (d>=-1e-5) return 2; // boundary detected
+                }
+            }
+            oldPoint = curPoint;
+        }
+       // System.out.printf("Intersected intercount %d %s\n",interCount, point.toString());
+        if (interCount%2 == 1) return 1; else return 0;
+    }
+    
+    public static Collection<OsmPrimitive> selectAllInside(Collection<OsmPrimitive> selected, DataSet dataset) {
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(selected, Way.class);
+        Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(selected, Relation.class);
+
+        for (Iterator<Relation> it = selectedRels.iterator(); it.hasNext();) {
+            Relation r = it.next();
+            if (!r.isMultipolygon()) {
+                it.remove();
+            }
+        }
+
+        Set<Way> newWays = new HashSet<Way>();
+        Set<Node> newNodes = new HashSet<Node>();
+        // select ways attached to already selected ways
+        if (!selectedWays.isEmpty()) {
+            for (Way w: selectedWays) {
+                addAllInsideWay(dataset,w,newWays,newNodes);
+            }
+        }
+        if (!selectedRels.isEmpty()) {
+            for (Relation r: selectedRels) {
+                addAllInsideMultipolygon(dataset,r,newWays,newNodes);
+            }
+        }
+        
+        Set<OsmPrimitive> insideSelection = new HashSet<OsmPrimitive>();
+        if (!newWays.isEmpty() || !newNodes.isEmpty()) {
+            insideSelection.addAll(newWays);
+            insideSelection.addAll(newNodes);
+        }
+        return insideSelection;
+    }
+
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectAllInsideAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectAllInsideAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectAllInsideAction.java	(revision 28028)
@@ -0,0 +1,66 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection by selecting nodes on all touched ways
+ */
+public class SelectAllInsideAction extends JosmAction {
+
+    public SelectAllInsideAction() {
+        super(tr("All inside [testing]"), "selinside", tr("Select all inside selected polygons"),
+                Shortcut.registerShortcut("tools:selinside", tr("Tool: {0}","All inside"),
+                KeyEvent.VK_I, Shortcut.ALT_SHIFT), true);
+        putValue("help", ht("/Action/SelectAllInside"));
+    }
+    
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        long t=System.currentTimeMillis();
+        Collection<OsmPrimitive> insideSelected = NodeWayUtils.selectAllInside(getCurrentDataSet().getSelected(), getCurrentDataSet());
+        
+        if (!insideSelected.isEmpty()) {
+            getCurrentDataSet().addSelected(insideSelected);
+        } else{
+        JOptionPane.showMessageDialog(Main.parent,
+               tr("Nothing found. Please select some closed ways or multipolygons to find all primitives inside them!"),
+               tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectBoundaryAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectBoundaryAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectBoundaryAction.java	(revision 28028)
@@ -0,0 +1,145 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import java.util.LinkedHashSet;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Extends current selection by selecting nodes on all touched ways
+ */
+public class SelectBoundaryAction extends JosmAction {
+    private Way lastUsedStartingWay; //used for repeated calls
+    private boolean lastUsedLeft;
+
+    public SelectBoundaryAction() {
+        super(tr("Area boundary [testing]"), "selboundary", tr("Select relation or all ways that forms area boundary"),
+                Shortcut.registerShortcut("tools:selboundary", tr("Tool: {0}","Area boundary [testing]"),
+                KeyEvent.VK_SLASH, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/SelectAreaBoundary"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        long t=System.currentTimeMillis();
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Node.class);
+        LinkedHashSet<Relation> selectedRelations = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Relation.class);
+        
+        Set<Way> newWays = new HashSet<Way>();
+        
+        Way w=null;
+        Relation selectedRelation=null;
+        
+        if (selectedRelations.size()==1) {
+            selectedRelation = selectedRelations.iterator().next();
+            if (selectedRelation.getMemberPrimitives().contains(lastUsedStartingWay)) {
+                w=lastUsedStartingWay; 
+                // repeated call for selected relation
+            }
+        } else if (selectedWays.isEmpty()) {
+            if (selectedNodes.size()==1 ) {
+                for (OsmPrimitive p : selectedNodes.iterator().next().getReferrers()) {
+                    if (p instanceof Way && p.isSelectable()) {
+                        //if (w!=null) return; // ifwe want only one way
+                        w=(Way) p;
+                        break;
+                    }
+                }
+            }
+        } else if (selectedWays.size()==1)  {
+            w = selectedWays.iterator().next();
+        } else if (selectedWays.contains(lastUsedStartingWay)) { 
+            w=lastUsedStartingWay; //repeated call for selected way
+            lastUsedLeft = !lastUsedLeft;
+        }
+
+        
+        if (w==null) return; //no starting way found
+        if (!w.isSelectable()) return;
+        if (w.isClosed()) return;
+        if (w.getNodesCount()<2) return;
+
+        newWays.add(w);
+        lastUsedStartingWay = w;
+        
+        List<Relation> rels=new ArrayList<Relation>();
+        for (OsmPrimitive p : w.getReferrers()) {
+            if (p instanceof Relation && p.isSelectable()) {
+                rels.add((Relation) p);
+            }
+        }
+        if (selectedRelation!=null) {
+            int idx = rels.indexOf(selectedRelation); 
+            // selectedRelation has number idx in active relation list
+            if (idx>=0) {
+               // select next relation
+               if (idx+1<rels.size())
+                   getCurrentDataSet().setSelected(Arrays.asList(rels.get(idx+1)));
+               else 
+                   getCurrentDataSet().setSelected(Arrays.asList(rels.get(0))); 
+               return;
+            }
+        } else if (rels.size()>0) {
+               getCurrentDataSet().setSelected(Arrays.asList(rels.get(0)));
+               return;
+        }
+
+        
+        
+        // try going left at each turn
+        if (! NodeWayUtils.addAreaBoundary(w, newWays, lastUsedLeft) ) {
+            NodeWayUtils.addAreaBoundary(w, newWays, !lastUsedLeft); // try going right at each turn
+        }
+        
+        
+        
+        if (!newWays.isEmpty() ) {
+            getCurrentDataSet().setSelected(newWays);
+        } else{
+        JOptionPane.showMessageDialog(Main.parent,
+               tr("Nothing found. Please select way that is a part of some polygon formed by connected ways"),
+               tr("Warning"), JOptionPane.WARNING_MESSAGE);
+        }
+
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+
+
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectHighwayAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectHighwayAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectHighwayAction.java	(revision 28028)
@@ -0,0 +1,195 @@
+// License: PD
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.*;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Select all connected ways for a street if one way is selected (determine by name/ref),
+ * select highway ways between two selected ways.
+ * 
+ * @author zverik
+ */
+public class SelectHighwayAction extends JosmAction {
+
+    public SelectHighwayAction() {
+        super(tr("Select Highway"), "selecthighway", tr("Select highway for the name/ref given"),
+                Shortcut.registerShortcut("tools:selecthighway", tr("Tool: {0}","Select Highway"),
+                KeyEvent.VK_W, Shortcut.ALT_CTRL), true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        List<Way> selectedWays = OsmPrimitive.getFilteredList(getCurrentDataSet().getSelected(), Way.class);
+
+        if( selectedWays.size() == 1 ) {
+            getCurrentDataSet().setSelected(selectNamedRoad(selectedWays.get(0)));
+        } else if( selectedWays.size() == 2 ) {
+            getCurrentDataSet().setSelected(selectHighwayBetween(selectedWays.get(0), selectedWays.get(1)));
+        } else {
+            JOptionPane.showMessageDialog(Main.parent, tr("Please select one or two ways for this action"), "Select Highway", JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    private Set<Way> selectNamedRoad( Way firstWay ) {
+        Set<Way> newWays = new HashSet<Way>();
+        String key = firstWay.hasKey("name") ? "name" : "ref";
+        if( firstWay.hasKey(key) ) {
+            String value = firstWay.get(key);
+            Queue<Node> nodeQueue = new LinkedList<Node>();
+            nodeQueue.add(firstWay.firstNode());
+            while( !nodeQueue.isEmpty() ) {
+                Node node = nodeQueue.remove();
+                for( Way p : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class) ) {
+                    if( !newWays.contains(p) && p.hasKey(key) && p.get(key).equals(value) ) {
+                        newWays.add(p);
+                        nodeQueue.add(p.firstNode().equals(node) ? p.lastNode() : p.firstNode());
+                    }
+                }
+            }
+        }
+        return newWays;
+    }
+    
+    private Set<Way> selectHighwayBetween( Way firstWay, Way lastWay ) {
+        int minRank = Math.min(getHighwayRank(firstWay), getHighwayRank(lastWay));
+	HighwayTree firstTree = new HighwayTree(firstWay, minRank);
+	HighwayTree secondTree = new HighwayTree(lastWay, minRank);
+	Way intersection = firstTree.getIntersection(secondTree);
+	while( intersection == null && (firstTree.canMoveOn() || secondTree.canMoveOn()) ) {
+	    firstTree.processNextLevel();
+	    secondTree.processNextLevel();
+	    intersection = firstTree.getIntersection(secondTree);
+	}
+	Set<Way> newWays = new HashSet<Way>();
+	newWays.addAll(firstTree.getPath(intersection));
+	newWays.addAll(secondTree.getPath(intersection));
+	return newWays;
+    }
+    
+    private static int getHighwayRank( OsmPrimitive way ) {
+        if( !way.hasKey("highway") )
+            return 0;
+        String highway = way.get("highway");
+        if( highway.equals("path") || highway.equals("footway") || highway.equals("cycleway") )
+            return 1;
+        else if( highway.equals("track") || highway.equals("service") )
+            return 2;
+        else if( highway.equals("unclassified") || highway.equals("residential") )
+            return 3;
+        else if( highway.equals("tertiary") || highway.equals("tertiary_link") )
+            return 4;
+        else if( highway.equals("secondary") || highway.equals("secondary_link") )
+            return 5;
+        else if( highway.equals("primary") || highway.equals("primary_link") )
+            return 6;
+        else if( highway.equals("trunk") || highway.equals("trunk_link") || highway.equals("motorway") || highway.equals("motorway_link") )
+            return 7;
+        return 0;
+    }
+    
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null)
+            setEnabled(false);
+        else
+            updateEnabledState(getCurrentDataSet().getSelected());
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        int count = 0, rank = 100;
+        for( OsmPrimitive p : selection ) {
+            if( p instanceof Way ) {
+                count++;
+                rank = Math.min(rank, getHighwayRank(p));
+            }
+        }
+        setEnabled(count == 1 || (count == 2 && rank > 0));
+    }
+    
+    private static class HighwayTree {
+	private List<Way> tree;
+	private List<Integer> refs;
+	private List<Node> nodesToCheck;
+	private List<Integer> nodeRefs;
+	private int minHighwayRank;
+
+	public HighwayTree( Way from, int minHighwayRank ) {
+	    tree = new ArrayList<Way>(1);
+	    refs = new ArrayList<Integer>(1);
+	    tree.add(from);
+	    refs.add(Integer.valueOf(-1));
+	    this.minHighwayRank = minHighwayRank;
+	    nodesToCheck = new ArrayList<Node>(2);
+	    nodeRefs = new ArrayList<Integer>(2);
+	    nodesToCheck.add(from.firstNode());
+	    nodesToCheck.add(from.lastNode());
+	    nodeRefs.add(Integer.valueOf(0));
+	    nodeRefs.add(Integer.valueOf(0));
+	}
+	
+	public void processNextLevel() {
+	    List<Node> newNodes = new ArrayList<Node>();
+	    List<Integer> newIdx = new ArrayList<Integer>();
+	    for( int i = 0; i < nodesToCheck.size(); i++ ) {
+		Node node = nodesToCheck.get(i);
+		Integer nodeRef = nodeRefs.get(i);
+                for( Way way : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class) ) {
+		    if( (way.firstNode().equals(node) || way.lastNode().equals(node)) &&
+			!tree.contains(way) && suits(way) ) {
+			tree.add(way);
+			refs.add(nodeRef);
+			Node newNode = way.firstNode().equals(node) ? way.lastNode() : way.firstNode();
+			newNodes.add(newNode);
+			newIdx.add(Integer.valueOf(tree.size() - 1));
+		    }
+		}
+	    }
+	    nodesToCheck = newNodes;
+	    nodeRefs = newIdx;
+	}
+	
+	private boolean suits( Way w ) {
+	    return getHighwayRank(w) >= minHighwayRank;
+	}
+	
+	public boolean canMoveOn() {
+	    return !nodesToCheck.isEmpty() && tree.size() < 10000;
+	}
+	
+	public Way getIntersection( HighwayTree other ) {
+	    for( Way w : other.tree )
+		if( tree.contains(w) )
+		    return w;
+	    return null;
+	}
+	
+	public List<Way> getPath( Way to ) {
+	    if( to == null )
+		return Collections.singletonList(tree.get(0));
+	    int pos = tree.indexOf(to);
+	    if( pos < 0 )
+		throw new ArrayIndexOutOfBoundsException("Way " + to + " is not in the tree.");
+	    List<Way> result = new ArrayList<Way>(1);
+	    while( pos >= 0 ) {
+		result.add(tree.get(pos));
+		pos = refs.get(pos);
+	    }
+	    return result;
+	}
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModNodesAction.java	(revision 28028)
@@ -0,0 +1,87 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and Martin Ždila
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import org.openstreetmap.josm.command.Command;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Unselects all nodes
+ */
+public class SelectModNodesAction extends JosmAction {
+    private int lastHash;
+    private Command lastCmd;
+
+    public SelectModNodesAction() {
+        super(tr("Select last modified nodes"), "selmodnodes",
+                tr("Select last modified nodes"),
+                Shortcut.registerShortcut("tools:selmodnodes", tr("Tool: {0}","Select last modified nodes"),
+                KeyEvent.VK_Z, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/SelectLastModifiedNodes"));
+    }
+
+     public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+        getCurrentDataSet().clearSelection(selectedNodes);
+        Command cmd =null;
+
+        if (Main.main.undoRedo.commands == null) return;
+        int num=Main.main.undoRedo.commands.size();
+        if (num==0) return;
+        int k=0,idx;
+        if (selection!=null && !selection.isEmpty() && selection.hashCode() == lastHash) {
+            // we are selecting next command in history if nothing is selected
+            idx = Main.main.undoRedo.commands.indexOf(lastCmd);
+           // System.out.println("My previous selection found "+idx);
+        } else {
+            idx=num;
+           // System.out.println("last history item taken");
+        }
+
+        Set<Node> nodes = new HashSet<Node>(10);
+        do {  //  select next history element
+            if (idx>0) idx--; else idx=num-1;
+            cmd = Main.main.undoRedo.commands.get(idx);
+            Collection<? extends OsmPrimitive> pp = cmd.getParticipatingPrimitives();
+            nodes.clear();
+            for ( OsmPrimitive p : pp) {  // find all affected ways
+                if (p instanceof Node && !p.isDeleted()) nodes.add((Node)p);
+            }
+            if (!nodes.isEmpty()) {
+                getCurrentDataSet().setSelected(nodes);
+                lastCmd = cmd; // remember last used command and last selection
+                lastHash = getCurrentDataSet().getSelected().hashCode();
+                return;
+                }
+            k++;
+            //System.out.println("no nodes found, previous...");
+        } while ( k < num ); // try to find previous command if this affects nothing
+        lastCmd=null; lastHash=0;
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(true);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModWaysAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModWaysAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectModWaysAction.java	(revision 28028)
@@ -0,0 +1,86 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and Martin Ždila
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Unselects all nodes
+ */
+public class SelectModWaysAction extends JosmAction {
+    private int lastHash;
+    private Command lastCmd;
+
+    public SelectModWaysAction() {
+        super(tr("Select last modified ways"), "selmodways",
+                tr("Select last modified ways"),
+                Shortcut.registerShortcut("tools:selmodways", tr("Tool: {0}","Select last modified ways"),
+                KeyEvent.VK_Z,  Shortcut.ALT_SHIFT), true);
+        putValue("help", ht("/Action/SelectLastModifiedWays"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+        getCurrentDataSet().clearSelection(selectedNodes);
+        Command cmd =null;
+
+        if (Main.main.undoRedo.commands == null) return;
+        int num=Main.main.undoRedo.commands.size();
+        if (num==0) return;
+        int k=0,idx;
+        if (selection!=null && !selection.isEmpty() && selection.hashCode() == lastHash) {
+            // we are selecting next command in history if nothing is selected
+            idx = Main.main.undoRedo.commands.indexOf(lastCmd);
+           // System.out.println("My previous selection found "+idx);
+        } else {
+            idx=num;
+           // System.out.println("last history item taken");
+        }
+
+        Set<Way> ways = new HashSet<Way>(10);
+        do {  //  select next history element
+            if (idx>0) idx--; else idx=num-1;
+            cmd = Main.main.undoRedo.commands.get(idx);
+            Collection<? extends OsmPrimitive> pp = cmd.getParticipatingPrimitives();
+            ways.clear();
+            for ( OsmPrimitive p : pp) {  // find all affected ways
+                if (p instanceof Way && !p.isDeleted()) ways.add((Way)p);
+            }
+            if (!ways.isEmpty() && !getCurrentDataSet().getSelected().containsAll(ways)) {
+                getCurrentDataSet().setSelected(ways);
+                lastCmd = cmd; // remember last used command and last selection
+                lastHash = getCurrentDataSet().getSelected().hashCode();
+                return;
+                }
+            k++;
+        } while ( k < num ); // try to find previous command if this affects nothing
+        lastCmd=null; lastHash=0;
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(true);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectWayNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectWayNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/SelectWayNodesAction.java	(revision 28028)
@@ -0,0 +1,91 @@
+// License: GPL. Copyright 2010 by Hanno Hecker
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Select all nodes of a selected way.
+ *
+ */
+
+public class SelectWayNodesAction extends JosmAction {
+
+    private Node selectedNode;
+    private ArrayList<Node> selectedNodes;
+
+    /**
+     * Create a new SelectWayNodesAction
+     */
+    public SelectWayNodesAction() {
+        super(tr("Select Way Nodes"),"selectwaynodes" , tr("Select all nodes of a selected way."),
+                Shortcut.registerShortcut("tools:selectwaynodes", tr("Tool: {0}", tr("Select Way Nodes")), KeyEvent.VK_N, Shortcut.CTRL_SHIFT), true);
+        putValue("help", ht("/Action/SelectWayNodes"));
+    }
+
+    /**
+     * Called when the action is executed.
+     *
+     * This method does some checking on the selection and calls the matching selectWayNodes method.
+     */
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+
+        String errMsg = null;
+        for (OsmPrimitive p : selection) {
+            if (p instanceof Way) {
+                Way w = (Way) p;
+                if (!w.isUsable() || w.getNodesCount() < 1) {
+                    continue;
+                }
+                selectWayNodes(w);
+            }
+            else if (p instanceof Node) {
+                Node n = (Node) p;
+                if (selectedNodes == null) {
+                    selectedNodes = new ArrayList<Node>();
+                }
+                selectedNodes.add(n);
+            }
+        }
+            
+        getCurrentDataSet().setSelected(selectedNodes);
+        selectedNodes = null;
+    }
+
+    private void selectWayNodes(Way w) {
+        
+        for (Node n : w.getNodes()) {
+            if (selectedNodes == null) {
+                selectedNodes = new ArrayList<Node>();
+            }
+            selectedNodes.add(n);
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+}
+
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UndoSelectionAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UndoSelectionAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UndoSelectionAction.java	(revision 28028)
@@ -0,0 +1,82 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and Martin Ždila
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Use selection istory to restore previous selection
+ */
+public class UndoSelectionAction extends JosmAction {
+    
+    public UndoSelectionAction() {
+        super(tr("Undo selection"), "undoselection",
+                tr("Reselect last added object or selection form history"),
+                Shortcut.registerShortcut("tools:undoselection", tr("Tool: {0}","Undo selection"),
+                KeyEvent.VK_Z, Shortcut.CTRL_SHIFT), true);
+        putValue("help", ht("/Action/UndoSelection"));
+    }
+
+    private int myAutomaticSelectionHash;
+    private Collection<OsmPrimitive> lastSel;
+    private int index;
+    public void actionPerformed(ActionEvent e) {
+        LinkedList<Collection<? extends OsmPrimitive>>history
+                    = getCurrentDataSet().getSelectionHistory();
+        int num=history.size();
+        if (history==null || num==0) return; // empty history
+        
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+
+
+        if (selection!= null &&  selection.hashCode() != myAutomaticSelectionHash) {
+            // manual selection or another pluging selection noticed
+            index=history.indexOf(lastSel);
+            // first is selected, next list is previous selection
+        }
+        int k=0;
+        Set<OsmPrimitive> newsel = new HashSet<OsmPrimitive>();
+        do {
+            if (index+1<history.size()) index++; else index=0;
+            Collection<? extends OsmPrimitive> histsel = history.get(index);
+            // remove deleted entities from selection
+            newsel.clear();
+            newsel.addAll(histsel);
+            newsel.retainAll(getCurrentDataSet().allNonDeletedPrimitives());
+            if (newsel.size() > 0 ) break;
+            k++;
+        } while ( k < num );
+
+        getCurrentDataSet().setSelected(newsel);
+        lastSel = getCurrentDataSet().getSelected();
+        myAutomaticSelectionHash = lastSel.hashCode();
+        // remeber last automatic selection
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(true);
+    }
+}
Index: applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UnselectNodesAction.java
===================================================================
--- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UnselectNodesAction.java	(revision 28028)
+++ applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/selection/UnselectNodesAction.java	(revision 28028)
@@ -0,0 +1,53 @@
+// License: GPL. Copyright 2011 by Alexei Kasatkin and Martin Ždila
+package org.openstreetmap.josm.plugins.utilsplugin2.selection;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.Set;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.*;
+
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ *    Unselects all nodes
+ */
+public class UnselectNodesAction extends JosmAction {
+
+    
+    public UnselectNodesAction() {
+        super(tr("Unselect nodes"), "unsnodes",
+                tr("Removes all nodes from selection"),
+                Shortcut.registerShortcut("tools:unsnodes", tr("Tool: {0}","Unselect nodes"),
+                KeyEvent.VK_U, Shortcut.SHIFT), true);
+        putValue("help", ht("/Action/UnselectNodes"));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
+        getCurrentDataSet().clearSelection(selectedNodes);
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        if (selection == null) {
+            setEnabled(false);
+            return;
+        }
+        setEnabled(!selection.isEmpty());
+    }
+}
