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

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

checkstyle: redundant modifiers

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