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