Ticket #5599: joinAreas_fixes.diff

File joinAreas_fixes.diff, 30.6 KB (added by extropy, 15 years ago)
  • src/org/openstreetmap/josm/actions/JoinAreasAction.java

     
    4747import org.openstreetmap.josm.tools.Pair;
    4848import org.openstreetmap.josm.tools.Shortcut;
    4949
     50
    5051/**
    5152 * Join Areas (i.e. closed ways and multipolygons)
    5253 */
     
    401402        }
    402403
    403404        //find intersection points
    404         ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds);
     405        Set<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds);
    405406        return nodes.size() > 0;
    406407    }
    407408
     
    437438        }
    438439
    439440        //find intersection points
    440         ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds);
     441        Set<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds);
    441442
    442443        //no intersections, return.
    443444        if (nodes.isEmpty())
     
    473474        //find polygons
    474475        List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries);
    475476
     477
    476478        //assemble final polygons
    477479        List<Multipolygon> polygons = new ArrayList<Multipolygon>();
     480        Set<Relation> relationsToDelete = new LinkedHashSet<Relation>();
     481
    478482        for (AssembledMultipolygon pol : preparedPolygons) {
    479483
    480484            //create the new ways
     
    484488            RelationRole ownMultipolygonRelation = addOwnMultigonRelation(resultPol.innerWays, resultPol.outerWay);
    485489
    486490            //add back the original relations, merged with our new multipolygon relation
    487             fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation);
     491            fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation, relationsToDelete);
    488492
    489493            //strip tags from inner ways
    490494            //TODO: preserve tags on existing inner ways
     
    495499
    496500        commitCommands(marktr("Assemble new polygons"));
    497501
     502        for(Relation rel: relationsToDelete) {
     503            cmds.add(new DeleteCommand(rel));
     504        }
     505
     506        commitCommands(marktr("Delete relations"));
     507
    498508        // Delete the discarded inner ways
    499509        if (discardedWays.size() > 0) {
    500510            cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), discardedWays, true));
     
    827837     * Uses  SplitWayAction.splitWay for the heavy lifting.
    828838     * @return list of split ways (or original ways if no splitting is done).
    829839     */
    830     private ArrayList<Way> splitWayOnNodes(Way way, Collection<Node> nodes) {
     840    private ArrayList<Way> splitWayOnNodes(Way way, Set<Node> nodes) {
    831841
    832842        ArrayList<Way> result = new ArrayList<Way>();
    833843        List<List<Node>> chunks = buildNodeChunks(way, nodes);
     
    10941104     */
    10951105    public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) {
    10961106        Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes());
     1107        List<Node> insideNodes = inside.getNodes();
    10971108
    1098         for (Node insideNode : inside.getNodes()) {
     1109        for (Node insideNode : insideNodes) {
    10991110
    11001111            if (!outsideNodes.contains(insideNode))
    11011112                //simply test the one node
     
    13631374     * members of both. This function depends on multigon relations to be valid already, it won't fix them.
    13641375     * @param ArrayList<RelationRole> List of relations with roles the (original) ways were part of
    13651376     * @param Way The newly created outer area/way
     1377     * @param relationsToDelete - set of relations to delete.
    13661378     */
    1367     private void fixRelations(ArrayList<RelationRole> rels, Way outer, RelationRole ownMultipol) {
     1379    private void fixRelations(ArrayList<RelationRole> rels, Way outer, RelationRole ownMultipol, Set<Relation> relationsToDelete) {
    13681380        ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>();
    13691381
    13701382        if (ownMultipol != null){
     
    14091421                    newRel.put(key, r.rel.get(key));
    14101422                }
    14111423                // Delete old relation
    1412                 cmds.add(new DeleteCommand(r.rel));
     1424                relationsToDelete.add(r.rel);
    14131425            }
    14141426            newRel.addMember(new RelationMember("outer", outer));
    14151427            cmds.add(new AddCommand(newRel));
  • src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

     
    4040import org.openstreetmap.josm.gui.layer.Layer;
    4141import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    4242import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     43import org.openstreetmap.josm.tools.Geometry;
    4344import org.openstreetmap.josm.tools.ImageProvider;
    4445import org.openstreetmap.josm.tools.Shortcut;
    4546
     
    305306
    306307                    //find if the new points overlap existing segments (in case of 90 degree angles)
    307308                    Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
    308                     boolean nodeOverlapsSegment = prevNode != null && segmentsParralel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en);
     309                    boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParralel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en);
    309310                    boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);
    310311
    311312                    if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
     
    322323
    323324                    //find if the new points overlap existing segments (in case of 90 degree angles)
    324325                    Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
    325                     nodeOverlapsSegment = nextNode != null && segmentsParralel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en);
     326                    nodeOverlapsSegment = nextNode != null && Geometry.segmentsParralel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en);
    326327                    hasOtherWays = hasNodeOtherWays(selectedSegment.getSecondNode(), selectedSegment.way);
    327328
    328329                    if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
     
    386387     */
    387388    private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2, EastNorth moveDirection,
    388389            EastNorth targetPos) {
    389         EastNorth intersectionPoint = getLineLineIntersection(segmentP1, segmentP2, targetPos,
     390        EastNorth intersectionPoint = Geometry.getLineLineIntersection(segmentP1, segmentP2, targetPos,
    390391                new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY()));
    391392
    392393        if (intersectionPoint == null)
     
    394395        else
    395396            //return distance form base to target position
    396397            return new EastNorth(targetPos.getX() - intersectionPoint.getX(),
    397                         targetPos.getY() - intersectionPoint.getY());
     398                    targetPos.getY() - intersectionPoint.getY());
    398399    }
    399400
    400     /**
    401      * Finds the intersection of two lines of infinite length.
    402      * @return EastNorth null if no intersection was found, the coordinates of the intersection otherwise
    403      */
    404     public static EastNorth getLineLineIntersection(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
    405         // Convert line from (point, point) form to ax+by=c
    406         double a1 = p2.getY() - p1.getY();
    407         double b1 = p1.getX() - p2.getX();
    408         double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();
    409401
    410         double a2 = p4.getY() - p3.getY();
    411         double b2 = p3.getX() - p4.getX();
    412         double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();
    413 
    414         // Solve the equations
    415         double det = a1 * b2 - a2 * b1;
    416         if (det == 0)
    417             return null; // Lines are parallel
    418 
    419         return new EastNorth((b1 * c2 - b2 * c1) / det, (a2 * c1 - a1 * c2) / det);
    420     }
    421 
    422     private static boolean segmentsParralel(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
    423 
    424         // Convert line from (point, point) form to ax+by=c
    425         double a1 = p2.getY() - p1.getY();
    426         double b1 = p1.getX() - p2.getX();
    427 
    428         double a2 = p4.getY() - p3.getY();
    429         double b2 = p3.getX() - p4.getX();
    430 
    431         // Solve the equations
    432         double det = a1 * b2 - a2 * b1;
    433         return Math.abs(det) < 1e-13;
    434     }
    435 
    436402    /**
    437403     * Gets a node from selected way before given index.
    438404     * @param index  index of current node
  • src/org/openstreetmap/josm/data/osm/NodePositionComparator.java

     
    1111public class NodePositionComparator implements Comparator<Node> {
    1212    @Override
    1313    public int compare(Node n1, Node n2) {
     14
     15        if (n1.getCoor().equalsEpsilon(n2.getCoor()))
     16            return 0;
     17
    1418        double dLat = n1.getCoor().lat() - n2.getCoor().lat();
    1519        if (dLat > 0)
    1620            return 1;
  • src/org/openstreetmap/josm/tools/Geometry.java

     
    44import java.awt.geom.Line2D;
    55import java.util.ArrayList;
    66import java.util.Comparator;
     7import java.util.HashSet;
    78import java.util.LinkedHashSet;
    89import java.util.List;
    910import java.util.Set;
     11
    1012import org.openstreetmap.josm.Main;
    1113import org.openstreetmap.josm.command.AddCommand;
    1214import org.openstreetmap.josm.command.ChangeCommand;
    1315import org.openstreetmap.josm.command.Command;
    1416import org.openstreetmap.josm.data.coor.EastNorth;
    15 import org.openstreetmap.josm.data.coor.LatLon;
     17import org.openstreetmap.josm.data.osm.BBox;
    1618import org.openstreetmap.josm.data.osm.Node;
    1719import org.openstreetmap.josm.data.osm.NodePositionComparator;
    1820import org.openstreetmap.josm.data.osm.Way;
     
    2325 * @author viesturs
    2426 */
    2527public class Geometry {
     28    public enum PolygonIntersection {FIRST_INSIDE_SECOND, SECOND_INSIDE_FIRST, OUTSIDE, CROSSING}
     29
     30
     31
    2632    /**
    2733     * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too.
    2834     * And make commands to add the intersection points to ways.
     
    3036     * @return ArrayList<Node> List of new nodes
    3137     * Prerequisite: no two nodes have the same coordinates.
    3238     */
    33     public static ArrayList<Node> addIntersections(List<Way> ways, boolean test, List<Command> cmds) {
    34         //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes )
     39    public static Set<Node> addIntersections(List<Way> ways, boolean test, List<Command> cmds) {
    3540
    3641        //stupid java, cannot instantiate array of generic classes..
    3742        @SuppressWarnings("unchecked")
    3843        ArrayList<Node>[] newNodes = new ArrayList[ways.size()];
     44        BBox[] wayBounds = new BBox[ways.size()];
    3945        boolean[] changedWays = new boolean[ways.size()];
    4046
    4147        Set<Node> intersectionNodes = new LinkedHashSet<Node>();
    4248
     49        //copy node arrays for local usage.
    4350        for (int pos = 0; pos < ways.size(); pos ++) {
    4451            newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes());
     52            wayBounds[pos] = getNodesBounds(newNodes[pos]);
    4553            changedWays[pos] = false;
    4654        }
    4755
    48         //iterate over all segment pairs and introduce the intersections
    49 
     56        //iterate over all way pairs and introduce the intersections
    5057        Comparator<Node> coordsComparator = new NodePositionComparator();
    5158
    52         int seg1Way = 0;
    53         int seg1Pos = -1;
     59        WayLoop: for (int seg1Way = 0; seg1Way < ways.size(); seg1Way ++) {
     60            for (int seg2Way = seg1Way; seg2Way < ways.size(); seg2Way ++) {
    5461
    55         while (true) {
    56             //advance to next segment
    57             seg1Pos++;
    58             if (seg1Pos > newNodes[seg1Way].size() - 2) {
    59                 seg1Way++;
    60                 seg1Pos = 0;
    61 
    62                 if (seg1Way == ways.size()) { //finished
    63                     break;
     62                //do not waste time on bounds that do not intersect
     63                if (!wayBounds[seg1Way].intersects(wayBounds[seg2Way])) {
     64                    continue;
    6465                }
    65             }
    6666
     67                ArrayList<Node> way1Nodes = newNodes[seg1Way];
     68                ArrayList<Node> way2Nodes = newNodes[seg2Way];
    6769
    68             //iterate over secondary segment
     70                //iterate over primary segmemt
     71                for (int seg1Pos = 0; seg1Pos + 1 < way1Nodes.size(); seg1Pos ++) {
    6972
    70             int seg2Way = seg1Way;
    71             int seg2Pos = seg1Pos + 1;//skip the adjacent segment
     73                    //iterate over secondary segment
     74                    int seg2Start = seg1Way != seg2Way ? 0: seg1Pos + 2;//skip the adjacent segment
    7275
    73             while (true) {
     76                    for (int seg2Pos = seg2Start; seg2Pos + 1< way2Nodes.size(); seg2Pos ++) {
    7477
    75                 //advance to next segment
    76                 seg2Pos++;
    77                 if (seg2Pos > newNodes[seg2Way].size() - 2) {
    78                     seg2Way++;
    79                     seg2Pos = 0;
     78                        //need to get them again every time, because other segments may be changed
     79                        Node seg1Node1 = way1Nodes.get(seg1Pos);
     80                        Node seg1Node2 = way1Nodes.get(seg1Pos + 1);
     81                        Node seg2Node1 = way2Nodes.get(seg2Pos);
     82                        Node seg2Node2 = way2Nodes.get(seg2Pos + 1);
    8083
    81                     if (seg2Way == ways.size()) { //finished
    82                         break;
    83                     }
    84                 }
     84                        int commonCount = 0;
     85                        //test if we have common nodes to add.
     86                        if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) {
     87                            commonCount ++;
    8588
    86                 //need to get them again every time, because other segments may be changed
    87                 Node seg1Node1 = newNodes[seg1Way].get(seg1Pos);
    88                 Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1);
    89                 Node seg2Node1 = newNodes[seg2Way].get(seg2Pos);
    90                 Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1);
     89                            if (seg1Way == seg2Way &&
     90                                    seg1Pos == 0 &&
     91                                    seg2Pos == way2Nodes.size() -2) {
     92                                //do not add - this is first and last segment of the same way.
     93                            } else {
     94                                intersectionNodes.add(seg1Node1);
     95                            }
     96                        }
    9197
    92                 int commonCount = 0;
    93                 //test if we have common nodes to add.
    94                 if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) {
    95                     commonCount ++;
     98                        if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) {
     99                            commonCount ++;
    96100
    97                     if (seg1Way == seg2Way &&
    98                             seg1Pos == 0 &&
    99                             seg2Pos == newNodes[seg2Way].size() -2) {
    100                         //do not add - this is first and last segment of the same way.
    101                     } else {
    102                         intersectionNodes.add(seg1Node1);
    103                     }
    104                 }
     101                            intersectionNodes.add(seg1Node2);
     102                        }
    105103
    106                 if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) {
    107                     commonCount ++;
     104                        //no common nodes - find intersection
     105                        if (commonCount == 0) {
     106                            EastNorth intersection = getSegmentSegmentIntersection(
     107                                    seg1Node1.getEastNorth(), seg1Node2.getEastNorth(),
     108                                    seg2Node1.getEastNorth(), seg2Node2.getEastNorth());
    108109
    109                     intersectionNodes.add(seg1Node2);
    110                 }
     110                            if (intersection != null) {
     111                                if (test) {
     112                                    intersectionNodes.add(seg2Node1);
     113                                    return intersectionNodes;
     114                                }
    111115
    112                 //no common nodes - find intersection
    113                 if (commonCount == 0) {
    114                     LatLon intersection = getLineLineIntersection(
    115                             seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(),
    116                             seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(),
    117                             seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(),
    118                             seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north());
     116                                Node newNode = new Node(Main.proj.eastNorth2latlon(intersection));
     117                                Node intNode = newNode;
     118                                boolean insertInSeg1 = false;
     119                                boolean insertInSeg2 = false;
    119120
    120                     if (intersection != null) {
    121                         if (test) {
    122                             intersectionNodes.add(seg2Node1);
    123                             return new ArrayList<Node>(intersectionNodes);
    124                         }
     121                                //find if the intersection point is at end point of one of the segments, if so use that point
    125122
    126                         Node newNode = new Node(intersection);
    127                         Node intNode = newNode;
    128                         boolean insertInSeg1 = false;
    129                         boolean insertInSeg2 = false;
     123                                //segment 1
     124                                if (coordsComparator.compare(newNode, seg1Node1) == 0) {
     125                                    intNode = seg1Node1;
     126                                } else if (coordsComparator.compare(newNode, seg1Node2) == 0) {
     127                                    intNode = seg1Node2;
     128                                } else {
     129                                    insertInSeg1 = true;
     130                                }
    130131
    131                         //find if the intersection point is at end point of one of the segments, if so use that point
     132                                //segment 2
     133                                if (coordsComparator.compare(newNode, seg2Node1) == 0) {
     134                                    intNode = seg2Node1;
     135                                } else if (coordsComparator.compare(newNode, seg2Node2) == 0) {
     136                                    intNode = seg2Node2;
     137                                } else {
     138                                    insertInSeg2 = true;
     139                                }
    132140
    133                         //segment 1
    134                         if (coordsComparator.compare(newNode, seg1Node1) == 0) {
    135                             intNode = seg1Node1;
    136                         } else if (coordsComparator.compare(newNode, seg1Node2) == 0) {
    137                             intNode = seg1Node2;
    138                         } else {
    139                             insertInSeg1 = true;
    140                         }
     141                                if (insertInSeg1) {
     142                                    way1Nodes.add(seg1Pos +1, intNode);
     143                                    changedWays[seg1Way] = true;
    141144
    142                         //segment 2
    143                         if (coordsComparator.compare(newNode, seg2Node1) == 0) {
    144                             intNode = seg2Node1;
    145                         } else if (coordsComparator.compare(newNode, seg2Node2) == 0) {
    146                             intNode = seg2Node2;
    147                         } else {
    148                             insertInSeg2 = true;
    149                         }
     145                                    //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment.
     146                                    if (seg2Way == seg1Way) {
     147                                        seg2Pos ++;
     148                                    }
     149                                }
    150150
    151                         if (insertInSeg1) {
    152                             newNodes[seg1Way].add(seg1Pos +1, intNode);
    153                             changedWays[seg1Way] = true;
     151                                if (insertInSeg2) {
     152                                    way2Nodes.add(seg2Pos +1, intNode);
     153                                    changedWays[seg2Way] = true;
    154154
    155                             //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment.
    156                             if (seg2Way == seg1Way) {
    157                                 seg2Pos ++;
    158                             }
    159                         }
     155                                    //Do not need to compare again to already split segment
     156                                    seg2Pos ++;
     157                                }
    160158
    161                         if (insertInSeg2) {
    162                             newNodes[seg2Way].add(seg2Pos +1, intNode);
    163                             changedWays[seg2Way] = true;
     159                                intersectionNodes.add(intNode);
    164160
    165                             //Do not need to compare again to already split segment
    166                             seg2Pos ++;
     161                                if (intNode == newNode) {
     162                                    cmds.add(new AddCommand(intNode));
     163                                }
     164                            }
    167165                        }
    168 
    169                         intersectionNodes.add(intNode);
    170 
    171                         if (intNode == newNode) {
    172                             cmds.add(new AddCommand(intNode));
    173                         }
     166                        else if (test && intersectionNodes.size() > 0)
     167                            return intersectionNodes;
    174168                    }
    175169                }
    176                 else if (test && intersectionNodes.size() > 0)
    177                     return new ArrayList<Node>(intersectionNodes);
    178170            }
    179171        }
    180172
     173
    181174        for (int pos = 0; pos < ways.size(); pos ++) {
    182175            if (changedWays[pos] == false) {
    183176                continue;
     
    190183            cmds.add(new ChangeCommand(way, newWay));
    191184        }
    192185
    193         return new ArrayList<Node>(intersectionNodes);
     186        return intersectionNodes;
    194187    }
    195188
    196     /**
    197      * Finds the intersection of two lines
    198      * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise
    199      */
    200     static private LatLon getLineLineIntersection(
    201             double x1, double y1, double x2, double y2,
    202             double x3, double y3, double x4, double y4) {
    203189
    204         if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;
    205190
    206         // Convert line from (point, point) form to ax+by=c
    207         double a1 = y2 - y1;
    208         double b1 = x1 - x2;
    209         double c1 = x2*y1 - x1*y2;
     191    private static BBox getNodesBounds(ArrayList<Node> nodes) {
    210192
    211         double a2 = y4 - y3;
    212         double b2 = x3 - x4;
    213         double c2 = x4*y3 - x3*y4;
     193        BBox bounds = new BBox(nodes.get(0));
    214194
    215         // Solve the equations
    216         double det = a1*b2 - a2*b1;
    217         if (det == 0) return null; // Lines are parallel
     195        for(Node n: nodes) {
     196            bounds.add(n.getCoor());
     197        }
    218198
    219         return Main.proj.eastNorth2latlon(new EastNorth(
    220                 (b1*c2 - b2*c1)/det,
    221                 (a2*c1 -a1*c2)/det
    222         ));
     199        return bounds;
    223200    }
    224    
     201
     202
    225203    /**
    226204     * Tests if given point is to the right side of path consisting of 3 points.
    227205     * @param lineP1 first point in path
     
    258236    }
    259237
    260238    /**
     239     * Finds the intersection of two line segments
     240     * @return EastNorth null if no intersection was found, the EastNorth coordinates of the intersection otherwise
     241     */
     242    static public EastNorth getSegmentSegmentIntersection(
     243            EastNorth p1, EastNorth p2,
     244            EastNorth p3, EastNorth p4) {
     245        double x1 = p1.getX();
     246        double y1 = p1.getY();
     247        double x2 = p2.getX();
     248        double y2 = p2.getY();
     249        double x3 = p3.getX();
     250        double y3 = p3.getY();
     251        double x4 = p4.getX();
     252        double y4 = p4.getY();
     253
     254        //TODO: do this locally.
     255        if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;
     256
     257        // Convert line from (point, point) form to ax+by=c
     258        double a1 = y2 - y1;
     259        double b1 = x1 - x2;
     260        double c1 = x2*y1 - x1*y2;
     261
     262        double a2 = y4 - y3;
     263        double b2 = x3 - x4;
     264        double c2 = x4*y3 - x3*y4;
     265
     266        // Solve the equations
     267        double det = a1*b2 - a2*b1;
     268        if (det == 0) return null; // Lines are parallel
     269
     270        double x = (b1*c2 - b2*c1)/det;
     271        double y = (a2*c1 -a1*c2)/det;
     272
     273        return new EastNorth(x, y);
     274    }
     275
     276    /**
     277     * Finds the intersection of two lines of infinite length.
     278     * @return EastNorth null if no intersection was found, the coordinates of the intersection otherwise
     279     */
     280    public static EastNorth getLineLineIntersection(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
     281
     282        // Convert line from (point, point) form to ax+by=c
     283        double a1 = p2.getY() - p1.getY();
     284        double b1 = p1.getX() - p2.getX();
     285        double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();
     286
     287        double a2 = p4.getY() - p3.getY();
     288        double b2 = p3.getX() - p4.getX();
     289        double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();
     290
     291        // Solve the equations
     292        double det = a1 * b2 - a2 * b1;
     293        if (det == 0)
     294            return null; // Lines are parallel
     295
     296        return new EastNorth((b1 * c2 - b2 * c1) / det, (a2 * c1 - a1 * c2) / det);
     297    }
     298
     299    public static boolean segmentsParralel(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
     300
     301        // Convert line from (point, point) form to ax+by=c
     302        double a1 = p2.getY() - p1.getY();
     303        double b1 = p1.getX() - p2.getX();
     304
     305        double a2 = p4.getY() - p3.getY();
     306        double b2 = p3.getX() - p4.getX();
     307
     308        // Solve the equations
     309        double det = a1 * b2 - a2 * b1;
     310        return Math.abs(det) < 1e-13;
     311    }
     312
     313    /**
     314     * Calculates closest point to a line segment.
     315     * @param segmentP1
     316     * @param segmentP2
     317     * @param point
     318     * @return segmentP1 if it is the closest point, segmentP2 if it is the closest point,
     319     * a new point if closest point is between segmentP1 and segmentP2.
     320     */
     321    public static EastNorth closestPointToSegment(EastNorth segmentP1, EastNorth segmentP2, EastNorth point) {
     322
     323        double ldx = segmentP2.getX() - segmentP1.getX();
     324        double ldy = segmentP2.getY() - segmentP1.getY();
     325
     326        if (ldx == 0 && ldy == 0) //segment zero length
     327            return segmentP1;
     328
     329        double pdx = point.getX() - segmentP1.getX();
     330        double pdy = point.getY() - segmentP1.getY();
     331
     332        double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy);
     333
     334        if (offset <= 0)
     335            return segmentP1;
     336        else if (offset >= 1)
     337            return segmentP2;
     338        else
     339            return new EastNorth(segmentP1.getX() + ldx * offset, segmentP1.getY() + ldy * offset);
     340
     341    }
     342
     343    /**
     344     * This method tests if secondNode is clockwise to first node.
     345     * @param commonNode starting point for both vectors
     346     * @param firstNode first vector end node
     347     * @param secondNode second vector end node
     348     * @return true if first vector is clockwise before second vector.
     349     */
     350
     351    public static boolean angleIsClockwise(EastNorth commonNode, EastNorth firstNode, EastNorth secondNode) {
     352        double dy1 = (firstNode.getY() - commonNode.getY());
     353        double dy2 = (secondNode.getY() - commonNode.getY());
     354        double dx1 = (firstNode.getX() - commonNode.getX());
     355        double dx2 = (secondNode.getX() - commonNode.getX());
     356
     357        return dy1 * dx2 - dx1 * dy2 > 0;
     358    }
     359
     360
     361    /**
     362     * Tests if two polygons intersect.
     363     * @param first
     364     * @param second
     365     * @return intersection kind
     366     * TODO: test segments, not only points
     367     * TODO: is O(N*M), should use sweep for better performance.
     368     */
     369    public static PolygonIntersection polygonIntersection(List<Node> first, List<Node> second) {
     370        Set<Node> firstSet = new HashSet<Node>(first);
     371        Set<Node> secondSet = new HashSet<Node>(second);
     372
     373        int nodesInsideSecond = 0;
     374        int nodesOutsideSecond = 0;
     375        int nodesInsideFirst = 0;
     376        int nodesOutsideFirst = 0;
     377
     378        for (Node insideNode : first) {
     379            if (secondSet.contains(insideNode)) {
     380                continue;
     381                //ignore touching nodes.
     382            }
     383
     384            if (nodeInsidePolygon(insideNode, second)) {
     385                nodesInsideSecond ++;
     386            }
     387            else {
     388                nodesOutsideSecond ++;
     389            }
     390        }
     391
     392        for (Node insideNode : second) {
     393            if (firstSet.contains(insideNode)) {
     394                continue;
     395                //ignore touching nodes.
     396            }
     397
     398            if (nodeInsidePolygon(insideNode, first)) {
     399                nodesInsideFirst ++;
     400            }
     401            else {
     402                nodesOutsideFirst ++;
     403            }
     404        }
     405
     406        if (nodesInsideFirst == 0) {
     407            if (nodesInsideSecond == 0){
     408                if (nodesOutsideFirst + nodesInsideSecond > 0)
     409                    return PolygonIntersection.OUTSIDE;
     410                else
     411                    //all nodes common
     412                    return PolygonIntersection.CROSSING;
     413            } else
     414                return PolygonIntersection.FIRST_INSIDE_SECOND;
     415        }
     416        else
     417        {
     418            if (nodesInsideSecond == 0)
     419                return PolygonIntersection.SECOND_INSIDE_FIRST;
     420            else
     421                return PolygonIntersection.CROSSING;
     422        }
     423    }
     424
     425
     426    /**
    261427     * Tests if point is inside a polygon. The polygon can be self-intersecting. In such case the contains function works in xor-like manner.
    262428     * @param polygonNodes list of nodes from polygon path.
    263429     * @param point the point to test
    264430     * @return true if the point is inside polygon.
    265431     */
    266432    public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes) {
    267         if (polygonNodes.size() < 3)
     433        if (polygonNodes.size() < 2)
    268434            return false;
    269435
    270436        boolean inside = false;