/[schmitzm]/branches/2.0-GP14/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java
ViewVC logotype

Annotation of /branches/2.0-GP14/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 671 - (hide annotations)
Wed Feb 3 19:08:37 2010 UTC (15 years ago) by alfonx
Original Path: branches/2.0-RC2/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java
File MIME type: text/plain
File size: 17156 byte(s)


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

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