View Javadoc
1   package fr.ifremer.dali.ui.swing.util.map;
2   
3   /*-
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (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 Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  // docs start source
27  /*
28   *    GeoTools - The Open Source Java GIS Toolkit
29   *    http://geotools.org
30   *
31   *    (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
32   *
33   *    This file is hereby placed into the Public Domain. This means anyone is
34   *    free to do whatever they wish with this file. Use it well and enjoy!
35   */
36  
37          import com.vividsolutions.jts.geom.LineString;
38  import com.vividsolutions.jts.geom.MultiLineString;
39  import com.vividsolutions.jts.geom.MultiPolygon;
40  import com.vividsolutions.jts.geom.Polygon;
41  import org.geotools.data.FileDataStore;
42  import org.geotools.data.FileDataStoreFinder;
43  import org.geotools.data.simple.SimpleFeatureCollection;
44  import org.geotools.data.simple.SimpleFeatureIterator;
45  import org.geotools.data.simple.SimpleFeatureSource;
46  import org.geotools.factory.CommonFactoryFinder;
47  import org.geotools.geometry.jts.ReferencedEnvelope;
48  import org.geotools.map.FeatureLayer;
49  import org.geotools.map.Layer;
50  import org.geotools.map.MapContent;
51  import org.geotools.styling.*;
52  import org.geotools.styling.Stroke;
53  import org.geotools.swing.JMapFrame;
54  import org.geotools.swing.data.JFileDataStoreChooser;
55  import org.geotools.swing.event.MapMouseEvent;
56  import org.geotools.swing.tool.CursorTool;
57  import org.opengis.feature.simple.SimpleFeature;
58  import org.opengis.feature.type.GeometryDescriptor;
59  import org.opengis.filter.Filter;
60  import org.opengis.filter.FilterFactory2;
61  import org.opengis.filter.identity.FeatureId;
62  
63  import javax.swing.*;
64  import java.awt.*;
65  import java.awt.geom.AffineTransform;
66  import java.awt.geom.Rectangle2D;
67  import java.io.File;
68  import java.util.HashSet;
69  import java.util.Set;
70  
71  /**
72   * In this example we create a map tool to select a feature clicked
73   * with the mouse. The selected feature will be painted yellow.
74   *
75   * @see <a href="http://svn.osgeo.org/geotools/trunk/demo/example/src/main/java/org/geotools/demo/SelectionLab.java">SelectionLab demo</a>
76   */
77  public class SelectionLab {
78  
79      /*
80       * Factories that we will use to create style and filter objects
81       */
82      private StyleFactory sf = CommonFactoryFinder.getStyleFactory();
83      private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
84  
85      /*
86       * Convenient constants for the type of feature geometry in the shapefile
87       */
88      private enum GeomType { POINT, LINE, POLYGON }
89  
90      /*
91       * Some default style variables
92       */
93      private static final Color LINE_COLOUR = Color.BLUE;
94      private static final Color FILL_COLOUR = Color.CYAN;
95      private static final Color SELECTED_COLOUR = Color.YELLOW;
96      private static final float OPACITY = 1.0f;
97      private static final float LINE_WIDTH = 1.0f;
98      private static final float POINT_SIZE = 10.0f;
99  
100     private JMapFrame mapFrame;
101     private SimpleFeatureSource featureSource;
102 
103     private String geometryAttributeName;
104     private GeomType geometryType;
105 
106     /*
107      * The application method
108      */
109     public static void main(String[] args) throws Exception {
110         SelectionLab me = new SelectionLab();
111 
112         File file = JFileDataStoreChooser.showOpenFile("shp", null);
113         if (file == null) {
114             return;
115         }
116 
117         me.displayShapefile(file);
118     }
119 // docs end main
120 
121 // docs start display shapefile
122     /**
123      * This method connects to the shapefile; retrieves information about
124      * its features; creates a map frame to display the shapefile and adds
125      * a custom feature selection tool to the toolbar of the map frame.
126      */
127     public void displayShapefile(File file) throws Exception {
128         FileDataStore store = FileDataStoreFinder.getDataStore(file);
129         featureSource = store.getFeatureSource();
130         setGeometry();
131 
132         /*
133          * Create the JMapFrame and set it to display the shapefile's features
134          * with a default line and colour style
135          */
136         MapContent map = new MapContent();
137         map.setTitle("Feature selection tool example");
138         Style style = createDefaultStyle();
139         Layer layer = new FeatureLayer(featureSource, style);
140         map.addLayer(layer);
141         mapFrame = new JMapFrame(map);
142         mapFrame.enableToolBar(true);
143         mapFrame.enableStatusBar(true);
144 
145         /*
146          * Before making the map frame visible we add a new button to its
147          * toolbar for our custom feature selection tool
148          */
149         JToolBar toolBar = mapFrame.getToolBar();
150         JButton btn = new JButton("Select");
151         toolBar.addSeparator();
152         toolBar.add(btn);
153 
154         /*
155          * When the user clicks the button we want to enable
156          * our custom feature selection tool. Since the only
157          * mouse action we are intersted in is 'clicked', and
158          * we are not creating control icons or cursors here,
159          * we can just create our tool as an anonymous sub-class
160          * of CursorTool.
161          */
162         btn.addActionListener(e -> mapFrame.getMapPane().setCursorTool(
163                 new CursorTool() {
164 
165                     @Override
166                     public void onMouseClicked(MapMouseEvent ev) {
167                         selectFeatures(ev);
168                     }
169                 }));
170 
171         /*
172          * Finally, we display the map frame. When it is closed
173          * this application will exit.
174          */
175         mapFrame.setSize(600, 600);
176         mapFrame.setVisible(true);
177     }
178 // docs end display shapefile
179 
180 // docs start select features
181     /**
182      * This method is called by our feature selection tool when
183      * the user has clicked on the map.
184      *
185      * @param ev the mouse event being handled
186      */
187     void selectFeatures(MapMouseEvent ev) {
188 
189         System.out.println("Mouse click at: " + ev.getMapPosition());
190 
191         /*
192          * Construct a 5x5 pixel rectangle centred on the mouse click position
193          */
194         Point screenPos = ev.getPoint();
195         Rectangle screenRect = new Rectangle(screenPos.x-2, screenPos.y-2, 5, 5);
196 
197         /*
198          * Transform the screen rectangle into bounding box in the coordinate
199          * reference system of our map context. Note: we are using a naive method
200          * here but GeoTools also offers other, more accurate methods.
201          */
202         AffineTransform screenToWorld = mapFrame.getMapPane().getScreenToWorldTransform();
203         Rectangle2D worldRect = screenToWorld.createTransformedShape(screenRect).getBounds2D();
204         ReferencedEnvelope bbox = new ReferencedEnvelope(
205                 worldRect,
206                 mapFrame.getMapContent().getCoordinateReferenceSystem());
207 
208         /*
209          * Create a Filter to select features that intersect with
210          * the bounding box
211          */
212         Filter filter = ff.intersects(ff.property(geometryAttributeName), ff.literal(bbox));
213 
214         /*
215          * Use the filter to identify the selected features
216          */
217         try {
218             SimpleFeatureCollection selectedFeatures =
219                     featureSource.getFeatures(filter);
220 
221             Set<FeatureId> IDs = new HashSet<>();
222             try (SimpleFeatureIterator iter = selectedFeatures.features()) {
223                 while (iter.hasNext()) {
224                     SimpleFeature feature = iter.next();
225                     IDs.add(feature.getIdentifier());
226 
227                     System.out.println("   " + feature.getIdentifier());
228                 }
229 
230             }
231 
232             if (IDs.isEmpty()) {
233                 System.out.println("   no feature selected");
234             }
235 
236             displaySelectedFeatures(IDs);
237 
238         } catch (Exception ex) {
239             ex.printStackTrace();
240         }
241     }
242 // docs end select features
243 
244 // docs start display selected
245     /**
246      * Sets the display to paint selected features yellow and
247      * unselected features in the default style.
248      *
249      * @param IDs identifiers of currently selected features
250      */
251     public void displaySelectedFeatures(Set<FeatureId> IDs) {
252         Style style;
253 
254         if (IDs.isEmpty()) {
255             style = createDefaultStyle();
256 
257         } else {
258             style = createSelectedStyle(IDs);
259         }
260 
261         Layer layer = mapFrame.getMapContent().layers().get(0);
262         ((FeatureLayer) layer).setStyle(style);
263         mapFrame.getMapPane().repaint();
264     }
265 // docs end display selected
266 
267 // docs start default style
268     /**
269      * Create a default Style for feature display
270      */
271     private Style createDefaultStyle() {
272         Rule rule = createRule(LINE_COLOUR, FILL_COLOUR);
273 
274         FeatureTypeStyle fts = sf.createFeatureTypeStyle();
275         fts.rules().add(rule);
276 
277         Style style = sf.createStyle();
278         style.featureTypeStyles().add(fts);
279         return style;
280     }
281 // docs end default style
282 
283 // docs start selected style
284     /**
285      * Create a Style where features with given IDs are painted
286      * yellow, while others are painted with the default colors.
287      */
288     private Style createSelectedStyle(Set<FeatureId> IDs) {
289         Rule selectedRule = createRule(SELECTED_COLOUR, SELECTED_COLOUR);
290         selectedRule.setFilter(ff.id(IDs));
291 
292         Rule otherRule = createRule(LINE_COLOUR, FILL_COLOUR);
293         otherRule.setElseFilter(true);
294 
295         FeatureTypeStyle fts = sf.createFeatureTypeStyle();
296         fts.rules().add(selectedRule);
297         fts.rules().add(otherRule);
298 
299         Style style = sf.createStyle();
300         style.featureTypeStyles().add(fts);
301         return style;
302     }
303 // docs end selected style
304 
305 // docs start create rule
306     /**
307      * Helper for createXXXStyle methods. Creates a new Rule containing
308      * a Symbolizer tailored to the geometry type of the features that
309      * we are displaying.
310      */
311     private Rule createRule(Color outlineColor, Color fillColor) {
312         Symbolizer symbolizer = null;
313         Fill fill = null;
314         Stroke stroke = sf.createStroke(ff.literal(outlineColor), ff.literal(LINE_WIDTH));
315 
316         switch (geometryType) {
317             case POLYGON:
318                 fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
319                 symbolizer = sf.createPolygonSymbolizer(stroke, fill, geometryAttributeName);
320                 break;
321 
322             case LINE:
323                 symbolizer = sf.createLineSymbolizer(stroke, geometryAttributeName);
324                 break;
325 
326             case POINT:
327                 fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
328 
329                 Mark mark = sf.getCircleMark();
330                 mark.setFill(fill);
331                 mark.setStroke(stroke);
332 
333                 Graphic graphic = sf.createDefaultGraphic();
334                 graphic.graphicalSymbols().clear();
335                 graphic.graphicalSymbols().add(mark);
336                 graphic.setSize(ff.literal(POINT_SIZE));
337 
338                 symbolizer = sf.createPointSymbolizer(graphic, geometryAttributeName);
339         }
340 
341         Rule rule = sf.createRule();
342         rule.symbolizers().add(symbolizer);
343         return rule;
344     }
345 // docs end create rule
346 
347 // docs start set geometry
348     /**
349      * Retrieve information about the feature geometry
350      */
351     private void setGeometry() {
352         GeometryDescriptor geomDesc = featureSource.getSchema().getGeometryDescriptor();
353         geometryAttributeName = geomDesc.getLocalName();
354 
355         Class<?> clazz = geomDesc.getType().getBinding();
356 
357         if (Polygon.class.isAssignableFrom(clazz) ||
358                 MultiPolygon.class.isAssignableFrom(clazz)) {
359             geometryType = GeomType.POLYGON;
360 
361         } else if (LineString.class.isAssignableFrom(clazz) ||
362                 MultiLineString.class.isAssignableFrom(clazz)) {
363 
364             geometryType = GeomType.LINE;
365 
366         } else {
367             geometryType = GeomType.POINT;
368         }
369 
370     }
371 // docs end set geometry
372 
373 }