Example #1
0
 @Override
 public void visit(Node n) {
   if (e.child == null && left.matches(new Environment(n))) {
     if (e.osm instanceof Way && Geometry.nodeInsidePolygon(n, ((Way) e.osm).getNodes())
         || e.osm instanceof Relation
             && ((Relation) e.osm).isMultipolygon()
             && Geometry.isNodeInsideMultiPolygon(n, (Relation) e.osm, null)) {
       e.child = n;
     }
   }
 }
Example #2
0
    private WayInPolygon advanceNextWay(boolean rightmost) {

      Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode();
      Node prevNode =
          !lastWayReverse
              ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2)
              : lastWay.way.getNode(1);

      // find best next way
      WayInPolygon bestWay = null;
      Node bestWayNextNode = null;
      boolean bestWayReverse = false;

      for (WayInPolygon way : availableWays) {
        if (way.way.firstNode().equals(headNode)) {
          // start adjacent to headNode
          Node nextNode = way.way.getNode(1);

          if (nextNode.equals(prevNode)) {
            // this is the path we came from - ignore it.
          } else if (bestWay == null
              || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)
                  == rightmost)) {
            // the new way is better
            bestWay = way;
            bestWayReverse = false;
            bestWayNextNode = nextNode;
          }
        }

        if (way.way.lastNode().equals(headNode)) {
          // end adjacent to headNode
          Node nextNode = way.way.getNode(way.way.getNodesCount() - 2);

          if (nextNode.equals(prevNode)) {
            // this is the path we came from - ignore it.
          } else if (bestWay == null
              || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)
                  == rightmost)) {
            // the new way is better
            bestWay = way;
            bestWayReverse = true;
            bestWayNextNode = nextNode;
          }
        }
      }

      lastWay = bestWay;
      lastWayReverse = bestWayReverse;

      return lastWay;
    }
Example #3
0
 @Override
 public void visit(Way w) {
   if (e.child == null && left.matches(new Environment(w))) {
     if (e.osm instanceof Way
             && Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals(
                 Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))
         || e.osm instanceof Relation
             && ((Relation) e.osm).isMultipolygon()
             && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) e.osm, null)) {
       e.child = w;
     }
   }
 }
Example #4
0
  @Override
  public void visit(Way w) {

    if (!w.isUsable() || !w.isClosed()) return;

    String natural = w.get("natural");
    if (natural == null) return;
    else if ("coastline".equals(natural) && Geometry.isClockwise(w)) {
      reportError(w, tr("Reversed coastline: land not on left side"), WRONGLY_ORDERED_COAST);
    } else if ("land".equals(natural) && Geometry.isClockwise(w)) {
      reportError(w, tr("Reversed land: land not on left side"), WRONGLY_ORDERED_LAND);
    } else return;
  }
Example #5
0
 @Override
 public void visit(Way w) {
   if (e.child == null && left.matches(new Environment(w))) {
     if (e.osm instanceof Way
         && Geometry.PolygonIntersection.CROSSING.equals(
             Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))) {
       e.child = w;
     }
   }
 }
Example #6
0
 protected void checkDistance(OsmPrimitive house, Collection<Way> street) {
   EastNorth centroid;
   if (house instanceof Node) {
     centroid = ((Node) house).getEastNorth();
   } else if (house instanceof Way) {
     List<Node> nodes = ((Way) house).getNodes();
     if (house.hasKey(ADDR_INTERPOLATION)) {
       for (Node n : nodes) {
         if (n.hasKey(ADDR_HOUSE_NUMBER)) {
           checkDistance(n, street);
         }
       }
       return;
     }
     centroid = Geometry.getCentroid(nodes);
   } else {
     return; // TODO handle multipolygon houses ?
   }
   if (centroid == null) return; // fix #8305
   double maxDistance = Main.pref.getDouble("validator.addresses.max_street_distance", 200.0);
   boolean hasIncompleteWays = false;
   for (Way streetPart : street) {
     for (Pair<Node, Node> chunk : streetPart.getNodePairs(false)) {
       EastNorth closest =
           Geometry.closestPointToSegment(
               chunk.a.getEastNorth(), chunk.b.getEastNorth(), centroid);
       if (closest.distance(centroid) <= maxDistance) {
         return;
       }
     }
     if (!hasIncompleteWays && streetPart.isIncomplete()) {
       hasIncompleteWays = true;
     }
   }
   // No street segment found near this house, report error on if the relation does not contain
   // incomplete street ways (fix #8314)
   if (hasIncompleteWays) return;
   List<OsmPrimitive> errorList = new ArrayList<OsmPrimitive>(street);
   errorList.add(0, house);
   errors.add(
       new AddressError(HOUSE_NUMBER_TOO_FAR, errorList, tr("House number too far from street")));
 }
Example #7
0
  /**
   * Tests if the areas have some intersections to join.
   *
   * @param areas Areas to test
   * @return @{code true} if areas are joinable
   */
  private boolean testJoin(List<Multipolygon> areas) {
    List<Way> allStartingWays = new ArrayList<Way>();

    for (Multipolygon area : areas) {
      allStartingWays.add(area.outerWay);
      allStartingWays.addAll(area.innerWays);
    }

    // find intersection points
    Set<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds);
    return !nodes.isEmpty();
  }
Example #8
0
  /**
   * Tests if way is inside other way
   *
   * @param outside outer polygon description
   * @param inside inner polygon description
   * @return {@code true} if inner is inside outer
   */
  public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) {
    Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes());
    List<Node> insideNodes = inside.getNodes();

    for (Node insideNode : insideNodes) {

      if (!outsideNodes.contains(insideNode))
        // simply test the one node
        return Geometry.nodeInsidePolygon(insideNode, outside.getNodes());
    }

    // all nodes shared.
    return false;
  }
 void addSpecial(OsmPrimitive o) {
   if (o instanceof Node) {
     addCoordinates((Node) o);
   } else if (o instanceof Way) {
     addBbox(o);
     add(
         tr("Centroid: "),
         Main.getProjection()
             .eastNorth2latlon(Geometry.getCentroid(((Way) o).getNodes()))
             .toStringCSV(", "));
     addWayNodes((Way) o);
   } else if (o instanceof Relation) {
     addBbox(o);
     addRelationMembers((Relation) o);
   }
 }
Example #10
0
  /**
   * Returns the coordinate of intersection of segment sp1-sp2 and an altitude to it starting at
   * point ap. If the line defined with sp1-sp2 intersects its altitude out of sp1-sp2, null is
   * returned.
   *
   * @param sp1
   * @param sp2
   * @param ap
   * @return Intersection coordinate or null
   */
  public static EastNorth getSegmentAltituteIntersection(
      EastNorth sp1, EastNorth sp2, EastNorth ap) {
    Double segmentLenght = sp1.distance(sp2);
    Double altitudeAngle = getSegmentAngle(sp1, sp2) + Math.PI / 2;

    // Taking a random point on the altitude line (angle is known).
    EastNorth ap2 =
        new EastNorth(
            ap.east() + 1000 * Math.cos(altitudeAngle),
            ap.north() + 1000 * Math.sin(altitudeAngle));

    // Finding the intersection of two lines
    EastNorth resultCandidate = Geometry.getLineLineIntersection(sp1, sp2, ap, ap2);

    // Filtering result
    if (resultCandidate != null
        && resultCandidate.distance(sp1) * .999 < segmentLenght
        && resultCandidate.distance(sp2) * .999 < segmentLenght) {
      return resultCandidate;
    } else {
      return null;
    }
  }
Example #11
0
  /**
   * This method analyzes the way and assigns each part what direction polygon "inside" is.
   *
   * @param parts the split parts of the way
   * @param isInner - if true, reverts the direction (for multipolygon islands)
   * @return list of parts, marked with the inside orientation.
   */
  private List<WayInPolygon> markWayInsideSide(List<Way> parts, boolean isInner) {

    List<WayInPolygon> result = new ArrayList<WayInPolygon>();

    // prepare prev and next maps
    Map<Way, Way> nextWayMap = new HashMap<Way, Way>();
    Map<Way, Way> prevWayMap = new HashMap<Way, Way>();

    for (int pos = 0; pos < parts.size(); pos++) {

      if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode()))
        throw new RuntimeException("Way not circular");

      nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size()));
      prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size()));
    }

    // find the node with minimum y - it's guaranteed to be outer. (What about the south pole?)
    Way topWay = null;
    Node topNode = null;
    int topIndex = 0;
    double minY = Double.POSITIVE_INFINITY;

    for (Way way : parts) {
      for (int pos = 0; pos < way.getNodesCount(); pos++) {
        Node node = way.getNode(pos);

        if (node.getEastNorth().getY() < minY) {
          minY = node.getEastNorth().getY();
          topWay = way;
          topNode = node;
          topIndex = pos;
        }
      }
    }

    // get the upper way and it's orientation.

    boolean wayClockwise; // orientation of the top way.

    if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode())) {
      Node headNode = null; // the node at junction
      Node prevNode = null; // last node from previous path
      wayClockwise = false;

      // node is in split point - find the outermost way from this point

      headNode = topNode;
      // make a fake node that is downwards from head node (smaller Y). It will be a division point
      // between paths.
      prevNode =
          new Node(
              new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5));

      topWay = null;
      wayClockwise = false;
      Node bestWayNextNode = null;

      for (Way way : parts) {
        if (way.firstNode().equals(headNode)) {
          Node nextNode = way.getNode(1);

          if (topWay == null
              || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
            // the new way is better
            topWay = way;
            wayClockwise = true;
            bestWayNextNode = nextNode;
          }
        }

        if (way.lastNode().equals(headNode)) {
          // end adjacent to headNode
          Node nextNode = way.getNode(way.getNodesCount() - 2);

          if (topWay == null
              || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
            // the new way is better
            topWay = way;
            wayClockwise = false;
            bestWayNextNode = nextNode;
          }
        }
      }
    } else {
      // node is inside way - pick the clockwise going end.
      Node prev = topWay.getNode(topIndex - 1);
      Node next = topWay.getNode(topIndex + 1);

      // there will be no parallel segments in the middle of way, so all fine.
      wayClockwise = Geometry.angleIsClockwise(prev, topNode, next);
    }

    Way curWay = topWay;
    boolean curWayInsideToTheRight = wayClockwise ^ isInner;

    // iterate till full circle is reached
    while (true) {

      // add cur way
      WayInPolygon resultWay = new WayInPolygon(curWay, curWayInsideToTheRight);
      result.add(resultWay);

      // process next way
      Way nextWay = nextWayMap.get(curWay);
      Node prevNode = curWay.getNode(curWay.getNodesCount() - 2);
      Node headNode = curWay.lastNode();
      Node nextNode = nextWay.getNode(1);

      if (nextWay == topWay) {
        // full loop traversed - all done.
        break;
      }

      // find intersecting segments
      // the intersections will look like this:
      //
      //                       ^
      //                       |
      //                       X wayBNode
      //                       |
      //                  wayB |
      //                       |
      //             curWay    |       nextWay
      // ----X----------------->X----------------------X---->
      //    prevNode           ^headNode              nextNode
      //                       |
      //                       |
      //                  wayA |
      //                       |
      //                       X wayANode
      //                       |

      int intersectionCount = 0;

      for (Way wayA : parts) {

        if (wayA == curWay) {
          continue;
        }

        if (wayA.lastNode().equals(headNode)) {

          Way wayB = nextWayMap.get(wayA);

          // test if wayA is opposite wayB relative to curWay and nextWay

          Node wayANode = wayA.getNode(wayA.getNodesCount() - 2);
          Node wayBNode = wayB.getNode(1);

          boolean wayAToTheRight =
              Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode);
          boolean wayBToTheRight =
              Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode);

          if (wayAToTheRight != wayBToTheRight) {
            intersectionCount++;
          }
        }
      }

      // if odd number of crossings, invert orientation
      if (intersectionCount % 2 != 0) {
        curWayInsideToTheRight = !curWayInsideToTheRight;
      }

      curWay = nextWay;
    }

    return result;
  }
Example #12
0
  /**
   * Will join two or more overlapping areas
   *
   * @param areas list of areas to join
   * @return new area formed.
   */
  private JoinAreasResult joinAreas(List<Multipolygon> areas) throws UserCancelException {

    JoinAreasResult result = new JoinAreasResult();
    result.hasChanges = false;

    List<Way> allStartingWays = new ArrayList<Way>();
    List<Way> innerStartingWays = new ArrayList<Way>();
    List<Way> outerStartingWays = new ArrayList<Way>();

    for (Multipolygon area : areas) {
      outerStartingWays.add(area.outerWay);
      innerStartingWays.addAll(area.innerWays);
    }

    allStartingWays.addAll(innerStartingWays);
    allStartingWays.addAll(outerStartingWays);

    // first remove nodes in the same coordinate
    boolean removedDuplicates = false;
    removedDuplicates |= removeDuplicateNodes(allStartingWays);

    if (removedDuplicates) {
      result.hasChanges = true;
      commitCommands(marktr("Removed duplicate nodes"));
    }

    // find intersection points
    Set<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds);

    // no intersections, return.
    if (nodes.isEmpty()) return result;
    commitCommands(marktr("Added node on all intersections"));

    List<RelationRole> relations = new ArrayList<RelationRole>();

    // Remove ways from all relations so ways can be combined/split quietly
    for (Way way : allStartingWays) {
      relations.addAll(removeFromAllRelations(way));
    }

    // Don't warn now, because it will really look corrupted
    boolean warnAboutRelations = !relations.isEmpty() && allStartingWays.size() > 1;

    List<WayInPolygon> preparedWays = new ArrayList<WayInPolygon>();

    for (Way way : outerStartingWays) {
      List<Way> splitWays = splitWayOnNodes(way, nodes);
      preparedWays.addAll(markWayInsideSide(splitWays, false));
    }

    for (Way way : innerStartingWays) {
      List<Way> splitWays = splitWayOnNodes(way, nodes);
      preparedWays.addAll(markWayInsideSide(splitWays, true));
    }

    // Find boundary ways
    List<Way> discardedWays = new ArrayList<Way>();
    List<AssembledPolygon> bounadries = findBoundaryPolygons(preparedWays, discardedWays);

    // find polygons
    List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries);

    // assemble final polygons
    List<Multipolygon> polygons = new ArrayList<Multipolygon>();
    Set<Relation> relationsToDelete = new LinkedHashSet<Relation>();

    for (AssembledMultipolygon pol : preparedPolygons) {

      // create the new ways
      Multipolygon resultPol = joinPolygon(pol);

      // create multipolygon relation, if necessary.
      RelationRole ownMultipolygonRelation =
          addOwnMultigonRelation(resultPol.innerWays, resultPol.outerWay);

      // add back the original relations, merged with our new multipolygon relation
      fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation, relationsToDelete);

      // strip tags from inner ways
      // TODO: preserve tags on existing inner ways
      stripTags(resultPol.innerWays);

      polygons.add(resultPol);
    }

    commitCommands(marktr("Assemble new polygons"));

    for (Relation rel : relationsToDelete) {
      cmds.add(new DeleteCommand(rel));
    }

    commitCommands(marktr("Delete relations"));

    // Delete the discarded inner ways
    if (!discardedWays.isEmpty()) {
      Command deleteCmd = DeleteCommand.delete(Main.main.getEditLayer(), discardedWays, true);
      if (deleteCmd != null) {
        cmds.add(deleteCmd);
        commitCommands(marktr("Delete Ways that are not part of an inner multipolygon"));
      }
    }

    makeCommitsOneAction(marktr("Joined overlapping areas"));

    if (warnAboutRelations) {
      new Notification(
              tr(
                  "Some of the ways were part of relations that have been modified.<br>Please verify no errors have been introduced."))
          .setIcon(JOptionPane.INFORMATION_MESSAGE)
          .setDuration(Notification.TIME_LONG)
          .show();
    }

    result.hasChanges = true;
    result.polygons = polygons;
    return result;
  }
Example #13
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();
  }