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

Last change on this file since 13925 was 13925, checked in by Don-vip, 4 months ago

fix #13467 - use DataSelectionListener everywhere. Deprecate SelectionChangedListener

  • Property svn:eol-style set to native
File size: 12.3 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.osm.DataSelectionListener;
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.SelectionEventManager;
26import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
27import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
28import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
29import org.openstreetmap.josm.data.projection.Projection;
30import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
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        Main.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) {
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.