/[schmitzm]/branches/1.0-gt2-2.6/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java
ViewVC logotype

Contents of /branches/1.0-gt2-2.6/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 559 - (show annotations)
Tue Nov 24 14:12:41 2009 UTC (15 years, 3 months ago) by alfonx
File MIME type: text/plain
File size: 17210 byte(s)
bugix: Under some conditions removing parts of a big selection has not been painted in the mappane

1 /*******************************************************************************
2 * Copyright (c) 2009 Martin O. J. Schmitz.
3 *
4 * This file is part of the SCHMITZM library - a collection of utility
5 * classes based on Java 1.6, focusing (not only) on Java Swing
6 * and the Geotools library.
7 *
8 * The SCHMITZM project is hosted at:
9 * http://wald.intevation.org/projects/schmitzm/
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * as published by the Free Software Foundation; either version 3
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License (license.txt)
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 * or try this link: http://www.gnu.org/licenses/lgpl.html
25 *
26 * Contributors:
27 * Martin O. J. Schmitz - initial API and implementation
28 * Stefan A. Krüger - additional utility classes
29 ******************************************************************************/
30 /**
31 Copyright 2008 Stefan Alfons Krüger
32
33 atlas-framework - This file is part of the Atlas Framework
34
35 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
36 This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
37 You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
38
39 Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
40 Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
41 Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
42 **/
43 package skrueger.geotools.selection;
44
45 import java.awt.RenderingHints;
46 import java.beans.PropertyChangeEvent;
47 import java.beans.PropertyChangeListener;
48 import java.io.File;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.Iterator;
52 import java.util.Set;
53 import java.util.Vector;
54
55 import javax.swing.JTable;
56 import javax.swing.ListSelectionModel;
57 import javax.swing.event.ListSelectionListener;
58
59 import org.geotools.feature.FeatureCollection;
60 import org.geotools.feature.FeatureIterator;
61 import org.geotools.filter.FidFilterImpl;
62 import org.geotools.map.MapLayer;
63 import org.geotools.renderer.GTRenderer;
64 import org.geotools.renderer.label.LabelCacheImpl;
65 import org.geotools.renderer.lite.StreamingRenderer;
66 import org.geotools.styling.FeatureTypeStyle;
67 import org.geotools.styling.Style;
68 import org.geotools.styling.visitor.DuplicatingStyleVisitor;
69 import org.opengis.feature.simple.SimpleFeature;
70 import org.opengis.feature.simple.SimpleFeatureType;
71 import org.opengis.filter.Filter;
72 import org.opengis.filter.Not;
73 import org.opengis.filter.identity.FeatureId;
74
75 import schmitzm.geotools.FilterUtil;
76 import schmitzm.geotools.GTUtil;
77 import schmitzm.geotools.gui.SelectableXMapPane;
78 import schmitzm.geotools.map.event.FeatureSelectedEvent;
79 import schmitzm.geotools.map.event.JMapPaneListener;
80 import schmitzm.geotools.map.event.MapPaneEvent;
81 import schmitzm.geotools.styling.StylingUtil;
82 import skrueger.geotools.MapPaneToolBar;
83 import skrueger.geotools.StyledFeaturesInterface;
84
85 /**
86 * This class keeps the selection of a (feature) {@link JTable} synchronized
87 * with the {@link StyledLayerSelectionModel} of a layer. This is done by
88 * implementing:
89 * <ul>
90 * <li>a {@link PropertyChangeListener} which listens to the
91 * {@link StyledLayerSelectionModel} and accordingly changes the {@link JTable}
92 * selection</li>
93 * <li>a {@link ListSelectionListener} which listens to the {@link JTable} and
94 * accordingly changes the {@link StyledLayerSelectionModel} selection</li>
95 * </ul>
96 * After creating, the instance of this synchronizer must be added as listener
97 * to both, the {@link StyledLayerSelectionModel} and the table's
98 * {@link ListSelectionModel}.
99 *
100 * @author <a href="mailto:[email protected]">Martin Schmitz</a>
101 * (University of Bonn/Germany)
102 */
103 public class FeatureMapLayerSelectionSynchronizer extends
104 StyledLayerSelectionModelSynchronizer<StyledFeatureLayerSelectionModel>
105 implements JMapPaneListener {
106
107 /**
108 * This constant is set as the {@link FeatureTypeStyle#getName()} attribute
109 * in the {@link FeatureTypeStyle}s that only exist to present the selected
110 * features
111 **/
112 public static final String SELECTION_STYLING_FTS_NAME = "SELECTION";
113
114 /**
115 * Holds the {@link MapLayer} to keep synchronized with the layer selection
116 * model.
117 */
118 protected final MapLayer mapLayer;
119 protected final StyledFeaturesInterface<?> styledLayer;
120 protected final SelectableXMapPane mapPane;
121 private final MapPaneToolBar toolBar;
122
123 /**
124 * Creates a new synchronizer
125 *
126 * @param layerSelModel
127 * layer selection model to keep synchronized with the
128 * {@link MapLayer}
129 *
130 * @param mapLayer
131 * {@link MapLayer} to keep synchronized with.
132 */
133 public FeatureMapLayerSelectionSynchronizer(
134 StyledFeatureLayerSelectionModel layerSelModel,
135 StyledFeaturesInterface<?> styledLayer, MapLayer mapLayer,
136 SelectableXMapPane mapPane, MapPaneToolBar toolBar) {
137
138 super(layerSelModel);
139 this.styledLayer = styledLayer;
140
141 this.mapLayer = mapLayer;
142 this.mapPane = mapPane;
143 this.toolBar = toolBar;
144 }
145
146 /**
147 * Called by {@link StyledLayerSelectionModel} when a the selection on other
148 * selection components (map, chart, ...) has changed. When calling this
149 *
150 * method changes the {@link MapLayer} selection according to the
151 * {@link StyledLayerSelectionModel} selection.
152 *
153 * @param evt
154 * an event
155 */
156 @Override
157 public void propertyChange(PropertyChangeEvent evt) {
158
159 if (!isEnabled())
160 return;
161
162 if (!(evt instanceof StyledLayerSelectionEvent))
163 return;
164 StyledLayerSelectionEvent selEvt = (StyledLayerSelectionEvent) evt;
165 // Only react on own layer and ignore events invoked by
166 // this component itself
167 if (selEvt.getEmitter() != layerSelModel || selectionChangeCausedByMe)
168 return;
169 // Apply new selection on table (except this event is one of
170 // some more events)
171 Vector<String> newSelection = layerSelModel.getSelection();
172 if (newSelection == null)
173 return;
174
175 // Avoid event circles in valueChanged(..)
176 selectionChangeCausedByMe = true;
177
178 changeLayerStyle(newSelection);
179
180 // Danger of event circles in valueChanged(..) banned
181 selectionChangeCausedByMe = false;
182 }
183
184 /**
185 * Changes the Style of the {@link MapLayer} to reflect changes of the
186 * selection.
187 *
188 * @param newSelection
189 * A {@link Vector} of SimpleFeature-IDs that are selected.
190 */
191 private void changeLayerStyle(final Vector<String> newSelection) {
192 try {
193
194 Style originalStyle = mapLayer.getStyle();
195
196 if (newSelection.isEmpty()) {
197
198 // Check if the Style contains a SELECTION FTS
199
200 FeatureTypeStyle[] clone = originalStyle.featureTypeStyles()
201 .toArray(new FeatureTypeStyle[] {}).clone();
202
203 for (FeatureTypeStyle fts : clone) {
204 if (fts.getName() != null
205 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
206 originalStyle.featureTypeStyles().remove(fts);
207
208 mapLayer.setStyle(originalStyle);
209 // replaceRenderer();
210 // // mapPane.refresh();
211
212 return;
213 }
214 }
215
216 } else {
217
218 LOGGER.debug("SELECTION .. change style");
219
220 // Saving a repaint if the selection didn't change
221 if (!selectionChanged(newSelection, originalStyle)) {
222 return;
223 }
224
225 // We take Style from the MapLayer that is displayed at the
226 // moment. We do not use the styledLayer.getStyle, because in
227 // the atlas, this always return the default style, but
228 // additional styles might be selected.
229 // Taking the style from the mapLayer indicated, that we have to
230 // remove any selection rules first.
231
232 FeatureTypeStyle selectionFTStyle = StylingUtil
233 .createSelectionStyle(styledLayer.getGeoObject());
234
235 selectionFTStyle.setName(SELECTION_STYLING_FTS_NAME);
236
237 /**
238 *
239 * Add a Filter to the selectionMapStyle, so that it is only
240 * used on objects that are selected. <br/>
241 * To optimize rendering speed, the filter checks whether more
242 * than half of the features are selected. If so, the filter is
243 * inverted to be shorter.
244 *
245 */
246 final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollectionFiltered = styledLayer
247 .getFeatureCollectionFiltered();
248 if (newSelection.size() > featureCollectionFiltered.size() / 2) {
249 FeatureIterator<SimpleFeature> iterator = featureCollectionFiltered
250 .features();
251 Set<FeatureId> antiFids = new HashSet<FeatureId>();
252 try {
253 while (iterator.hasNext()) {
254 SimpleFeature next = iterator.next();
255 if (!newSelection.contains(next.getID()))
256 antiFids.add(FilterUtil.FILTER_FAC2
257 .featureId(next.getID()));
258 }
259
260 selectionFTStyle.rules().get(0).setFilter(
261 FilterUtil.FILTER_FAC2
262 .not(FilterUtil.FILTER_FAC2
263 .id(antiFids)));
264
265 } finally {
266 featureCollectionFiltered.close(iterator);
267 }
268 } else {
269 Set<FeatureId> fids = new HashSet<FeatureId>();
270 for (String fid : newSelection) {
271 fids.add(FilterUtil.FILTER_FAC2.featureId(fid));
272 }
273
274 selectionFTStyle.rules().get(0).setFilter(
275 FilterUtil.FILTER_FAC2.id(fids));
276 }
277
278 // Maybe there has already been another selection
279 // FeatureTypeStyle... Let's replace it...
280 boolean foundAndReplaced = false;
281 for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
282 if (fts.getName() != null
283 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
284 foundAndReplaced = true;
285 fts.rules().clear();
286 fts.rules().addAll(selectionFTStyle.rules());
287 break;
288 }
289 }
290 if (!foundAndReplaced) {
291 originalStyle.featureTypeStyles().add(selectionFTStyle);
292 }
293
294 // Refresh the map
295 // mapPane.refresh();
296
297 DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
298 dsv.visit(originalStyle);
299 Style newStyle = (Style) dsv.getCopy();
300
301 // SK-Debug
302 try {
303 //
304 StylingUtil.saveStyleToSLD(newStyle, new File(
305 "/home/stefan/Desktop/selection.sld"));
306 } catch (Exception e) {
307 }
308
309 // DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
310 // dsv.visit(originalStyle);
311 // Style newStyle = originalStyle;
312
313 // Style newStyle = StylingUtil.STYLE_BUILDER.createStyle();
314 // newStyle.featureTypeStyles().addAll(originalStyle.featureTypeStyles());
315 mapLayer.setStyle(newStyle);
316
317 replaceRenderer();
318 }
319
320 } catch (Exception e) {
321 LOGGER.error("Error while trying to create a selection style", e);
322 }
323 }
324
325 /**
326 * Analyses whether the selection has changed in comparison to the selection
327 * stored in the mapLayer.Style
328 *
329 * @param newSelection
330 * @param originalStyle
331 * @return
332 */
333 private boolean selectionChanged(Vector<String> newSelection,
334 Style originalStyle) {
335
336 boolean SELECTION_STYLING_foundInMapStyle = false;
337
338 /**
339 * testing, whether the selection really changed. If not, we can save
340 * one rendering!
341 */
342 for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
343
344 if (fts.getName() != null
345 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
346
347 SELECTION_STYLING_foundInMapStyle = true;
348
349 Filter filter = fts.rules().get(0).getFilter();
350 if (filter instanceof Not) {
351 FidFilterImpl antiOrigFidsFilter = (FidFilterImpl) ((Not) filter)
352 .getFilter();
353
354 // Check one way
355 final Set<String> antiFids = antiOrigFidsFilter
356 .getFidsSet();
357
358 if (antiFids.isEmpty() && !newSelection.isEmpty())
359 return true;
360
361 for (String fid : antiFids) {
362 if (newSelection.contains(fid)) {
363 return true;
364 }
365 }
366
367 // Check the other way
368 for (String fid : newSelection) {
369 if (antiFids.contains(fid)) {
370 return true;
371 }
372 }
373
374 if (antiFids.size() + newSelection.size() != styledLayer
375 .getFeatureCollectionFiltered().size())
376 return true;
377
378 } else {
379 FidFilterImpl origFidsFilter = (FidFilterImpl) filter;
380
381 // Check one way
382 final Set<String> fids = origFidsFilter.getFidsSet();
383
384 if (fids.isEmpty() && newSelection.isEmpty())
385 return false;
386 if (fids.size() != newSelection.size())
387 return true;
388
389 for (String fid : fids) {
390 if (!newSelection.contains(fid)) {
391 return true;
392 }
393 }
394
395 // Check the other way
396 for (String fid : newSelection) {
397 if (!fids.contains(fid)) {
398 return true;
399 }
400 }
401
402 }
403
404 break;
405 }
406 }
407
408 if (!SELECTION_STYLING_foundInMapStyle && !newSelection.isEmpty())
409 return true;
410
411 return false;
412 }
413
414 /**
415 * Replaces the local renderer
416 */
417 private void replaceRenderer() {
418 }
419
420 /**
421 * Used to synchronize {@link FeatureSelectedEvent}s with the
422 * {@link StyledFeatureLayerSelectionModel}
423 */
424 @Override
425 public void performMapPaneEvent(MapPaneEvent e) {
426
427 // Ignore event if it is caused by us or the synchronizer is disabled.
428 if (!isEnabled() || selectionChangeCausedByMe)
429 return;
430
431 if (!(e instanceof FeatureSelectedEvent)) {
432 // LOGGER.debug("Ignoring event " + e);
433 return;
434 }
435
436 /**
437 * Only listen to FeatureSelectedEvents if an appropriate tool is
438 * selected.
439 */
440 final int selectedTool = toolBar.getSelectedTool();
441 if (selectedTool != MapPaneToolBar.TOOL_SELECTION_ADD
442 && selectedTool != MapPaneToolBar.TOOL_SELECTION_REMOVE
443 && selectedTool != MapPaneToolBar.TOOL_SELECTION_SET) {
444 return;
445 }
446
447 // only listen to events directly coming from JMapPane
448 // selection (ignore selections from FilterDialog)
449 if (e.getSourceObject() != this.mapPane)
450 return;
451
452 /**
453 * Checking, that the FeatureSelectedEvent actually contains features
454 * from this layer
455 */
456 FeatureSelectedEvent fse = (FeatureSelectedEvent) e;
457 final String sourceID = fse.getSourceLayer().getTitle();
458 final String syncForID = mapLayer.getTitle();
459 if (sourceID != null && syncForID != null
460 && !sourceID.equals(syncForID)) {
461 LOGGER.debug("Ignoring a FeatureSelectedEvent from " + sourceID);
462 return;
463 }
464
465 // LOGGER.debug("do event " + fse);
466
467 // Avoid event circles in propertyChange(..)
468 selectionChangeCausedByMe = true;
469
470 final FeatureCollection<SimpleFeatureType, SimpleFeature> selectionResult = fse
471 .getSelectionResult();
472 Iterator<SimpleFeature> fi = selectionResult.iterator();
473 try {
474
475 // reset the selection of the DpLayerSelectionModel
476 // layerSelModel.setValueIsAdjusting(true);
477
478 if (selectedTool == MapPaneToolBar.TOOL_SELECTION_ADD) {
479 layerSelModel.setValueIsAdjusting(true);
480
481 for (int fIdx = 0; fi.hasNext(); fIdx++) {
482 SimpleFeature f = fi.next();
483 layerSelModel.addSelection(f.getID());
484 }
485 layerSelModel.setValueIsAdjusting(false);
486 } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_SET) {
487 layerSelModel.setValueIsAdjusting(true);
488 layerSelModel.clearSelection();
489
490 for (int fIdx = 0; fi.hasNext(); fIdx++) {
491 SimpleFeature f = fi.next();
492 layerSelModel.addSelection(f.getID());
493 }
494
495 // LOGGER.debug("Setting selection to " + fi.());
496
497 layerSelModel.setValueIsAdjusting(false);
498 } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_REMOVE) {
499 layerSelModel.setValueIsAdjusting(true);
500 for (int fIdx = 0; fi.hasNext(); fIdx++) {
501 SimpleFeature f = fi.next();
502 layerSelModel.removeSelection(f.getID());
503 }
504 layerSelModel.setValueIsAdjusting(false);
505 }
506
507 } finally {
508 selectionResult.close(fi);
509 }
510
511 // Show selected features in the map, which is not automatically done by
512 // the origin: FeatureSelectedEvent
513 changeLayerStyle(layerSelModel.getSelection());
514
515 // Danger of event circles in propertyChange(..) banned
516 selectionChangeCausedByMe = false;
517 }
518
519 }

Properties

Name Value
svn:eol-style native
svn:keywords Id
svn:mime-type text/plain

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26