/** * This method removes duplicate points (if any) from the input way. * * @param ways the ways to process * @return {@code true} if any changes where made */ private boolean removeDuplicateNodes(List<Way> ways) { // TODO: maybe join nodes with JoinNodesAction, rather than reconnect the ways. Map<Node, Node> nodeMap = new TreeMap<Node, Node>(new NodePositionComparator()); int totalNodesRemoved = 0; for (Way way : ways) { if (way.getNodes().size() < 2) { continue; } int nodesRemoved = 0; List<Node> newNodes = new ArrayList<Node>(); Node prevNode = null; for (Node node : way.getNodes()) { if (!nodeMap.containsKey(node)) { // new node nodeMap.put(node, node); // avoid duplicate nodes if (prevNode != node) { newNodes.add(node); } else { nodesRemoved++; } } else { // node with same coordinates already exists, substitute with existing node Node representator = nodeMap.get(node); if (representator != node) { nodesRemoved++; } // avoid duplicate node if (prevNode != representator) { newNodes.add(representator); } } prevNode = node; } if (nodesRemoved > 0) { if (newNodes.size() == 1) { // all nodes in the same coordinate - add one more node, to have closed way. newNodes.add(newNodes.get(0)); } Way newWay = new Way(way); newWay.setNodes(newNodes); cmds.add(new ChangeCommand(way, newWay)); totalNodesRemoved += nodesRemoved; } } return totalNodesRemoved > 0; }
/** * Return a list of all objects in the selection, respecting the different modifier. * * @param alt Whether the alt key was pressed, which means select all objects that are touched, * instead those which are completely covered. * @return The collection of selected objects. */ public Collection<OsmPrimitive> getSelectedObjects(boolean alt) { Collection<OsmPrimitive> selection = new LinkedList<>(); // whether user only clicked, not dragged. boolean clicked = false; Rectangle bounding = lasso.getBounds(); if (bounding.height <= 2 && bounding.width <= 2) { clicked = true; } if (clicked) { Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]); OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false); if (osm != null) { selection.add(osm); } } else { // nodes for (Node n : nc.getCurrentDataSet().getNodes()) { if (n.isSelectable() && lasso.contains(nc.getPoint2D(n))) { selection.add(n); } } // ways for (Way w : nc.getCurrentDataSet().getWays()) { if (!w.isSelectable() || w.getNodesCount() == 0) { continue; } if (alt) { for (Node n : w.getNodes()) { if (!n.isIncomplete() && lasso.contains(nc.getPoint2D(n))) { selection.add(w); break; } } } else { boolean allIn = true; for (Node n : w.getNodes()) { if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) { allIn = false; break; } } if (allIn) { selection.add(w); } } } } return selection; }
@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; } } }
/** * Extract and store relation information based on the relation member * * @param src The relation member to store information about */ public RelMember(RelationMember src) { role = src.getRole(); type = src.getType(); rel_id = 0; coor = new ArrayList<LatLon>(); if (src.isNode()) { Node r = src.getNode(); tags = r.getKeys(); coor = new ArrayList<LatLon>(1); coor.add(r.getCoor()); } if (src.isWay()) { Way r = src.getWay(); tags = r.getKeys(); List<Node> wNodes = r.getNodes(); coor = new ArrayList<LatLon>(wNodes.size()); for (Node wNode : wNodes) { coor.add(wNode.getCoor()); } } if (src.isRelation()) { Relation r = src.getRelation(); tags = r.getKeys(); rel_id = r.getId(); coor = new ArrayList<LatLon>(); } }
@Test public void testBackrefrenceForWay_Full() throws OsmTransferException { Way w = lookupWay(ds, 1); assertNotNull(w); // way with name "way-1" is referred to by two relations // OsmServerBackreferenceReader reader = new OsmServerBackreferenceReader(w); reader.setReadFull(true); DataSet referers = reader.parseOsm(NullProgressMonitor.INSTANCE); assertEquals(6, referers.getWays().size()); // 6 ways referred by two relations for (Way w1 : referers.getWays()) { assertEquals(false, w1.isIncomplete()); } assertEquals(2, referers.getRelations().size()); // two relations referring to Set<Long> expectedNodeIds = new HashSet<Long>(); for (Way way : referers.getWays()) { Way orig = (Way) ds.getPrimitiveById(way); for (Node n : orig.getNodes()) { expectedNodeIds.add(n.getId()); } } assertEquals(expectedNodeIds.size(), referers.getNodes().size()); for (Node n : referers.getNodes()) { assertEquals(true, expectedNodeIds.contains(n.getId())); } Relation r = lookupRelation(referers, 0); assertNotNull(r); assertEquals(false, r.isIncomplete()); r = lookupRelation(referers, 1); assertEquals(false, r.isIncomplete()); }
protected Set<Long> getNodeIdsInWay(Way way) { HashSet<Long> ret = new HashSet<Long>(); if (way == null) return ret; for (Node n : way.getNodes()) { ret.add(n.getId()); } return ret; }
void addWayNodes(Way w) { add(tr("{0} Nodes: ", w.getNodesCount())); for (Node n : w.getNodes()) { s.append(INDENT).append(INDENT); addNameAndId(n); s.append(NL); } }
/** * Splits the nodes of {@code wayToSplit} into a list of node sequences which are separated at the * nodes in {@code splitPoints}. * * <p>This method displays warning messages if {@code wayToSplit} and/or {@code splitPoints} * aren't consistent. * * <p>Returns null, if building the split chunks fails. * * @param wayToSplit the way to split. Must not be null. * @param splitPoints the nodes where the way is split. Must not be null. * @return the list of chunks */ public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) { CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit"); CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints"); Set<Node> nodeSet = new HashSet<>(splitPoints); List<List<Node>> wayChunks = new LinkedList<>(); List<Node> currentWayChunk = new ArrayList<>(); wayChunks.add(currentWayChunk); Iterator<Node> it = wayToSplit.getNodes().iterator(); while (it.hasNext()) { Node currentNode = it.next(); boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext(); currentWayChunk.add(currentNode); if (nodeSet.contains(currentNode) && !atEndOfWay) { currentWayChunk = new ArrayList<>(); currentWayChunk.add(currentNode); wayChunks.add(currentWayChunk); } } // Handle circular ways specially. // If you split at a circular way at two nodes, you just want to split // it at these points, not also at the former endpoint. // So if the last node is the same first node, join the last and the // first way chunk. List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1); if (wayChunks.size() >= 2 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1) && !nodeSet.contains(wayChunks.get(0).get(0))) { if (wayChunks.size() == 2) { new Notification(tr("You must select two or more nodes to split a circular way.")) .setIcon(JOptionPane.WARNING_MESSAGE) .show(); return null; } lastWayChunk.remove(lastWayChunk.size() - 1); lastWayChunk.addAll(wayChunks.get(0)); wayChunks.remove(wayChunks.size() - 1); wayChunks.set(0, lastWayChunk); } if (wayChunks.size() < 2) { if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) { new Notification(tr("You must select two or more nodes to split a circular way.")) .setIcon(JOptionPane.WARNING_MESSAGE) .show(); } else { new Notification( tr( "The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)")) .setIcon(JOptionPane.WARNING_MESSAGE) .show(); } return null; } return wayChunks; }
@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; } } }
public void visit(Way w) { if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) { // upload new ways as well as modified and deleted ones hull.add(w); for (Node n : w.getNodes()) { // we upload modified nodes even if they aren't in the current // selection. n.visit(this); } } }
@Override public void cloneFrom(OsmPrimitive osm) { boolean locked = writeLock(); try { super.cloneFrom(osm); Way otherWay = (Way) osm; setNodes(otherWay.getNodes()); } finally { writeUnlock(locked); } }
/** * Collect all nodes with more than one referrer. * * @param ways Ways from witch nodes are selected * @return List of nodes with more than one referrer */ private static List<Node> collectNodesWithExternReferers(List<Way> ways) { List<Node> withReferrers = new ArrayList<>(); for (Way w : ways) { for (Node n : w.getNodes()) { if (n.getReferrers().size() > 1) { withReferrers.add(n); } } } return withReferrers; }
Segment(Node start, Way way, Node end) { this.start = start; this.way = way; this.end = end; final List<Node> ns = way.getNodes(); if (way.lastNode().equals(start)) { Collections.reverse(ns); } this.nodes = Collections.unmodifiableList(ns); }
/** * Assuming all ways can be joined into polygon, create an ordered list of node. * * @param ways List of ways to be joined * @return Nodes anticlockwise ordered */ private static List<Node> collectNodesAnticlockwise(List<Way> ways) { List<Node> nodes = new ArrayList<>(); Node firstNode = ways.get(0).firstNode(); Node lastNode = null; Way lastWay = null; while (firstNode != lastNode) { if (lastNode == null) lastNode = firstNode; for (Way way : ways) { if (way == lastWay) continue; if (way.firstNode() == lastNode) { List<Node> wayNodes = way.getNodes(); for (int i = 0; i < wayNodes.size() - 1; i++) { nodes.add(wayNodes.get(i)); } lastNode = way.lastNode(); lastWay = way; break; } if (way.lastNode() == lastNode) { List<Node> wayNodes = way.getNodes(); for (int i = wayNodes.size() - 1; i > 0; i--) { nodes.add(wayNodes.get(i)); } lastNode = way.firstNode(); lastWay = way; break; } } } // Check if nodes are in anticlockwise order int nc = nodes.size(); double area = 0; for (int i = 0; i < nc; i++) { EastNorth p1 = nodes.get(i).getEastNorth(); EastNorth p2 = nodes.get((i + 1) % nc).getEastNorth(); area += p1.east() * p2.north() - p2.east() * p1.north(); } if (area < 0) Collections.reverse(nodes); return nodes; }
@Override public void visit(Way w) { if (!w.isUsable()) return; List<Node> wNodes = w.getNodes(); List<LatLon> wLat = new ArrayList<LatLon>(wNodes.size()); for (int i = 0; i < wNodes.size(); i++) { wLat.add(wNodes.get(i).getCoor()); } Map<String, String> wkeys = w.getKeys(); removeUninterestingKeys(wkeys); WayPair wKey = new WayPair(wLat, wkeys); ways.put(wKey, w); WayPairNoTags wKeyN = new WayPairNoTags(wLat); waysNoTags.put(wKeyN, w); }
/** * Returns area of a closed way in square meters. (approximate(?), but should be OK for small * areas) * * <p>Relies on the current projection: Works correctly, when one unit in projected coordinates * corresponds to one meter. This is true for most projections, but not for WGS84 and Mercator * (EPSG:3857). * * @param way Way to measure, should be closed (first node is the same as last node) * @return area of the closed way. */ public static double closedWayArea(Way way) { // http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ double area = 0; Node lastN = null; for (Node n : way.getNodes()) { if (lastN != null) { n.getEastNorth().getX(); area += (calcX(n) * calcY(lastN)) - (calcY(n) * calcX(lastN)); } lastN = n; } return Math.abs(area / 2); }
/** * My dataset includes a deleted node. Their dataset includes a way with three nodes, the first * one being my node. * * <p>=> the merged way should include all three nodes. Deleted node should have deleted=false and * special conflict with isDeleted should exist */ @Test public void wayComplex_mergingADeletedNode() { // -- my dataset Node mn1 = new Node(new LatLon(0, 0)); mn1.setOsmId(1, 1); mn1.setDeleted(true); my.addPrimitive(mn1); Node tn1 = new Node(new LatLon(0, 0)); tn1.setOsmId(1, 1); their.addPrimitive(tn1); Node tn2 = new Node(new LatLon(1, 1)); tn2.setOsmId(2, 1); their.addPrimitive(tn2); Node tn3 = new Node(new LatLon(2, 2)); tn3.setOsmId(3, 1); their.addPrimitive(tn3); // -- their data set Way theirWay = new Way(); theirWay.setOsmId(4, 1); theirWay.addNode(tn1); theirWay.addNode(tn2); theirWay.addNode(tn3); theirWay.setUser(User.createOsmUser(1111, "their")); theirWay.setTimestamp(new Date()); their.addPrimitive(theirWay); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); assertEquals(1, visitor.getConflicts().size()); assertTrue(visitor.getConflicts().get(0).isMyDeleted()); Way myWay = (Way) my.getPrimitiveById(4, OsmPrimitiveType.WAY); assertEquals(3, myWay.getNodesCount()); Node n = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertTrue(myWay.getNodes().contains(n)); assertFalse(myWay.isModified()); }
public void visit(Way w) { if (w.isIncomplete() || w.getNodesCount() < 2) return; c.setStrokeStyle(w.isSelected() ? "#ff0000" : "#000000"); c.beginPath(); Iterator<Node> it = w.getNodes().iterator(); if (it.hasNext()) { Point lastP = nc.getPoint(it.next()); c.moveTo(lastP.x, lastP.y); for (int orderNumber = 1; it.hasNext(); orderNumber++) { Point p = nc.getPoint(it.next()); c.lineTo(p.x, p.y); lastP = p; } c.stroke(); } }
/** * Determines if ways can be joined into a polygon. * * @param ways The ways collection to check * @return true if all ways can be joined into a polygon */ protected static boolean checkWaysArePolygon(Collection<Way> ways) { // For each way, nodes strictly between first and last should't be reference by an other way for (Way way : ways) { for (Node node : way.getNodes()) { if (way.isFirstLastNode(node)) continue; for (Way wayOther : ways) { if (way == wayOther) continue; if (node.getReferrers().contains(wayOther)) return false; } } } // Test if ways can be joined Way currentWay = null; Node startNode = null, endNode = null; int used = 0; while (true) { Way nextWay = null; for (Way w : ways) { if (w.isClosed()) return ways.size() == 1; if (w == currentWay) continue; if (currentWay == null) { nextWay = w; startNode = w.firstNode(); endNode = w.lastNode(); break; } if (w.firstNode() == endNode) { nextWay = w; endNode = w.lastNode(); break; } if (w.lastNode() == endNode) { nextWay = w; endNode = w.firstNode(); break; } } if (nextWay == null) return false; used += 1; currentWay = nextWay; if (endNode == startNode) return used == ways.size(); } }
/** * Simple chunking version. Does not care about circular ways and result being proper, we will * glue it all back together later on. * * @param way the way to chunk * @param splitNodes the places where to cut. * @return list of node paths to produce. */ private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes) { List<List<Node>> result = new ArrayList<List<Node>>(); List<Node> curList = new ArrayList<Node>(); for (Node node : way.getNodes()) { curList.add(node); if (curList.size() > 1 && splitNodes.contains(node)) { result.add(curList); curList = new ArrayList<Node>(); curList.add(node); } } if (curList.size() > 1) { result.add(curList); } return result; }
/** * Estimate the direction of the segments, given the first segment points in the direction * <code>pInitialDirection</code>. Then sum up all horizontal / vertical segments to have a good * guess for the heading of the entire way. * * @param pInitialDirection initial direction * @throws InvalidUserInputException if selected ways have an angle different from 90 or 180 * degrees */ public void calcDirections(Direction pInitialDirection) throws InvalidUserInputException { final EastNorth[] en = new EastNorth[nNode]; // alias: way.getNodes().get(i).getEastNorth() ---> en[i] for (int i = 0; i < nNode; i++) { en[i] = new EastNorth( way.getNodes().get(i).getEastNorth().east(), way.getNodes().get(i).getEastNorth().north()); } segDirections = new Direction[nSeg]; Direction direction = pInitialDirection; segDirections[0] = direction; for (int i = 0; i < nSeg - 1; i++) { double h1 = EN.polar(en[i], en[i + 1]); double h2 = EN.polar(en[i + 1], en[i + 2]); try { direction = direction.changeBy(angleToDirectionChange(h2 - h1, TOLERANCE1)); } catch (RejectedAngleException ex) { throw new InvalidUserInputException( tr("Please select ways with angles of approximately 90 or 180 degrees."), ex); } segDirections[i + 1] = direction; } // sum up segments EastNorth h = new EastNorth(0., 0.); EastNorth v = new EastNorth(0., 0.); for (int i = 0; i < nSeg; ++i) { EastNorth segment = EN.diff(en[i + 1], en[i]); if (segDirections[i] == Direction.RIGHT) { h = EN.sum(h, segment); } else if (segDirections[i] == Direction.UP) { v = EN.sum(v, segment); } else if (segDirections[i] == Direction.LEFT) { h = EN.diff(h, segment); } else if (segDirections[i] == Direction.DOWN) { v = EN.diff(v, segment); } else throw new IllegalStateException(); } // rotate the vertical vector by 90 degrees (clockwise) and add it to the horizontal vector segSum = EN.sum(h, new EastNorth(v.north(), -v.east())); this.heading = EN.polar(new EastNorth(0., 0.), segSum); }
/** * Replies the collection of nodes referred to by primitives in <code>primitivesToDelete</code> * which can be deleted too. A node can be deleted if * * <ul> * <li>it is untagged (see {@link Node#isTagged()} * <li>it is not referred to by other non-deleted primitives outside of <code>primitivesToDelete * </code> * </ul> * * @param primitivesToDelete the primitives to delete * @return the collection of nodes referred to by primitives in <code>primitivesToDelete</code> * which can be deleted too */ protected static Collection<Node> computeNodesToDelete( Collection<OsmPrimitive> primitivesToDelete) { Collection<Node> nodesToDelete = new HashSet<>(); for (Way way : OsmPrimitive.getFilteredList(primitivesToDelete, Way.class)) { for (Node n : way.getNodes()) { if (n.isTagged()) { continue; } Collection<OsmPrimitive> referringPrimitives = n.getReferrers(); referringPrimitives.removeAll(primitivesToDelete); int count = 0; for (OsmPrimitive p : referringPrimitives) { if (!p.isDeleted()) { count++; } } if (count == 0) { nodesToDelete.add(n); } } } return nodesToDelete; }
/** * 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. --> 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 --> 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 --> 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(); }
WayData(Way pWay) { way = pWay; nNode = way.getNodes().size(); nSeg = nNode - 1; }
@Override public Iterable<Node> getNodes(Way way) { return new FilteredOsmPrimitiveIterable<>(way.getNodes()); }
/** * Gets called whenever the shortcut is pressed or the menu entry is selected Checks whether the * selected objects are suitable to join and joins them if so */ @Override public void actionPerformed(ActionEvent e) { LinkedList<Way> ways = new LinkedList<Way>(Main.main.getCurrentDataSet().getSelectedWays()); if (ways.isEmpty()) { new Notification(tr("Please select at least one closed way that should be joined.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .show(); return; } List<Node> allNodes = new ArrayList<Node>(); for (Way way : ways) { if (!way.isClosed()) { new Notification( tr("One of the selected ways is not closed and therefore cannot be joined.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .show(); return; } allNodes.addAll(way.getNodes()); } // TODO: Only display this warning when nodes outside dataSourceArea are deleted boolean ok = Command.checkAndConfirmOutlyingOperation( "joinarea", tr("Join area confirmation"), trn( "The selected way has nodes outside of the downloaded data region.", "The selected ways have nodes outside of the downloaded data region.", ways.size()) + "<br/>" + tr("This can lead to nodes being deleted accidentally.") + "<br/>" + tr("Are you really sure to continue?") + tr("Please abort if you are not sure"), tr("The selected area is incomplete. Continue?"), allNodes, null); if (!ok) return; // analyze multipolygon relations and collect all areas List<Multipolygon> areas = collectMultipolygons(ways); if (areas == null) // too complex multipolygon relations found return; if (!testJoin(areas)) { new Notification(tr("No intersection found. Nothing was changed.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .show(); return; } if (!resolveTagConflicts(areas)) return; // user canceled, do nothing. try { JoinAreasResult result = joinAreas(areas); if (result.hasChanges) { List<Way> allWays = new ArrayList<Way>(); for (Multipolygon pol : result.polygons) { allWays.add(pol.outerWay); allWays.addAll(pol.innerWays); } DataSet ds = Main.main.getCurrentDataSet(); ds.setSelected(allWays); Main.map.mapView.repaint(); } else { new Notification(tr("No intersection found. Nothing was changed.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .show(); } } catch (UserCancelException exception) { // revert changes // FIXME: this is dirty hack makeCommitsOneAction(tr("Reverting changes")); Main.main.undoRedo.undo(); Main.main.undoRedo.redoCommands.clear(); } }
/** * Create OSM graph for routing * * @return */ public void createGraph() { logger.debug("Creating Graph..."); graph = new DirectedWeightedMultigraph<>(OsmEdge.class); rgDelegator = new RoutingGraphDelegator(graph); rgDelegator.setRouteType(this.routeType); // iterate all ways and segments for all nodes: for (Way way : data.getWays()) { // skip way if not suitable for routing. if (way == null || way.isDeleted() || !this.isvalidWay(way) || way.getNodes().size() < 1) continue; // INIT Node from = null; Node to = null; List<Node> nodes = way.getNodes(); int nodes_count = nodes.size(); /* * Assume node is A B C D E. The procedure should be * * case 1 - bidirectional ways: * 1) Add vertex A B C D E * 2) Link A<->B, B<->C, C<->D, D<->E as Edges * * case 2 - oneway reverse: * 1) Add vertex A B C D E * 2) Link B->A,C->B,D->C,E->D as Edges. result: A<-B<-C<-D<-E * * case 3 - oneway normal: * 1) Add vertex A B C D E * 2) Link A->B, B->C, C->D, D->E as Edges. result: A->B->C->D->E * * */ String oneway_val = way.get("oneway"); /* get (oneway=?) tag for this way. */ String junction_val = way.get("junction"); /* get (junction=?) tag for this way. */ from = nodes.get(0); /* 1st node A */ graph.addVertex(from); /* add vertex A */ for (int i = 1; i < nodes_count; i++) { /* loop from B until E */ to = nodes.get(i); /* 2nd node B */ if (to != null && !to.isDeleted()) { graph.addVertex(to); /* add vertex B */ // this is where we link the vertices if (!routingProfile.isOnewayUsed()) { // "Ignore oneways" is selected addEdgeBidirectional(way, from, to); } else if (oneway_val == null && junction_val == "roundabout") { // Case (roundabout): oneway=implicit yes addEdgeNormalOneway(way, from, to); } else if (oneway_val == null || oneway_val == "false" || oneway_val == "no" || oneway_val == "0") { // Case (bi-way): oneway=false OR oneway=unset OR oneway=0 OR oneway=no addEdgeBidirectional(way, from, to); } else if (oneway_val == "-1") { // Case (oneway reverse): oneway=-1 addEdgeReverseOneway(way, from, to); } else if (oneway_val == "1" || oneway_val == "yes" || oneway_val == "true") { // Case (oneway normal): oneway=yes OR 1 OR true addEdgeNormalOneway(way, from, to); } from = to; /* we did A<->B, next loop we will do B<->C, so from=B,to=C for next loop. */ } } // end of looping thru nodes } // end of looping thru ways logger.debug("End Create Graph"); logger.debug("Vertex: " + graph.vertexSet().size()); logger.debug("Edges: " + graph.edgeSet().size()); }
@Override public boolean matches(Environment e) { if (!right.matches(e)) return false; if (ChildOrParentSelectorType.ELEMENT_OF.equals(type)) { if (e.osm instanceof Node || e.osm.getDataSet() == null) { // nodes cannot contain elements return false; } ContainsFinder containsFinder; try { // if right selector also matches relations and if matched primitive is a way which is // part of a multipolygon, // use the multipolygon for further analysis if (!(e.osm instanceof Way) || (right instanceof OptimizedGeneralSelector && !((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.RELATION))) { throw new NoSuchElementException(); } final Collection<Relation> multipolygons = Utils.filteredCollection( Utils.filter(e.osm.getReferrers(), Predicates.hasTag("type", "multipolygon")), Relation.class); final Relation multipolygon = multipolygons.iterator().next(); if (multipolygon == null) throw new NoSuchElementException(); containsFinder = new ContainsFinder(new Environment(multipolygon)) { @Override public boolean isPrimitiveUsable(OsmPrimitive p) { return super.isPrimitiveUsable(p) && !multipolygon.getMemberPrimitives().contains(p); } }; } catch (NoSuchElementException ignore) { containsFinder = new ContainsFinder(e); } e.parent = e.osm; if (left instanceof OptimizedGeneralSelector) { if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) { containsFinder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox())); } if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) { containsFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); } } else { // use slow test containsFinder.visit(e.osm.getDataSet().allPrimitives()); } return e.child != null; } else if (ChildOrParentSelectorType.CROSSING.equals(type) && e.osm instanceof Way) { e.parent = e.osm; final CrossingFinder crossingFinder = new CrossingFinder(e); if (right instanceof OptimizedGeneralSelector && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) { crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); } return e.child != null; } else if (ChildOrParentSelectorType.SIBLING.equals(type)) { if (e.osm instanceof Node) { for (Way w : Utils.filteredCollection(e.osm.getReferrers(true), Way.class)) { final int i = w.getNodes().indexOf(e.osm); if (i - 1 >= 0) { final Node n = w.getNode(i - 1); final Environment e2 = e.withPrimitive(n).withParent(w).withChild(e.osm); if (left.matches(e2) && link.matches(e2.withLinkContext())) { e.child = n; e.index = i; e.count = w.getNodesCount(); e.parent = w; return true; } } } } } else if (ChildOrParentSelectorType.CHILD.equals(type) && link.conds != null && !link.conds.isEmpty() && link.conds.get(0) instanceof Condition.OpenEndPseudoClassCondition) { if (e.osm instanceof Node) { e.osm.visitReferrers(new MultipolygonOpenEndFinder(e)); return e.parent != null; } } else if (ChildOrParentSelectorType.CHILD.equals(type)) { MatchingReferrerFinder collector = new MatchingReferrerFinder(e); e.osm.visitReferrers(collector); if (e.parent != null) return true; } else if (ChildOrParentSelectorType.PARENT.equals(type)) { if (e.osm instanceof Way) { List<Node> wayNodes = ((Way) e.osm).getNodes(); for (int i = 0; i < wayNodes.size(); i++) { Node n = wayNodes.get(i); if (left.matches(e.withPrimitive(n))) { if (link.matches(e.withChildAndIndexAndLinkContext(n, i, wayNodes.size()))) { e.child = n; e.index = i; e.count = wayNodes.size(); return true; } } } } else if (e.osm instanceof Relation) { List<RelationMember> members = ((Relation) e.osm).getMembers(); for (int i = 0; i < members.size(); i++) { OsmPrimitive member = members.get(i).getMember(); if (left.matches(e.withPrimitive(member))) { if (link.matches(e.withChildAndIndexAndLinkContext(member, i, members.size()))) { e.child = member; e.index = i; e.count = members.size(); return true; } } } } } return false; }
public void visit(Way w) { visit(w.getNodes()); }
@Override public void actionPerformed(ActionEvent e) { if (!isEnabled()) return; Collection<OsmPrimitive> sel = getCurrentDataSet().getAllSelected(); layer = Main.map.mapView.getEditLayer(); toPurge = new HashSet<OsmPrimitive>(sel); toPurgeAdditionally = new ArrayList<OsmPrimitive>(); toPurgeChecked = new HashSet<OsmPrimitive>(); // Add referrer, unless the object to purge is not new // and the parent is a relation HashSet<OsmPrimitive> toPurgeRecursive = new HashSet<OsmPrimitive>(); while (!toPurge.isEmpty()) { for (OsmPrimitive osm : toPurge) { for (OsmPrimitive parent : osm.getReferrers()) { if (toPurge.contains(parent) || toPurgeChecked.contains(parent) || toPurgeRecursive.contains(parent)) { continue; } if (parent instanceof Way || (parent instanceof Relation && osm.isNew())) { toPurgeAdditionally.add(parent); toPurgeRecursive.add(parent); } } toPurgeChecked.add(osm); } toPurge = toPurgeRecursive; toPurgeRecursive = new HashSet<OsmPrimitive>(); } makeIncomplete = new HashSet<OsmPrimitive>(); // Find the objects that will be incomplete after purging. // At this point, all parents of new to-be-purged primitives are // also to-be-purged and // all parents of not-new to-be-purged primitives are either // to-be-purged or of type relation. TOP: for (OsmPrimitive child : toPurgeChecked) { if (child.isNew()) { continue; } for (OsmPrimitive parent : child.getReferrers()) { if (parent instanceof Relation && !toPurgeChecked.contains(parent)) { makeIncomplete.add(child); continue TOP; } } } // Add untagged way nodes. Do not add nodes that have other // referrers not yet to-be-purged. if (Main.pref.getBoolean("purge.add_untagged_waynodes", true)) { Set<OsmPrimitive> wayNodes = new HashSet<OsmPrimitive>(); for (OsmPrimitive osm : toPurgeChecked) { if (osm instanceof Way) { Way w = (Way) osm; NODE: for (Node n : w.getNodes()) { if (n.isTagged() || toPurgeChecked.contains(n)) { continue; } for (OsmPrimitive ref : n.getReferrers()) { if (ref != w && !toPurgeChecked.contains(ref)) { continue NODE; } } wayNodes.add(n); } } } toPurgeChecked.addAll(wayNodes); toPurgeAdditionally.addAll(wayNodes); } if (Main.pref.getBoolean("purge.add_relations_with_only_incomplete_members", true)) { Set<Relation> relSet = new HashSet<Relation>(); for (OsmPrimitive osm : toPurgeChecked) { for (OsmPrimitive parent : osm.getReferrers()) { if (parent instanceof Relation && !(toPurgeChecked.contains(parent)) && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relSet)) { relSet.add((Relation) parent); } } } /** Add higher level relations (list gets extended while looping over it) */ List<Relation> relLst = new ArrayList<Relation>(relSet); for (int i = 0; i < relLst.size(); ++i) { for (OsmPrimitive parent : relLst.get(i).getReferrers()) { if (!(toPurgeChecked.contains(parent)) && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relLst)) { relLst.add((Relation) parent); } } } relSet = new HashSet<Relation>(relLst); toPurgeChecked.addAll(relSet); toPurgeAdditionally.addAll(relSet); } boolean modified = false; for (OsmPrimitive osm : toPurgeChecked) { if (osm.isModified()) { modified = true; break; } } ExtendedDialog confirmDlg = new ExtendedDialog( Main.parent, tr("Confirm Purging"), new String[] {tr("Purge"), tr("Cancel")}); confirmDlg.setContent(buildPanel(modified), false); confirmDlg.setButtonIcons(new String[] {"ok", "cancel"}); int answer = confirmDlg.showDialog().getValue(); if (answer != 1) return; Main.pref.put("purge.clear_undo_redo", cbClearUndoRedo.isSelected()); Main.main.undoRedo.add( new PurgeCommand(Main.map.mapView.getEditLayer(), toPurgeChecked, makeIncomplete)); if (cbClearUndoRedo.isSelected()) { Main.main.undoRedo.clean(); getCurrentDataSet().clearSelectionHistory(); } }