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

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

sonar - squid:AssignmentInSubExpressionCheck - Assignments should not be made from within sub-expressions

  • Property svn:eol-style set to native
File size: 12.6 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.MapView;
31import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
32import org.openstreetmap.josm.gui.NavigatableComponent;
33import org.openstreetmap.josm.gui.layer.Layer;
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<NavigatableComponent, 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 MapView.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 nc The navigatable component
67 * @param r The multipolygon relation
68 * @return A multipolygon object for the given relation, or {@code null}
69 */
70 public Multipolygon get(NavigatableComponent nc, Relation r) {
71 return get(nc, r, false);
72 }
73
74 /**
75 * Gets a multipolygon from cache.
76 * @param nc The navigatable component
77 * @param r The multipolygon relation
78 * @param forceRefresh if {@code true}, a new object will be created even of present in cache
79 * @return A multipolygon object for the given relation, or {@code null}
80 */
81 public Multipolygon get(NavigatableComponent nc, Relation r, boolean forceRefresh) {
82 Multipolygon multipolygon = null;
83 if (nc != null && r != null) {
84 Map<DataSet, Map<Relation, Multipolygon>> map1 = cache.get(nc);
85 if (map1 == null) {
86 map1 = new ConcurrentHashMap<>();
87 cache.put(nc, map1);
88 }
89 Map<Relation, Multipolygon> map2 = map1.get(r.getDataSet());
90 if (map2 == null) {
91 map2 = new ConcurrentHashMap<>();
92 map1.put(r.getDataSet(), map2);
93 }
94 multipolygon = map2.get(r);
95 if (multipolygon == null || forceRefresh) {
96 multipolygon = new Multipolygon(r);
97 map2.put(r, multipolygon);
98 for (PolyData pd : multipolygon.getCombinedPolygons()) {
99 if (pd.selected) {
100 selectedPolyData.add(pd);
101 }
102 }
103 }
104 }
105 return multipolygon;
106 }
107
108 /**
109 * Clears the cache for the given navigatable component.
110 * @param nc the navigatable component
111 */
112 public void clear(NavigatableComponent nc) {
113 Map<DataSet, Map<Relation, Multipolygon>> map = cache.remove(nc);
114 if (map != null) {
115 map.clear();
116 map = null;
117 }
118 }
119
120 /**
121 * Clears the cache for the given dataset.
122 * @param ds the data set
123 */
124 public void clear(DataSet ds) {
125 for (Map<DataSet, Map<Relation, Multipolygon>> map1 : cache.values()) {
126 Map<Relation, Multipolygon> map2 = map1.remove(ds);
127 if (map2 != null) {
128 map2.clear();
129 map2 = null;
130 }
131 }
132 }
133
134 /**
135 * Clears the whole cache.
136 */
137 public void clear() {
138 cache.clear();
139 }
140
141 private Collection<Map<Relation, Multipolygon>> getMapsFor(DataSet ds) {
142 List<Map<Relation, Multipolygon>> result = new ArrayList<>();
143 for (Map<DataSet, Map<Relation, Multipolygon>> map : cache.values()) {
144 Map<Relation, Multipolygon> map2 = map.get(ds);
145 if (map2 != null) {
146 result.add(map2);
147 }
148 }
149 return result;
150 }
151
152 private static boolean isMultipolygon(OsmPrimitive p) {
153 return p instanceof Relation && ((Relation) p).isMultipolygon();
154 }
155
156 private void updateMultipolygonsReferringTo(AbstractDatasetChangedEvent event) {
157 updateMultipolygonsReferringTo(event, event.getPrimitives(), event.getDataset());
158 }
159
160 private void updateMultipolygonsReferringTo(
161 final AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, DataSet ds) {
162 updateMultipolygonsReferringTo(event, primitives, ds, null);
163 }
164
165 private Collection<Map<Relation, Multipolygon>> updateMultipolygonsReferringTo(
166 AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives,
167 DataSet ds, Collection<Map<Relation, Multipolygon>> initialMaps) {
168 Collection<Map<Relation, Multipolygon>> maps = initialMaps;
169 if (primitives != null) {
170 for (OsmPrimitive p : primitives) {
171 if (isMultipolygon(p)) {
172 if (maps == null) {
173 maps = getMapsFor(ds);
174 }
175 processEvent(event, (Relation) p, maps);
176
177 } else if (p instanceof Way && p.getDataSet() != null) {
178 for (OsmPrimitive ref : p.getReferrers()) {
179 if (isMultipolygon(ref)) {
180 if (maps == null) {
181 maps = getMapsFor(ds);
182 }
183 processEvent(event, (Relation) ref, maps);
184 }
185 }
186 } else if (p instanceof Node && p.getDataSet() != null) {
187 maps = updateMultipolygonsReferringTo(event, p.getReferrers(), ds, maps);
188 }
189 }
190 }
191 return maps;
192 }
193
194 private static void processEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
195 if (event instanceof NodeMovedEvent || event instanceof WayNodesChangedEvent) {
196 dispatchEvent(event, r, maps);
197 } else if (event instanceof PrimitivesRemovedEvent) {
198 if (event.getPrimitives().contains(r)) {
199 removeMultipolygonFrom(r, maps);
200 }
201 } else {
202 // Default (non-optimal) action: remove multipolygon from cache
203 removeMultipolygonFrom(r, maps);
204 }
205 }
206
207 private static void dispatchEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
208 for (Map<Relation, Multipolygon> map : maps) {
209 Multipolygon m = map.get(r);
210 if (m != null) {
211 for (PolyData pd : m.getCombinedPolygons()) {
212 if (event instanceof NodeMovedEvent) {
213 pd.nodeMoved((NodeMovedEvent) event);
214 } else if (event instanceof WayNodesChangedEvent) {
215 pd.wayNodesChanged((WayNodesChangedEvent) event);
216 }
217 }
218 }
219 }
220 }
221
222 private static void removeMultipolygonFrom(Relation r, Collection<Map<Relation, Multipolygon>> maps) {
223 for (Map<Relation, Multipolygon> map : maps) {
224 map.remove(r);
225 }
226 // Erase style cache for polygon members
227 for (OsmPrimitive member : r.getMemberPrimitives()) {
228 member.clearCachedStyle();
229 }
230 }
231
232 @Override
233 public void primitivesAdded(PrimitivesAddedEvent event) {
234 // Do nothing
235 }
236
237 @Override
238 public void primitivesRemoved(PrimitivesRemovedEvent event) {
239 updateMultipolygonsReferringTo(event);
240 }
241
242 @Override
243 public void tagsChanged(TagsChangedEvent event) {
244 updateMultipolygonsReferringTo(event);
245 }
246
247 @Override
248 public void nodeMoved(NodeMovedEvent event) {
249 updateMultipolygonsReferringTo(event);
250 }
251
252 @Override
253 public void wayNodesChanged(WayNodesChangedEvent event) {
254 updateMultipolygonsReferringTo(event);
255 }
256
257 @Override
258 public void relationMembersChanged(RelationMembersChangedEvent event) {
259 updateMultipolygonsReferringTo(event);
260 }
261
262 @Override
263 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
264 // Do nothing
265 }
266
267 @Override
268 public void dataChanged(DataChangedEvent event) {
269 // Do not call updateMultipolygonsReferringTo as getPrimitives()
270 // can return all the data set primitives for this event
271 Collection<Map<Relation, Multipolygon>> maps = null;
272 for (OsmPrimitive p : event.getPrimitives()) {
273 if (isMultipolygon(p)) {
274 if (maps == null) {
275 maps = getMapsFor(event.getDataset());
276 }
277 for (Map<Relation, Multipolygon> map : maps) {
278 // DataChangedEvent is sent after downloading incomplete members (see #7131),
279 // without having received RelationMembersChangedEvent or PrimitivesAddedEvent
280 // OR when undoing a move of a large number of nodes (see #7195),
281 // without having received NodeMovedEvent
282 // This ensures concerned multipolygons will be correctly redrawn
283 map.remove(p);
284 }
285 }
286 }
287 }
288
289 @Override
290 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
291 // Do nothing
292 }
293
294 @Override
295 public void layerAdded(Layer newLayer) {
296 // Do nothing
297 }
298
299 @Override
300 public void layerRemoved(Layer oldLayer) {
301 if (oldLayer instanceof OsmDataLayer) {
302 clear(((OsmDataLayer) oldLayer).data);
303 }
304 }
305
306 @Override
307 public void projectionChanged(Projection oldValue, Projection newValue) {
308 clear();
309 }
310
311 @Override
312 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
313
314 for (Iterator<PolyData> it = selectedPolyData.iterator(); it.hasNext();) {
315 it.next().selected = false;
316 it.remove();
317 }
318
319 DataSet ds = null;
320 Collection<Map<Relation, Multipolygon>> maps = null;
321 for (OsmPrimitive p : newSelection) {
322 if (p instanceof Way && p.getDataSet() != null) {
323 if (ds == null) {
324 ds = p.getDataSet();
325 }
326 for (OsmPrimitive ref : p.getReferrers()) {
327 if (isMultipolygon(ref)) {
328 if (maps == null) {
329 maps = getMapsFor(ds);
330 }
331 for (Map<Relation, Multipolygon> map : maps) {
332 Multipolygon multipolygon = map.get(ref);
333 if (multipolygon != null) {
334 for (PolyData pd : multipolygon.getCombinedPolygons()) {
335 if (pd.getWayIds().contains(p.getUniqueId())) {
336 pd.selected = true;
337 selectedPolyData.add(pd);
338 }
339 }
340 }
341 }
342 }
343 }
344 }
345 }
346 }
347}
Note: See TracBrowser for help on using the repository browser.