/[schmitzm]/trunk/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java
ViewVC logotype

Annotation of /trunk/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 509 - (hide annotations)
Thu Nov 5 08:51:33 2009 UTC (15 years, 3 months ago) by alfonx
Original Path: branches/1.0-gt2-2.6/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java
File MIME type: text/plain
File size: 17700 byte(s)
new XMapPane and SelectableMapPane to replace and restructure JMapPanes
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 509 import schmitzm.geotools.map.event.MapPaneEvent;
74 alfonx 97 import schmitzm.geotools.map.event.JMapPaneListener;
75     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 111 public static final String SELECTION_STYLING = "SELECTION";
101 alfonx 97 /**
102     * Holds the {@link MapLayer} to keep synchronized with the layer selection
103     * model.
104     */
105     protected final MapLayer mapLayer;
106 alfonx 489 protected final StyledFeaturesInterface<?> styledLayer;
107 alfonx 509 protected final SelectableXMapPane mapPane;
108 alfonx 111 private final MapPaneToolBar toolBar;
109 alfonx 97
110     /**
111     * Creates a new synchronizer
112     *
113     * @param layerSelModel
114     * layer selection model to keep synchronized with the
115     * {@link MapLayer}
116     *
117     * @param mapLayer
118     * {@link MapLayer} to keep synchronized with.
119     */
120     public FeatureMapLayerSelectionSynchronizer(
121     StyledFeatureLayerSelectionModel layerSelModel,
122 alfonx 489 StyledFeaturesInterface<?> styledLayer, MapLayer mapLayer,
123 alfonx 509 SelectableXMapPane mapPane, MapPaneToolBar toolBar) {
124 alfonx 97
125     super(layerSelModel);
126 alfonx 224 this.styledLayer = styledLayer;
127 alfonx 97
128     this.mapLayer = mapLayer;
129     this.mapPane = mapPane;
130 alfonx 111 this.toolBar = toolBar;
131 alfonx 97 }
132    
133     /**
134     * Called by {@link StyledLayerSelectionModel} when a the selection on other
135     * selection components (map, chart, ...) has changed. When calling this
136     *
137     * method changes the {@link MapLayer} selection according to the
138     * {@link StyledLayerSelectionModel} selection.
139     *
140     * @param evt
141     * an event
142     */
143     @Override
144     public void propertyChange(PropertyChangeEvent evt) {
145 alfonx 151
146     if (!isEnabled())
147     return;
148    
149 alfonx 97 if (!(evt instanceof StyledLayerSelectionEvent))
150     return;
151     StyledLayerSelectionEvent selEvt = (StyledLayerSelectionEvent) evt;
152     // Only react on own layer and ignore events invoked by
153     // this component itself
154     if (selEvt.getEmitter() != layerSelModel || selectionChangeCausedByMe)
155     return;
156     // Apply new selection on table (except this event is one of
157     // some more events)
158 alfonx 111 Vector<String> newSelection = layerSelModel.getSelection();
159 alfonx 97 if (newSelection == null)
160     return;
161    
162     // Avoid event circles in valueChanged(..)
163     selectionChangeCausedByMe = true;
164    
165     changeLayerStyle(newSelection);
166    
167     // Danger of event circles in valueChanged(..) banned
168     selectionChangeCausedByMe = false;
169     }
170    
171 alfonx 151 /**
172     * Changes the Style of the {@link MapLayer} to reflect changes of the
173     * selection.
174     *
175     * @param newSelection
176 alfonx 318 * A {@link Vector} of SimpleFeature-IDs that are selected.
177 alfonx 151 */
178 alfonx 111 private void changeLayerStyle(final Vector<String> newSelection) {
179 alfonx 97 try {
180 alfonx 455
181 alfonx 412 Style originalStyle = mapLayer.getStyle();
182 alfonx 500
183 alfonx 97 if (newSelection.isEmpty()) {
184    
185 alfonx 412 // Check if the Style contains a SELECTION FTS
186 alfonx 455
187     FeatureTypeStyle[] clone = originalStyle.featureTypeStyles()
188     .toArray(new FeatureTypeStyle[] {}).clone();
189    
190 alfonx 412 for (FeatureTypeStyle fts : clone) {
191     if (fts.getName() != null
192     && fts.getName().equals(SELECTION_STYLING)) {
193     originalStyle.featureTypeStyles().remove(fts);
194 alfonx 455
195 alfonx 419 replaceRenderer();
196 alfonx 455 // mapPane.refresh();
197 alfonx 106
198 alfonx 412 return;
199     }
200     }
201    
202 alfonx 97 } else {
203 alfonx 500
204 alfonx 100 LOGGER.debug("SELECTION .. change style");
205 alfonx 106
206 alfonx 500 // Saving a repaint if the selection didn't change
207     if (!selectionChanged(newSelection, originalStyle)) {
208     return;
209     }
210    
211 alfonx 123 // We take Style from the MapLayer that is displayed at the
212 alfonx 224 // moment. We do not use the styledLayer.getStyle, because in
213 alfonx 123 // the atlas, this always return the default style, but
214 alfonx 224 // additional styles might be selected.
215 alfonx 123 // Taking the style from the mapLayer indicated, that we have to
216 alfonx 125 // remove any selection rules first.
217 alfonx 101
218 alfonx 412 FeatureTypeStyle selectionFTStyle = StylingUtil
219 alfonx 224 .createSelectionStyle(styledLayer.getGeoObject());
220 alfonx 106
221 alfonx 412 selectionFTStyle.setName(SELECTION_STYLING);
222 alfonx 123
223 alfonx 158 /**
224     *
225     * Add a Filter to the selectionMapStyle, so that it is only
226     * used on objects that are selected. <br/>
227 alfonx 489 * To optimize rendering speed, the filter checks whether more
228     * than half of the features are selected. If so, the filter is
229     * inverted to be shorter.
230 alfonx 158 *
231     */
232 alfonx 489 final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollectionFiltered = styledLayer
233     .getFeatureCollectionFiltered();
234     if (newSelection.size() > featureCollectionFiltered.size() / 2) {
235     FeatureIterator<SimpleFeature> iterator = featureCollectionFiltered
236     .features();
237     Set<FeatureId> antiFids = new HashSet<FeatureId>();
238     try {
239     while (iterator.hasNext()) {
240     SimpleFeature next = iterator.next();
241     if (!newSelection.contains(next.getID()))
242     antiFids.add(FilterUtil.FILTER_FAC2
243     .featureId(next.getID()));
244     }
245    
246     selectionFTStyle.rules().get(0).setFilter(
247     FilterUtil.FILTER_FAC2
248     .not(FilterUtil.FILTER_FAC2
249     .id(antiFids)));
250    
251     } finally {
252     featureCollectionFiltered.close(iterator);
253     }
254     } else {
255     Set<FeatureId> fids = new HashSet<FeatureId>();
256     for (String fid : newSelection) {
257     fids.add(FilterUtil.FILTER_FAC2.featureId(fid));
258     }
259    
260     selectionFTStyle.rules().get(0).setFilter(
261     FilterUtil.FILTER_FAC2.id(fids));
262 alfonx 158 }
263 alfonx 106
264 alfonx 412 // Maybe there has already been another selection
265     // FeatureTypeStyle... Let's replace it...
266     boolean foundAndReplaced = false;
267     for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
268     if (fts.getName() != null
269     && fts.getName().equals(SELECTION_STYLING)) {
270     foundAndReplaced = true;
271     fts.rules().clear();
272     fts.rules().addAll(selectionFTStyle.rules());
273     break;
274     }
275 alfonx 123 }
276 alfonx 412 if (!foundAndReplaced) {
277     originalStyle.featureTypeStyles().add(selectionFTStyle);
278     }
279 alfonx 151
280 alfonx 412 // Refresh the map
281 alfonx 455 // mapPane.refresh();
282    
283 alfonx 419 DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
284     dsv.visit(originalStyle);
285 alfonx 455 Style newStyle = (Style) dsv.getCopy();
286 mojays 493
287 alfonx 500 // SK-Debug
288     try {
289     //
290     StylingUtil.saveStyleToSLD(newStyle, new File(
291     "/home/stefan/Desktop/selection.sld"));
292     } catch (Exception e) {
293     }
294 mojays 493
295     // DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
296 alfonx 489 // dsv.visit(originalStyle);
297     // Style newStyle = originalStyle;
298    
299 alfonx 455 // Style newStyle = StylingUtil.STYLE_BUILDER.createStyle();
300     // newStyle.featureTypeStyles().addAll(originalStyle.featureTypeStyles());
301 alfonx 419 mapLayer.setStyle(newStyle);
302 alfonx 455
303 alfonx 419 replaceRenderer();
304 alfonx 97 }
305    
306     } catch (Exception e) {
307     LOGGER.error("Error while trying to create a selection style", e);
308     }
309     }
310    
311 alfonx 500 /**
312     * Analyses whether the selection has changed in comparison to the selection
313     * stored in the mapLayer.Style
314     *
315     * @param newSelection
316     * @param originalStyle
317     * @return
318     */
319     private boolean selectionChanged(Vector<String> newSelection,
320     Style originalStyle) {
321    
322     boolean SELECTION_STYLING_foundInMapStyle = false;
323    
324     /**
325     * testing, whether the selection really changed. If not, we can save
326     * one paint!
327     */
328     for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
329    
330     if (fts.getName() != null
331     && fts.getName().equals(SELECTION_STYLING)) {
332    
333     SELECTION_STYLING_foundInMapStyle = true;
334    
335     Filter filter = fts.rules().get(0).getFilter();
336     if (filter instanceof Not) {
337     FidFilterImpl antiOrigFidsFilter = (FidFilterImpl) ((Not) filter)
338     .getFilter();
339    
340     // Check one way
341     final Set<String> antiFids = antiOrigFidsFilter
342     .getFidsSet();
343    
344     if (antiFids.isEmpty() && !newSelection.isEmpty())
345     return true;
346    
347     for (String fid : antiFids) {
348     if (newSelection.contains(fid)) {
349     return true;
350     }
351     }
352    
353     // Check the other way
354     for (String fid : newSelection) {
355     if (antiFids.contains(fid)) {
356     return true;
357     }
358     }
359    
360     } else {
361     FidFilterImpl origFidsFilter = (FidFilterImpl) filter;
362    
363     // Check one way
364     final Set<String> fids = origFidsFilter.getFidsSet();
365    
366     if (fids.isEmpty() && newSelection.isEmpty())
367     return false;
368     if (fids.size() != newSelection.size())
369     return true;
370    
371     for (String fid : fids) {
372     if (!newSelection.contains(fid)) {
373     return true;
374     }
375     }
376    
377     // Check the other way
378     for (String fid : newSelection) {
379     if (!fids.contains(fid)) {
380     return true;
381     }
382     }
383    
384     }
385    
386     break;
387     }
388     }
389    
390     if (!SELECTION_STYLING_foundInMapStyle && !newSelection.isEmpty())
391     return true;
392    
393     return false;
394     }
395    
396 alfonx 509 /**
397     * Replaces the local renderer
398     */
399 alfonx 419 private void replaceRenderer() {
400 alfonx 509 // //
401     // // // Has to be done before we apply the new Renderer
402     // // mapLayer.setStyle(style);
403     //
404     // GTRenderer oldRenderer = mapPane.getLocalRenderer();
405     //
406     // /**
407     // * Explicitly putting a new instance of LabelCacheDefault into the
408     // * renderer instance, so JMapPane doesn't reuse the old one. This is
409     // * very useful when changing the TextSymbolizer with AtlasStyler<br/>
410     // * SK 9.7.09: It's not enought to user LabelCache.clear(). We can not
411     // * reuse the old Renderer - better to create a new one!
412     // */
413     // final GTRenderer newRenderer = GTUtil.createGTRenderer();
414     //
415     // final HashMap<Object, Object> rendererHints = defaultGTRenderingHints;
416     // rendererHints.put(StreamingRenderer.LABEL_CACHE_KEY,
417     // new LabelCacheImpl());
418     //
419     // newRenderer.setRendererHints(rendererHints);
420     // mapPane.setLocalRenderer(newRenderer);
421     //
422     // if (oldRenderer != null) {
423     //
424     // RenderingHints java2DHints = oldRenderer.getJava2DHints();
425     // if (java2DHints != null) {
426     // newRenderer.setJava2DHints(java2DHints);
427     // }
428     //
429     // oldRenderer.setContext(null);
430     // oldRenderer = null;
431     // }
432 alfonx 419
433     mapPane.refresh();
434    
435     }
436    
437 alfonx 151 /**
438     * Used to synchronize {@link FeatureSelectedEvent}s with the
439     * {@link StyledFeatureLayerSelectionModel}
440     */
441 alfonx 97 @Override
442 alfonx 509 public void performMapPaneEvent(MapPaneEvent e) {
443 alfonx 106
444 alfonx 151 // Ignore event if it is caused by us or the synchronizer is disabled.
445     if (!isEnabled() || selectionChangeCausedByMe)
446     return;
447    
448     if (!(e instanceof FeatureSelectedEvent)) {
449     // LOGGER.debug("Ignoring event " + e);
450     return;
451     }
452    
453 alfonx 111 /**
454 alfonx 151 * Only listen to FeatureSelectedEvents if an appropriate tool is
455 alfonx 111 * selected.
456     */
457     final int selectedTool = toolBar.getSelectedTool();
458     if (selectedTool != MapPaneToolBar.TOOL_SELECTION_ADD
459     && selectedTool != MapPaneToolBar.TOOL_SELECTION_REMOVE
460     && selectedTool != MapPaneToolBar.TOOL_SELECTION_SET) {
461     return;
462     }
463    
464 mojays 109 // only listen to events directly coming from JMapPane
465     // selection (ignore selections from FilterDialog)
466 alfonx 111 if (e.getSourceObject() != this.mapPane)
467     return;
468    
469 alfonx 151 /**
470 alfonx 158 * Checking, that the FeatureSelectedEvent actually contains features
471     * from this layer
472 alfonx 151 */
473 alfonx 492 FeatureSelectedEvent fse = (FeatureSelectedEvent) e;
474 alfonx 151 final String sourceID = fse.getSourceLayer().getTitle();
475     final String syncForID = mapLayer.getTitle();
476     if (sourceID != null && syncForID != null
477 alfonx 158 && !sourceID.equals(syncForID)) {
478     LOGGER.debug("Ignoring a FeatureSelectedEvent from " + sourceID);
479 alfonx 97 return;
480 alfonx 151 }
481 alfonx 97
482 alfonx 489 // LOGGER.debug("do event " + fse);
483 alfonx 111
484 alfonx 97 // Avoid event circles in propertyChange(..)
485     selectionChangeCausedByMe = true;
486    
487 alfonx 455 final FeatureCollection<SimpleFeatureType, SimpleFeature> selectionResult = fse
488     .getSelectionResult();
489     Iterator<SimpleFeature> fi = selectionResult.iterator();
490     try {
491 alfonx 97
492 alfonx 455 // reset the selection of the DpLayerSelectionModel
493     // layerSelModel.setValueIsAdjusting(true);
494 alfonx 97
495 alfonx 455 if (selectedTool == MapPaneToolBar.TOOL_SELECTION_ADD) {
496     layerSelModel.setValueIsAdjusting(true);
497 alfonx 409
498 alfonx 455 for (int fIdx = 0; fi.hasNext(); fIdx++) {
499     SimpleFeature f = fi.next();
500     layerSelModel.addSelection(f.getID());
501     }
502     layerSelModel.setValueIsAdjusting(false);
503     } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_SET) {
504     layerSelModel.setValueIsAdjusting(true);
505     layerSelModel.clearSelection();
506 alfonx 97
507 alfonx 455 for (int fIdx = 0; fi.hasNext(); fIdx++) {
508     SimpleFeature f = fi.next();
509     layerSelModel.addSelection(f.getID());
510     }
511 alfonx 111
512 alfonx 455 // LOGGER.debug("Setting selection to " + fi.());
513 alfonx 409
514 alfonx 455 layerSelModel.setValueIsAdjusting(false);
515     } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_REMOVE) {
516     layerSelModel.setValueIsAdjusting(true);
517     for (int fIdx = 0; fi.hasNext(); fIdx++) {
518     SimpleFeature f = fi.next();
519     layerSelModel.removeSelection(f.getID());
520     }
521     layerSelModel.setValueIsAdjusting(false);
522 alfonx 111 }
523 alfonx 455
524     } finally {
525     selectionResult.close(fi);
526 alfonx 151 }
527 alfonx 111
528     // Show selected features in the map, which is not automatically done by
529     // the origin: FeatureSelectedEvent
530 alfonx 97 changeLayerStyle(layerSelModel.getSelection());
531 alfonx 106
532 alfonx 97 // Danger of event circles in propertyChange(..) banned
533     selectionChangeCausedByMe = false;
534     }
535 alfonx 151
536 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