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