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 }