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

Contents of /branches/2.1/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 807 - (show annotations)
Mon Apr 19 18:09:00 2010 UTC (14 years, 10 months ago) by alfonx
File MIME type: text/plain
File size: 17152 byte(s)
Aktuelle version wird als 2.1 gebranched, damit 2.2-SNAPSHOT auf GT2.6.3 umsteigen kann.

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.beans.PropertyChangeEvent;
46 import java.beans.PropertyChangeListener;
47 import java.io.File;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.Set;
51 import java.util.Vector;
52
53 import javax.swing.JTable;
54 import javax.swing.ListSelectionModel;
55 import javax.swing.event.ListSelectionListener;
56
57 import org.geotools.feature.FeatureCollection;
58 import org.geotools.feature.FeatureIterator;
59 import org.geotools.filter.FidFilterImpl;
60 import org.geotools.map.MapLayer;
61 import org.geotools.styling.FeatureTypeStyle;
62 import org.geotools.styling.Style;
63 import org.geotools.styling.visitor.DuplicatingStyleVisitor;
64 import org.opengis.feature.simple.SimpleFeature;
65 import org.opengis.feature.simple.SimpleFeatureType;
66 import org.opengis.filter.Filter;
67 import org.opengis.filter.Not;
68 import org.opengis.filter.identity.FeatureId;
69
70 import schmitzm.geotools.FilterUtil;
71 import schmitzm.geotools.gui.SelectableXMapPane;
72 import schmitzm.geotools.gui.XMapPaneEvent;
73 import schmitzm.geotools.map.event.FeatureSelectedEvent;
74 import schmitzm.geotools.map.event.JMapPaneListener;
75 import schmitzm.geotools.styling.StylingUtil;
76 import skrueger.geotools.MapPaneToolBar;
77 import skrueger.geotools.StyledFeaturesInterface;
78
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 StyledLayerSelectionModelSynchronizer<StyledFeatureLayerSelectionModel>
99 implements JMapPaneListener {
100
101 /**
102 * 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 * Holds the {@link MapLayer} to keep synchronized with the layer selection
110 * model.
111 */
112 protected final MapLayer mapLayer;
113 protected final StyledFeaturesInterface<?> styledLayer;
114 protected final SelectableXMapPane mapPane;
115 private final MapPaneToolBar toolBar;
116
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 StyledFeaturesInterface<?> styledLayer, MapLayer mapLayer,
130 SelectableXMapPane mapPane, MapPaneToolBar toolBar) {
131
132 super(layerSelModel);
133 this.styledLayer = styledLayer;
134
135 this.mapLayer = mapLayer;
136 this.mapPane = mapPane;
137 this.toolBar = toolBar;
138 }
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
153 if (!isEnabled())
154 return;
155
156 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 Vector<String> newSelection = layerSelModel.getSelection();
166 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 /**
179 * Changes the Style of the {@link MapLayer} to reflect changes of the
180 * selection.
181 *
182 * @param newSelection
183 * A {@link Vector} of SimpleFeature-IDs that are selected.
184 */
185 private void changeLayerStyle(final Vector<String> newSelection) {
186 try {
187
188 Style originalStyle = mapLayer.getStyle();
189
190 if (newSelection.isEmpty()) {
191
192 // Check if the Style contains a SELECTION FTS
193
194 FeatureTypeStyle[] clone = originalStyle.featureTypeStyles()
195 .toArray(new FeatureTypeStyle[] {}).clone();
196
197 for (FeatureTypeStyle fts : clone) {
198 if (fts.getName() != null
199 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
200 originalStyle.featureTypeStyles().remove(fts);
201
202 mapLayer.setStyle(originalStyle);
203 // replaceRenderer();
204 // // mapPane.refresh();
205
206 return;
207 }
208 }
209
210 } else {
211
212 LOGGER.debug("SELECTION .. change style");
213
214 // Saving a repaint if the selection didn't change
215 if (!selectionChanged(newSelection, originalStyle)) {
216 return;
217 }
218
219 // We take Style from the MapLayer that is displayed at the
220 // moment. We do not use the styledLayer.getStyle, because in
221 // the atlas, this always return the default style, but
222 // additional styles might be selected.
223 // Taking the style from the mapLayer indicated, that we have to
224 // remove any selection rules first.
225
226 FeatureTypeStyle selectionFTStyle = StylingUtil
227 .createSelectionStyle(styledLayer.getGeoObject());
228
229 selectionFTStyle.setName(SELECTION_STYLING_FTS_NAME);
230
231 /**
232 *
233 * Add a Filter to the selectionMapStyle, so that it is only
234 * used on objects that are selected. <br/>
235 * 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 *
239 */
240 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 if (fid != null)
266 fids.add(FilterUtil.FILTER_FAC2.featureId(fid));
267 }
268
269 selectionFTStyle.rules().get(0).setFilter(
270 FilterUtil.FILTER_FAC2.id(fids));
271 }
272
273 // 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 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
279 foundAndReplaced = true;
280 fts.rules().clear();
281 fts.rules().addAll(selectionFTStyle.rules());
282 break;
283 }
284 }
285 if (!foundAndReplaced) {
286 originalStyle.featureTypeStyles().add(selectionFTStyle);
287 }
288
289 // Refresh the map
290 // mapPane.refresh();
291
292 DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
293 dsv.visit(originalStyle);
294 Style newStyle = (Style) dsv.getCopy();
295
296 // SK-Debug
297 try {
298 //
299 StylingUtil.saveStyleToSLD(newStyle, new File(
300 "/home/stefan/Desktop/selection.sld"));
301 } catch (Exception e) {
302 }
303
304 // DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
305 // dsv.visit(originalStyle);
306 // Style newStyle = originalStyle;
307
308 // Style newStyle = StylingUtil.STYLE_BUILDER.createStyle();
309 // newStyle.featureTypeStyles().addAll(originalStyle.featureTypeStyles());
310 mapLayer.setStyle(newStyle);
311
312 replaceRenderer();
313 }
314
315 } catch (Exception e) {
316 LOGGER.error("Error while trying to create a selection style", e);
317 }
318 }
319
320 /**
321 * Analyzes whether the selection has changed in comparison to the selection
322 * stored in the mapLayer.Style
323 *
324 * @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 */
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 * one rendering!
336 */
337 for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
338
339 if (fts.getName() != null
340 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
341
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 if (antiFids.size() + newSelection.size() != styledLayer
370 .getFeatureCollectionFiltered().size())
371 return true;
372
373 } 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 /**
410 * Replaces the local renderer
411 */
412 private void replaceRenderer() {
413 }
414
415 /**
416 * Used to synchronize {@link FeatureSelectedEvent}s with the
417 * {@link StyledFeatureLayerSelectionModel}
418 */
419 @Override
420 public void performMapPaneEvent(XMapPaneEvent e) {
421
422 // 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 /**
432 * Only listen to FeatureSelectedEvents if an appropriate tool is
433 * 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 // only listen to events directly coming from JMapPane
443 // selection (ignore selections from FilterDialog)
444 if (e.getSourceObject() != this.mapPane)
445 return;
446
447 /**
448 * Checking, that the FeatureSelectedEvent actually contains features
449 * from this layer
450 */
451 FeatureSelectedEvent fse = (FeatureSelectedEvent) e;
452 final String sourceID = fse.getSourceLayer().getTitle();
453 final String syncForID = mapLayer.getTitle();
454 if (sourceID != null && syncForID != null
455 && !sourceID.equals(syncForID)) {
456 LOGGER.debug("Ignoring a FeatureSelectedEvent from " + sourceID);
457 return;
458 }
459
460 // LOGGER.debug("do event " + fse);
461
462 // Avoid event circles in propertyChange(..)
463 selectionChangeCausedByMe = true;
464
465 final FeatureCollection<SimpleFeatureType, SimpleFeature> selectionResult = fse
466 .getSelectionResult();
467 Iterator<SimpleFeature> fi = selectionResult.iterator();
468 try {
469
470 // reset the selection of the DpLayerSelectionModel
471 // layerSelModel.setValueIsAdjusting(true);
472
473 if (selectedTool == MapPaneToolBar.TOOL_SELECTION_ADD) {
474 layerSelModel.setValueIsAdjusting(true);
475
476 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
485 for (int fIdx = 0; fi.hasNext(); fIdx++) {
486 SimpleFeature f = fi.next();
487 layerSelModel.addSelection(f.getID());
488 }
489
490 // LOGGER.debug("Setting selection to " + fi.());
491
492 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 }
501
502 } finally {
503 selectionResult.close(fi);
504 }
505
506 // Show selected features in the map, which is not automatically done by
507 // the origin: FeatureSelectedEvent
508 changeLayerStyle(layerSelModel.getSelection());
509
510 // Danger of event circles in propertyChange(..) banned
511 selectionChangeCausedByMe = false;
512 }
513
514 }

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