/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.view;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import org.openstreetmap.gui.jmapviewer.Coordinate;
import org.openstreetmap.gui.jmapviewer.DefaultMapController;
import org.openstreetmap.gui.jmapviewer.JMapViewer;
import org.openstreetmap.gui.jmapviewer.TileStoreLoader;
import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
import com.rgi.common.Dimensions;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfile;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfileFactory;
import com.rgi.store.tiles.TileStoreException;
import com.rgi.store.tiles.TileStoreReader;
/**
* View a supported tile store within a map viewer.
*
* @author Steven D. Lander
* @author Luke D. Lambert
* @author Jenifer Cochran
*
*/
public class MapViewWindow extends JFrame implements JMapViewerEventListener
{
private com.rgi.common.coordinate.Coordinate<Double> center = new com.rgi.common.coordinate.Coordinate<>(0.0, 0.0);
private int minZoomLevel = 0;
private final Collection<TileStoreReader> tileStoreReaders;
JMapViewer viewer;
boolean treeSelected = false;
private final JLabel currentZoomLevelValue = new JLabel("");
private final JLabel unitsPerPixelXValue = new JLabel("");
private final JLabel unitsPerPixelYValue = new JLabel("");
private final JLabel coordinatePositionValue = new JLabel("");
private final ButtonGroup mainGroup = new ButtonGroup();
private final JPanel eastPanel = new JPanel();
private final JPanel eastPanelSouthComponents= new JPanel();
/**
* Constructor
*
* @param tileStoreReaders
* Tile stores to display
*/
public MapViewWindow(final Collection<TileStoreReader> tileStoreReaders)
{
super("Tile Viewer");
if(tileStoreReaders == null)
{
throw new IllegalArgumentException("Tile store reader collection may not be null");
}
if(tileStoreReaders.isEmpty())
{
throw new IllegalArgumentException("There must be at least one Tile Store reader to display.");
}
this.tileStoreReaders = tileStoreReaders;
this.viewer = new JMapViewer();
this.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(final WindowEvent windowEvent)
{
MapViewWindow.this.cleanUpResources();
}
});
this.viewer.addJMVListener(this);
this.setLayout(new BorderLayout());
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
new DefaultMapController(this.viewer).setMovementMouseButton(MouseEvent.BUTTON1);
//add tile grid checkbox
final JCheckBox showTileGrid = new JCheckBox("Tile grid visible");
this.addCheckboxForTileGridLines(showTileGrid);
//this adds a button to set the display to the center at the lowest integer zoom level
final JButton backToCenterButton = new JButton("Center");
backToCenterButton.setHorizontalAlignment(SwingConstants.LEFT);
this.addCenterButton(backToCenterButton);
//add Listener for the current coordinate
this.mouseCoordinateListener();
//create North panel and add components
final JPanel northPanel = new JPanel();
final JPanel panelTop = new JPanel();
//West Panel
final JPanel westPanel = new JPanel();
//Set list of tileStore Radio Buttons
this.eastPanel.setLayout(new BorderLayout());
this.setListOfTileStores(westPanel);
this.add(northPanel, BorderLayout.NORTH);
northPanel.setLayout(new BorderLayout());
northPanel.add(panelTop, BorderLayout.NORTH);
panelTop.add(backToCenterButton);
panelTop.add(showTileGrid);
this.setSize(950, 800);
this.repaint();
}
private void mouseCoordinateListener()
{
this.viewer.addMouseMotionListener(new MouseMotionListener()
{
@Override
public void mouseMoved(final MouseEvent e)
{
final Coordinate latLong = MapViewWindow.this.viewer.getPosition(e.getPoint());
MapViewWindow.this.updateCoordinate(latLong);
}
@Override
public void mouseDragged(final MouseEvent e)
{
//no desired action required
}
});
}
protected void updateCoordinate(final Coordinate latLong)
{
this.coordinatePositionValue.setText(String.format("Latitude: %f Longitude: %f", latLong.getLat(), latLong.getLon()));
}
private void setListOfTileStores(final JPanel westPanel)
{
final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
//west panel with the radio buttons
westPanel.add(this.createRadioButtons());
//east panel that has the viewer
this.eastPanel.add(this.viewer, BorderLayout.CENTER);
//east panel that has zoom/meters per pixel/ and lat long at the bottom of the screen
this.eastPanelSouthComponents.setLayout(new GridLayout(1, 1, 0, 5));
this.eastPanelSouthComponents.add(this.currentZoomLevelValue);
this.eastPanelSouthComponents.add(this.unitsPerPixelXValue);
this.eastPanelSouthComponents.add(this.coordinatePositionValue);
this.eastPanel.add(this.eastPanelSouthComponents, BorderLayout.SOUTH);
splitPane.setLeftComponent(westPanel);
splitPane.setRightComponent(this.eastPanel);
this.add(splitPane, BorderLayout.CENTER);
splitPane.setOneTouchExpandable(true);
//Provide minimum sizes for the two components in the split pane
final Dimension minimumSize = new Dimension(100, 50);
this.viewer.setMinimumSize(minimumSize);
this.repaint();
}
private JPanel createRadioButtons()
{
final JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new GridLayout(this.tileStoreReaders.size(), 1));
final List<TileStoreRadioButton> buttonList = new ArrayList<>();
this.tileStoreReaders.stream().forEach(store -> {
final TileStoreRadioButton button = new TileStoreRadioButton(store);
this.mainGroup.add(button);
buttonPanel.add(button);
button.addActionListener(this.createActionListener());
buttonList.add(button);
});
this.mainGroup.setSelected(buttonList.get(0).getModel(), true);
try
{
this.viewer.setTileLoader(new TileStoreLoader(this.getSelectedStore(), this.viewer));
this.viewer.setTileSource(new TileStoreTileSource(this.getSelectedStore()));
}
catch (final TileStoreException e)
{
e.printStackTrace();
}
this.setInitialDisplayPosition(this.getSelectedStore());
return buttonPanel;
}
private ActionListener createActionListener()
{
return e -> {
final Object source = e.getSource();
if(source.getClass() == (TileStoreRadioButton.class))
{
final TileStoreRadioButton button = (TileStoreRadioButton) source;
if(button.isSelected())
{
//view tiles
try
{
MapViewWindow.this.viewer.setTileLoader(new TileStoreLoader(button.getTileStore(), MapViewWindow.this.viewer));
MapViewWindow.this.viewer.setTileSource(new TileStoreTileSource(button.getTileStore()));
MapViewWindow.this.setInitialDisplayPosition(button.getTileStore());
}
catch (final TileStoreException e1)
{
e1.printStackTrace();
}
}
}
};
}
private void addCenterButton(final JButton backToCenterButton)
{
backToCenterButton.addActionListener(e -> {
MapViewWindow.this.setInitialDisplayPosition(this.getSelectedStore());
MapViewWindow.this.updateZoomParameters();
});
}
private TileStoreReader getSelectedStore()
{
final Enumeration<AbstractButton> selectedTileStore = this.mainGroup.getElements();
while(selectedTileStore.hasMoreElements())
{
final TileStoreRadioButton button = (TileStoreRadioButton) selectedTileStore.nextElement();
if(button.isSelected())
{
return button.store;
}
}
return null;
}
private void addCheckboxForTileGridLines(final JCheckBox showTileGrid)
{
showTileGrid.setSelected(this.viewer.isTileGridVisible());
showTileGrid.addActionListener(e -> MapViewWindow.this.viewer.setTileGridVisible(showTileGrid.isSelected()));
}
private void updateUnitsPerPixel()
{
try
{
final int currentZoom = this.viewer.getZoom();
final double boundsWidth = this.getSelectedStore().getBounds().getWidth();
final Dimensions<Integer> tileDimensions = this.getSelectedStore().getImageDimensions();
if(tileDimensions == null)
{
throw new IllegalArgumentException("Tile dimensions must be defined.");//added for coverity scan
}
final double tileSizeX = tileDimensions.getWidth();
final int matrixWidth = this.getSelectedStore().getTileScheme().dimensions(currentZoom).getWidth();
final Double unitsPerPixelValueXCalculation = boundsWidth /(tileSizeX * matrixWidth);
this.unitsPerPixelXValue.setText(String.format("Meters/Pixel: %.7f", unitsPerPixelValueXCalculation));
}
catch (final Exception e)
{
this.unitsPerPixelXValue.setText("Unable To Calculate at this zoom level");
if(this.unitsPerPixelYValue.isVisible())
{
this.unitsPerPixelYValue.setText("Unable To Calculate at this zoom level");
}
}
}
@Override
public void processCommand(final JMVCommandEvent command)
{
if(command.getCommand().equals(JMVCommandEvent.COMMAND.ZOOM) ||
command.getCommand().equals(JMVCommandEvent.COMMAND.MOVE))
{
this.updateZoomParameters();
}
}
private void cleanUpResources()
{
for(final TileStoreReader tileStoreReader : this.tileStoreReaders)
{
try
{
tileStoreReader.close();
}
catch(final Exception ex)
{
ex.printStackTrace();
}
}
}
private void updateZoomParameters()
{
this.updateUnitsPerPixel();
this.currentZoomLevelValue.setText(String.format("Zoom Level: %s", this.viewer.getZoom()));
}
private void setInitialDisplayPosition(final TileStoreReader store)
{
try
{
final CrsProfile profile = CrsProfileFactory.create(store.getCoordinateReferenceSystem());
MapViewWindow.this.center = profile.toGlobalGeodetic(store.getBounds().getCenter());
if(!store.getZoomLevels().isEmpty()) // TODO attn Jen: error message?
{
MapViewWindow.this.minZoomLevel = Collections.min(store.getZoomLevels());
MapViewWindow.this.viewer
.setDisplayPosition(new Coordinate(this.center.getY(),
this.center.getX()),
this.minZoomLevel);
this.updateZoomParameters();
}
}
catch(final TileStoreException ex)
{
ex.printStackTrace();
}
}
private static final long serialVersionUID = 1337L;
}