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

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

see #15229 - deprecate all Main methods related to projections. New ProjectionRegistry class

  • Property svn:eol-style set to native
File size: 12.4 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 static boolean isMultipolygon(OsmPrimitive p) {
133 return p instanceof Relation && ((Relation) p).isMultipolygon();
134 }
135
136 private void updateMultipolygonsReferringTo(AbstractDatasetChangedEvent event) {
137 updateMultipolygonsReferringTo(event, event.getPrimitives(), event.getDataset());
138 }
139
140 private void updateMultipolygonsReferringTo(
141 final AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives, DataSet ds) {
142 updateMultipolygonsReferringTo(event, primitives, ds, null);
143 }
144
145 private Collection<Map<Relation, Multipolygon>> updateMultipolygonsReferringTo(
146 AbstractDatasetChangedEvent event, Collection<? extends OsmPrimitive> primitives,
147 DataSet ds, Collection<Map<Relation, Multipolygon>> initialMaps) {
148 Collection<Map<Relation, Multipolygon>> maps = initialMaps;
149 if (primitives != null) {
150 for (OsmPrimitive p : primitives) {
151 if (isMultipolygon(p)) {
152 if (maps == null) {
153 maps = getMapsFor(ds);
154 }
155 processEvent(event, (Relation) p, maps);
156
157 } else if (p instanceof Way && p.getDataSet() != null) {
158 for (OsmPrimitive ref : p.getReferrers()) {
159 if (isMultipolygon(ref)) {
160 if (maps == null) {
161 maps = getMapsFor(ds);
162 }
163 processEvent(event, (Relation) ref, maps);
164 }
165 }
166 } else if (p instanceof Node && p.getDataSet() != null) {
167 maps = updateMultipolygonsReferringTo(event, p.getReferrers(), ds, maps);
168 }
169 }
170 }
171 return maps;
172 }
173
174 private static void processEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
175 if (event instanceof NodeMovedEvent || event instanceof WayNodesChangedEvent) {
176 dispatchEvent(event, r, maps);
177 } else if (event instanceof PrimitivesRemovedEvent) {
178 if (event.getPrimitives().contains(r)) {
179 removeMultipolygonFrom(r, maps);
180 }
181 } else {
182 // Default (non-optimal) action: remove multipolygon from cache
183 removeMultipolygonFrom(r, maps);
184 }
185 }
186
187 private static void dispatchEvent(AbstractDatasetChangedEvent event, Relation r, Collection<Map<Relation, Multipolygon>> maps) {
188 for (Map<Relation, Multipolygon> map : maps) {
189 Multipolygon m = map.get(r);
190 if (m != null) {
191 for (PolyData pd : m.getCombinedPolygons()) {
192 if (event instanceof NodeMovedEvent) {
193 pd.nodeMoved((NodeMovedEvent) event);
194 } else if (event instanceof WayNodesChangedEvent) {
195 final boolean oldClosedStatus = pd.isClosed();
196 pd.wayNodesChanged((WayNodesChangedEvent) event);
197 if (pd.isClosed() != oldClosedStatus) {
198 removeMultipolygonFrom(r, maps); // see ticket #13591
199 return;
200 }
201 }
202 }
203 }
204 }
205 }
206
207 private static void removeMultipolygonFrom(Relation r, Collection<Map<Relation, Multipolygon>> maps) {
208 for (Map<Relation, Multipolygon> map : maps) {
209 map.remove(r);
210 }
211 // Erase style cache for polygon members
212 for (OsmPrimitive member : r.getMemberPrimitivesList()) {
213 member.clearCachedStyle();
214 }
215 }
216
217 @Override
218 public void primitivesAdded(PrimitivesAddedEvent event) {
219 // Do nothing
220 }
221
222 @Override
223 public void primitivesRemoved(PrimitivesRemovedEvent event) {
224 updateMultipolygonsReferringTo(event);
225 }
226
227 @Override
228 public void tagsChanged(TagsChangedEvent event) {
229 updateMultipolygonsReferringTo(event);
230 }
231
232 @Override
233 public void nodeMoved(NodeMovedEvent event) {
234 updateMultipolygonsReferringTo(event);
235 }
236
237 @Override
238 public void wayNodesChanged(WayNodesChangedEvent event) {
239 updateMultipolygonsReferringTo(event);
240 }
241
242 @Override
243 public void relationMembersChanged(RelationMembersChangedEvent event) {
244 updateMultipolygonsReferringTo(event);
245 }
246
247 @Override
248 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
249 // Do nothing
250 }
251
252 @Override
253 public void dataChanged(DataChangedEvent event) {
254 // Do not call updateMultipolygonsReferringTo as getPrimitives()
255 // can return all the data set primitives for this event
256 Collection<Map<Relation, Multipolygon>> maps = null;
257 for (OsmPrimitive p : event.getPrimitives()) {
258 if (isMultipolygon(p)) {
259 if (maps == null) {
260 maps = getMapsFor(event.getDataset());
261 }
262 for (Map<Relation, Multipolygon> map : maps) {
263 // DataChangedEvent is sent after downloading incomplete members (see #7131),
264 // without having received RelationMembersChangedEvent or PrimitivesAddedEvent
265 // OR when undoing a move of a large number of nodes (see #7195),
266 // without having received NodeMovedEvent
267 // This ensures concerned multipolygons will be correctly redrawn
268 map.remove(p);
269 }
270 }
271 }
272 }
273
274 @Override
275 public void layerAdded(LayerAddEvent e) {
276 // Do nothing
277 }
278
279 @Override
280 public void layerOrderChanged(LayerOrderChangeEvent e) {
281 // Do nothing
282 }
283
284 @Override
285 public void layerRemoving(LayerRemoveEvent e) {
286 if (e.getRemovedLayer() instanceof OsmDataLayer) {
287 clear(((OsmDataLayer) e.getRemovedLayer()).data);
288 }
289 }
290
291 @Override
292 public void projectionChanged(Projection oldValue, Projection newValue) {
293 clear();
294 }
295
296 @Override
297 public synchronized void selectionChanged(SelectionChangeEvent event) {
298
299 for (Iterator<PolyData> it = selectedPolyData.iterator(); it.hasNext();) {
300 it.next().setSelected(false);
301 it.remove();
302 }
303
304 DataSet ds = null;
305 Collection<Map<Relation, Multipolygon>> maps = null;
306 for (OsmPrimitive p : event.getSelection()) {
307 if (p instanceof Way && p.getDataSet() != null) {
308 if (ds == null) {
309 ds = p.getDataSet();
310 }
311 for (OsmPrimitive ref : p.getReferrers()) {
312 if (isMultipolygon(ref)) {
313 if (maps == null) {
314 maps = getMapsFor(ds);
315 }
316 for (Map<Relation, Multipolygon> map : maps) {
317 Multipolygon multipolygon = map.get(ref);
318 if (multipolygon != null) {
319 for (PolyData pd : multipolygon.getCombinedPolygons()) {
320 if (pd.getWayIds().contains(p.getUniqueId())) {
321 pd.setSelected(true);
322 selectedPolyData.add(pd);
323 }
324 }
325 }
326 }
327 }
328 }
329 }
330 }
331 }
332}
Note: See TracBrowser for help on using the repository browser.