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

Contents of /branches/2.0-RC2/src/skrueger/geotools/selection/FeatureMapLayerSelectionSynchronizer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 621 - (show annotations)
Thu Jan 28 10:06:05 2010 UTC (15 years, 1 month ago) by alfonx
File MIME type: text/plain
File size: 17132 byte(s)
2.0-RC2 ist für die weiterentwicklung und soll bald in den trunk mergen
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.map.event.FeatureSelectedEvent;
73 import schmitzm.geotools.map.event.JMapPaneListener;
74 import schmitzm.geotools.map.event.MapPaneEvent;
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 fids.add(FilterUtil.FILTER_FAC2.featureId(fid));
266 }
267
268 selectionFTStyle.rules().get(0).setFilter(
269 FilterUtil.FILTER_FAC2.id(fids));
270 }
271
272 // Maybe there has already been another selection
273 // FeatureTypeStyle... Let's replace it...
274 boolean foundAndReplaced = false;
275 for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
276 if (fts.getName() != null
277 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
278 foundAndReplaced = true;
279 fts.rules().clear();
280 fts.rules().addAll(selectionFTStyle.rules());
281 break;
282 }
283 }
284 if (!foundAndReplaced) {
285 originalStyle.featureTypeStyles().add(selectionFTStyle);
286 }
287
288 // Refresh the map
289 // mapPane.refresh();
290
291 DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
292 dsv.visit(originalStyle);
293 Style newStyle = (Style) dsv.getCopy();
294
295 // SK-Debug
296 try {
297 //
298 StylingUtil.saveStyleToSLD(newStyle, new File(
299 "/home/stefan/Desktop/selection.sld"));
300 } catch (Exception e) {
301 }
302
303 // DuplicatingStyleVisitor dsv = new DuplicatingStyleVisitor();
304 // dsv.visit(originalStyle);
305 // Style newStyle = originalStyle;
306
307 // Style newStyle = StylingUtil.STYLE_BUILDER.createStyle();
308 // newStyle.featureTypeStyles().addAll(originalStyle.featureTypeStyles());
309 mapLayer.setStyle(newStyle);
310
311 replaceRenderer();
312 }
313
314 } catch (Exception e) {
315 LOGGER.error("Error while trying to create a selection style", e);
316 }
317 }
318
319 /**
320 * Analyzes whether the selection has changed in comparison to the selection
321 * stored in the mapLayer.Style
322 *
323 * @param newSelection a List<String> of all newly selected FIDs
324 * @param originalStyle the original {@link Style} that has an earlier selection coded into the {@link Style}
325 * @return <code>true</code> if changed.
326 */
327 private boolean selectionChanged(Vector<String> newSelection,
328 Style originalStyle) {
329
330 boolean SELECTION_STYLING_foundInMapStyle = false;
331
332 /**
333 * testing, whether the selection really changed. If not, we can save
334 * one rendering!
335 */
336 for (FeatureTypeStyle fts : originalStyle.featureTypeStyles()) {
337
338 if (fts.getName() != null
339 && fts.getName().equals(SELECTION_STYLING_FTS_NAME)) {
340
341 SELECTION_STYLING_foundInMapStyle = true;
342
343 Filter filter = fts.rules().get(0).getFilter();
344 if (filter instanceof Not) {
345 FidFilterImpl antiOrigFidsFilter = (FidFilterImpl) ((Not) filter)
346 .getFilter();
347
348 // Check one way
349 final Set<String> antiFids = antiOrigFidsFilter
350 .getFidsSet();
351
352 if (antiFids.isEmpty() && !newSelection.isEmpty())
353 return true;
354
355 for (String fid : antiFids) {
356 if (newSelection.contains(fid)) {
357 return true;
358 }
359 }
360
361 // Check the other way
362 for (String fid : newSelection) {
363 if (antiFids.contains(fid)) {
364 return true;
365 }
366 }
367
368 if (antiFids.size() + newSelection.size() != styledLayer
369 .getFeatureCollectionFiltered().size())
370 return true;
371
372 } else {
373 FidFilterImpl origFidsFilter = (FidFilterImpl) filter;
374
375 // Check one way
376 final Set<String> fids = origFidsFilter.getFidsSet();
377
378 if (fids.isEmpty() && newSelection.isEmpty())
379 return false;
380 if (fids.size() != newSelection.size())
381 return true;
382
383 for (String fid : fids) {
384 if (!newSelection.contains(fid)) {
385 return true;
386 }
387 }
388
389 // Check the other way
390 for (String fid : newSelection) {
391 if (!fids.contains(fid)) {
392 return true;
393 }
394 }
395
396 }
397
398 break;
399 }
400 }
401
402 if (!SELECTION_STYLING_foundInMapStyle && !newSelection.isEmpty())
403 return true;
404
405 return false;
406 }
407
408 /**
409 * Replaces the local renderer
410 */
411 private void replaceRenderer() {
412 }
413
414 /**
415 * Used to synchronize {@link FeatureSelectedEvent}s with the
416 * {@link StyledFeatureLayerSelectionModel}
417 */
418 @Override
419 public void performMapPaneEvent(MapPaneEvent e) {
420
421 // Ignore event if it is caused by us or the synchronizer is disabled.
422 if (!isEnabled() || selectionChangeCausedByMe)
423 return;
424
425 if (!(e instanceof FeatureSelectedEvent)) {
426 // LOGGER.debug("Ignoring event " + e);
427 return;
428 }
429
430 /**
431 * Only listen to FeatureSelectedEvents if an appropriate tool is
432 * selected.
433 */
434 final int selectedTool = toolBar.getSelectedTool();
435 if (selectedTool != MapPaneToolBar.TOOL_SELECTION_ADD
436 && selectedTool != MapPaneToolBar.TOOL_SELECTION_REMOVE
437 && selectedTool != MapPaneToolBar.TOOL_SELECTION_SET) {
438 return;
439 }
440
441 // only listen to events directly coming from JMapPane
442 // selection (ignore selections from FilterDialog)
443 if (e.getSourceObject() != this.mapPane)
444 return;
445
446 /**
447 * Checking, that the FeatureSelectedEvent actually contains features
448 * from this layer
449 */
450 FeatureSelectedEvent fse = (FeatureSelectedEvent) e;
451 final String sourceID = fse.getSourceLayer().getTitle();
452 final String syncForID = mapLayer.getTitle();
453 if (sourceID != null && syncForID != null
454 && !sourceID.equals(syncForID)) {
455 LOGGER.debug("Ignoring a FeatureSelectedEvent from " + sourceID);
456 return;
457 }
458
459 // LOGGER.debug("do event " + fse);
460
461 // Avoid event circles in propertyChange(..)
462 selectionChangeCausedByMe = true;
463
464 final FeatureCollection<SimpleFeatureType, SimpleFeature> selectionResult = fse
465 .getSelectionResult();
466 Iterator<SimpleFeature> fi = selectionResult.iterator();
467 try {
468
469 // reset the selection of the DpLayerSelectionModel
470 // layerSelModel.setValueIsAdjusting(true);
471
472 if (selectedTool == MapPaneToolBar.TOOL_SELECTION_ADD) {
473 layerSelModel.setValueIsAdjusting(true);
474
475 for (int fIdx = 0; fi.hasNext(); fIdx++) {
476 SimpleFeature f = fi.next();
477 layerSelModel.addSelection(f.getID());
478 }
479 layerSelModel.setValueIsAdjusting(false);
480 } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_SET) {
481 layerSelModel.setValueIsAdjusting(true);
482 layerSelModel.clearSelection();
483
484 for (int fIdx = 0; fi.hasNext(); fIdx++) {
485 SimpleFeature f = fi.next();
486 layerSelModel.addSelection(f.getID());
487 }
488
489 // LOGGER.debug("Setting selection to " + fi.());
490
491 layerSelModel.setValueIsAdjusting(false);
492 } else if (selectedTool == MapPaneToolBar.TOOL_SELECTION_REMOVE) {
493 layerSelModel.setValueIsAdjusting(true);
494 for (int fIdx = 0; fi.hasNext(); fIdx++) {
495 SimpleFeature f = fi.next();
496 layerSelModel.removeSelection(f.getID());
497 }
498 layerSelModel.setValueIsAdjusting(false);
499 }
500
501 } finally {
502 selectionResult.close(fi);
503 }
504
505 // Show selected features in the map, which is not automatically done by
506 // the origin: FeatureSelectedEvent
507 changeLayerStyle(layerSelModel.getSelection());
508
509 // Danger of event circles in propertyChange(..) banned
510 selectionChangeCausedByMe = false;
511 }
512
513 }

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