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

Last change on this file was 16421, checked in by simon04, 4 years ago

Use IPrimitive.isMultipolygon

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