Index: applications/editors/josm/plugins/simplifyarea/build.xml
===================================================================
--- applications/editors/josm/plugins/simplifyarea/build.xml	(revision 34585)
+++ applications/editors/josm/plugins/simplifyarea/build.xml	(revision 34586)
@@ -10,5 +10,5 @@
     -->
     <property name="plugin.author" value="Martin Ždila &lt;m.zdila@gmail.com&gt;"/>
-    <property name="plugin.class" value="sk.zdila.josm.plugin.simplify.SimplifyAreaPlugin"/>
+    <property name="plugin.class" value="org.openstreetmap.josm.plugins.simplifyarea.SimplifyAreaPlugin"/>
     <property name="plugin.description" value="Simplify area by removing nodes on very obtuse angles. This can be constrained by maximum removed area size. Also average nearby nodes."/>
     <property name="plugin.icon" value="images/preferences/simplifyArea.png"/>
Index: applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaAction.java
===================================================================
--- applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaAction.java	(revision 34586)
+++ applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaAction.java	(revision 34586)
@@ -0,0 +1,466 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.simplifyarea;
+
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static java.lang.Math.toRadians;
+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.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.UndoRedoHandler;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public final class SimplifyAreaAction extends JosmAction {
+
+    public SimplifyAreaAction() {
+        super(tr("Simplify Area"), "simplify", tr("Delete unnecessary nodes from an area."),
+                Shortcut.registerShortcut("tools:simplifyArea", tr("Tool: {0}", tr("Simplify Area")), KeyEvent.VK_Y, Shortcut.CTRL_SHIFT),
+                true, "simplifyarea", true);
+    }
+
+    private List<Bounds> getCurrentEditBounds() {
+        return MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds();
+    }
+
+    private static boolean isInBounds(final Node node, final List<Bounds> bounds) {
+        for (final Bounds b : bounds) {
+            if (b.contains(node.getCoor())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean confirmWayWithNodesOutsideBoundingBox() {
+        final ButtonSpec[] options = new ButtonSpec[] { new ButtonSpec(tr("Yes, delete nodes"), ImageProvider.get("ok"), tr("Delete nodes outside of downloaded data regions"), null),
+                new ButtonSpec(tr("No, abort"), ImageProvider.get("cancel"), tr("Cancel operation"), null) };
+        return 0 == HelpAwareOptionPane.showOptionDialog(
+                MainApplication.getMainFrame(),
+                "<html>" + trn("The selected way has nodes outside of the downloaded data region.", "The selected ways have nodes outside of the downloaded data region.", 
+                        MainApplication.getLayerManager().getEditDataSet().getSelectedWays().size())
+                + "<br>" + tr("This can lead to nodes being deleted accidentally.") + "<br>" + tr("Do you want to delete them anyway?") + "</html>",
+                tr("Delete nodes outside of data regions?"), JOptionPane.WARNING_MESSAGE, null, // no special icon
+                options, options[0], null);
+    }
+
+    private void alertSelectAtLeastOneWay() {
+        HelpAwareOptionPane.showOptionDialog(MainApplication.getMainFrame(), tr("Please select at least one way to simplify."), tr("Warning"), JOptionPane.WARNING_MESSAGE, null);
+    }
+
+    private boolean confirmSimplifyManyWays(final int numWays) {
+        final ButtonSpec[] options = new ButtonSpec[] { new ButtonSpec(tr("Yes"), ImageProvider.get("ok"), tr("Simplify all selected ways"), null),
+                new ButtonSpec(tr("Cancel"), ImageProvider.get("cancel"), tr("Cancel operation"), null) };
+        return 0 == HelpAwareOptionPane.showOptionDialog(MainApplication.getMainFrame(), tr("The selection contains {0} ways. Are you sure you want to simplify them all?", numWays), 
+                tr("Simplify ways?"),
+                JOptionPane.WARNING_MESSAGE, null, // no special icon
+                options, options[0], null);
+    }
+
+    @Override
+    public void actionPerformed(final ActionEvent e) {
+        final Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
+
+        final List<Bounds> bounds = getCurrentEditBounds();
+        for (final OsmPrimitive prim : selection) {
+            if (prim instanceof Way && bounds.size() > 0) {
+                final Way way = (Way) prim;
+                // We check if each node of each way is at least in one download
+                // bounding box. Otherwise nodes may get deleted that are necessary by
+                // unloaded ways (see Ticket #1594)
+                for (final Node node : way.getNodes()) {
+                    if (!isInBounds(node, bounds)) {
+                        if (!confirmWayWithNodesOutsideBoundingBox()) {
+                            return;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        final Collection<Way> ways = OsmPrimitive.getFilteredSet(selection, Way.class);
+        if (ways.isEmpty()) {
+            alertSelectAtLeastOneWay();
+            return;
+        } else if (ways.size() > 10) {
+            if (!confirmSimplifyManyWays(ways.size())) {
+                return;
+            }
+        }
+
+        final List<Node> nodesToDelete = new ArrayList<>(); // can contain duplicate instances
+
+        for (final Way way : ways) {
+            addNodesToDelete(nodesToDelete, way);
+        }
+
+        final Map<Node, Integer> nodeCountMap = new HashMap<>();
+        for (final Node node : nodesToDelete) {
+            Integer count = nodeCountMap.get(node);
+            if (count == null) {
+                count = 0;
+            }
+            nodeCountMap.put(node, ++count);
+        }
+
+        final Collection<Node> nodesReallyToRemove = new ArrayList<>();
+
+        for (final Entry<Node, Integer> entry : nodeCountMap.entrySet()) {
+            final Node node = entry.getKey();
+            final Integer count = entry.getValue();
+
+            if (!node.isTagged() && node.getReferrers().size() == count) {
+                nodesReallyToRemove.add(node);
+            }
+        }
+
+        final Collection<Command> allCommands = new ArrayList<>();
+
+        if (!nodesReallyToRemove.isEmpty()) {
+            for (final Way way : ways) {
+                final List<Node> nodes = way.getNodes();
+                final boolean closed = nodes.get(0).equals(nodes.get(nodes.size() - 1));
+                if (closed) {
+                    nodes.remove(nodes.size() - 1);
+                }
+
+                if (nodes.removeAll(nodesReallyToRemove)) {
+                    if (closed) {
+                        nodes.add(nodes.get(0));
+                    }
+
+                    final Way newWay = new Way(way);
+                    newWay.setNodes(nodes);
+                    allCommands.add(new ChangeCommand(way, newWay));
+                }
+            }
+
+            allCommands.add(new DeleteCommand(nodesReallyToRemove));
+        }
+
+        final Collection<Command> avgCommands = averageNearbyNodes(ways, nodesReallyToRemove);
+        if (avgCommands != null && !avgCommands.isEmpty()) {
+            allCommands.add(new SequenceCommand(tr("average nearby nodes"), avgCommands));
+        }
+
+        if (!allCommands.isEmpty()) {
+            final SequenceCommand rootCommand = new SequenceCommand(trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()), allCommands);
+            UndoRedoHandler.getInstance().add(rootCommand);
+            MainApplication.getMap().repaint();
+        }
+    }
+
+    private static boolean nodeGluesWays(final Node node) {
+        Set<Node> referenceNeighbours = null;
+        for (final OsmPrimitive ref : node.getReferrers()) {
+            if (ref.getType() == OsmPrimitiveType.WAY) {
+                final Way way = ((Way) ref);
+                final Set<Node> neighbours = way.getNeighbours(node);
+                if (referenceNeighbours == null) {
+                    referenceNeighbours = neighbours;
+                } else if (!referenceNeighbours.containsAll(neighbours)) {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+
+    // average nearby nodes
+    private static Collection<Command> averageNearbyNodes(final Collection<Way> ways, final Collection<Node> nodesAlreadyDeleted) {
+        final double mergeThreshold = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.MERGE_THRESHOLD, 0.2);
+
+        final Map<Node, LatLon> coordMap = new HashMap<>();
+        for (final Way way : ways) {
+            for (final Node n : way.getNodes()) {
+                coordMap.put(n, n.getCoor());
+            }
+        }
+
+        coordMap.keySet().removeAll(nodesAlreadyDeleted);
+
+        for (final Way w : ways) {
+            final List<Node> nodes = w.getNodes();
+
+            final Node lastNode = nodes.get(nodes.size() - 1);
+            final boolean closed = nodes.get(0).equals(lastNode);
+            if (closed) {
+                nodes.remove(lastNode);
+            }
+
+            nodes.retainAll(coordMap.keySet()); // removes already deleted nodes
+
+            while (true) {
+                double minDist = Double.POSITIVE_INFINITY;
+                Node node1 = null;
+                Node node2 = null;
+
+                final int len = nodes.size();
+                if (len == 0) {
+                    break;
+                }
+
+                // find smallest distance
+                for (int i = 0; i <= len; i++) {
+                    final Node n1 = nodes.get(i % len);
+                    final Node n2 = nodes.get((i + 1) % len);
+
+                    if (n1.isTagged() || n2.isTagged()) {
+                        continue;
+                    }
+
+                    // test if both nodes are on the same ways
+                    final List<OsmPrimitive> referrers = n1.getReferrers();
+                    if (!ways.containsAll(referrers)) {
+                        continue;
+                    }
+
+                    final List<OsmPrimitive> referrers2 = n2.getReferrers();
+                    if (!ways.containsAll(referrers2)) {
+                        continue;
+                    }
+
+                    // test if both nodes have same parents
+                    if (!referrers.containsAll(referrers2) || !referrers2.containsAll(referrers)) {
+                        continue;
+                    }
+
+                    final LatLon a = coordMap.get(n1);
+                    final LatLon b = coordMap.get(n2);
+                    
+                    if (a != null && b != null) {
+                        final double dist = a.greatCircleDistance(b);
+                        if (dist < minDist && dist < mergeThreshold) {
+                            minDist = dist;
+                            node1 = n1;
+                            node2 = n2;
+                        }
+                    }
+                }
+
+                if (node1 == null || node2 == null) {
+                    break;
+                }
+
+                final LatLon coord = coordMap.get(node1).getCenter(coordMap.get(node2));
+                coordMap.put(node1, coord);
+
+                nodes.remove(node2);
+                coordMap.remove(node2);
+            }
+        }
+
+        final Collection<Command> commands = new ArrayList<>();
+        final Set<Node> nodesToDelete2 = new HashSet<>();
+        for (final Way way : ways) {
+            final List<Node> nodesToDelete = way.getNodes();
+            nodesToDelete.removeAll(nodesAlreadyDeleted);
+            if (nodesToDelete.removeAll(coordMap.keySet())) {
+                nodesToDelete2.addAll(nodesToDelete);
+                final Way newWay = new Way(way);
+                final List<Node> nodes = way.getNodes();
+                final boolean closed = nodes.get(0).equals(nodes.get(nodes.size() - 1));
+                if (closed) {
+                    nodes.remove(nodes.size() - 1);
+                }
+                nodes.retainAll(coordMap.keySet());
+                if (closed) {
+                    nodes.add(nodes.get(0));
+                }
+
+                newWay.setNodes(nodes);
+                if (!way.getNodes().equals(nodes)) {
+                    commands.add(new ChangeCommand(way, newWay));
+                }
+            }
+        }
+
+        if (!nodesToDelete2.isEmpty()) {
+            commands.add(new DeleteCommand(nodesToDelete2));
+        }
+
+        for (final Entry<Node, LatLon> entry : coordMap.entrySet()) {
+            final Node node = entry.getKey();
+            final LatLon coord = entry.getValue();
+            if (!node.getCoor().equals(coord)) {
+                commands.add(new MoveCommand(node, coord));
+            }
+        }
+
+        return commands;
+    }
+
+    private static void addNodesToDelete(final Collection<Node> nodesToDelete, final Way w) {
+        final double angleThreshold = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.ANGLE_THRESHOLD, 10);
+        final double angleFactor = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.ANGLE_FACTOR, 1.0);
+        final double areaThreshold = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.AREA_THRESHOLD, 5.0);
+        final double areaFactor = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.AREA_FACTOR, 1.0);
+        final double distanceThreshold = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.DIST_THRESHOLD, 3);
+        final double distanceFactor = Config.getPref().getDouble(SimplifyAreaPreferenceSetting.DIST_FACTOR, 3);
+
+        final List<Node> nodes = w.getNodes();
+        final int size = nodes.size();
+
+        if (size == 0) {
+            return;
+        }
+
+        final boolean closed = nodes.get(0).equals(nodes.get(size - 1));
+
+        if (closed) {
+            nodes.remove(size - 1); // remove end node ( = start node)
+        }
+
+        // remove nodes within threshold
+
+        final List<Double> weightList = new ArrayList<>(nodes.size()); // weight cache
+        for (int i = 0; i < nodes.size(); i++) {
+            weightList.add(null);
+        }
+
+        while (true) {
+            Node prevNode = null;
+            LatLon coord1 = null;
+            LatLon coord2 = null;
+            int prevIndex = -1;
+
+            double minWeight = Double.POSITIVE_INFINITY;
+            Node bestMatch = null;
+
+            final int size2 = nodes.size();
+
+            if (size2 == 0) {
+                break;
+            }
+
+            for (int i = 0, len = size2 + (closed ? 2 : 1); i < len; i++) {
+                final int index = i % size2;
+
+                final Node n = nodes.get(index);
+                final LatLon coord3 = n.getCoor();
+
+                if (coord1 != null) {
+                    final double weight;
+
+                    if (weightList.get(prevIndex) == null) {
+                        final double angleWeight = computeConvectAngle(coord1, coord2, coord3) / angleThreshold;
+                        final double areaWeight = computeArea(coord1, coord2, coord3) / areaThreshold;
+                        final double distanceWeight = Math.abs(crossTrackError(coord1, coord2, coord3)) / distanceThreshold;
+
+                        weight = (!closed && i == len - 1) || // don't remove last node of the not closed way
+                                nodeGluesWays(prevNode) ||
+                                angleWeight > 1.0 || areaWeight > 1.0 || distanceWeight > 1.0 ? Double.POSITIVE_INFINITY :
+                                angleWeight * angleFactor + areaWeight * areaFactor + distanceWeight * distanceFactor;
+
+                        weightList.set(prevIndex, weight);
+                    } else {
+                        weight = weightList.get(prevIndex);
+                    }
+
+                    if (weight < minWeight) {
+                        minWeight = weight;
+                        bestMatch = prevNode;
+                    }
+                }
+
+                coord1 = coord2;
+                coord2 = coord3;
+                prevNode = n;
+                prevIndex = index;
+            }
+
+            if (bestMatch == null) {
+                break;
+            }
+
+            final int index = nodes.indexOf(bestMatch);
+
+            weightList.set((index - 1 + size2) % size2, null);
+            weightList.set((index + 1 + size2) % size2, null);
+            weightList.remove(index);
+            nodes.remove(index);
+        }
+
+        final HashSet<Node> delNodes = new HashSet<>(w.getNodes());
+        delNodes.removeAll(nodes);
+
+        nodesToDelete.addAll(delNodes);
+    }
+
+    public static double computeConvectAngle(final LatLon coord1, final LatLon coord2, final LatLon coord3) {
+        final double angle =  Math.abs(heading(coord2, coord3) - heading(coord1, coord2));
+        return Math.toDegrees(angle < Math.PI ? angle : 2 * Math.PI - angle);
+    }
+
+    public static double computeArea(final LatLon coord1, final LatLon coord2, final LatLon coord3) {
+        final double a = coord1.greatCircleDistance(coord2);
+        final double b = coord2.greatCircleDistance(coord3);
+        final double c = coord3.greatCircleDistance(coord1);
+
+        final double p = (a + b + c) / 2.0;
+
+        final double q = p * (p - a) * (p - b) * (p - c); // I found this negative in one case (:-o) when nodes were in line on a small area
+        return q < 0.0 ? 0.0 : Math.sqrt(q);
+    }
+
+    public static double R = 6378135;
+
+    public static double crossTrackError(final LatLon l1, final LatLon l2, final LatLon l3) {
+        return R * Math.asin(sin(l1.greatCircleDistance(l2) / R) * sin(heading(l1, l2) - heading(l1, l3)));
+    }
+
+    public static double heading(final LatLon a, final LatLon b) {
+        double hd = Math.atan2(sin(toRadians(a.lon() - b.lon())) * cos(toRadians(b.lat())),
+                cos(toRadians(a.lat())) * sin(toRadians(b.lat())) -
+                sin(toRadians(a.lat())) * cos(toRadians(b.lat())) * cos(toRadians(a.lon() - b.lon())));
+        hd %= 2 * Math.PI;
+        if (hd < 0) {
+            hd += 2 * Math.PI;
+        }
+        return hd;
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getLayerManager().getEditDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getLayerManager().getEditDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(final Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+}
Index: applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPlugin.java
===================================================================
--- applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPlugin.java	(revision 34586)
+++ applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPlugin.java	(revision 34586)
@@ -0,0 +1,21 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.simplifyarea;
+
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+public class SimplifyAreaPlugin extends Plugin {
+
+    public SimplifyAreaPlugin(final PluginInformation info) {
+        super(info);
+        MainMenu.add(MainApplication.getMenu().moreToolsMenu, new SimplifyAreaAction());
+    }
+
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new SimplifyAreaPreferenceSetting();
+    }
+}
Index: applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPreferenceSetting.java
===================================================================
--- applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPreferenceSetting.java	(revision 34586)
+++ applications/editors/josm/plugins/simplifyarea/src/org/openstreetmap/josm/plugins/simplifyarea/SimplifyAreaPreferenceSetting.java	(revision 34586)
@@ -0,0 +1,93 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.simplifyarea;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.GBC;
+
+public class SimplifyAreaPreferenceSetting extends DefaultTabPreferenceSetting {
+
+    static final String DIST_FACTOR = "simplify-area.dist.factor";
+    static final String DIST_THRESHOLD = "simplify-area.dist.threshold";
+    static final String AREA_FACTOR = "simplify-area.area.factor";
+    static final String AREA_THRESHOLD = "simplify-area.area.threshold";
+    static final String ANGLE_FACTOR = "simplify-area.angle.factor";
+    static final String ANGLE_THRESHOLD = "simplify-area.angle.threshold";
+    static final String MERGE_THRESHOLD = "simplify-area.merge.threshold";
+
+    private final JTextField mergeThreshold = new JosmTextField(8);
+    private final JTextField angleThreshold = new JosmTextField(8);
+    private final JTextField angleFactor = new JosmTextField(8);
+    private final JTextField areaThreshold = new JosmTextField(8);
+    private final JTextField areaFactor = new JosmTextField(8);
+    private final JTextField distanceThreshold = new JosmTextField(8);
+    private final JTextField distanceFactor = new JosmTextField(8);
+
+    public SimplifyAreaPreferenceSetting() {
+        super("simplifyArea", tr("Simplify Area"), tr("Node of the way (area) is removed if all of <u>Angle Weight</u>, <u>Area Weight</u> and <u>Distance Weight</u> are greater than 1. " +
+                "<u>Weight</u> is computed as <u>Value</u> / <u>Threshold</u>, where <u>Value</u> is one of <u>Angle</u>, <u>Area</u> and <u>Distance</u> " +
+                "computed from every three adjanced points of the way." +
+                "<ul><li><u>Value</u> of <u>Angle</u> is angle in degrees on the second node</li>" +
+                "<li><u>Value</u> of <u>Area</u> is area formed by triangle</li>" +
+                "<li><u>Value</u> of the <u>Distance</u> is Cross Track Error Distance</li></ul>" +
+                "All three <u>Weight</u>s multiplied by its <u>Factor</u>s are summed and node of the lowest sum is removed first. " +
+                "Removal continues until there is no node to remove." +
+                "Merge Nearby Nodes is another step of the simplification that merges adjanced nodes that are closer than <u>Threshold</u> meters."));
+    }
+
+    @Override
+    public void addGui(final PreferenceTabbedPane gui) {
+        final JPanel tab = gui.createPreferenceTab(this);
+
+        angleThreshold.setText(Config.getPref().get(ANGLE_THRESHOLD, "10"));
+        tab.add(new JLabel(tr("Angle Threshold")), GBC.std());
+        tab.add(angleThreshold, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        angleFactor.setText(Config.getPref().get(ANGLE_FACTOR, "1.0"));
+        tab.add(new JLabel(tr("Angle Factor")), GBC.std());
+        tab.add(angleFactor, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        areaThreshold.setText(Config.getPref().get(AREA_THRESHOLD, "5.0"));
+        tab.add(new JLabel(tr("Area Threshold")), GBC.std());
+        tab.add(areaThreshold, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        areaFactor.setText(Config.getPref().get(AREA_FACTOR, "1.0"));
+        tab.add(new JLabel(tr("Area Factor")), GBC.std());
+        tab.add(areaFactor, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        distanceThreshold.setText(Config.getPref().get(DIST_THRESHOLD, "3"));
+        tab.add(new JLabel(tr("Distance Threshold")), GBC.std());
+        tab.add(distanceThreshold, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        distanceFactor.setText(Config.getPref().get(DIST_FACTOR, "3"));
+        tab.add(new JLabel(tr("Distance Factor")), GBC.std());
+        tab.add(distanceFactor, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        mergeThreshold.setText(Config.getPref().get(MERGE_THRESHOLD, "0.2"));
+        tab.add(new JLabel(tr("Merge Nearby Nodes Threshold")), GBC.std());
+        tab.add(mergeThreshold, GBC.eol().fill(GBC.HORIZONTAL).insets(5,0,0,5));
+
+        tab.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
+    }
+
+    @Override
+    public boolean ok() {
+        Config.getPref().put(MERGE_THRESHOLD, mergeThreshold.getText());
+        Config.getPref().put(ANGLE_THRESHOLD, angleThreshold.getText());
+        Config.getPref().put(ANGLE_FACTOR, angleFactor.getText());
+        Config.getPref().put(AREA_THRESHOLD, areaThreshold.getText());
+        Config.getPref().put(AREA_FACTOR, areaFactor.getText());
+        Config.getPref().put(DIST_THRESHOLD, distanceThreshold.getText());
+        Config.getPref().put(DIST_FACTOR, distanceFactor.getText());
+        return false;
+    }
+}
