001 package org.openstreetmap.josm.data.osm.visitor.paint.relations; 002 003 import java.util.ArrayList; 004 import java.util.Collection; 005 import java.util.HashMap; 006 import java.util.Iterator; 007 import java.util.List; 008 import java.util.Map; 009 010 import org.openstreetmap.josm.Main; 011 import org.openstreetmap.josm.data.SelectionChangedListener; 012 import org.openstreetmap.josm.data.osm.DataSet; 013 import org.openstreetmap.josm.data.osm.Node; 014 import org.openstreetmap.josm.data.osm.OsmPrimitive; 015 import org.openstreetmap.josm.data.osm.Relation; 016 import org.openstreetmap.josm.data.osm.Way; 017 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 018 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 019 import org.openstreetmap.josm.data.osm.event.DataSetListener; 020 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 021 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 022 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 023 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 024 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 025 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 026 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 027 import org.openstreetmap.josm.data.projection.Projection; 028 import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 029 import org.openstreetmap.josm.gui.MapView; 030 import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 031 import org.openstreetmap.josm.gui.NavigatableComponent; 032 import org.openstreetmap.josm.gui.layer.Layer; 033 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 034 035 /* 036 * A memory cache for Multipolygon objects. 037 * 038 */ 039 public class MultipolygonCache implements DataSetListener, LayerChangeListener, ProjectionChangeListener, SelectionChangedListener { 040 041 private static final MultipolygonCache instance = new MultipolygonCache(); 042 043 private final Map<NavigatableComponent, Map<DataSet, Map<Relation, Multipolygon>>> cache; 044 045 private final Collection<PolyData> selectedPolyData; 046 047 private MultipolygonCache() { 048 this.cache = new HashMap<NavigatableComponent, Map<DataSet, Map<Relation, Multipolygon>>>(); 049 this.selectedPolyData = new ArrayList<Multipolygon.PolyData>(); 050 Main.addProjectionChangeListener(this); 051 DataSet.addSelectionListener(this); 052 MapView.addLayerChangeListener(this); 053 } 054 055 public static final MultipolygonCache getInstance() { 056 return instance; 057 } 058 059 public final Multipolygon get(NavigatableComponent nc, Relation r) { 060 return get(nc, r, false); 061 } 062 063 public final Multipolygon get(NavigatableComponent nc, Relation r, boolean forceRefresh) { 064 Multipolygon multipolygon = null; 065 if (nc != null && r != null) { 066 Map<DataSet, Map<Relation, Multipolygon>> map1 = cache.get(nc); 067 if (map1 == null) { 068 cache.put(nc, map1 = new HashMap<DataSet, Map<Relation, Multipolygon>>()); 069 } 070 Map<Relation, Multipolygon> map2 = map1.get(r.getDataSet()); 071 if (map2 == null) { 072 map1.put(r.getDataSet(), map2 = new HashMap<Relation, Multipolygon>()); 073 } 074 multipolygon = map2.get(r); 075 if (multipolygon == null || forceRefresh) { 076 map2.put(r, multipolygon = new Multipolygon(r)); 077 for (PolyData pd : multipolygon.getCombinedPolygons()) { 078 if (pd.selected) { 079 selectedPolyData.add(pd); 080 } 081 } 082 } 083 } 084 return multipolygon; 085 } 086 087 public final void clear(NavigatableComponent nc) { 088 Map<DataSet, Map<Relation, Multipolygon>> map = cache.remove(nc); 089 if (map != null) { 090 map.clear(); 091 map = null; 092 } 093 } 094 095 public final void clear(DataSet ds) { 096 for (Map<DataSet, Map<Relation, Multipolygon>> map1 : cache.values()) { 097 Map<Relation, Multipolygon> map2 = map1.remove(ds); 098 if (map2 != null) { 099 map2.clear(); 100 map2 = null; 101 } 102 } 103 } 104 105 public final void clear() { 106 cache.clear(); 107 } 108 109 private final Collection<Map<Relation, Multipolygon>> getMapsFor(DataSet ds) { 110 List<Map<Relation, Multipolygon>> result = new ArrayList<Map<Relation, Multipolygon>>(); 111 for (Map<DataSet, Map<Relation, Multipolygon>> map : cache.values()) { 112 Map<Relation, Multipolygon> map2 = map.get(ds); 113 if (map2 != null) { 114 result.add(map2); 115 } 116 } 117 return result; 118 } 119 120 private static final boolean isMultipolygon(OsmPrimitive p) { 121 return p instanceof Relation && ((Relation) p).isMultipolygon(); 122 } 123 124 private final void updateMultipolygonsReferringTo(AbstractDatasetChangedEvent event) { 125 updateMultipolygonsReferringTo(event, event.getPrimitives(), event.getDataset()); 126 } 127 128 private final void updateMultipolygonsReferringTo( 129 final AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, DataSet ds) { 130 updateMultipolygonsReferringTo(event, primitives, ds, null); 131 } 132 133 private final Collection<Map<Relation, Multipolygon>> updateMultipolygonsReferringTo( 134 AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, 135 DataSet ds, Collection<Map<Relation, Multipolygon>> initialMaps) { 136 Collection<Map<Relation, Multipolygon>> maps = initialMaps; 137 if (primitives != null) { 138 for (OsmPrimitive p : primitives) { 139 if (isMultipolygon(p)) { 140 if (maps == null) { 141 maps = getMapsFor(ds); 142 } 143 processEvent(event, (Relation) p, maps); 144 145 } else if (p instanceof Way && p.getDataSet() != null) { 146 for (OsmPrimitive ref : p.getReferrers()) { 147 if (isMultipolygon(ref)) { 148 if (maps == null) { 149 maps = getMapsFor(ds); 150 } 151 processEvent(event, (Relation) ref, maps); 152 } 153 } 154 } else if (p instanceof Node && p.getDataSet() != null) { 155 maps = updateMultipolygonsReferringTo(event, p.getReferrers(), ds, maps); 156 } 157 } 158 } 159 return maps; 160 } 161 162 private final void processEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) { 163 if (event instanceof NodeMovedEvent || event instanceof WayNodesChangedEvent) { 164 dispatchEvent(event, r, maps); 165 } else if (event instanceof PrimitivesRemovedEvent) { 166 if (event.getPrimitives().contains(r)) { 167 removeMultipolygonFrom(r, maps); 168 } 169 } else { 170 // Default (non-optimal) action: remove multipolygon from cache 171 removeMultipolygonFrom(r, maps); 172 } 173 } 174 175 private final void dispatchEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) { 176 for (Map<Relation, Multipolygon> map : maps) { 177 Multipolygon m = map.get(r); 178 if (m != null) { 179 for (PolyData pd : m.getCombinedPolygons()) { 180 if (event instanceof NodeMovedEvent) { 181 pd.nodeMoved((NodeMovedEvent) event); 182 } else if (event instanceof WayNodesChangedEvent) { 183 pd.wayNodesChanged((WayNodesChangedEvent)event); 184 } 185 } 186 } 187 } 188 } 189 190 private final void removeMultipolygonFrom(Relation r, Collection<Map<Relation, Multipolygon>> maps) { 191 for (Map<Relation, Multipolygon> map : maps) { 192 map.remove(r); 193 } 194 } 195 196 @Override 197 public void primitivesAdded(PrimitivesAddedEvent event) { 198 // Do nothing 199 } 200 201 @Override 202 public void primitivesRemoved(PrimitivesRemovedEvent event) { 203 updateMultipolygonsReferringTo(event); 204 } 205 206 @Override 207 public void tagsChanged(TagsChangedEvent event) { 208 // Do nothing 209 } 210 211 @Override 212 public void nodeMoved(NodeMovedEvent event) { 213 updateMultipolygonsReferringTo(event); 214 } 215 216 @Override 217 public void wayNodesChanged(WayNodesChangedEvent event) { 218 updateMultipolygonsReferringTo(event); 219 } 220 221 @Override 222 public void relationMembersChanged(RelationMembersChangedEvent event) { 223 updateMultipolygonsReferringTo(event); 224 } 225 226 @Override 227 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 228 // Do nothing 229 } 230 231 @Override 232 public void dataChanged(DataChangedEvent event) { 233 // Do not call updateMultipolygonsReferringTo as getPrimitives() 234 // can return all the data set primitives for this event 235 Collection<Map<Relation, Multipolygon>> maps = null; 236 for (OsmPrimitive p : event.getPrimitives()) { 237 if (isMultipolygon(p)) { 238 if (maps == null) { 239 maps = getMapsFor(event.getDataset()); 240 } 241 for (Map<Relation, Multipolygon> map : maps) { 242 // DataChangedEvent is sent after downloading incomplete members (see #7131), 243 // without having received RelationMembersChangedEvent or PrimitivesAddedEvent 244 // OR when undoing a move of a large number of nodes (see #7195), 245 // without having received NodeMovedEvent 246 // This ensures concerned multipolygons will be correctly redrawn 247 map.remove(p); 248 } 249 } 250 } 251 } 252 253 @Override 254 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 255 // Do nothing 256 } 257 258 @Override 259 public void layerAdded(Layer newLayer) { 260 // Do nothing 261 } 262 263 @Override 264 public void layerRemoved(Layer oldLayer) { 265 if (oldLayer instanceof OsmDataLayer) { 266 clear(((OsmDataLayer) oldLayer).data); 267 } 268 } 269 270 @Override 271 public void projectionChanged(Projection oldValue, Projection newValue) { 272 clear(); 273 } 274 275 @Override 276 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 277 278 for (Iterator<PolyData> it = selectedPolyData.iterator(); it.hasNext();) { 279 it.next().selected = false; 280 it.remove(); 281 } 282 283 DataSet ds = null; 284 Collection<Map<Relation, Multipolygon>> maps = null; 285 for (OsmPrimitive p : newSelection) { 286 if (p instanceof Way && p.getDataSet() != null) { 287 if (ds == null) { 288 ds = p.getDataSet(); 289 } 290 for (OsmPrimitive ref : p.getReferrers()) { 291 if (isMultipolygon(ref)) { 292 if (maps == null) { 293 maps = getMapsFor(ds); 294 } 295 for (Map<Relation, Multipolygon> map : maps) { 296 Multipolygon multipolygon = map.get(ref); 297 if (multipolygon != null) { 298 for (PolyData pd : multipolygon.getCombinedPolygons()) { 299 if (pd.getWayIds().contains(p.getUniqueId())) { 300 pd.selected = true; 301 selectedPolyData.add(pd); 302 } 303 } 304 } 305 } 306 } 307 } 308 } 309 } 310 } 311 }