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