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

Last change on this file since 11038 was 11038, checked in by simon04, 8 years ago

Use Relation.getMemberPrimitivesList where possible to avoid unnecessary set creation

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