1 package fr.ifremer.dali.ui.swing.util.map;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.collect.Lists;
27 import fr.ifremer.dali.map.Maps;
28 import fr.ifremer.dali.ui.swing.util.map.layer.*;
29 import fr.ifremer.dali.ui.swing.util.map.layer.tile.MapTileLayer;
30 import org.apache.commons.collections4.CollectionUtils;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.geotools.geometry.jts.ReferencedEnvelope;
34 import org.geotools.map.Layer;
35 import org.geotools.map.MapContent;
36 import org.geotools.map.MapViewport;
37 import org.geotools.referencing.CRS;
38 import org.geotools.referencing.crs.DefaultGeographicCRS;
39 import org.geotools.renderer.GTRenderer;
40 import org.geotools.renderer.label.LabelCacheImpl;
41 import org.geotools.renderer.lite.LabelCache;
42 import org.geotools.renderer.lite.StreamingRenderer;
43 import org.geotools.swing.AbstractMapPane;
44 import org.geotools.swing.RenderingExecutor;
45 import org.geotools.swing.event.MapPaneAdapter;
46 import org.geotools.swing.event.MapPaneEvent;
47 import org.opengis.geometry.Envelope;
48
49 import java.awt.*;
50 import java.awt.event.ComponentListener;
51 import java.awt.image.BufferedImage;
52 import java.awt.image.ColorModel;
53 import java.awt.image.RenderedImage;
54 import java.awt.image.WritableRaster;
55 import java.util.List;
56 import java.util.*;
57
58
59
60
61
62
63 public class DataMapPane extends AbstractMapPane {
64
65 private static final Log LOG = LogFactory.getLog(DataMapPane.class);
66
67
68
69
70 public static final double ZOOM_STEP_RATIO = 0.3;
71
72 private GTRenderer renderer;
73 private BufferedImage baseImage;
74 private Graphics2D baseImageGraphics;
75
76
77 private BufferedImage interstitialImage;
78 private Rectangle interstitialRectangle = new Rectangle();
79 private boolean drawInterstitialImageOnce;
80 private boolean busy;
81 private boolean newContentPending;
82 private boolean graticuleVisible = true;
83 private boolean legendVisible = false;
84
85
86 private MapBuilder mapBuilder;
87
88
89 private final List<DataLayerCollection> dataLayerCollections;
90 private final Set<DataSelectionListener> selectionListeners = new HashSet<>();
91
92
93
94
95 public DataMapPane() {
96 this(null);
97 }
98
99
100
101
102
103
104
105 public DataMapPane(MapContent content) {
106 this(content, null, null);
107 }
108
109
110
111
112
113
114
115
116 public DataMapPane(MapContent content, RenderingExecutor executor, GTRenderer renderer) {
117 super(content, executor);
118 doSetRenderer(renderer);
119 setDoubleBuffered(true);
120
121 dataLayerCollections = Lists.newArrayList();
122
123
124 setCursorTool(new MultipleCursorTool(this));
125
126
127 addMapPaneListener(new MapPaneAdapter() {
128 @Override
129 public void onDisplayAreaChanged(MapPaneEvent ev) {
130 ReferencedEnvelope newDisplayArea = (ReferencedEnvelope) ev.getData();
131 pendingDisplayArea = newDisplayArea;
132
133 if (!newContentPending) {
134 setLayersVisible(newDisplayArea);
135 }
136 }
137
138 @Override
139 public void onRenderingStarted(MapPaneEvent ev) {
140 busy = true;
141 }
142
143 @Override
144 public void onRenderingStopped(MapPaneEvent ev) {
145 busy = false;
146 }
147 });
148 }
149
150 public MapBuilder getMapBuilder() {
151 return mapBuilder;
152 }
153
154 public void setMapBuilder(MapBuilder mapBuilder) {
155 this.mapBuilder = mapBuilder;
156
157
158 setBackground(mapBuilder.getBackgroundColor());
159 }
160
161 public List<DataLayerCollection> getDataLayerCollections() {
162 return dataLayerCollections;
163 }
164
165 public void clearDataLayerCollections() {
166 dataLayerCollections.clear();
167 }
168
169 public DataLayerCollection newDataLayerCollection(Object dataObject) throws Exception {
170
171 DataLayerCollection dataLayerCollection = mapBuilder.buildDataLayerCollection(dataObject);
172 dataLayerCollections.add(dataLayerCollection);
173 return dataLayerCollection;
174
175 }
176
177
178
179
180
181
182 public ReferencedEnvelope getOverallEnvelope() {
183 ReferencedEnvelope overallEnvelope = new ReferencedEnvelope(
184 !CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, getMapBuilder().getTargetCRS())
185 ? DefaultGeographicCRS.WGS84
186 : null);
187 for (DataLayerCollection dataLayerCollection : getDataLayerCollections()) {
188 overallEnvelope.expandToInclude(dataLayerCollection.getEnvelope());
189 }
190 return overallEnvelope;
191 }
192
193
194
195
196 public ReferencedEnvelope getOverallDisplayEnvelope() {
197 ReferencedEnvelope envelope = getOverallEnvelope();
198 if (!envelope.isEmpty()) {
199
200 if (envelope.getHeight() == 0 && envelope.getWidth() == 0)
201 envelope.expandBy(0.05);
202 else
203 envelope.expandBy(Math.max(envelope.getHeight(), envelope.getWidth()) * 0.1);
204 }
205 return envelope;
206 }
207
208 public void addDataSelectionListener(DataSelectionListener listener) {
209 if (listener == null) {
210 throw new IllegalArgumentException("listener must not be null");
211 }
212 selectionListeners.add(listener);
213 }
214
215 public void removeDataSelectionListener(DataSelectionListener listener) {
216 if (listener != null) selectionListeners.remove(listener);
217 }
218
219 void publishSelectionEvent(DataSelectionEvent event) {
220 for (DataSelectionListener listener : selectionListeners) {
221 switch (event.getType()) {
222 case DATA_LAYER_SELECTED:
223 listener.onDataLayerSelected(event);
224 break;
225 case EMPTY_SELECTION:
226 listener.onEmptySelection(event);
227 break;
228 }
229 }
230 }
231
232
233
234
235
236 public void clearMapContent() {
237 if (getMapContent() != null) {
238 getMapContent().dispose();
239 setMapContent(null);
240 }
241 }
242
243
244
245
246
247
248 @Override
249 public void setMapContent(MapContent content) {
250
251
252 paramsLock.writeLock().lock();
253 try {
254 doSetMapContent(content);
255
256 } finally {
257 paramsLock.writeLock().unlock();
258 }
259
260 if (content != null && getRenderer() != null) {
261
262
263
264 if (getRenderer().getMapContent() != content) {
265 getRenderer().setMapContent(mapContent);
266 }
267 }
268 }
269
270
271
272
273
274
275 private void doSetMapContent(MapContent newMapContent) {
276 if (mapContent != newMapContent) {
277
278 newContentPending = true;
279
280 if (mapContent != null) {
281 mapContent.removeMapLayerListListener(this);
282 for (Layer layer : mapContent.layers()) {
283 if (layer instanceof ComponentListener) {
284 removeComponentListener((ComponentListener) layer);
285 }
286 }
287 }
288
289 mapContent = newMapContent;
290
291 if (mapContent != null) {
292 MapViewport viewport = mapContent.getViewport();
293 viewport.setMatchingAspectRatio(true);
294 Rectangle rect = getVisibleRect();
295 if (!rect.isEmpty()) {
296 viewport.setScreenArea(rect);
297 }
298
299 mapContent.addMapLayerListListener(this);
300 mapContent.addMapBoundsListener(this);
301
302 if (!mapContent.layers().isEmpty()) {
303
304 for (Layer layer : mapContent.layers()) {
305 layer.setSelected(true);
306
307 if (layer instanceof ComponentListener) {
308 addComponentListener((ComponentListener) layer);
309 }
310
311
312 if (layer instanceof MapTileLayer) {
313 ((MapTileLayer) layer).setMapPane(this);
314 }
315 }
316
317 setFullExtent();
318
319
320 if (pendingDisplayArea == null) {
321 doSetDisplayArea(mapContent.getViewport().getBounds());
322 } else {
323
324
325 doSetDisplayArea(pendingDisplayArea);
326 }
327 }
328 }
329
330 MapPaneEvent event = new MapPaneEvent(this, MapPaneEvent.Type.NEW_MAPCONTENT, mapContent);
331 publishEvent(event);
332
333
334 clearLabelCache();
335
336 drawLayers(true);
337 newContentPending = false;
338 }
339 }
340
341 @Override
342 public void setDisplayArea(Envelope envelope) {
343
344 InfoPanelLayer infoPanelLayer = getInfoPanelLayer();
345 if (infoPanelLayer != null)
346 infoPanelLayer.clear();
347
348 imageOrigin.setLocation(0, 0);
349 if (!drawInterstitialImageOnce)
350 interstitialRectangle = new Rectangle();
351
352 super.setDisplayArea(envelope);
353 }
354
355 @Override
356 protected void doSetDisplayArea(Envelope envelope) {
357
358 if (envelope instanceof ReferencedEnvelope) {
359 ReferencedEnvelope referencedEnvelope = (ReferencedEnvelope) envelope;
360
361
362 referencedEnvelope = Maps.transformReferencedEnvelope(referencedEnvelope,
363 getMapContent() != null ? getMapContent().getCoordinateReferenceSystem() : getMapBuilder().getTargetCRS());
364
365
366 if (fullExtent != null) {
367
368 if (referencedEnvelope.getMinY() < fullExtent.getMinY() && referencedEnvelope.getMaxY() > fullExtent.getMaxY()
369 && referencedEnvelope.getMinX() < fullExtent.getMinX() && referencedEnvelope.getMaxX() > fullExtent.getMaxX()) {
370
371
372 referencedEnvelope = fullExtent;
373 if (LOG.isDebugEnabled()) LOG.debug("envelope reset to full extent");
374
375 } else {
376
377
378 if (referencedEnvelope.getMinY() < fullExtent.getMinY() && referencedEnvelope.getMaxY() < fullExtent.getMaxY()) {
379 referencedEnvelope.translate(0, Math.min(fullExtent.getMaxY() - referencedEnvelope.getMaxY(), fullExtent.getMinY() - referencedEnvelope.getMinY()));
380 if (LOG.isDebugEnabled()) LOG.debug("Y ↑");
381 }
382 if (referencedEnvelope.getMinY() > fullExtent.getMinY() && referencedEnvelope.getMaxY() > fullExtent.getMaxY()) {
383 referencedEnvelope.translate(0, Math.max(fullExtent.getMaxY() - referencedEnvelope.getMaxY(), fullExtent.getMinY() - referencedEnvelope.getMinY()));
384 if (LOG.isDebugEnabled()) LOG.debug("Y ↓");
385 }
386
387
388 if (referencedEnvelope.getMinX() < fullExtent.getMinX() && referencedEnvelope.getMaxX() < fullExtent.getMaxX()) {
389 referencedEnvelope.translate(Math.min(fullExtent.getMaxX() - referencedEnvelope.getMaxX(), fullExtent.getMinX() - referencedEnvelope.getMinX()), 0);
390 if (LOG.isDebugEnabled()) LOG.debug("← X");
391 }
392 if (referencedEnvelope.getMinX() > fullExtent.getMinX() && referencedEnvelope.getMaxX() > fullExtent.getMaxX()) {
393 referencedEnvelope.translate(Math.max(fullExtent.getMaxX() - referencedEnvelope.getMaxX(), fullExtent.getMinX() - referencedEnvelope.getMinX()), 0);
394 if (LOG.isDebugEnabled()) LOG.debug("X →");
395 }
396 }
397 }
398
399 super.doSetDisplayArea(referencedEnvelope);
400
401 } else {
402
403 super.doSetDisplayArea(envelope);
404 }
405
406 }
407
408 private void clearLabelCache() {
409 if (labelCache != null) {
410 labelCache.stop();
411 labelCache.clear();
412 }
413 }
414
415
416
417
418
419
420 public GTRenderer getRenderer() {
421 if (renderer == null) {
422 doSetRenderer(new StreamingRenderer());
423 }
424 return renderer;
425 }
426
427
428
429
430
431
432 public void setRenderer(GTRenderer renderer) {
433 doSetRenderer(renderer);
434 }
435
436 private void doSetRenderer(GTRenderer newRenderer) {
437 if (newRenderer != null) {
438 Map<Object, Object> hints = newRenderer.getRendererHints();
439 if (hints == null) {
440 hints = new HashMap<>();
441 }
442
443 if (newRenderer instanceof StreamingRenderer) {
444 if (hints.containsKey(StreamingRenderer.LABEL_CACHE_KEY)) {
445 labelCache = (LabelCache) hints.get(StreamingRenderer.LABEL_CACHE_KEY);
446 } else {
447 labelCache = new LabelCacheImpl();
448 hints.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
449 }
450
451
452 hints.put(StreamingRenderer.TEXT_RENDERING_KEY, StreamingRenderer.TEXT_RENDERING_OUTLINE);
453 }
454
455 Map<RenderingHints.Key, Object> j2dHintsMap = new HashMap<>();
456 j2dHintsMap.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
457 j2dHintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
458 RenderingHints j2dHints = new RenderingHints(j2dHintsMap);
459
460 newRenderer.setJava2DHints(j2dHints);
461 newRenderer.setRendererHints(hints);
462
463 if (mapContent != null) {
464 newRenderer.setMapContent(mapContent);
465 }
466 }
467
468 renderer = newRenderer;
469 }
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 public RenderedImage getBaseImage() {
486 return this.baseImage;
487 }
488
489 public boolean isBusy() {
490 return busy;
491 }
492
493 @Override
494 public void reset() {
495
496 super.reset();
497
498
499 pendingDisplayArea = null;
500 }
501
502 @Override
503 protected void onShownOrResized() {
504
505
506 if (mapContent == null) return;
507
508 super.onShownOrResized();
509
510 }
511
512 @Override
513 public void moveImage(int dx, int dy) {
514 drawingLock.lock();
515 try {
516 if (isShowing() && !getVisibleRect().isEmpty()) {
517
518 if (interstitialRectangle.isEmpty()) {
519 interstitialRectangle = getMapContent().getViewport().getScreenArea().getBounds();
520 }
521
522 interstitialRectangle.translate(dx, dy);
523 drawInterstitialImageOnce = false;
524 repaint();
525 }
526
527 } finally {
528 drawingLock.unlock();
529 }
530 }
531
532 public void zoomIn() {
533 applyZoom(1 + ZOOM_STEP_RATIO, null);
534 }
535
536 public void zoomOut() {
537 applyZoom(1 - ZOOM_STEP_RATIO, null);
538 }
539
540 public void applyZoom(double zoomRatio, Point zoomCenter) {
541
542
543 ReferencedEnvelope displayArea = getDisplayArea();
544 Rectangle bounds = getMapContent().getViewport().getScreenArea();
545
546
547 double deltaWidth = displayArea.getWidth() * (zoomRatio - 1);
548 double deltaHeight = displayArea.getHeight() * (zoomRatio - 1);
549
550 int deltaViewWidth = (int) ((bounds.width * (zoomRatio * 1.1 - 1)));
551 int deltaViewHeight = (int) ((bounds.height * (zoomRatio * 1.1 - 1)));
552
553
554 double ratioLeft = zoomCenter != null ? zoomCenter.getX() * 1d / getWidth() : 0.5;
555 double ratioTop = zoomCenter != null ? 1 - (zoomCenter.getY() * 1d / getHeight()) : 0.5;
556
557
558 double deltaLeft = deltaWidth * ratioLeft;
559 double deltaRight = deltaLeft - deltaWidth;
560 int deltaViewLeft = (int) (deltaViewWidth * ratioLeft);
561
562
563 double deltaTop = deltaHeight * ratioTop;
564 double deltaBottom = deltaTop - deltaHeight;
565 int deltaViewTop = (int) (deltaViewHeight * (1 - ratioTop));
566
567 if (LOG.isDebugEnabled()) {
568 LOG.debug(String.format("Map mouse zoom (zoom ratio : %s, deltaLeft : %s, deltaRight : %s, deltaTop : %s, deltaBottom : %s)",
569 zoomRatio, deltaLeft, deltaRight, deltaRight, deltaBottom));
570 }
571
572
573 ReferencedEnvelope newDisplayArea = new ReferencedEnvelope(
574 displayArea.getMinX() + deltaLeft,
575 displayArea.getMaxX() + deltaRight,
576 displayArea.getMinY() + deltaTop,
577 displayArea.getMaxY() + deltaBottom,
578 displayArea.getCoordinateReferenceSystem()
579 );
580
581
582 Rectangle newViewBounds = new Rectangle(
583 bounds.x - deltaViewLeft,
584 bounds.y - deltaViewTop,
585 bounds.width + deltaViewWidth,
586 bounds.height + deltaViewHeight
587 );
588
589 resizeImage(newViewBounds);
590
591
592 setDisplayArea(newDisplayArea);
593
594 }
595
596
597
598
599
600
601 public void resizeImage(Rectangle rectangle) {
602 if (isShowing() && !getVisibleRect().isEmpty()
603
604 && mapBuilder.getCurrentMapMode().isOnline()) {
605
606
607 interstitialRectangle.setBounds(rectangle);
608 drawInterstitialImageOnce = true;
609 repaint();
610 }
611 }
612
613 @Override
614 protected void paintComponent(Graphics g) {
615 if (drawingLock.tryLock()) {
616 try {
617 Graphics2D g2 = (Graphics2D) g;
618
619
620 if (interstitialImage != null && interstitialImage.getWidth() > 0 && !interstitialRectangle.isEmpty()) {
621
622
623 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
624 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
625
626
627 g2.drawImage(interstitialImage, interstitialRectangle.x, interstitialRectangle.y, interstitialRectangle.width, interstitialRectangle.height, null);
628
629
630 if (drawInterstitialImageOnce)
631 interstitialRectangle = new Rectangle();
632
633 } else
634
635
636 if (baseImage != null) {
637
638
639 g2.drawImage(baseImage, imageOrigin.x, imageOrigin.y, null);
640
641
642 interstitialImage = deepCopy(baseImage);
643
644 }
645 } finally {
646 drawingLock.unlock();
647 }
648 }
649 }
650
651 public void redrawLayers() {
652 redrawLayers(false);
653 }
654
655 public void redrawLayers(boolean createNewImage) {
656 if (mapContent != null) {
657 clearLabelCache.set(true);
658 if (createNewImage) clearLabelCache();
659 drawLayers(createNewImage);
660 }
661 }
662
663
664
665
666
667
668 @Override
669 protected void drawLayers(boolean createNewImage) {
670 drawingLock.lock();
671 try {
672 if (mapContent != null
673 && !mapContent.getViewport().isEmpty()
674 && acceptRepaintRequests.get()) {
675
676 Rectangle r = getVisibleRect();
677 if (baseImage == null || createNewImage) {
678 baseImage = GraphicsEnvironment.getLocalGraphicsEnvironment().
679 getDefaultScreenDevice().getDefaultConfiguration().
680 createCompatibleImage(r.width, r.height, Transparency.TRANSLUCENT);
681
682 if (baseImageGraphics != null) {
683 baseImageGraphics.dispose();
684 }
685
686 baseImageGraphics = baseImage.createGraphics();
687 clearLabelCache.set(true);
688
689 }
690
691 if (baseImageGraphics != null) {
692 baseImageGraphics.setBackground(getBackground());
693
694
695 if (mapBuilder.getCurrentMapMode().isTransparent()
696
697 || isCurrentDisplayAreaOverFullExtend()) {
698
699 baseImageGraphics.clearRect(0, 0, r.width, r.height);
700 }
701 }
702
703 if (mapContent != null && !mapContent.layers().isEmpty()) {
704
705
706 getRenderingExecutor().submit(mapContent, getRenderer(), baseImageGraphics, this);
707 }
708 }
709 } finally {
710 drawingLock.unlock();
711 }
712 }
713
714 private boolean isCurrentDisplayAreaOverFullExtend() {
715 if (fullExtent != null) {
716 return !fullExtent.covers(getDisplayArea());
717 }
718 return false;
719 }
720
721 private static BufferedImage deepCopy(BufferedImage bi) {
722 ColorModel cm = bi.getColorModel();
723 boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
724 WritableRaster raster = bi.copyData(null);
725 return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
726 }
727
728
729
730
731
732
733 public void setLayersVisible(ReferencedEnvelope envelope) {
734 if (mapContent != null) {
735 setLayersVisible(mapContent.layers(), envelope);
736 }
737 }
738
739
740
741
742
743
744
745 public void setLayersVisible(List<Layer> layers, ReferencedEnvelope envelope) {
746
747 if (CollectionUtils.isNotEmpty(layers)) {
748 List<String> displayableLayerNames = mapBuilder.getDisplayableLayerNames(envelope);
749 for (Layer layer : layers) {
750 if (layer instanceof MapLayer) {
751
752 layer.setVisible(displayableLayerNames.contains(layer.getTitle()));
753 } else if (layer instanceof GraticuleLayer) {
754
755 layer.setVisible(isGraticuleVisible());
756 } else if (layer instanceof LegendLayer) {
757
758 layer.setVisible(isLegendVisible());
759 } else {
760
761 layer.setVisible(true);
762 }
763 }
764 }
765 }
766
767
768
769
770 public void toggleGraticuleVisibility() {
771 graticuleVisible = !graticuleVisible;
772
773 if (getMapContent() != null) {
774 for (Layer layer : getMapContent().layers()) {
775 if (layer instanceof GraticuleLayer) {
776 layer.setVisible(graticuleVisible);
777 }
778 }
779 }
780
781 }
782
783
784
785
786 public boolean isGraticuleVisible() {
787 return graticuleVisible;
788 }
789
790
791
792
793 public void toggleLegendVisibility() {
794 legendVisible = !legendVisible;
795
796 if (getMapContent() != null) {
797 for (Layer layer : getMapContent().layers()) {
798 if (layer instanceof LegendLayer) {
799 layer.setVisible(legendVisible);
800 }
801 }
802 }
803
804 }
805
806
807
808
809 public boolean isLegendVisible() {
810 return legendVisible;
811 }
812
813 public InfoPanelLayer getInfoPanelLayer() {
814 if (mapContent != null) {
815 for (Layer layer : mapContent.layers()) {
816 if (layer instanceof InfoPanelLayer) return (InfoPanelLayer) layer;
817 }
818 }
819 return null;
820 }
821
822 public void fixFullExtend() {
823
824 getRenderer().stopRendering();
825
826 try {
827 Thread.sleep(1000);
828 } catch (InterruptedException ignored) {
829 }
830
831 zoomIn();
832
833 }
834 }