source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/MultipolygonCache.java@ 11779

Last change on this file since 11779 was 11779, checked in by Don-vip, 7 years ago

fix #14572 - Don't index MultipolygonCache by NavigatableComponent

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.visitor.paint.relations;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Map;
9import java.util.concurrent.ConcurrentHashMap;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.data.SelectionChangedListener;
13import org.openstreetmap.josm.data.osm.DataSet;
14import org.openstreetmap.josm.data.osm.Node;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.Relation;
17import org.openstreetmap.josm.data.osm.Way;
18import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
19import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
20import org.openstreetmap.josm.data.osm.event.DataSetListener;
21import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
22import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
23import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
24import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
25import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
26import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
27import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
30import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
31import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
32import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
33import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
34import org.openstreetmap.josm.gui.layer.OsmDataLayer;
35
36/**
37 * A memory cache for {@link Multipolygon} objects.
38 * @since 4623
39 */
40public final class MultipolygonCache implements DataSetListener, LayerChangeListener, ProjectionChangeListener, SelectionChangedListener {
41
42 private static final MultipolygonCache INSTANCE = new MultipolygonCache();
43
44 private final Map<DataSet, Map<Relation, Multipolygon>> cache;
45
46 private final Collection<PolyData> selectedPolyData;
47
48 private MultipolygonCache() {
49 this.cache = new ConcurrentHashMap<>(); // see ticket 11833
50 this.selectedPolyData = new ArrayList<>();
51 Main.addProjectionChangeListener(this);
52 DataSet.addSelectionListener(this);
53 Main.getLayerManager().addLayerChangeListener(this);
54 }
55
56 /**
57 * Replies the unique instance.
58 * @return the unique instance
59 */
60 public static MultipolygonCache getInstance() {
61 return INSTANCE;
62 }
63
64 /**
65 * Gets a multipolygon from cache.
66 * @param r The multipolygon relation
67 * @return A multipolygon object for the given relation, or {@code null}
68 * @since 11779
69 */
70 public Multipolygon get(Relation r) {
71 return get(r, false);
72 }
73
74 /**
75 * Gets a multipolygon from cache.
76 * @param r The multipolygon relation
77 * @param forceRefresh if {@code true}, a new object will be created even of present in cache
78 * @return A multipolygon object for the given relation, or {@code null}
79 * @since 11779
80 */
81 public Multipolygon get(Relation r, boolean forceRefresh) {
82 Multipolygon multipolygon = null;
83 if (r != null) {
84 Map<Relation, Multipolygon> map2 = cache.get(r.getDataSet());
85 if (map2 == null) {
86 map2 = new ConcurrentHashMap<>();
87 cache.put(r.getDataSet(), map2);
88 }
89 multipolygon = map2.get(r);
90 if (multipolygon == null || forceRefresh) {
91 multipolygon = new Multipolygon(r);
92 map2.put(r, multipolygon);
93 for (PolyData pd : multipolygon.getCombinedPolygons()) {
94 if (pd.isSelected()) {
95 selectedPolyData.add(pd);
96 }
97 }
98 }
99 }
100 return multipolygon;
101 }
102
103 /**
104 * Clears the cache for the given dataset.
105 * @param ds the data set
106 */
107 public void clear(DataSet ds) {
108 Map<Relation, Multipolygon> map2 = cache.remove(ds);
109 if (map2 != null) {
110 map2.clear();
111 }
112 }
113
114 /**
115 * Clears the whole cache.
116 */
117 public void clear() {
118 cache.clear();
119 }
120
121 private Collection<Map<Relation, Multipolygon>> getMapsFor(DataSet ds) {
122 List<Map<Relation, Multipolygon>> result = new ArrayList<>();
123 Map<Relation, Multipolygon> map2 = cache.get(ds);
124 if (map2 != null) {
125 result.add(map2);
126 }
127 return result;
128 }
129
130 private static boolean isMultipolygon(OsmPrimitive p) {
131 return p instanceof Relation && ((Relation) p).isMultipolygon();
132 }
133
134 private void updateMultipolygonsReferringTo(AbstractDatasetChangedEvent event) {
135 updateMultipolygonsReferringTo(event, event.getPrimitives(), event.getDataset());
136 }
137
138 private void updateMultipolygonsReferringTo(
139 final AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, DataSet ds) {
140 updateMultipolygonsReferringTo(event, primitives, ds, null);
141 }
142
143 private Collection<Map<Relation, Multipolygon>> updateMultipolygonsReferringTo(
144 AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives,
145 DataSet ds, Collection<Map<Relation, Multipolygon>> initialMaps) {
146 Collection<Map<Relation, Multipolygon>> maps = initialMaps;
147 if (primitives != null) {
148 for (OsmPrimitive p : primitives) {
149 if (isMultipolygon(p)) {
150 if (maps == null) {
151 maps = getMapsFor(ds);
152 }
153 processEvent(event, (Relation) p, maps);
154
155 } else if (p instanceof Way && p.getDataSet() != null) {
156 for (OsmPrimitive ref : p.getReferrers()) {
157 if (isMultipolygon(ref)) {
158 if (maps == null) {
159 maps = getMapsFor(ds);
160 }
161 processEvent(event, (Relation) ref, maps);
162 }
163 }
164 } else if (p instanceof Node && p.getDataSet() != null) {
165 maps = updateMultipolygonsReferringTo(event, p.getReferrers(), ds, maps);
166 }
167 }
168 }
169 return maps;
170 }
171
172 private static void processEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
173 if (event instanceof NodeMovedEvent || event instanceof WayNodesChangedEvent) {
174 dispatchEvent(event, r, maps);
175 } else if (event instanceof PrimitivesRemovedEvent) {
176 if (event.getPrimitives().contains(r)) {
177 removeMultipolygonFrom(r, maps);
178 }
179 } else {
180 // Default (non-optimal) action: remove multipolygon from cache
181 removeMultipolygonFrom(r, maps);
182 }
183 }
184
185 private static void dispatchEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
186 for (Map<Relation, Multipolygon> map : maps) {
187 Multipolygon m = map.get(r);
188 if (m != null) {
189 for (PolyData pd : m.getCombinedPolygons()) {
190 if (event instanceof NodeMovedEvent) {
191 pd.nodeMoved((NodeMovedEvent) event);
192 } else if (event instanceof WayNodesChangedEvent) {
193 final boolean oldClosedStatus = pd.isClosed();
194 pd.wayNodesChanged((WayNodesChangedEvent) event);
195 if (pd.isClosed() != oldClosedStatus) {
196 removeMultipolygonFrom(r, maps); // see ticket #13591
197 return;
198 }
199 }
200 }
201 }
202 }
203 }
204
205 private static void removeMultipolygonFrom(Relation r, Collection<Map<Relation, Multipolygon>> maps) {
206 for (Map<Relation, Multipolygon> map : maps) {
207 map.remove(r);
208 }
209 // Erase style cache for polygon members
210 for (OsmPrimitive member : r.getMemberPrimitivesList()) {
211 member.clearCachedStyle();
212 }
213 }
214
215 @Override
216 public void primitivesAdded(PrimitivesAddedEvent event) {
217 // Do nothing
218 }
219
220 @Override
221 public void primitivesRemoved(PrimitivesRemovedEvent event) {
222 updateMultipolygonsReferringTo(event);
223 }
224
225 @Override
226 public void tagsChanged(TagsChangedEvent event) {
227 updateMultipolygonsReferringTo(event);
228 }
229
230 @Override
231 public void nodeMoved(NodeMovedEvent event) {
232 updateMultipolygonsReferringTo(event);
233 }
234
235 @Override
236 public void wayNodesChanged(WayNodesChangedEvent event) {
237 updateMultipolygonsReferringTo(event);
238 }
239
240 @Override
241 public void relationMembersChanged(RelationMembersChangedEvent event) {
242 updateMultipolygonsReferringTo(event);
243 }
244
245 @Override
246 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
247 // Do nothing
248 }
249
250 @Override
251 public void dataChanged(DataChangedEvent event) {
252 // Do not call updateMultipolygonsReferringTo as getPrimitives()
253 // can return all the data set primitives for this event
254 Collection<Map<Relation, Multipolygon>> maps = null;
255 for (OsmPrimitive p : event.getPrimitives()) {
256 if (isMultipolygon(p)) {
257 if (maps == null) {
258 maps = getMapsFor(event.getDataset());
259 }
260 for (Map<Relation, Multipolygon> map : maps) {
261 // DataChangedEvent is sent after downloading incomplete members (see #7131),
262 // without having received RelationMembersChangedEvent or PrimitivesAddedEvent
263 // OR when undoing a move of a large number of nodes (see #7195),
264 // without having received NodeMovedEvent
265 // This ensures concerned multipolygons will be correctly redrawn
266 map.remove(p);
267 }
268 }
269 }
270 }
271
272 @Override
273 public void layerAdded(LayerAddEvent e) {
274 // Do nothing
275 }
276
277 @Override
278 public void layerOrderChanged(LayerOrderChangeEvent e) {
279 // Do nothing
280 }
281
282 @Override
283 public void layerRemoving(LayerRemoveEvent e) {
284 if (e.getRemovedLayer() instanceof OsmDataLayer) {
285 clear(((OsmDataLayer) e.getRemovedLayer()).data);
286 }
287 }
288
289 @Override
290 public void projectionChanged(Projection oldValue, Projection newValue) {
291 clear();
292 }
293
294 @Override
295 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
296
297 for (Iterator<PolyData> it = selectedPolyData.iterator(); it.hasNext();) {
298 it.next().setSelected(false);
299 it.remove();
300 }
301
302 DataSet ds = null;
303 Collection<Map<Relation, Multipolygon>> maps = null;
304 for (OsmPrimitive p : newSelection) {
305 if (p instanceof Way && p.getDataSet() != null) {
306 if (ds == null) {
307 ds = p.getDataSet();
308 }
309 for (OsmPrimitive ref : p.getReferrers()) {
310 if (isMultipolygon(ref)) {
311 if (maps == null) {
312 maps = getMapsFor(ds);
313 }
314 for (Map<Relation, Multipolygon> map : maps) {
315 Multipolygon multipolygon = map.get(ref);
316 if (multipolygon != null) {
317 for (PolyData pd : multipolygon.getCombinedPolygons()) {
318 if (pd.getWayIds().contains(p.getUniqueId())) {
319 pd.setSelected(true);
320 selectedPolyData.add(pd);
321 }
322 }
323 }
324 }
325 }
326 }
327 }
328 }
329 }
330}
Note: See TracBrowser for help on using the repository browser.