Esempio n. 1
0
  /**
   * Perform AlignInCircle action.
   *
   * <p>A fixed node is a node for which it is forbidden to change the angle relative to center of
   * the circle. All other nodes are uniformly distributed.
   *
   * <p>Case 1: One unclosed way. --&gt; allow action, and align selected way nodes If nodes
   * contained by this way are selected, there are fix. If nodes outside from the way are selected
   * there are ignored.
   *
   * <p>Case 2: One or more ways are selected and can be joined into a polygon --&gt; allow action,
   * and align selected ways nodes If 1 node outside of way is selected, it became center If 1 node
   * outside and 1 node inside are selected there define center and radius If no outside node and 2
   * inside nodes are selected those 2 nodes define diameter In all other cases outside nodes are
   * ignored In all cases, selected nodes are fix, nodes with more than one referrers are fix (first
   * referrer is the selected way)
   *
   * <p>Case 3: Only nodes are selected --&gt; Align these nodes, all are fix
   */
  @Override
  public void actionPerformed(ActionEvent e) {
    if (!isEnabled()) return;

    Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
    List<Node> nodes = new LinkedList<>();
    // fixNodes: All nodes for which the angle relative to center should not be modified
    Set<Node> fixNodes = new HashSet<>();
    List<Way> ways = new LinkedList<>();
    EastNorth center = null;
    double radius = 0;

    for (OsmPrimitive osm : sel) {
      if (osm instanceof Node) {
        nodes.add((Node) osm);
      } else if (osm instanceof Way) {
        ways.add((Way) osm);
      }
    }

    if (ways.size() == 1 && !ways.get(0).isClosed()) {
      // Case 1
      Way w = ways.get(0);
      fixNodes.add(w.firstNode());
      fixNodes.add(w.lastNode());
      fixNodes.addAll(nodes);
      fixNodes.addAll(collectNodesWithExternReferers(ways));
      // Temporary closed way used to reorder nodes
      Way closedWay = new Way(w);
      closedWay.addNode(w.firstNode());
      List<Way> usedWays = new ArrayList<>(1);
      usedWays.add(closedWay);
      nodes = collectNodesAnticlockwise(usedWays);
    } else if (!ways.isEmpty() && checkWaysArePolygon(ways)) {
      // Case 2
      List<Node> inside = new ArrayList<>();
      List<Node> outside = new ArrayList<>();

      for (Node n : nodes) {
        boolean isInside = false;
        for (Way w : ways) {
          if (w.getNodes().contains(n)) {
            isInside = true;
            break;
          }
        }
        if (isInside) inside.add(n);
        else outside.add(n);
      }

      if (outside.size() == 1 && inside.isEmpty()) {
        center = outside.get(0).getEastNorth();
      } else if (outside.size() == 1 && inside.size() == 1) {
        center = outside.get(0).getEastNorth();
        radius = distance(center, inside.get(0).getEastNorth());
      } else if (inside.size() == 2 && outside.isEmpty()) {
        // 2 nodes inside, define diameter
        EastNorth en0 = inside.get(0).getEastNorth();
        EastNorth en1 = inside.get(1).getEastNorth();
        center = new EastNorth((en0.east() + en1.east()) / 2, (en0.north() + en1.north()) / 2);
        radius = distance(en0, en1) / 2;
      }

      fixNodes.addAll(inside);
      fixNodes.addAll(collectNodesWithExternReferers(ways));
      nodes = collectNodesAnticlockwise(ways);
      if (nodes.size() < 4) {
        new Notification(tr("Not enough nodes in selected ways."))
            .setIcon(JOptionPane.INFORMATION_MESSAGE)
            .setDuration(Notification.TIME_SHORT)
            .show();
        return;
      }
    } else if (ways.isEmpty() && nodes.size() > 3) {
      // Case 3
      fixNodes.addAll(nodes);
      // No need to reorder nodes since all are fix
    } else {
      // Invalid action
      new Notification(tr("Please select at least four nodes."))
          .setIcon(JOptionPane.INFORMATION_MESSAGE)
          .setDuration(Notification.TIME_SHORT)
          .show();
      return;
    }

    if (center == null) {
      // Compute the center of nodes
      center = Geometry.getCenter(nodes);
      if (center == null) {
        new Notification(tr("Cannot determine center of selected nodes."))
            .setIcon(JOptionPane.INFORMATION_MESSAGE)
            .setDuration(Notification.TIME_SHORT)
            .show();
        return;
      }
    }

    // Now calculate the average distance to each node from the
    // center. This method is ok as long as distances are short
    // relative to the distance from the N or S poles.
    if (radius == 0) {
      for (Node n : nodes) {
        radius += distance(center, n.getEastNorth());
      }
      radius = radius / nodes.size();
    }

    if (!actionAllowed(nodes)) return;

    Collection<Command> cmds = new LinkedList<>();

    // Move each node to that distance from the center.
    // Nodes that are not "fix" will be adjust making regular arcs.
    int nodeCount = nodes.size();
    // Search first fixed node
    int startPosition = 0;
    for (startPosition = 0; startPosition < nodeCount; startPosition++) {
      if (fixNodes.contains(nodes.get(startPosition % nodeCount))) break;
    }
    int i = startPosition; // Start position for current arc
    int j; // End position for current arc
    while (i < startPosition + nodeCount) {
      for (j = i + 1; j < startPosition + nodeCount; j++) {
        if (fixNodes.contains(nodes.get(j % nodeCount))) break;
      }
      Node first = nodes.get(i % nodeCount);
      PolarCoor pcFirst = new PolarCoor(first.getEastNorth(), center, 0);
      pcFirst.radius = radius;
      cmds.add(pcFirst.createMoveCommand(first));
      if (j > i + 1) {
        double delta;
        if (j == i + nodeCount) {
          delta = 2 * Math.PI / nodeCount;
        } else {
          PolarCoor pcLast = new PolarCoor(nodes.get(j % nodeCount).getEastNorth(), center, 0);
          delta = pcLast.angle - pcFirst.angle;
          if (delta < 0) // Assume each PolarCoor.angle is in range ]-pi; pi]
          delta += 2 * Math.PI;
          delta /= j - i;
        }
        for (int k = i + 1; k < j; k++) {
          PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k - i) * delta, center, 0);
          cmds.add(p.createMoveCommand(nodes.get(k % nodeCount)));
        }
      }
      i = j; // Update start point for next iteration
    }

    Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds));
    Main.map.repaint();
  }