Index: /applications/editors/josm/plugins/duplicateway/.classpath
===================================================================
--- /applications/editors/josm/plugins/duplicateway/.classpath	(revision 4246)
+++ /applications/editors/josm/plugins/duplicateway/.classpath	(revision 4246)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: /applications/editors/josm/plugins/duplicateway/.project
===================================================================
--- /applications/editors/josm/plugins/duplicateway/.project	(revision 4246)
+++ /applications/editors/josm/plugins/duplicateway/.project	(revision 4246)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>josm-duplicateway</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayAction.java
===================================================================
--- /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayAction.java	(revision 4246)
+++ /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayAction.java	(revision 4246)
@@ -0,0 +1,311 @@
+package org.openstreetmap.josm.plugins.duplicateway;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Cursor;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.ArrayList;
+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.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+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.Segment;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A plugin to add ways manipulation things
+ * 
+ * @author Thomas.Walraet
+ */
+class DuplicateWayAction extends JosmAction implements SelectionChangedListener, MouseListener {
+
+  private static final long serialVersionUID = 1L;
+  protected String name;
+  protected Cursor oldCursor;
+  protected List<Way> selectedWays;
+
+  public DuplicateWayAction(String name) {
+    super(name, "duplicateway", tr("Duplicate selected ways."), KeyEvent.VK_W, KeyEvent.CTRL_MASK
+        | KeyEvent.SHIFT_MASK, true);
+    this.name = name;
+    setEnabled(false);
+    DataSet.listeners.add(this);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+
+//    DataSet d = Main.ds;
+    selectedWays = new ArrayList<Way>();
+    for (OsmPrimitive osm : Main.ds.getSelected()) {
+      if (osm instanceof Way) {
+        Way way = (Way) osm;
+        EastNorth last = null;
+        for (Segment seg : way.segments) {
+          if (last != null) {
+            if (! seg.from.eastNorth.equals(last)) {
+              JOptionPane.showMessageDialog(Main.parent, tr("Can't duplicate unnordered way."));
+              return;
+            }
+          }
+          last = seg.to.eastNorth;
+        }
+        selectedWays.add(way);
+      }
+    }
+
+    if (Main.map == null) {
+      JOptionPane.showMessageDialog(Main.parent, tr("No data loaded."));
+      return;
+    }
+    
+    if (selectedWays.isEmpty()) {
+      JOptionPane.showMessageDialog(Main.parent, tr("You must select at least one way."));
+      return;
+    }
+
+    oldCursor = Main.map.mapView.getCursor();
+    Main.map.mapView.setCursor(ImageProvider.getCursor("crosshair", "duplicate"));
+    Main.map.mapView.addMouseListener(this);
+  }
+  
+  public static Node createNode(double east, double north) {
+    return new Node(Main.proj.eastNorth2latlon(new EastNorth(east, north)));
+  }
+  /**
+   * Duplicate the selected ways. The distance to be offset is 
+   * determined by finding the distance of the 'offset' point from 
+   * the nearest segment. 
+   * 
+   * @param offset The point in screen co-ordinates used to calculate the offset distance
+   */
+  protected void duplicate(Point clickPoint) {
+
+    EastNorth clickEN = Main.map.mapView.getEastNorth(clickPoint.x, clickPoint.y);
+
+    /*
+     * First, find the nearest Segment belonging to a selected way
+     */
+    Segment cs = null;
+    for (Way way : selectedWays) {
+      double minDistance = Double.MAX_VALUE;
+      // segments
+      for (Segment ls : way.segments) {
+        if (ls.deleted || ls.incomplete)
+          continue;
+        double perDist = JosmVector.perpDistance(ls, clickEN);
+        if (perDist < minDistance) {
+          minDistance = perDist;
+          cs = ls;
+        }
+      }
+    }
+    
+    if (cs == null) {
+      return;
+    }
+    
+    /*
+     * Find the distance we need to offset the new way
+     * +ve offset is to the right of the initial way, -ve to the left
+     */
+    JosmVector closestSegment = new JosmVector(cs);
+    double offset = closestSegment.calculateOffset(clickEN);
+    
+    Collection<Command> commands = new LinkedList<Command>();
+    Collection<Way> ways = new LinkedList<Way>();
+    
+    /*
+     * First new node is offset 90 degrees from the first point
+     */
+    for (Way way : selectedWays) {
+      Way newWay = new Way();
+      
+      Node lastNode = null;
+      JosmVector lastLine = null;
+    
+      for (Segment seg : way.segments) {
+        JosmVector currentLine = new JosmVector(seg);
+        Node newNode = null;
+        
+        if (lastNode == null) {
+          JosmVector perpVector = new JosmVector(currentLine);
+          perpVector.rotate90(offset);
+          newNode = createNode(perpVector.getP2().getX(), perpVector.getP2().getY());
+          commands.add(new AddCommand(newNode));
+        }
+        else {
+//          if (lastLine != null &&
+//              ((lastLine.getTheta() < 0 && currentLine.getTheta() > 0) ||
+//              (lastLine.getTheta() > 0 && currentLine.getTheta() < 0))) {
+//            offset = -offset;
+//          }
+//          if ()
+//              ) {
+//            offset = -offset;
+//          }
+          JosmVector bisector = lastLine.bisector(currentLine, offset);
+          newNode = createNode(bisector.getP2().getX(), bisector.getP2().getY());
+          commands.add(new AddCommand(newNode));
+          Segment s = new Segment (newNode, lastNode);
+          commands.add(new AddCommand(s));
+          newWay.segments.add(0, s);
+//          if ((lastLine.direction().equals("ne") && currentLine.direction().equals("se")) ||
+//              (lastLine.direction().equals("se") && currentLine.direction().equals("ne")) ||
+//              (lastLine.direction().equals("nw") && currentLine.direction().equals("sw")) ||
+//              (lastLine.direction().equals("sw") && currentLine.direction().equals("nw"))) {
+//            offset = -offset;
+//          }
+        }
+
+        lastLine = currentLine;
+        lastNode = newNode;
+        
+      }
+      lastLine.reverse();
+      lastLine.rotate90(-offset);
+      Node newNode = createNode(lastLine.getP2().getX(), lastLine.getP2().getY());
+      commands.add(new AddCommand(newNode));
+      Segment s = new Segment (newNode, lastNode);
+      commands.add(new AddCommand(s));
+      newWay.segments.add(0, s);
+      
+      for (String key : way.keySet()) {
+        newWay.put(key, way.get(key));
+      }
+      commands.add(new AddCommand(newWay));
+      ways.add(newWay);
+    }
+  
+    Main.main.undoRedo.add(new SequenceCommand(tr("Create duplicate way"), commands));
+    Main.ds.setSelected(ways);
+  }
+
+  protected Node offsetNode(Node oldNode, double offsetE, double offsetN) {
+    EastNorth en = Main.proj.latlon2eastNorth(oldNode.coor);
+    EastNorth newEn = new EastNorth(en.east()+offsetE, en.north()+offsetN);
+    LatLon ll = Main.proj.eastNorth2latlon(newEn);   
+    return new Node(ll);
+  }
+  
+  /**
+   * Enable the "Duplicate way" menu option if at least one way is selected
+   */
+  public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+    for (OsmPrimitive osm : newSelection) {
+      if (osm instanceof Way) {
+          setEnabled(true);
+          return;
+      }
+    }
+    setEnabled(false);
+  }
+
+  public void mouseClicked(MouseEvent e) {
+    Main.map.mapView.removeMouseListener(this);
+    Main.map.mapView.setCursor(oldCursor);
+    duplicate(e.getPoint());
+  }
+
+  public void mouseEntered(MouseEvent e) {
+    // TODO Auto-generated method stub
+    
+  }
+
+  public void mouseExited(MouseEvent e) {
+    // TODO Auto-generated method stub
+    
+  }
+
+  public void mousePressed(MouseEvent e) {
+    // TODO Auto-generated method stub
+    
+  }
+
+  public void mouseReleased(MouseEvent e) {
+    // TODO Auto-generated method stub
+    
+  }
+  
+//  class DuplicateDialog extends JDialog {
+//    private static final long serialVersionUID = 1L;
+//    protected Box mainPanel;
+//    protected IntConfigurer offset;
+//    protected boolean cancelled;
+//    protected String right;
+//    protected String left;
+//    protected JComboBox moveCombo;
+//
+//    public DuplicateDialog(String title) {
+//      super();
+//      this.setTitle(title);
+//      this.setModal(true);
+//      initComponents();
+//    }
+//
+//    protected void initComponents() {
+//      mainPanel = Box.createVerticalBox();
+//      offset = new IntConfigurer("", tr("Offset (metres):  "), new Integer(15));
+//      mainPanel.add(offset.getControls());
+//      getContentPane().add(mainPanel);
+//
+//      right = tr("right/down");
+//      left = tr("left/up");
+//      Box movePanel = Box.createHorizontalBox();
+//      movePanel.add(new JLabel(tr("Create new segments to the ")));
+//      moveCombo = new JComboBox(new String[] {right, left});
+//      movePanel.add(moveCombo);
+//      movePanel.add(new JLabel(tr(" of existing segments.")));
+//      mainPanel.add(movePanel);
+//
+//      Box buttonPanel = Box.createHorizontalBox();
+//      JButton okButton = new JButton(tr("Ok"));
+//      okButton.addActionListener(new ActionListener() {
+//        public void actionPerformed(ActionEvent e) {
+//          cancelled = false;
+//          setVisible(false);
+//
+//        }
+//      });
+//      JButton canButton = new JButton(tr("Cancel"));
+//      canButton.addActionListener(new ActionListener() {
+//        public void actionPerformed(ActionEvent e) {
+//          cancelled = true;
+//          setVisible(false);
+//        }
+//      });
+//      buttonPanel.add(okButton);
+//      buttonPanel.add(canButton);
+//      mainPanel.add(buttonPanel);
+//
+//      pack();
+//    }
+//
+//    protected int getOffset() {
+//      int off = offset.getIntValue(15);
+//      return right.equals(moveCombo.getSelectedItem()) ? off : -off;
+//    }
+//
+//    protected boolean isCancelled() {
+//      return cancelled;
+//    }
+//
+//  }
+}
Index: /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayPlugin.java
===================================================================
--- /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayPlugin.java	(revision 4246)
+++ /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/DuplicateWayPlugin.java	(revision 4246)
@@ -0,0 +1,43 @@
+package org.openstreetmap.josm.plugins.duplicateway;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.Plugin;
+
+/**
+ * A plugin to add a duplicate way option to assist with creating divided roads
+ * 
+ * @author Brent Easton
+ */
+public class DuplicateWayPlugin extends Plugin {
+
+  protected String name;
+
+  public DuplicateWayPlugin() {
+    name = tr("Duplicate Way");
+    JMenu toolsMenu = null;
+    for (int i = 0; i < Main.main.menu.getMenuCount() && toolsMenu == null; i++) {
+      JMenu menu = Main.main.menu.getMenu(i);
+      String name = menu.getText();
+      if (name != null && name.equals(tr("Tools"))) {
+        toolsMenu = menu;
+      }
+    }
+
+    if (toolsMenu == null) {
+      toolsMenu = new JMenu(name);
+      toolsMenu.add(new JMenuItem(new DuplicateWayAction(name)));
+      Main.main.menu.add(toolsMenu, 2);
+    }
+    else {
+      toolsMenu.addSeparator();
+      toolsMenu.add(new JMenuItem(new DuplicateWayAction(name)));
+    }
+    
+  }
+
+}
Index: /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/JosmVector.java
===================================================================
--- /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/JosmVector.java	(revision 4246)
+++ /applications/editors/josm/plugins/duplicateway/src/org/openstreetmap/josm/plugins/duplicateway/JosmVector.java	(revision 4246)
@@ -0,0 +1,304 @@
+package org.openstreetmap.josm.plugins.duplicateway;
+
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Segment;
+
+public class JosmVector extends Line2D.Double {
+  
+  public static final double EARTH_CIRCUMFERENCE = 40041455;
+  protected static final double PI_ON_2 = Math.PI / 2.0;
+  protected Point2D.Double slopeIntercept = null;
+  protected Point2D.Double rtheta = null;
+  
+  public JosmVector(double x1, double y1, double x2, double y2) {
+    super(x1, y1, x2, y2);
+  }
+  
+  public JosmVector(JosmVector ls) {
+    super();
+    x1 = ls.x1;
+    x2 = ls.x2;
+    y1 = ls.y1;
+    y2 = ls.y2;
+  }
+  
+  public JosmVector (Segment s) {
+    super(s.from.eastNorth.east(), s.from.eastNorth.north(), s.to.eastNorth.east(), s.to.eastNorth.north());
+  }
+  
+  /*
+   * Calculate slope/intersect from cartesian co-ords
+   */
+  protected void calculateSlopeIntercept() {
+    double slope = (y2 - y1) /(x2 - x1);
+    double intersect = y1 - (slope * x1);
+    slopeIntercept = new Point2D.Double(slope, intersect);
+  }
+  
+  public double getSlope() {
+    if (slopeIntercept == null) {
+      calculateSlopeIntercept();
+    }
+    return slopeIntercept.x;
+  }
+  
+  public double getIntercept() {
+    if (slopeIntercept == null) {
+      calculateSlopeIntercept();
+    }
+    return slopeIntercept.y;
+  }
+  
+  /*
+   * Calculate the polar coordinates for this line as a ray with
+   * the from point as origin
+   */
+  protected void calculatePolar() {
+    double x = x2 - x1;
+    double y = y2 - y1;
+    double r = Math.sqrt(x * x + y * y);
+    double theta = Math.atan2(y, x);
+    rtheta = new Point2D.Double(r, theta);
+  }
+  
+  public double getLength() {
+    if (rtheta == null) {
+      calculatePolar();
+    }
+    return rtheta.x;
+  }
+  
+  public double getTheta() {
+    if (rtheta == null) {
+      calculatePolar();
+    }
+    return rtheta.y;
+  }
+  
+  /*
+   * Set the Cartesian co-ords of the to point from the Polar co-ords
+   */
+  protected void polarToCartesian() {
+    double newx2 = x1 + getLength() * Math.cos(getTheta());
+    double newy2 = y1 + getLength() * Math.sin(getTheta());
+    x2 = newx2;
+    y2 = newy2;
+    slopeIntercept = null;
+  }
+  
+  protected void setPolar(double r, double theta) {
+    rtheta = new Point2D.Double(r, theta);
+    polarToCartesian();
+  }
+  
+  protected void setLength(double l) {
+    rtheta.x = l;
+    polarToCartesian();
+  }
+  /*
+   * Reverse the direction of the segment
+   */
+  public void reverse() {
+    double t = x2;
+    x2 = x1;
+    x1 = t;
+    t = y2;
+    y2 = y1;
+    y1 = t;
+    slopeIntercept = null;
+    rtheta = null;
+  }
+  
+  /*
+   * Rotate the line 
+   */
+  protected void rotate(double rot) {
+    if (rtheta == null) {
+       calculatePolar();
+    }
+    rtheta.y = normalize(rtheta.y + rot);
+    polarToCartesian();
+  }
+  
+  protected void rotate90CW() {
+    rotate(-PI_ON_2);
+  }
+  
+  protected void rotate90CCW() {
+    rotate(PI_ON_2);
+  }
+  
+  /*
+   * Normalize theta to be in the range -PI < theta < PI
+   */
+  protected double normalize(double theta) {
+    while (theta < -Math.PI || theta > Math.PI) {
+      if (theta > Math.PI) {
+        return (theta - 2 * Math.PI);
+      }
+      if (theta < -Math.PI) {
+        return (theta + 2 * Math.PI);
+      }
+    }
+    return theta;
+  }
+  
+//  /*
+//   * Rotate vector and set lenngth. If offset is positive,
+//   * rotate so the vector points more towards the right, 
+//   * otherwise towards the left
+//   */
+//  protected void rotate(double theta, double offset) {
+//    if (getTheta() > 0) {
+//      if (offset > 0) {
+//        rotate(-theta);
+//      }
+//      else {
+//        rotate(theta);
+//      }
+//    }
+//    else {
+//      if (offset > 0) {
+//        rotate(theta);
+//      }
+//      else {
+//        rotate(-theta);
+//      }
+//    }
+//    setLength(Math.abs(offset));
+//  }
+ 
+  protected void rotate90(double offset) {
+    rotate(PI_ON_2 * (offset < 0 ? 1 : -1));
+    setLength(Math.abs(offset));
+  }
+
+  /* 
+   * Return the distance of the given point from this line. Offset is 
+   * -ve if the point is to the left of the line, or +ve if to the right
+   */
+  protected double calculateOffset(EastNorth target) {
+    
+    // Calculate the perpendicular interceptor to this point
+    EastNorth intersectPoint = perpIntersect(target);
+    JosmVector intersectRay = new JosmVector(intersectPoint.east(), intersectPoint.north(), target.east(), target.north());
+    
+    // Offset is equal to the length of the interceptor
+    double offset = intersectRay.getLength();
+    
+    // Check the angle between this line and the interceptor to calculate left/right
+    double theta = normalize(getTheta() - intersectRay.getTheta());
+    if (theta < 0) {
+      offset = -offset;
+    }
+    
+    return offset;
+  }
+  
+
+  /*
+   * Return the Perpendicular distance between a point
+   * and this line. Units is degrees.
+   */
+  public double perpDistance(Node n) {
+    return perpDistance(n.eastNorth);
+  }
+
+  public double perpDistance(EastNorth en) {
+   return ptLineDist(en.east(), en.north());
+  }
+
+  public static double perpDistance(Segment s, EastNorth en) {  
+   return Line2D.ptSegDist(s.from.eastNorth.east(), s.from.eastNorth.north(), s.to.eastNorth.east(), s.to.eastNorth.north(), en.east(), en.north());
+  }
+  /*
+   * Calculate the bisector between this and another Vector. A positive offset means
+   * the bisector must point to the right of the Vectors.
+   */
+  public JosmVector bisector(JosmVector ls, double offset) {
+    JosmVector newSeg = new JosmVector(ls);
+    double newTheta = Math.PI + ls.getTheta() - getTheta();
+    newSeg.setPolar(Math.abs(offset), newSeg.getTheta() - newTheta/2.0);
+    
+    double angle = normalize(getTheta() - newSeg.getTheta());
+    if ((angle < 0 && offset > 0) || (angle > 0 && offset < 0)) {
+      newSeg.rotate(Math.PI);
+    }
+    
+//    if (newSeg.getTheta() < -PI_ON_2) {
+//      if (offset > 0) {
+//        newSeg.rotate(Math.PI);
+//      }
+//    }
+//    else if (newSeg.getTheta() > PI_ON_2) {
+//      if (offset > 0) {
+//        newSeg.rotate(-Math.PI);
+//      }
+//    }
+//    else {   
+//     if (offset < 0) {
+//       newSeg.rotate(Math.PI);
+//      }
+//    }
+    return newSeg;
+  }
+  
+  /*
+   * Return the Perpendicular Intersector from a point to this line
+   */
+  public EastNorth perpIntersect(Node n) {
+    return perpIntersect(n.eastNorth);
+  }
+  
+  public EastNorth perpIntersect(EastNorth en) {
+    
+    /*
+     * Calculate the coefficients for the two lines
+     *  1. The segment: y = ax + b
+     *  2. The perpendicular line through the new point: y = cx + d
+     */
+    double perpSlope = -1 / getSlope();    
+    double perpIntercept = en.north() - (en.east() * perpSlope);
+    
+    /*
+     * Solve the simultaneous equation to calculate the intersection
+     *  ax+b = cx+d
+     *  ax - cx = d - b
+     *  x (a-c) = d - b
+     *  x = (d - b) / (a - c) 
+     */
+    double intersectE = (perpIntercept - getIntercept()) / (getSlope() - perpSlope);
+    double intersectN = intersectE * getSlope() + getIntercept();
+    
+    return new EastNorth(intersectE, intersectN);
+  }
+//  
+//  /*
+//   * Return a compass heading  
+//   */
+//  public String direction() {
+//    double theta = getTheta();
+//    String direction = "";
+//    if (theta >= 0) {
+//      if (theta < PI_ON_2) {
+//        direction = "ne";
+//      }
+//      else {
+//        direction = "nw";
+//      }
+//    }
+//    else {
+//      if (theta < -PI_ON_2) {
+//        direction = "sw";
+//      }
+//      else {
+//        direction = "se";
+//      }
+//    }
+//    return direction;
+//  }
+}
