001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.visitor; 003 004import java.util.Collection; 005import java.util.function.DoubleUnaryOperator; 006 007import org.openstreetmap.josm.data.Bounds; 008import org.openstreetmap.josm.data.ProjectionBounds; 009import org.openstreetmap.josm.data.coor.EastNorth; 010import org.openstreetmap.josm.data.coor.ILatLon; 011import org.openstreetmap.josm.data.coor.LatLon; 012import org.openstreetmap.josm.data.osm.INode; 013import org.openstreetmap.josm.data.osm.IPrimitive; 014import org.openstreetmap.josm.data.osm.IRelation; 015import org.openstreetmap.josm.data.osm.IRelationMember; 016import org.openstreetmap.josm.data.osm.IWay; 017import org.openstreetmap.josm.data.osm.Node; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.data.projection.ProjectionRegistry; 022import org.openstreetmap.josm.spi.preferences.Config; 023 024/** 025 * Calculates the total bounding rectangle of a series of {@link OsmPrimitive} objects, using the 026 * EastNorth values as reference. 027 * @author imi 028 */ 029public class BoundingXYVisitor implements OsmPrimitiveVisitor, PrimitiveVisitor { 030 /** default value for setting "edit.zoom-enlarge-bbox" */ 031 private static final double ENLARGE_DEFAULT = 0.0002; 032 033 private ProjectionBounds bounds; 034 035 @Override 036 public void visit(Node n) { 037 visit((ILatLon) n); 038 } 039 040 @Override 041 public void visit(Way w) { 042 visit((IWay<?>) w); 043 } 044 045 @Override 046 public void visit(Relation r) { 047 visit((IRelation<?>) r); 048 } 049 050 @Override 051 public void visit(INode n) { 052 visit((ILatLon) n); 053 } 054 055 @Override 056 public void visit(IWay<?> w) { 057 if (w.isIncomplete()) return; 058 for (INode n : w.getNodes()) { 059 visit(n); 060 } 061 } 062 063 @Override 064 public void visit(IRelation<?> r) { 065 // only use direct members 066 for (IRelationMember<?> m : r.getMembers()) { 067 if (!m.isRelation()) { 068 m.getMember().accept(this); 069 } 070 } 071 } 072 073 /** 074 * Visiting call for bounds. 075 * @param b bounds 076 */ 077 public void visit(Bounds b) { 078 if (b != null) { 079 ProjectionRegistry.getProjection().visitOutline(b, this::visit); 080 } 081 } 082 083 /** 084 * Visiting call for projection bounds. 085 * @param b projection bounds 086 */ 087 public void visit(ProjectionBounds b) { 088 if (b != null) { 089 visit(b.getMin()); 090 visit(b.getMax()); 091 } 092 } 093 094 /** 095 * Visiting call for lat/lon. 096 * @param latlon lat/lon 097 * @since 12725 (public for ILatLon parameter) 098 */ 099 public void visit(ILatLon latlon) { 100 if (latlon != null) { 101 visit(latlon.getEastNorth(ProjectionRegistry.getProjection())); 102 } 103 } 104 105 /** 106 * Visiting call for lat/lon. 107 * @param latlon lat/lon 108 */ 109 public void visit(LatLon latlon) { 110 visit((ILatLon) latlon); 111 } 112 113 /** 114 * Visiting call for east/north. 115 * @param eastNorth east/north 116 */ 117 public void visit(EastNorth eastNorth) { 118 if (eastNorth != null) { 119 if (bounds == null) { 120 bounds = new ProjectionBounds(eastNorth); 121 } else { 122 bounds.extend(eastNorth); 123 } 124 } 125 } 126 127 /** 128 * Determines if the visitor has a non null bounds area. 129 * @return {@code true} if the visitor has a non null bounds area 130 * @see ProjectionBounds#hasExtend 131 */ 132 public boolean hasExtend() { 133 return bounds != null && bounds.hasExtend(); 134 } 135 136 /** 137 * @return The bounding box or <code>null</code> if no coordinates have passed 138 */ 139 public ProjectionBounds getBounds() { 140 return bounds; 141 } 142 143 /** 144 * Enlarges the calculated bounding box by 0.0002 degrees or user value 145 * given in edit.zoom-enlarge-bbox. 146 * If the bounding box has not been set (<code>min</code> or <code>max</code> 147 * equal <code>null</code>) this method does not do anything. 148 */ 149 public void enlargeBoundingBox() { 150 final double enlarge = Config.getPref().getDouble("edit.zoom-enlarge-bbox", ENLARGE_DEFAULT); 151 enlargeBoundingBox(enlarge, enlarge); 152 } 153 154 /** 155 * Enlarges the calculated bounding box by the specified number of degrees. 156 * If the bounding box has not been set (<code>min</code> or <code>max</code> 157 * equal <code>null</code>) this method does not do anything. 158 * 159 * @param enlargeDegreeX number of degrees to enlarge on each side along X 160 * @param enlargeDegreeY number of degrees to enlarge on each side along Y 161 */ 162 public void enlargeBoundingBox(double enlargeDegreeX, double enlargeDegreeY) { 163 if (bounds == null) 164 return; 165 LatLon minLatlon = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMin()); 166 LatLon maxLatlon = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMax()); 167 bounds = new ProjectionBounds(new LatLon( 168 Math.max(-90, minLatlon.lat() - enlargeDegreeY), 169 Math.max(-180, minLatlon.lon() - enlargeDegreeX)).getEastNorth(ProjectionRegistry.getProjection()), 170 new LatLon( 171 Math.min(90, maxLatlon.lat() + enlargeDegreeY), 172 Math.min(180, maxLatlon.lon() + enlargeDegreeX)).getEastNorth(ProjectionRegistry.getProjection())); 173 } 174 175 /** 176 * Enlarges the bounding box up to 0.0002 degrees, depending on its size and user 177 * settings in edit.zoom-enlarge-bbox. If the bounding box is small, it will be enlarged more in relation 178 * to its beginning size. The larger the bounding box, the smaller the change, 179 * down to 0.0 degrees. 180 * 181 * If the bounding box has not been set (<code>min</code> or <code>max</code> 182 * equal <code>null</code>) this method does not do anything. 183 * 184 * @since 14628 185 */ 186 public void enlargeBoundingBoxLogarithmically() { 187 if (bounds == null) 188 return; 189 final LatLon min = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMin()); 190 final LatLon max = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMax()); 191 final double deltaLat = max.lat() - min.lat(); 192 final double deltaLon = max.lon() - min.lon(); 193 final double enlarge = Config.getPref().getDouble("edit.zoom-enlarge-bbox", ENLARGE_DEFAULT); 194 195 final DoubleUnaryOperator enlargement = deltaDegress -> { 196 if (deltaDegress < enlarge) { 197 // delta is very small, use configured minimum value 198 return enlarge; 199 } 200 if (deltaDegress < 0.1) { 201 return enlarge - deltaDegress / 100; 202 } 203 return 0.0; 204 }; 205 enlargeBoundingBox(enlargement.applyAsDouble(deltaLon), enlargement.applyAsDouble(deltaLat)); 206 } 207 208 @Override 209 public String toString() { 210 return "BoundingXYVisitor["+bounds+']'; 211 } 212 213 /** 214 * Compute the bounding box of a collection of primitives. 215 * @param primitives the collection of primitives 216 */ 217 public void computeBoundingBox(Collection<? extends IPrimitive> primitives) { 218 if (primitives == null) return; 219 for (IPrimitive p: primitives) { 220 if (p == null) { 221 continue; 222 } 223 p.accept(this); 224 } 225 } 226}