001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.visitor; 003 004import java.util.Collection; 005 006import org.openstreetmap.josm.Main; 007import org.openstreetmap.josm.data.Bounds; 008import org.openstreetmap.josm.data.ProjectionBounds; 009import org.openstreetmap.josm.data.coor.CachedLatLon; 010import org.openstreetmap.josm.data.coor.EastNorth; 011import org.openstreetmap.josm.data.coor.LatLon; 012import org.openstreetmap.josm.data.osm.Node; 013import org.openstreetmap.josm.data.osm.OsmPrimitive; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.RelationMember; 016import org.openstreetmap.josm.data.osm.Way; 017 018/** 019 * Calculates the total bounding rectangle of a series of {@link OsmPrimitive} objects, using the 020 * EastNorth values as reference. 021 * @author imi 022 */ 023public class BoundingXYVisitor extends AbstractVisitor { 024 025 private ProjectionBounds bounds = null; 026 027 @Override 028 public void visit(Node n) { 029 visit(n.getEastNorth()); 030 } 031 032 @Override 033 public void visit(Way w) { 034 if (w.isIncomplete()) return; 035 for (Node n : w.getNodes()) { 036 visit(n); 037 } 038 } 039 040 @Override 041 public void visit(Relation e) { 042 // only use direct members 043 for (RelationMember m : e.getMembers()) { 044 if (!m.isRelation()) { 045 m.getMember().accept(this); 046 } 047 } 048 } 049 050 public void visit(Bounds b) { 051 if(b != null) 052 { 053 visit(b.getMin()); 054 visit(b.getMax()); 055 } 056 } 057 058 public void visit(ProjectionBounds b) { 059 if(b != null) 060 { 061 visit(b.getMin()); 062 visit(b.getMax()); 063 } 064 } 065 066 public void visit(LatLon latlon) { 067 if(latlon != null) 068 { 069 if(latlon instanceof CachedLatLon) { 070 visit(((CachedLatLon)latlon).getEastNorth()); 071 } else { 072 visit(Main.getProjection().latlon2eastNorth(latlon)); 073 } 074 } 075 } 076 077 public void visit(EastNorth eastNorth) { 078 if (eastNorth != null) { 079 if (bounds == null) { 080 bounds = new ProjectionBounds(eastNorth); 081 } else { 082 bounds.extend(eastNorth); 083 } 084 } 085 } 086 087 public boolean hasExtend() 088 { 089 return bounds != null && !bounds.getMin().equals(bounds.getMax()); 090 } 091 092 /** 093 * @return The bounding box or <code>null</code> if no coordinates have passed 094 */ 095 public ProjectionBounds getBounds() { 096 return bounds; 097 } 098 099 /** 100 * Enlarges the calculated bounding box by 0.002 degrees. 101 * If the bounding box has not been set (<code>min</code> or <code>max</code> 102 * equal <code>null</code>) this method does not do anything. 103 */ 104 public void enlargeBoundingBox() { 105 enlargeBoundingBox(Main.pref.getDouble("edit.zoom-enlarge-bbox", 0.002)); 106 } 107 108 /** 109 * Enlarges the calculated bounding box by the specified number of degrees. 110 * If the bounding box has not been set (<code>min</code> or <code>max</code> 111 * equal <code>null</code>) this method does not do anything. 112 * 113 * @param enlargeDegree 114 */ 115 public void enlargeBoundingBox(double enlargeDegree) { 116 if (bounds == null) 117 return; 118 LatLon minLatlon = Main.getProjection().eastNorth2latlon(bounds.getMin()); 119 LatLon maxLatlon = Main.getProjection().eastNorth2latlon(bounds.getMax()); 120 bounds = new ProjectionBounds( 121 Main.getProjection().latlon2eastNorth(new LatLon(minLatlon.lat() - enlargeDegree, minLatlon.lon() - enlargeDegree)), 122 Main.getProjection().latlon2eastNorth(new LatLon(maxLatlon.lat() + enlargeDegree, maxLatlon.lon() + enlargeDegree))); 123 } 124 125 /** 126 * Enlarges the bounding box up to <code>maxEnlargePercent</code>, depending on 127 * its size. If the bounding box is small, it will be enlarged more in relation 128 * to its beginning size. The larger the bounding box, the smaller the change, 129 * down to the minimum of 1% enlargement. 130 * 131 * Warning: if the bounding box only contains a single node, no expansion takes 132 * place because a node has no width/height. Use <code>enlargeToMinDegrees</code> 133 * instead. 134 * 135 * Example: You specify enlargement to be up to 100%. 136 * 137 * Bounding box is a small house: enlargement will be 95?100%, i.e. 138 * making enough space so that the house fits twice on the screen in 139 * each direction. 140 * 141 * Bounding box is a large landuse, like a forest: Enlargement will 142 * be 1?10%, i.e. just add a little border around the landuse. 143 * 144 * If the bounding box has not been set (<code>min</code> or <code>max</code> 145 * equal <code>null</code>) this method does not do anything. 146 * 147 * @param maxEnlargePercent 148 */ 149 public void enlargeBoundingBoxLogarithmically(double maxEnlargePercent) { 150 if (bounds == null) 151 return; 152 153 double diffEast = bounds.getMax().east() - bounds.getMin().east(); 154 double diffNorth = bounds.getMax().north() - bounds.getMin().north(); 155 156 double enlargeEast = Math.min(maxEnlargePercent - 10*Math.log(diffEast), 1)/100; 157 double enlargeNorth = Math.min(maxEnlargePercent - 10*Math.log(diffNorth), 1)/100; 158 159 visit(bounds.getMin().add(-enlargeEast/2, -enlargeNorth/2)); 160 visit(bounds.getMax().add(+enlargeEast/2, +enlargeNorth/2)); 161 } 162 163 /** 164 * Specify a degree larger than 0 in order to make the bounding box at least 165 * the specified size in width and height. The value is ignored if the 166 * bounding box is already larger than the specified amount. 167 * 168 * If the bounding box has not been set (<code>min</code> or <code>max</code> 169 * equal <code>null</code>) this method does not do anything. 170 * 171 * If the bounding box contains objects and is to be enlarged, the objects 172 * will be centered within the new bounding box. 173 * 174 * @param size minimum width and height in meter 175 */ 176 public void enlargeToMinSize(double size) { 177 if (bounds == null) 178 return; 179 // convert size from meters to east/north units 180 double en_size = size * Main.map.mapView.getScale() / Main.map.mapView.getDist100Pixel() * 100; 181 visit(bounds.getMin().add(-en_size/2, -en_size/2)); 182 visit(bounds.getMax().add(+en_size/2, +en_size/2)); 183 } 184 185 186 @Override public String toString() { 187 return "BoundingXYVisitor["+bounds+"]"; 188 } 189 190 public void computeBoundingBox(Collection<? extends OsmPrimitive> primitives) { 191 if (primitives == null) return; 192 for (OsmPrimitive p: primitives) { 193 if (p == null) { 194 continue; 195 } 196 p.accept(this); 197 } 198 } 199}