/**
   * their way has a higher version and different nodes. My way is modified.
   *
   * <p>=> merge onto my way not possible, create a conflict
   */
  @Test
  public void waySimple_DifferentNodesAndMyIsModified() {

    // -- the target dataset

    Node n1 = new Node(new LatLon(0, 0));
    n1.setOsmId(1, 1);
    my.addPrimitive(n1);

    Node n2 = new Node(new LatLon(1, 1));
    n2.setOsmId(2, 1);
    my.addPrimitive(n2);

    Way myWay = new Way();
    myWay.setOsmId(3, 1);

    myWay.addNode(n1);
    myWay.addNode(n2);
    myWay.setModified(true);
    myWay.put("key1", "value1");
    my.addPrimitive(myWay);

    // -- the source dataset

    Node n3 = new Node(new LatLon(0, 0));
    n3.setOsmId(1, 1);
    their.addPrimitive(n3);

    Node n5 = new Node(new LatLon(1, 1));
    n5.setOsmId(4, 1);
    their.addPrimitive(n5);

    Node n4 = new Node(new LatLon(2, 2));
    n4.setOsmId(2, 1);
    n4.put("key1", "value1");
    their.addPrimitive(n4);

    Way theirWay = new Way();
    theirWay.setOsmId(3, 2);

    theirWay.addNode(n3);
    theirWay.addNode(n5); // insert a node
    theirWay.addNode(n4); // this one is updated
    their.addPrimitive(theirWay);

    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY);
    assertEquals(1, visitor.getConflicts().size());
    assertEquals(3, merged.getId());
    assertEquals(1, merged.getVersion());
    assertEquals(2, merged.getNodesCount());
    assertEquals(1, merged.getNode(0).getId());
    assertEquals(2, merged.getNode(1).getId());
    assertEquals("value1", merged.get("key1"));
  }
  protected void populate() {
    Way w1 = new Way(1);
    w1.addNode(new Node(10));
    w1.addNode(new Node(11));

    Way w2 = new Way(1);
    w2.addNode(new Node(10));
    w2.addNode(new Node(11));

    dialog.getConflictResolver().populate(new Conflict<OsmPrimitive>(w1, w2));
  }
  /**
   * my and their way have no ids, nodes they refer to have an id. but my and their way are
   * semantically equal. so technical attributes of their way can be merged on my way. No conflict.
   */
  @Test
  public void waySimple_twoWaysWithNoId_NodesWithId() {

    // -- my data set

    Node n1 = new Node(new LatLon(0, 0));
    n1.setOsmId(1, 1);
    my.addPrimitive(n1);

    Node n2 = new Node(new LatLon(1, 1));
    n2.setOsmId(2, 1);
    my.addPrimitive(n2);

    Way myWay = new Way();
    myWay.addNode(n1);
    myWay.addNode(n2);
    my.addPrimitive(myWay);

    // -- their data set

    Node n3 = new Node(new LatLon(0, 0));
    n3.setOsmId(1, 1);
    their.addPrimitive(n3);

    Node n4 = new Node(new LatLon(1, 1));
    n4.setOsmId(2, 1);
    their.addPrimitive(n4);

    Way theirWay = new Way();
    theirWay.addNode(n3);
    theirWay.addNode(n4);
    User user = User.createOsmUser(1111, "their");
    theirWay.setUser(user);
    theirWay.setTimestamp(new Date());
    their.addPrimitive(theirWay);

    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    // -- tests
    Way merged = (Way) my.getWays().toArray()[0];
    assertEquals(0, visitor.getConflicts().size());
    assertEquals("their", merged.getUser().getName());
    assertEquals(1111, merged.getUser().getId());
    assertEquals(theirWay.getRawTimestamp(), merged.getRawTimestamp());
    assertSame(merged, myWay);
    assertSame(merged.getNode(0), n1);
    assertSame(merged.getNode(1), n2);

    assertFalse(merged.isModified());
  }
  /**
   * 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());
  }
 private SearchContext(String state) throws ParseError {
   m = SearchCompiler.compile(state);
   n = SearchCompiler.compile('-' + state);
   ds.addPrimitive(n1);
   ds.addPrimitive(n2);
   w1.addNode(n1);
   w1.addNode(n2);
   w2.addNode(n1);
   w2.addNode(n2);
   ds.addPrimitive(w1);
   ds.addPrimitive(w2);
   r1.addMember(new RelationMember("", w1));
   r1.addMember(new RelationMember("", w2));
   r2.addMember(new RelationMember("", w1));
   r2.addMember(new RelationMember("", w2));
   ds.addPrimitive(r1);
   ds.addPrimitive(r2);
 }
 protected static void populateTestDataSetWithWays(DataSet ds) {
   for (int i = 0; i < 20; i++) {
     Way w = new Way();
     for (int j = 0; j < 10; j++) {
       w.addNode(lookupNode(ds, i + j));
     }
     w.put("name", "way-" + i);
     ds.addPrimitive(w);
   }
 }
 private void appendNode(double x, double y) throws IOException {
   if (currentway == null) {
     throw new IOException("Shape is started incorectly");
   }
   Node nd = new Node(projection.eastNorth2latlon(center.add(x * scale, -y * scale)));
   if (nd.getCoor().isOutSideWorld()) {
     throw new IOException("Shape goes outside the world");
   }
   currentway.addNode(nd);
   nodes.add(nd);
   lastX = x;
   lastY = y;
 }
  /**
   * merge to complete nodes onto an incomplete way with the same two nodes, but incomplete. => both
   * the nodes and the way should be complete in the target dataset after merging
   */
  @Test
  public void twoCompleteNodesOntoAnIncompleteWay() {

    // -- source dataset

    // an complete node
    Node n1 = new Node(1, 1);
    n1.setCoor(new LatLon(1, 1));
    their.addPrimitive(n1);

    // another complete node
    Node n2 = new Node(2, 1);
    n2.setCoor(new LatLon(2, 2));
    their.addPrimitive(n2);

    // --- target dataset

    Node n4 = new Node(1);
    my.addPrimitive(n4);

    Node n5 = new Node(2);
    my.addPrimitive(n5);

    Way w6 = new Way(3, 1);
    w6.addNode(n4);
    w6.addNode(n5);
    my.addPrimitive(w6);

    // -- merge it
    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    // -- test it
    assertEquals(0, visitor.getConflicts().size());

    Node n = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE);
    assertNotNull(n);
    assertFalse(n.isIncomplete());

    n = (Node) my.getPrimitiveById(2, OsmPrimitiveType.NODE);
    assertNotNull(n);
    assertFalse(n.isIncomplete());

    Way w = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY);
    assertNotNull(w);
    assertFalse(w.hasIncompleteNodes());
    assertTrue(w.isUsable());
    assertEquals(2, w.getNodesCount());
    assertEquals(1, w.getNode(0).getId());
    assertEquals(2, w.getNode(1).getId());
  }
 /**
  * Search by nth.
  *
  * @throws ParseError if an error has been encountered while compiling
  */
 @Test
 public void testNth() throws ParseError {
   final DataSet dataSet = new DataSet();
   final Way way = new Way();
   final Node node0 = new Node(new LatLon(1, 1));
   final Node node1 = new Node(new LatLon(2, 2));
   final Node node2 = new Node(new LatLon(3, 3));
   dataSet.addPrimitive(way);
   dataSet.addPrimitive(node0);
   dataSet.addPrimitive(node1);
   dataSet.addPrimitive(node2);
   way.addNode(node0);
   way.addNode(node1);
   way.addNode(node2);
   assertFalse(SearchCompiler.compile("nth:2").match(node1));
   assertTrue(SearchCompiler.compile("nth:1").match(node1));
   assertFalse(SearchCompiler.compile("nth:0").match(node1));
   assertTrue(SearchCompiler.compile("nth:0").match(node0));
   assertTrue(SearchCompiler.compile("nth:2").match(node2));
   assertTrue(SearchCompiler.compile("nth:-1").match(node2));
   assertTrue(SearchCompiler.compile("nth:-2").match(node1));
   assertTrue(SearchCompiler.compile("nth:-3").match(node0));
 }
  /**
   * their way is not visible anymore.
   *
   * <p>=> conflict
   */
  @Test
  public void waySimple_TheirVersionNotVisibleMyIsModified() {

    Node mn1 = new Node(new LatLon(0, 0));
    mn1.setOsmId(1, 1);
    my.addPrimitive(mn1);

    Node mn2 = new Node(new LatLon(1, 1));
    mn2.setOsmId(2, 1);
    my.addPrimitive(mn2);

    Way myWay = new Way();
    myWay.setOsmId(3, 1);
    myWay.addNode(mn1);
    myWay.addNode(mn2);
    myWay.setModified(true);
    my.addPrimitive(myWay);

    Way theirWay = new Way();
    theirWay.setOsmId(3, 2);
    theirWay.setVisible(false);
    /* Invisible objects fetched from the server should be marked as "deleted".
     * Otherwise it's an error.
     */
    theirWay.setDeleted(true);
    their.addPrimitive(theirWay);

    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY);
    assertEquals(1, visitor.getConflicts().size());
    assertTrue(visitor.getConflicts().hasConflictForMy(myWay));
    assertTrue(visitor.getConflicts().hasConflictForTheir(theirWay));
    assertEquals(myWay, merged);
  }
Beispiel #11
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();
  }
  /**
   * builds a large data set to be used later for testing MULTI FETCH on the server
   *
   * @return a large data set
   */
  protected static DataSet buildTestDataSet() {
    DataSet ds = new DataSet();
    ds.setVersion("0.6");

    int numNodes = 1000;
    int numWays = 1000;
    int numRelations = 1000;

    ArrayList<Node> nodes = new ArrayList<>();
    ArrayList<Way> ways = new ArrayList<>();

    // create a set of nodes
    //
    for (int i = 0; i < numNodes; i++) {
      Node n = new Node();
      n.setCoor(new LatLon(-36.6, 47.6));
      n.put("name", "node-" + i);
      ds.addPrimitive(n);
      nodes.add(n);
    }

    // create a set of ways, each with a random number of nodes
    //
    for (int i = 0; i < numWays; i++) {
      Way w = new Way();
      int numNodesInWay = 2 + (int) Math.round(Math.random() * 5);
      int start = (int) Math.round(Math.random() * numNodes);
      for (int j = 0; j < numNodesInWay; j++) {
        int idx = (start + j) % numNodes;
        Node n = nodes.get(idx);
        w.addNode(n);
      }
      w.put("name", "way-" + i);
      ds.addPrimitive(w);
      ways.add(w);
    }

    // create a set of relations each with a random number of nodes, and ways
    //
    for (int i = 0; i < numRelations; i++) {
      Relation r = new Relation();
      r.put("name", "relation-" + i);
      int numNodesInRelation = (int) Math.round(Math.random() * 10);
      int start = (int) Math.round(Math.random() * numNodes);
      for (int j = 0; j < numNodesInRelation; j++) {
        int idx = (start + j) % 500;
        Node n = nodes.get(idx);
        r.addMember(new RelationMember("role-" + j, n));
      }
      int numWaysInRelation = (int) Math.round(Math.random() * 10);
      start = (int) Math.round(Math.random() * numWays);
      for (int j = 0; j < numWaysInRelation; j++) {
        int idx = (start + j) % 500;
        Way w = ways.get(idx);
        r.addMember(new RelationMember("role-" + j, w));
      }
      ds.addPrimitive(r);
    }

    return ds;
  }
  /**
   * their way has a higher version and different tags. And it has more nodes. Two of the existing
   * nodes are modified.
   *
   * <p>=> merge it onto my way, no conflict
   */
  @Test
  public void waySimple_AdditionalNodesAndChangedNodes() {

    // -- my data set

    Node n1 = new Node(new LatLon(0, 0));
    n1.setOsmId(1, 1);
    my.addPrimitive(n1);

    Node n2 = new Node(new LatLon(1, 1));
    n2.setOsmId(2, 1);
    my.addPrimitive(n2);

    Way myWay = new Way();
    myWay.setOsmId(3, 1);
    myWay.addNode(n1);
    myWay.addNode(n2);
    my.addPrimitive(myWay);

    // --- their data set

    Node n3 = new Node(new LatLon(0, 0));
    n3.setOsmId(1, 1);
    their.addPrimitive(n3);

    Node n5 = new Node(new LatLon(1, 1));
    n5.setOsmId(4, 1);

    their.addPrimitive(n5);

    Node n4 = new Node(new LatLon(2, 2));
    n4.setOsmId(2, 2);
    n4.put("key1", "value1");
    their.addPrimitive(n4);

    Way theirWay = new Way();
    theirWay.setOsmId(3, 2);
    theirWay.addNode(n3);
    theirWay.addNode(n5); // insert a node
    theirWay.addNode(n4); // this one is updated
    their.addPrimitive(theirWay);

    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    // -- tests
    Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY);
    assertEquals(0, visitor.getConflicts().size());
    assertEquals(3, merged.getId());
    assertEquals(2, merged.getVersion());
    assertEquals(3, merged.getNodesCount());
    assertEquals(1, merged.getNode(0).getId());
    assertEquals(4, merged.getNode(1).getId());
    assertEquals(2, merged.getNode(2).getId());
    assertEquals("value1", merged.getNode(2).get("key1"));

    assertSame(merged.getNode(0), n1);
    assertNotSame(merged.getNode(1), n5); // must be clone of the original node in their
    assertSame(merged.getNode(2), n2);

    assertFalse(
        merged
            .isModified()); // the target wasn't modified before merging, it mustn't be after
                            // merging
  }
  /**
   * their way has a higher version and different tags. the nodes are the same. My way is not
   * modified. Merge is possible. No conflict.
   *
   * <p>=> merge it onto my way.
   */
  @Test
  public void waySimple_IdenicalNodesDifferentTags() {

    // -- the target dataset

    Node n1 = new Node();
    n1.setCoor(new LatLon(0, 0));
    n1.setOsmId(1, 1);
    my.addPrimitive(n1);

    Node n2 = new Node();
    n2.setCoor(new LatLon(0, 0));
    n2.setOsmId(2, 1);

    my.addPrimitive(n2);

    Way myWay = new Way();
    myWay.setOsmId(3, 1);
    myWay.put("key1", "value1");
    myWay.addNode(n1);
    myWay.addNode(n2);
    my.addPrimitive(myWay);

    // -- the source data set

    Node n3 = new Node(new LatLon(0, 0));
    n3.setOsmId(1, 1);
    their.addPrimitive(n3);

    Node n4 = new Node(new LatLon(1, 1));
    n4.setOsmId(2, 1);
    their.addPrimitive(n4);

    Way theirWay = new Way();
    theirWay.setOsmId(3, 2);
    theirWay.put("key1", "value1");
    theirWay.put("key2", "value2");
    theirWay.addNode(n3);
    theirWay.addNode(n4);
    their.addPrimitive(theirWay);

    DataSetMerger visitor = new DataSetMerger(my, their);
    visitor.merge();

    // -- tests
    Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY);
    assertEquals(0, visitor.getConflicts().size());
    assertEquals("value1", merged.get("key1"));
    assertEquals("value2", merged.get("key2"));
    assertEquals(3, merged.getId());
    assertEquals(2, merged.getVersion());
    assertEquals(2, merged.getNodesCount());
    assertEquals(1, merged.getNode(0).getId());
    assertEquals(2, merged.getNode(1).getId());
    assertSame(merged, myWay);
    assertSame(merged.getDataSet(), my);

    Node mergedNode = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE);
    assertSame(mergedNode, n1);
    mergedNode = (Node) my.getPrimitiveById(2, OsmPrimitiveType.NODE);
    assertSame(mergedNode, n2);

    assertFalse(merged.isModified());
  }
 private void processElement(SVGElement el, AffineTransform transform) throws IOException {
   if (el instanceof Group) {
     AffineTransform oldTransform = transform;
     AffineTransform xform = ((Group) el).getXForm();
     if (transform == null) {
       transform = xform;
     } else if (xform != null) {
       transform = new AffineTransform(transform);
       transform.concatenate(xform);
     }
     for (Object child : ((Group) el).getChildren(null)) {
       processElement((SVGElement) child, transform);
     }
     transform = oldTransform;
   } else if (el instanceof ShapeElement) {
     Shape shape = ((ShapeElement) el).getShape();
     if (transform != null) {
       shape = transform.createTransformedShape(shape);
     }
     PathIterator it = shape.getPathIterator(null);
     while (!it.isDone()) {
       double[] coords = new double[6];
       switch (it.currentSegment(coords)) {
         case PathIterator.SEG_MOVETO:
           currentway = new Way();
           ways.add(currentway);
           appendNode(coords[0], coords[1]);
           break;
         case PathIterator.SEG_LINETO:
           appendNode(coords[0], coords[1]);
           break;
         case PathIterator.SEG_CLOSE:
           if (currentway.firstNode().getCoor().equalsEpsilon(nodes.getLast().getCoor())) {
             currentway.removeNode(nodes.removeLast());
           }
           currentway.addNode(currentway.firstNode());
           break;
         case PathIterator.SEG_QUADTO:
           double lastx = lastX;
           double lasty = lastY;
           for (int i = 1; i < Settings.getCurveSteps(); i++) {
             appendNode(
                 interpolate_quad(
                     lastx,
                     lasty,
                     coords[0],
                     coords[1],
                     coords[2],
                     coords[3],
                     i / Settings.getCurveSteps()));
           }
           appendNode(coords[2], coords[3]);
           break;
         case PathIterator.SEG_CUBICTO:
           lastx = lastX;
           lasty = lastY;
           for (int i = 1; i < Settings.getCurveSteps(); i++) {
             appendNode(
                 interpolate_cubic(
                     lastx,
                     lasty,
                     coords[0],
                     coords[1],
                     coords[2],
                     coords[3],
                     coords[4],
                     coords[5],
                     i / Settings.getCurveSteps()));
           }
           appendNode(coords[4], coords[5]);
           break;
       }
       it.next();
     }
   }
 }