View Javadoc
1   package fr.ifremer.dali.ui.swing.content.home.map;
2   
3   /*
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 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  import com.google.common.collect.Lists;
27  import com.google.common.collect.Sets;
28  import com.vividsolutions.jts.geom.Envelope;
29  import fr.ifremer.dali.decorator.DecoratorService;
30  import fr.ifremer.dali.dto.DaliBeans;
31  import fr.ifremer.dali.dto.data.sampling.SamplingOperationDTO;
32  import fr.ifremer.dali.dto.data.survey.SurveyDTO;
33  import fr.ifremer.dali.map.MapMode;
34  import fr.ifremer.dali.map.Maps;
35  import fr.ifremer.dali.service.DaliTechnicalException;
36  import fr.ifremer.dali.ui.swing.util.AbstractDaliUIHandler;
37  import fr.ifremer.dali.ui.swing.util.map.DataMapPane;
38  import fr.ifremer.dali.ui.swing.util.map.DataSelectionAdapter;
39  import fr.ifremer.dali.ui.swing.util.map.DataSelectionEvent;
40  import fr.ifremer.dali.ui.swing.util.map.layer.DataFeatureLayer;
41  import fr.ifremer.dali.ui.swing.util.map.layer.DataLayerCollection;
42  import fr.ifremer.dali.ui.swing.util.map.layer.InfoPanelLayer;
43  import fr.ifremer.dali.ui.swing.util.map.layer.LegendLayer;
44  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIModel;
45  import fr.ifremer.quadrige3.core.dao.technical.Dates;
46  import fr.ifremer.quadrige3.ui.swing.action.ActionFactory;
47  import jaxx.runtime.SwingUtil;
48  import org.apache.commons.collections4.CollectionUtils;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  import org.geotools.geometry.DirectPosition2D;
52  import org.geotools.geometry.jts.ReferencedEnvelope;
53  import org.geotools.map.Layer;
54  import org.geotools.map.MapContent;
55  import org.geotools.referencing.crs.DefaultGeographicCRS;
56  import org.geotools.referencing.operation.projection.ProjectionException;
57  import org.geotools.renderer.RenderListener;
58  import org.geotools.swing.event.MapMouseAdapter;
59  import org.geotools.swing.event.MapMouseEvent;
60  import org.geotools.swing.event.MapPaneAdapter;
61  import org.geotools.swing.event.MapPaneEvent;
62  import org.nuiton.jaxx.application.swing.util.Cancelable;
63  import org.opengis.feature.simple.SimpleFeature;
64  
65  import javax.swing.SwingWorker;
66  import java.beans.PropertyChangeListener;
67  import java.util.Collection;
68  import java.util.List;
69  import java.util.Optional;
70  import java.util.Set;
71  import java.util.concurrent.ExecutionException;
72  import java.util.concurrent.ThreadPoolExecutor;
73  
74  import static org.nuiton.i18n.I18n.t;
75  
76  /**
77   * Controleur pour la zone des prelevements de l'ecran d'accueil
78   */
79  public class SurveysMapUIHandler extends AbstractDaliUIHandler<SurveysMapUIModel, SurveysMapUI> implements Cancelable {
80  
81      /**
82       * Logger.
83       */
84      private static final Log LOG = LogFactory.getLog(SurveysMapUIHandler.class);
85      private ThreadPoolExecutor executor;
86      private PropertyChangeListener mapListener;
87  
88      /**
89       * {@inheritDoc}
90       */
91      @Override
92      public void beforeInit(final SurveysMapUI ui) {
93          super.beforeInit(ui);
94  
95          // create model and register to the JAXX context
96          final SurveysMapUIModel model = new SurveysMapUIModel();
97          ui.setContextValue(model);
98      }
99  
100     /**
101      * {@inheritDoc}
102      */
103     @Override
104     public void afterInit(SurveysMapUI ui) {
105 
106         initUI(ui);
107 
108 //        executor = ActionFactory.createSingleThreadExecutor(ActionFactory.ExecutionMode.LATEST);
109 
110         SurveysMapBuilder mapBuilder = new SurveysMapBuilder(getConfig());
111         ui.getMapPane().setMapBuilder(mapBuilder);
112         boolean checkOnline = mapBuilder.checkOnlineReachable();
113         ui.getOfflineButton().setEnabled(checkOnline);
114         ui.getOfflineButton().setSelected(!checkOnline);
115 
116         SwingUtil.setLayerUI(ui.getMapPane(), ui.getMapBlockLayer());
117 
118         initListeners();
119     }
120 
121     // Create thread executor for map loader (always execute the latest call)
122     private ThreadPoolExecutor getExecutor() {
123         if (executor == null || executor.isShutdown())
124             executor = ActionFactory.createSingleThreadExecutor(ActionFactory.ExecutionMode.LATEST);
125         return executor;
126     }
127 
128     private void shutdownExecutor() {
129         if (executor != null)
130             executor.shutdown();
131     }
132 
133     @Override
134     public void onCloseUI() {
135 
136 //        executor.shutdownNow();
137         shutdownExecutor();
138         getUI().getMapPane().clearMapContent();
139         super.onCloseUI();
140     }
141 
142     /**
143      * Initialisation des listeners.
144      */
145     private void initListeners() {
146 
147         getUI().getMapPane().addMapPaneListener(new MapPaneAdapter() {
148 
149             @Override
150             public void onRenderingStarted(MapPaneEvent ev) {
151                 getUI().getLoadingIndicator().setVisible(true);
152             }
153 
154             @Override
155             public void onRenderingStopped(MapPaneEvent ev) {
156                 getModel().setLoading(false);
157                 getUI().getLoadingIndicator().setVisible(false);
158             }
159 
160             @Override
161             public void onDisplayAreaChanged(MapPaneEvent ev) {
162                 ReferencedEnvelope envelope = (ReferencedEnvelope) ev.getData();
163                 checkVisibleEnvelope(Maps.transformReferencedEnvelope(envelope, DefaultGeographicCRS.WGS84));
164 
165                 // for test
166 //                DataMapPane map = getUI().getMapPane();
167 //                double scale = RendererUtilities.calculateOGCScale(envelope, map.getWidth(), map.getRenderer().getRendererHints());
168 //                getUI().getMapEnvelopeLabel().setText(envelope.toString() + " | scale=" + scale);
169             }
170         });
171 
172         getUI().getMapPane().addMouseListener(new MapMouseAdapter() {
173             @Override
174             public void onMouseEntered(MapMouseEvent ev) {
175                 displayCoordinates(ev.getWorldPos());
176             }
177 
178             @Override
179             public void onMouseExited(MapMouseEvent ev) {
180                 displayCoordinates(null);
181             }
182 
183             @Override
184             public void onMouseMoved(MapMouseEvent ev) {
185                 displayCoordinates(ev.getWorldPos());
186             }
187         });
188 
189         getUI().getMapPane().addDataSelectionListener(new DataSelectionAdapter() {
190 
191             boolean previouslySelected = false;
192 
193             @Override
194             public void onEmptySelection(DataSelectionEvent event) {
195                 if (previouslySelected) {
196 
197                     InfoPanelLayer infoPanelLayer = event.getSource().getInfoPanelLayer();
198                     if (infoPanelLayer != null) {
199                         infoPanelLayer.clear();
200                         event.getSource().redrawLayers();
201                     }
202                     previouslySelected = false;
203                 }
204             }
205 
206             @Override
207             public void onDataLayerSelected(DataSelectionEvent event) {
208 
209                 InfoPanelLayer infoPanelLayer = event.getSource().getInfoPanelLayer();
210                 if (infoPanelLayer == null) {
211                     // Don't process if info panel is not present
212                     return;
213                 }
214 
215                 // Filter layers on survey or operation and get the first one (if many selected at the same point)
216                 Optional<DataFeatureLayer> layerOptional = event.getSelectedDataLayers().stream().filter(
217                         layer -> SurveysMapBuilder.isSurveyLayer(layer) || SurveysMapBuilder.isOperationLayer(layer)).findFirst();
218                 if (!layerOptional.isPresent()) {
219                     infoPanelLayer.clear();
220                     event.getSource().redrawLayers();
221                     return;
222                 }
223                 DataFeatureLayer selectedLayer = layerOptional.get();
224 
225                 List<String> texts = Lists.newArrayList();
226 
227                 boolean isSurvey = SurveysMapBuilder.isSurveyLayer(selectedLayer);
228                 boolean isOperation = SurveysMapBuilder.isOperationLayer(selectedLayer);
229 
230                 if (isSurvey || isOperation) {
231 
232                     Integer surveyId = isSurvey ? selectedLayer.getId() : ((SurveyLayerCollection) selectedLayer.getDataLayerCollection()).getSurveyId();
233                     if (surveyId != null) {
234                         SurveyDTO survey = DaliBeans.findById(getModel().getSelectedSurveys(), surveyId);
235                         if (survey != null) {
236 
237                             // Build texts
238                             texts.add(String.format("%s: %s", t("dali.property.program"), decorate(survey.getProgram())));
239                             texts.add(String.format("%s: %s", t("dali.property.location"), decorate(survey.getLocation())));
240                             texts.add(String.format("%s: %s", t("dali.property.date"), Dates.formatDate(survey.getDate(), getConfig().getDateFormat())));
241                             texts.add(String.format("%s: %s", t("dali.property.mnemonic"), survey.getName()));
242 
243                             if (isOperation) {
244                                 Integer operationId = selectedLayer.getId();
245                                 if (operationId != null) {
246                                     SamplingOperationDTO operation = DaliBeans.findById(survey.getSamplingOperations(), operationId);
247                                     if (operation != null) {
248 
249                                         // Add operation text
250                                         texts.add(String.format("%s: %s", t("dali.property.samplingOperation"), decorate(operation, DecoratorService.CONCAT)));
251 
252                                     } else {
253                                         if (LOG.isWarnEnabled()) {
254                                             LOG.warn(String.format("The operation with id=%s should be found in survey's operations list", operationId));
255                                             return;
256                                         }
257                                     }
258 
259                                 } else {
260                                     // impossible to build operation info
261                                 }
262                             }
263 
264                         } else {
265                             if (LOG.isWarnEnabled()) {
266                                 LOG.warn(String.format("The survey with id=%s should be found in table", surveyId));
267                                 return;
268                             }
269                         }
270 
271                     } else {
272                         // impossible to build survey info
273                     }
274 
275                 } else {
276 
277                     // Not implemented
278                 }
279 
280 //                LOG.info(String.format("selected survey: %s at %s coordinate %s", event.getSelectedDataLayers().stream()
281 //                        .filter(dataLayer -> dataLayer.getDataLayerCollection() != null)
282 //                        .map(dataLayer -> ((SurveyLayerCollection) dataLayer.getDataLayerCollection()).getSurveyId()).collect(Collectors.toSet()),
283 //                        event.getScreenPoint(), event.getWorldPos()));
284 
285                 if (CollectionUtils.isNotEmpty(texts)) {
286 
287                     infoPanelLayer.setInfo(event.getScreenPoint(), texts);
288                     event.getSource().redrawLayers();
289                     previouslySelected = true;
290                 }
291             }
292         });
293 
294         getUI().getMapPane().getRenderer().addRenderListener(new RenderListener() {
295             @Override
296             public void featureRenderer(SimpleFeature feature) {
297 
298             }
299 
300             @Override
301             public void errorOccurred(Exception e) {
302                 if (e.getCause() instanceof ProjectionException)
303                     getUI().getMapPane().fixFullExtend();
304             }
305         });
306     }
307 
308     private void displayCoordinates(DirectPosition2D p) {
309         DirectPosition2D position = Maps.transformDirectPosition(p, DefaultGeographicCRS.WGS84);
310         getUI().getMapPositionLabel().setText(position != null ? String.format("[ %.4f, %.4f ]", position.getX(), position.getY()) : "[ ]");
311     }
312 
313     private void checkVisibleEnvelope(ReferencedEnvelope visibleEnvelope) {
314 
315         // If visible envelope does not include the data envelope, show Warning
316         ReferencedEnvelope overallEnvelope = getUI().getMapPane().getOverallEnvelope();
317         if (!overallEnvelope.isEmpty() && !visibleEnvelope.contains((Envelope) overallEnvelope)) {
318             getUI().getInfoLabel().setText(t("dali.home.map.warning.dataOutsideEnvelope"));
319         } else {
320             getUI().getInfoLabel().setText(null);
321         }
322     }
323 
324     public void openMap() {
325 
326         if (getModel().getSelectionModel() != null) {
327             getModel().getSelectionModel().addPropertyChangeListener(AbstractDaliTableUIModel.PROPERTY_SELECTED_ROWS, getMapListener());
328         }
329         buildMapPositions();
330         displayCoordinates(null);
331     }
332 
333     public void closeMap() {
334 
335         getUI().getMapPane().clearMapContent();
336 
337         if (getModel().getSelectionModel() != null) {
338             getModel().getSelectionModel().removePropertyChangeListener(AbstractDaliTableUIModel.PROPERTY_SELECTED_ROWS, getMapListener());
339         }
340     }
341 
342     private PropertyChangeListener getMapListener() {
343         if (mapListener == null) {
344             mapListener = evt -> SurveysMapUIHandler.this.buildMapPositions();
345         }
346         return mapListener;
347     }
348 
349     public void buildMapPositions() {
350 
351         // TEST
352 //        mapBuilder.setForceOffline(true);
353 
354         // don't build map if selection change is disabled (eg table sorting)
355         if (!getModel().isBuildMapOnSelectionChanged()) return;
356 
357         Collection<? extends SurveyDTO> selectedSurveys = getModel().getSelectedSurveys();
358 
359         if (selectedSurveys.size() > getConfig().getMaxSurveyInMap()) {
360             getContext().getDialogHelper().showWarningDialog(t("dali.home.map.warning.tooManySurveys", getConfig().getMaxSurveyInMap()));
361             return;
362         }
363 
364         getModel().setLoading(true);
365         getExecutor().execute(new MapLoader(selectedSurveys));
366         LOG.debug("MapLoader ask execute");
367     }
368 
369     public void zoomFit() {
370 
371         // get overall envelope of data
372         ReferencedEnvelope displayEnvelope = getUI().getMapPane().getOverallDisplayEnvelope();
373         if (!displayEnvelope.isEmpty()) {
374             getUI().getMapPane().setDisplayArea(displayEnvelope);
375         }
376     }
377 
378     public void toggleOffline() {
379 
380         boolean selected = getUI().getOfflineButton().isSelected();
381         getModel().setMapMode(selected ? MapMode.EMBEDDED_SHAPE_MAP_MODE : null);
382         buildMapPositions();
383 
384     }
385 
386     public void toggleGraticule() {
387 
388         getUI().getMapPane().toggleGraticuleVisibility();
389 
390     }
391 
392     public void toggleLegend() {
393 
394         getUI().getMapPane().toggleLegendVisibility();
395 
396     }
397 
398     public void fullScreen() {
399 
400         getUI().getFullScreenButton().setVisible(false);
401 
402         getModel().getParentUIModel().fireOpenFullScreenEvent();
403 
404     }
405 
406     @Override
407     public void cancel() {
408 
409         closeMap();
410 
411         getUI().getFullScreenButton().setVisible(true);
412 
413         getModel().getParentUIModel().fireCloseFullScreenEvent();
414 
415     }
416 
417     class MapLoader extends SwingWorker<MapContent, Void> {
418 
419         private final Collection<? extends SurveyDTO> selectedSurveys;
420 
421         MapLoader(Collection<? extends SurveyDTO> selectedSurveys) {
422             this.selectedSurveys = selectedSurveys;
423         }
424 
425         @Override
426         protected MapContent doInBackground() {
427 
428 //            Thread.sleep(100);
429 
430             if (LOG.isDebugEnabled()) LOG.debug("MapLoader BEGIN");
431 
432             DataMapPane mapPane = getUI().getMapPane();
433 
434             // dispose old content
435             mapPane.clearMapContent();
436 
437             // build new content
438             MapContent mapContent = mapPane.getMapBuilder().buildNewMapContent(getModel().getMapMode());
439 
440             mapPane.clearDataLayerCollections();
441 
442             boolean noData = false;
443 
444             if (CollectionUtils.isNotEmpty(selectedSurveys)) {
445 
446                 Set<Layer> allLayers = Sets.newLinkedHashSet();
447 
448                 // get Geometries from survey location, survey itself, and sampling operations
449                 for (SurveyDTO survey : selectedSurveys) {
450 
451                     // Prevent new line to be rendered without location
452                     if (selectedSurveys.size() == 1 && survey.getLocation() == null) {
453                         noData = true;
454                         break;
455                     }
456 
457                     // Skip unsaved survey
458                     // FIXME Not saved surveys can't be handle completely because survey id and operation ids are used in DataLayerCollection
459                     if (survey.getId() == null) {
460                         continue;
461                     }
462 
463                     // clone the survey to avoid concurrency access
464                     SurveyDTO clonedSurvey = getContext().getObservationService().duplicateSurvey(survey, false, true);
465                     clonedSurvey.setId(survey.getId());
466 
467                     getContext().getObservationService().loadSamplingOperationsFromSurvey(clonedSurvey, false);
468 
469                     // Create a layer collection for this survey
470                     DataLayerCollection dataLayerCollection;
471                     try {
472                         dataLayerCollection = mapPane.newDataLayerCollection(clonedSurvey);
473                     } catch (Exception e) {
474                         LOG.error(String.format("Error when create data layer collection for survey '%s'", decorate(clonedSurvey)), e);
475                         noData = true;
476                         break;
477                     }
478 
479                     List<Layer> dataLayers = null;
480 
481                     if (dataLayerCollection != null) {
482                         // Get layers
483                         dataLayers = dataLayerCollection.getLayers();
484                     }
485 
486                     if (CollectionUtils.isNotEmpty(dataLayers)) {
487                         // Add layers to new content
488                         allLayers.addAll(dataLayers);
489                     } else {
490                         // Maybe a problem
491                         if (LOG.isWarnEnabled()) {
492                             LOG.warn(String.format("No data layers built for survey '%s'", decorate(clonedSurvey)));
493                         }
494                     }
495                 }
496 
497                 if (CollectionUtils.isNotEmpty(allLayers)) {
498                     // Add all data layers
499                     mapContent.addLayers(allLayers);
500 
501                     // Add info panel layer
502                     mapContent.addLayer(new InfoPanelLayer());
503 
504                     // Add legend layer
505                     mapContent.addLayer(new LegendLayer());
506 
507                     // compute display area
508                     ReferencedEnvelope displayEnvelope = mapPane.getOverallDisplayEnvelope();
509                     if (!displayEnvelope.isEmpty()) {
510 
511                         // compute layer visibility at first render
512                         mapPane.setLayersVisible(mapContent.layers(), displayEnvelope);
513 
514                         // apply display area
515                         mapPane.setDisplayArea(displayEnvelope);
516                     }
517                 } else {
518                     noData = true;
519                 }
520 
521             } else {
522                 noData = true;
523             }
524 
525             if (noData) {
526                 mapPane.setLayersVisible(mapContent.layers(), mapPane.getDisplayArea());
527             }
528 
529             if (LOG.isDebugEnabled()) LOG.debug("MapLoader END");
530 
531             return mapContent;
532         }
533 
534         @Override
535         protected void done() {
536             try {
537                 if (isCancelled())
538                     return;
539 
540                 try {
541                     MapContent mapContent = get();
542 
543                     DataMapPane mapPane = getUI().getMapPane();
544 
545                     // fix Mantis #38358
546                     getUI().getOfflineButton().setEnabled(mapPane.getMapBuilder().checkOnlineReachable());
547                     getModel().setMapMode(mapPane.getMapBuilder().getCurrentMapMode());
548 
549                     mapPane.setMapContent(mapContent);
550 
551                     if (LOG.isDebugEnabled()) LOG.debug("MapLoader done OK");
552 
553                 } catch (InterruptedException | ExecutionException e) {
554                     throw new DaliTechnicalException(e);
555                 }
556 
557             } finally {
558                 getModel().setLoading(false);
559             }
560         }
561     }
562 
563 }