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 ordered by their area size (from small to large)
033     * which contain the internal point.
034     *
035     * @param internalPoint the internal point.
036     * @return the surrounding polygons/multipolygons
037     */
038    public static Collection<OsmPrimitive> getSurroundingObjects(EastNorth internalPoint) {
039        return getSurroundingObjects(Main.getLayerManager().getEditDataSet(), internalPoint, false);
040    }
041
042    /**
043     * Returns the surrounding polygons/multipolygons ordered by their area size (from small to large)
044     * which contain the internal point.
045     *
046     * @param ds the data set
047     * @param internalPoint the internal point.
048     * @param includeMultipolygonWays whether to include multipolygon ways in the result (false by default)
049     * @return the surrounding polygons/multipolygons
050     * @since 11247
051     */
052    public static Collection<OsmPrimitive> getSurroundingObjects(DataSet ds, EastNorth internalPoint, boolean includeMultipolygonWays) {
053        if (ds == null) {
054            return Collections.emptySet();
055        }
056        final Node n = new Node(internalPoint);
057        final Map<Double, OsmPrimitive> found = new TreeMap<>();
058        for (Way w : ds.getWays()) {
059            if (w.isUsable() && w.isClosed() && w.isSelectable() && Geometry.nodeInsidePolygon(n, w.getNodes())) {
060                found.put(Geometry.closedWayArea(w), w);
061            }
062        }
063        for (Relation r : ds.getRelations()) {
064            if (r.isUsable() && r.isMultipolygon() && r.isSelectable() && Geometry.isNodeInsideMultiPolygon(n, r, null)) {
065                if (!includeMultipolygonWays) {
066                    for (RelationMember m : r.getMembers()) {
067                        if (m.isWay() && m.getWay().isClosed()) {
068                            found.values().remove(m.getWay());
069                        }
070                    }
071                }
072                // estimate multipolygon size by its bounding box area
073                BBox bBox = r.getBBox();
074                EastNorth en1 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getTopLeft());
075                EastNorth en2 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getBottomRight());
076                double s = Math.abs((en1.east() - en2.east()) * (en1.north() - en2.north()));
077                found.put(s <= 0 ? 1e8 : s, r);
078            }
079        }
080        return found.values();
081    }
082
083    /**
084     * Returns the smallest surrounding polygon/multipolygon which contains the internal point.
085     *
086     * @param internalPoint the internal point.
087     * @return the smallest surrounding polygon/multipolygon
088     */
089    public static OsmPrimitive getSmallestSurroundingObject(EastNorth internalPoint) {
090        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
091        return surroundingObjects.isEmpty() ? null : surroundingObjects.iterator().next();
092    }
093
094    /**
095     * Select a polygon or multipolygon by an internal point.
096     *
097     * @param internalPoint the internal point.
098     * @param doAdd         whether to add selected polygon to the current selection.
099     * @param doRemove      whether to remove the selected polygon from the current selection.
100     */
101    public static void performSelection(EastNorth internalPoint, boolean doAdd, boolean doRemove) {
102        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
103        final DataSet ds = Main.getLayerManager().getEditDataSet();
104        if (surroundingObjects.isEmpty()) {
105            return;
106        } else if (doRemove) {
107            final Collection<OsmPrimitive> newSelection = new ArrayList<>(ds.getSelected());
108            newSelection.removeAll(surroundingObjects);
109            ds.setSelected(newSelection);
110        } else if (doAdd) {
111            final Collection<OsmPrimitive> newSelection = new ArrayList<>(ds.getSelected());
112            newSelection.add(surroundingObjects.iterator().next());
113            ds.setSelected(newSelection);
114        } else {
115            ds.setSelected(surroundingObjects.iterator().next());
116        }
117    }
118}