Ticket #5694: JOSM.selecting.patch

File JOSM.selecting.patch, 11.8 KB (added by anonymous, 15 years ago)

patch against stable version implementing both actions

  • src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

     
    1515import java.util.Collection;
    1616import java.util.Collections;
    1717import java.util.HashSet;
     18import java.util.Iterator;
    1819import java.util.LinkedList;
    1920import java.util.List;
    2021import java.util.Set;
     
    3132import org.openstreetmap.josm.command.RotateCommand;
    3233import org.openstreetmap.josm.command.SequenceCommand;
    3334import org.openstreetmap.josm.data.coor.EastNorth;
     35import org.openstreetmap.josm.data.coor.LatLon;
     36import org.openstreetmap.josm.data.osm.BBox;
    3437import org.openstreetmap.josm.data.osm.DataSet;
    3538import org.openstreetmap.josm.data.osm.Node;
    3639import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    5558 * If an selected object is under the mouse when dragging, move all selected objects.
    5659 * If an unselected object is under the mouse when dragging, it becomes selected
    5760 * and will be moved.
    58  * If no object is under the mouse, move all selected objects (if any)
    5961 *
    6062 * @author imi
    6163 */
     
    7375    }
    7476
    7577    enum Mode { move, rotate, select }
     78    public enum Intersection {INSIDE, OUTSIDE, CROSSING}
    7679    private Mode mode = null;
    7780    private long mouseDownTime = 0;
    7881    private boolean didMove = false;
     
    102105     */
    103106    private int initialMoveThreshold;
    104107    private boolean initialMoveThresholdExceeded = false;
     108    private int selectWaysRecursions = 0;
    105109    /**
    106110     * Create a new SelectAction
    107111     * @param mapFrame The MapFrame this action belongs to.
     
    336340    }
    337341
    338342    /**
    339      * Look, whether any object is selected. If not, select the nearest node.
     343     * Look, whether any object is selected. If not, select the nearest node
     344     * within snap distance.
    340345     * If there are no nodes in the dataset, do nothing.
    341346     *
    342347     * If the user did not press the left mouse button, do nothing.
     
    345350     * cursor to movement.
    346351     */
    347352    @Override public void mousePressed(MouseEvent e) {
    348         if(!Main.map.mapView.isActiveLayerVisible())
     353        if (!Main.map.mapView.isActiveLayerVisible())
    349354            return;
    350355        // request focus in order to enable the expected keyboard shortcuts
    351356        //
     
    361366
    362367        // We don't want to change to draw tool if the user tries to (de)select
    363368        // stuff but accidentally clicks in an empty area when selection is empty
    364         if(shift || ctrl) {
     369        if (shift || ctrl) {
    365370            cancelDrawMode = true;
    366371        }
    367372
     
    386391                    shift || getCurrentDataSet().getSelected().containsAll(osmColl),
    387392                    ctrl, false, false);
    388393            mode = Mode.move;
     394        } else if (alt) {
     395            Collection<Way> ways = getCurrentDataSet().getWays();
     396            LatLon latlon = Main.map.mapView.getLatLon(e.getX(), e.getY());
     397            double x = latlon.lon();
     398            double y = latlon.lat();
     399            BBox mbox = new BBox(latlon, latlon);
     400
     401            long start = System.currentTimeMillis();
     402            int polies = 0;
     403            List<Way> polygons = new LinkedList<Way>();
     404            for (Way way : ways)
     405            {
     406                // we're only looking for polygons
     407                if (!way.isClosed()) {
     408                    continue;
     409                }
     410                polies++;
     411                if (pointInPoly(way, mbox, x, y)) {
     412                    polygons.add(way);
     413                }
     414            }
     415            long end = System.currentTimeMillis();
     416            long time = end - start;
     417            System.out.println("processing " + polies + " polygons took " + time + " ms and found " + polygons.size() + " polygons");
     418
     419            start = System.currentTimeMillis();
     420            if (polygons.size() > 1)
     421            {
     422                // have to figure out which is the tighter one
     423                boolean removedOne = false;
     424                do {
     425                    removedOne = false;
     426                    Iterator<Way> it = polygons.iterator();
     427                    Way firstEntry = it.next();
     428                    while (it.hasNext()){
     429                        Way way = it.next();
     430                        Intersection res = polyInPoly(way, firstEntry);
     431                        if (res == Intersection.INSIDE) {
     432                            System.out.println("1 removing polygon " + polygons.get(0).getUniqueId());
     433                            polygons.remove(0);
     434                            removedOne = true;
     435                            break;
     436                        }
     437                        else
     438                            if (res == Intersection.OUTSIDE) {
     439                                System.out.println("2 removing polygon " + way.getUniqueId());
     440                                it.remove();
     441                                removedOne = true;
     442                            }
     443                    }
     444                } while (removedOne && polygons.size() > 1);
     445            }
     446            end = System.currentTimeMillis();
     447            time = end - start;
     448            System.out.println("determining the inner polygontook " + time + " ms and found " + polygons.size() + " polygons");
     449
     450            // Now we've only one polygon or multiple intersecting ones.
     451            // The first case is ok and for the second one I lack criteria to
     452            // say which one is the better fit, so take them all.
     453            selectPrims(new ArrayList<OsmPrimitive>(polygons), shift, false, false, false);
     454
     455            mode = Mode.select;
     456            oldCursor = Main.map.mapView.getCursor();
     457            selectionManager.register(Main.map.mapView);
    389458        } else {
    390459            mode = Mode.select;
    391460            oldCursor = Main.map.mapView.getCursor();
    392461            selectionManager.register(Main.map.mapView);
    393462            selectionManager.mousePressed(e);
    394463        }
    395         if(mode != Mode.move || shift || ctrl)
     464        if (mode != Mode.move || shift || ctrl)
    396465        {
    397466            virtualNode = null;
    398467            virtualWays.clear();
     
    402471        // Mode.select redraws when selectPrims is called
    403472        // Mode.move   redraws when mouseDragged is called
    404473        // Mode.rotate redraws here
    405         if(mode == Mode.rotate) {
     474        if (mode == Mode.rotate) {
    406475            Main.map.mapView.repaint();
    407476        }
    408477
     
    410479    }
    411480
    412481    /**
     482     * Tests if the first polygon lies within the second polygon or not.
     483     * "Lies within" is defined as all its points lie within, though that
     484     * could still mean the borders intersect.
     485     * This should be sufficient for our needs though.
     486     *
     487     * @param poly the way of the first polygon
     488     * @param poly the way of the second polygon
     489     * @return INSIDE if the first lies completely within the second polygon,
     490     *   OUTSIDE it lies completely outside of it and CROSSING if it partially
     491     *   lies within.
     492     */
     493    private Intersection polyInPoly(Way poly1, Way poly2)
     494    {
     495        int contains = 0;
     496        List <Node> nodes = poly1.getNodes();
     497        for (Node node : nodes) {
     498            double x = node.getCoor().lon();
     499            double y = node.getCoor().lat();
     500            if (pointInPoly(poly2, node.getBBox(), x, y)) {
     501                contains++;
     502            }
     503        }
     504
     505        if (contains == nodes.size()) return Intersection.INSIDE;
     506        if (contains == 0) return Intersection.OUTSIDE;
     507        return Intersection.CROSSING;
     508    }
     509
     510    /**
     511     * Tests if the given point is within the polygon or not.
     512     * The source of this code is http://www.visibone.com/inpoly/
     513     *
     514     * @param poly the way of the polygon
     515     * @param mbox bounding box of the mouse point
     516     * @param xt X coordinate of the mouse cursor
     517     * @param yt Y coordinate of the mouse cursor
     518     * @return true if the point is within the polygon, false otherwise
     519     */
     520    private boolean pointInPoly(Way poly, BBox mbox, double xt, double yt)
     521    {
     522        List <Node> nodes = poly.getNodes();
     523        int npoints = nodes.size();
     524        double xnew, ynew;
     525        double xold, yold;
     526        double x1, y1;
     527        double x2, y2;
     528
     529        if (npoints < 4)
     530            return(false);
     531
     532        // in this case intersects is a test for "is contained"
     533        if (!poly.getBBox().intersects(mbox))
     534            return false;
     535
     536        LatLon old_ = nodes.get(npoints - 1).getCoor();
     537        xold = old_.getX();
     538        yold = old_.getY();
     539        boolean inside = false;
     540        for (Node node : nodes) {
     541            LatLon new_ = node.getCoor();
     542            xnew = new_.getX();
     543            ynew = new_.getY();
     544            if (xnew > xold) {
     545                x1 = xold;
     546                x2 = xnew;
     547                y1 = yold;
     548                y2 = ynew;
     549            }
     550            else {
     551                x1 = xnew;
     552                x2 = xold;
     553                y1 = ynew;
     554                y2 = yold;
     555            }
     556
     557            if ((xnew < xt) == (xt <= xold)          /* edge "open" at one end */
     558                    && (yt - y1) * (x2 - x1)
     559                    < (y2 - y1) * (xt - x1)) {
     560                inside = !inside;
     561            }
     562
     563            xold = xnew;
     564            yold = ynew;
     565        }
     566
     567        return(inside);
     568    }
     569
     570
     571    /**
     572     * Handle left double clicks for selecting connected ways
     573     */
     574    @Override public void mouseClicked(MouseEvent e) {
     575        if (!Main.map.mapView.isActiveLayerVisible())
     576            return;
     577        // request focus in order to enable the expected keyboard shortcuts
     578        //
     579        Main.map.mapView.requestFocus();
     580
     581        cancelDrawMode = false;
     582        if (! (Boolean)this.getValue("active")) return;
     583        if (e.getButton() != MouseEvent.BUTTON1 ||
     584                e.getClickCount() != 2)
     585            return;
     586
     587        Collection<Way> sel = getCurrentDataSet().getSelectedWays();
     588        selectWaysRecursions = 0;
     589        // remove selection for the initial input to not fall prey
     590        // to the first condition in selectWays
     591        getCurrentDataSet().clearSelection(sel);
     592        selectWays(sel);
     593    }
     594
     595    /**
     596     * This is method recursively calls itself
     597     *
     598     * @param ways all selected ways
     599     */
     600    private void selectWays(Collection<Way> ways)
     601    {
     602        // limit the number of recursions to a reasonable amount
     603        if (selectWaysRecursions++ < 10) {
     604            for (Way way : ways) {
     605                // this way may have been selected already in a deeper recursion
     606                if (way.isSelected()) {
     607                    continue;
     608                }
     609                getCurrentDataSet().addSelected(way);
     610                for (Node node : way.getNodes())
     611                {
     612                    Collection<Way> newways = OsmPrimitive.getFilteredList(node.getReferrers(), Way.class);
     613                    newways.removeAll(getCurrentDataSet().getSelectedWays());
     614
     615                    if (!newways.isEmpty()) {
     616                        selectWays(newways);
     617                    }
     618                }
     619            }
     620        }
     621        selectWaysRecursions--;
     622    }
     623
     624    /**
    413625     * Restore the old mouse cursor.
    414626     */
    415627    @Override public void mouseReleased(MouseEvent e) {
     
    489701                        nodesToMerge.add(targetNode);
    490702                        if (!nodesToMerge.isEmpty()) {
    491703                            Command cmd = MergeNodesAction.mergeNodes(Main.main.getEditLayer(),nodesToMerge, targetNode);
    492                             if(cmd != null)
     704                            if(cmd != null) {
    493705                                Main.main.undoRedo.add(cmd);
     706                            }
    494707                        }
    495708                    }
    496709                }