// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm.visitor; import java.util.Collection; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.ProjectionBounds; import org.openstreetmap.josm.data.coor.CachedLatLon; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Way; /** * Calculates the total bounding rectangle of a series of {@link OsmPrimitive} objects, using the * EastNorth values as reference. * @author imi */ public class BoundingXYVisitor extends AbstractVisitor { private ProjectionBounds bounds; @Override public void visit(Node n) { visit(n.getEastNorth()); } @Override public void visit(Way w) { if (w.isIncomplete()) return; for (Node n : w.getNodes()) { visit(n); } } @Override public void visit(Relation e) { // only use direct members for (RelationMember m : e.getMembers()) { if (!m.isRelation()) { m.getMember().accept(this); } } } /** * Visiting call for bounds. * @param b bounds */ public void visit(Bounds b) { if (b != null) { b.visitEdge(Main.getProjection(), this::visit); } } /** * Visiting call for projection bounds. * @param b projection bounds */ public void visit(ProjectionBounds b) { if (b != null) { visit(b.getMin()); visit(b.getMax()); } } /** * Visiting call for lat/lon. * @param latlon lat/lon */ public void visit(LatLon latlon) { if (latlon != null) { if (latlon instanceof CachedLatLon) { visit(((CachedLatLon) latlon).getEastNorth()); } else { visit(Main.getProjection().latlon2eastNorth(latlon)); } } } /** * Visiting call for east/north. * @param eastNorth east/north */ public void visit(EastNorth eastNorth) { if (eastNorth != null) { if (bounds == null) { bounds = new ProjectionBounds(eastNorth); } else { bounds.extend(eastNorth); } } } /** * Determines if the visitor has a non null bounds area. * @return {@code true} if the visitor has a non null bounds area * @see ProjectionBounds#hasExtend */ public boolean hasExtend() { return bounds != null && bounds.hasExtend(); } /** * @return The bounding box or <code>null</code> if no coordinates have passed */ public ProjectionBounds getBounds() { return bounds; } /** * Enlarges the calculated bounding box by 0.002 degrees. * If the bounding box has not been set (<code>min</code> or <code>max</code> * equal <code>null</code>) this method does not do anything. */ public void enlargeBoundingBox() { enlargeBoundingBox(Main.pref.getDouble("edit.zoom-enlarge-bbox", 0.002)); } /** * Enlarges the calculated bounding box by the specified number of degrees. * If the bounding box has not been set (<code>min</code> or <code>max</code> * equal <code>null</code>) this method does not do anything. * * @param enlargeDegree number of degrees to enlarge on each side */ public void enlargeBoundingBox(double enlargeDegree) { if (bounds == null) return; LatLon minLatlon = Main.getProjection().eastNorth2latlon(bounds.getMin()); LatLon maxLatlon = Main.getProjection().eastNorth2latlon(bounds.getMax()); bounds = new ProjectionBounds( Main.getProjection().latlon2eastNorth(new LatLon( Math.max(-90, minLatlon.lat() - enlargeDegree), Math.max(-180, minLatlon.lon() - enlargeDegree))), Main.getProjection().latlon2eastNorth(new LatLon( Math.min(90, maxLatlon.lat() + enlargeDegree), Math.min(180, maxLatlon.lon() + enlargeDegree)))); } /** * Enlarges the bounding box up to <code>maxEnlargePercent</code>, depending on * its size. If the bounding box is small, it will be enlarged more in relation * to its beginning size. The larger the bounding box, the smaller the change, * down to the minimum of 1% enlargement. * * Warning: if the bounding box only contains a single node, no expansion takes * place because a node has no width/height. Use <code>enlargeToMinDegrees</code> * instead. * * Example: You specify enlargement to be up to 100%. * * Bounding box is a small house: enlargement will be 95–100%, i.e. * making enough space so that the house fits twice on the screen in * each direction. * * Bounding box is a large landuse, like a forest: Enlargement will * be 1–10%, i.e. just add a little border around the landuse. * * If the bounding box has not been set (<code>min</code> or <code>max</code> * equal <code>null</code>) this method does not do anything. * * @param maxEnlargePercent maximum enlargement in percentage (100.0 for 100%) */ public void enlargeBoundingBoxLogarithmically(double maxEnlargePercent) { if (bounds == null) return; double diffEast = bounds.getMax().east() - bounds.getMin().east(); double diffNorth = bounds.getMax().north() - bounds.getMin().north(); double enlargeEast = Math.min(maxEnlargePercent - 10*Math.log(diffEast), 1)/100; double enlargeNorth = Math.min(maxEnlargePercent - 10*Math.log(diffNorth), 1)/100; visit(bounds.getMin().add(-enlargeEast/2, -enlargeNorth/2)); visit(bounds.getMax().add(+enlargeEast/2, +enlargeNorth/2)); } /** * Specify a degree larger than 0 in order to make the bounding box at least * the specified size in width and height. The value is ignored if the * bounding box is already larger than the specified amount. * * If the bounding box has not been set (<code>min</code> or <code>max</code> * equal <code>null</code>) this method does not do anything. * * If the bounding box contains objects and is to be enlarged, the objects * will be centered within the new bounding box. * * @param size minimum width and height in meter */ public void enlargeToMinSize(double size) { if (bounds == null) return; // convert size from meters to east/north units double enSize = size * Main.map.mapView.getScale() / Main.map.mapView.getDist100Pixel() * 100; visit(bounds.getMin().add(-enSize/2, -enSize/2)); visit(bounds.getMax().add(+enSize/2, +enSize/2)); } @Override public String toString() { return "BoundingXYVisitor["+bounds+']'; } /** * Compute the bounding box of a collection of primitives. * @param primitives the collection of primitives */ public void computeBoundingBox(Collection<? extends OsmPrimitive> primitives) { if (primitives == null) return; for (OsmPrimitive p: primitives) { if (p == null) { continue; } p.accept(this); } } }