001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.Map;
008import java.util.TreeMap;
009
010import org.openstreetmap.josm.Main;
011import org.openstreetmap.josm.data.coor.EastNorth;
012import org.openstreetmap.josm.data.osm.BBox;
013import org.openstreetmap.josm.data.osm.DataSet;
014import org.openstreetmap.josm.data.osm.Node;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.data.osm.Relation;
017import org.openstreetmap.josm.data.osm.RelationMember;
018import org.openstreetmap.josm.data.osm.Way;
019import org.openstreetmap.josm.tools.Geometry;
020
021/**
022 * This allows to select a polygon/multipolygon by an internal point.
023 * @since 7144
024 */
025public final class SelectByInternalPointAction {
026
027    private SelectByInternalPointAction() {
028        // Hide public constructor for utility class
029    }
030
031    /**
032     * Returns the surrounding polygons/multipolygons
033     * ordered by their area size (from small to large)
034     * which contain the internal point.
035     *
036     * @param internalPoint the internal point.
037     * @return the surrounding polygons/multipolygons
038     */
039    public static Collection<OsmPrimitive> getSurroundingObjects(EastNorth internalPoint) {
040        final DataSet ds = JosmAction.getCurrentDataSet();
041        if (ds == null) {
042            return Collections.emptySet();
043        }
044        final Node n = new Node(internalPoint);
045        final Map<Double, OsmPrimitive> found = new TreeMap<>();
046        for (Way w : ds.getWays()) {
047            if (w.isUsable() && w.isClosed() && w.isSelectable() && Geometry.nodeInsidePolygon(n, w.getNodes())) {
048                found.put(Geometry.closedWayArea(w), w);
049            }
050        }
051        for (Relation r : ds.getRelations()) {
052            if (r.isUsable() && r.isMultipolygon() && r.isSelectable() && Geometry.isNodeInsideMultiPolygon(n, r, null)) {
053                for (RelationMember m : r.getMembers()) {
054                    if (m.isWay() && m.getWay().isClosed()) {
055                        found.values().remove(m.getWay());
056                    }
057                }
058                // estimate multipolygon size by its bounding box area
059                BBox bBox = r.getBBox();
060                EastNorth en1 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getTopLeft());
061                EastNorth en2 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getBottomRight());
062                double s = Math.abs((en1.east() - en2.east()) * (en1.north() - en2.north()));
063                found.put(s <= 0 ? 1e8 : s, r);
064            }
065        }
066        return found.values();
067    }
068
069    /**
070     * Returns the smallest surrounding polygon/multipolygon which contains the internal point.
071     *
072     * @param internalPoint the internal point.
073     * @return the smallest surrounding polygon/multipolygon
074     */
075    public static OsmPrimitive getSmallestSurroundingObject(EastNorth internalPoint) {
076        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
077        return surroundingObjects.isEmpty() ? null : surroundingObjects.iterator().next();
078    }
079
080    /**
081     * Select a polygon or multipolygon by an internal point.
082     *
083     * @param internalPoint the internal point.
084     * @param doAdd         whether to add selected polygon to the current selection.
085     * @param doRemove      whether to remove the selected polygon from the current selection.
086     */
087    public static void performSelection(EastNorth internalPoint, boolean doAdd, boolean doRemove) {
088        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
089        final DataSet ds = JosmAction.getCurrentDataSet();
090        if (surroundingObjects.isEmpty()) {
091            return;
092        } else if (doRemove) {
093            final Collection<OsmPrimitive> newSelection = new ArrayList<>(ds.getSelected());
094            newSelection.removeAll(surroundingObjects);
095            ds.setSelected(newSelection);
096        } else if (doAdd) {
097            final Collection<OsmPrimitive> newSelection = new ArrayList<>(ds.getSelected());
098            newSelection.add(surroundingObjects.iterator().next());
099            ds.setSelected(newSelection);
100        } else {
101            ds.setSelected(surroundingObjects.iterator().next());
102        }
103    }
104}