/*
* Copyright (c) 2014 Oculus Info Inc.
* http://www.oculusinfo.com/
*
* Released under the MIT License.
*
* 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.oculusinfo.binning.visualization;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.json.JSONObject;
import com.oculusinfo.binning.TileIndex;
import com.oculusinfo.binning.TilePyramid;
import com.oculusinfo.binning.impl.AOITilePyramid;
import com.oculusinfo.binning.impl.WebMercatorTilePyramid;
import com.oculusinfo.binning.io.PyramidIO;
import com.oculusinfo.binning.metadata.PyramidMetaData;
import com.oculusinfo.binning.util.AvroJSONConverter;
public class JsonTileVisualizer extends JFrame {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(BinVisualizer.class.getName());
static final String PYRAMID_IO = "pyramidIo";
public static void main (String[] args) {
new JsonTileVisualizer().setVisible(true);
}
private static enum IOEnum {
File,
HBase,
SQLite,
ZipStream
}
private static enum PyramidEnum {
Geographic,
AreaOfInterest
}
private JSONObject _tile;
private JTextArea _tileVis;
private PyramidIO _pyramidIO;
private TilePyramid _pyramid;
private String _pyramidId;
private GroupLayout _layout;
private JPanel _tileChooser;
private JFileChooser _fileChooser;
private JComboBox<IOEnum> _ioField;
private JPanel _ioSelectorContainer;
private PyramidIOSelector _ioSelector;
private JComboBox<PyramidEnum> _pyramidField;
private JLabel _pyramidDesc;
private JTextField _idField;
private JComboBox<Integer> _levelField;
private JComboBox<Integer> _xField;
private JComboBox<Integer> _yField;
private JButton _show;
private JCheckBox _showText;
public JsonTileVisualizer () {
setupMenus();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(200, 50);
setSize(1200, 1000);
_tile = null;
_tileVis = new JTextArea();
_pyramidIO = null;
_pyramid = null;
_pyramidId = null;
_fileChooser = new JFileChooser();
createTileChooser();
JSplitPane split = new JSplitPane();
split.setResizeWeight(0.8);
split.setLeftComponent(new JScrollPane(_tileVis));
split.setRightComponent(_tileChooser);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(split, BorderLayout.CENTER);
}
private void setupMenus () {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic('f');
JMenuItem exit = new JMenuItem("Exit");
exit.setMnemonic('x');
exit.addActionListener(new ActionListener() {
@Override
public void actionPerformed (ActionEvent e) {
System.exit(0);
}
});
fileMenu.add(exit);
menuBar.add(fileMenu);
setJMenuBar(menuBar);
}
private void createTileChooser () {
JLabel ioLabel = new JLabel("I/O type:");
ioLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_ioField = new JComboBox<>(IOEnum.values());
_ioField.addActionListener(new IOFieldUpdate());
_ioSelectorContainer = new JPanel();
_ioSelectorContainer.setMaximumSize(new Dimension(100, 75));
_ioSelectorContainer.setLayout(new BorderLayout());
_ioSelectorContainer.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
_ioSelector = null;
JLabel pyramidLabel = new JLabel("Pyramid type:");
pyramidLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_pyramidField = new JComboBox<>(PyramidEnum.values());
_pyramidField.setEnabled(false);
_pyramidDesc = new JLabel();
_pyramidDesc.setHorizontalAlignment(SwingConstants.RIGHT);
JLabel idLabel = new JLabel("Pyramid id:");
idLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_idField = new JTextField();
_idField.getDocument().addDocumentListener(new IDUpdate());
JLabel levelLabel = new JLabel("Zoom level:");
levelLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_levelField = new JComboBox<>();
_levelField.addActionListener(new LevelUpdate());
JLabel xLabel = new JLabel("Tile x coordinate:");
xLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_xField = new JComboBox<>();
JLabel yLabel = new JLabel("Tile y coordinate:");
yLabel.setHorizontalAlignment(SwingConstants.RIGHT);
_yField = new JComboBox<>();
_show = new JButton("Show tile");
_show.addActionListener(new ShowTile());
_showText = new JCheckBox("Bin values");
_showText.setHorizontalAlignment(SwingConstants.RIGHT);
JPanel chooser = new JPanel();
_layout = new GroupLayout(chooser);
chooser.setLayout(_layout);
int pref = GroupLayout.PREFERRED_SIZE;
int max = Short.MAX_VALUE;
JLabel extraArea = new JLabel();
_layout.setHorizontalGroup(
_layout.createParallelGroup()
.addGroup(_layout.createSequentialGroup().addComponent(ioLabel, 0, pref, max).addComponent(_ioField))
.addGroup(_layout.createSequentialGroup().addGap(25).addComponent(_ioSelectorContainer, 0, pref, max))
.addGroup(_layout.createSequentialGroup().addComponent(pyramidLabel, 0, pref, max).addComponent(_pyramidField))
.addGroup(_layout.createSequentialGroup().addComponent(_pyramidDesc, 0, pref, max))
.addGroup(_layout.createSequentialGroup().addComponent(idLabel, 0, pref, max).addComponent(_idField))
.addGroup(_layout.createSequentialGroup().addComponent(levelLabel, 0, pref, max).addComponent(_levelField))
.addGroup(_layout.createSequentialGroup().addComponent(xLabel, 0, pref, max).addComponent(_xField))
.addGroup(_layout.createSequentialGroup().addComponent(yLabel, 0, pref, max).addComponent(_yField))
.addComponent(_show, Alignment.TRAILING)
.addComponent(_showText, Alignment.TRAILING)
.addComponent(extraArea)
);
_layout.setVerticalGroup(
_layout.createSequentialGroup()
.addGroup(_layout.createParallelGroup().addComponent(ioLabel).addComponent(_ioField))
.addComponent(_ioSelectorContainer, 0, pref, 100)
.addGroup(_layout.createParallelGroup().addComponent(pyramidLabel).addComponent(_pyramidField))
.addGroup(_layout.createParallelGroup().addComponent(_pyramidDesc))
.addGroup(_layout.createParallelGroup().addComponent(idLabel).addComponent(_idField))
.addGroup(_layout.createParallelGroup().addComponent(levelLabel).addComponent(_levelField))
.addGroup(_layout.createParallelGroup().addComponent(xLabel).addComponent(_xField))
.addGroup(_layout.createParallelGroup().addComponent(yLabel).addComponent(_yField))
.addComponent(_show)
.addComponent(_showText)
.addComponent(extraArea, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
);
_layout.linkSize(_ioField, _pyramidField, _idField, _levelField, _xField, _yField);
_tileChooser = chooser;
_ioField.setSelectedIndex(0);
_pyramidField.setSelectedIndex(0);
}
private void setIOType (IOEnum type) {
switch (type) {
case File:
if (null != _ioSelector) {
if (_ioSelector instanceof FileSystemPyramidIOSelector) {
return;
}
_ioSelectorContainer.removeAll();
}
_ioSelector = new FileSystemPyramidIOSelector(_fileChooser);
_ioSelectorContainer.add(_ioSelector.getPanel(), BorderLayout.CENTER);
break;
case HBase:
if (null != _ioSelector) {
if (_ioSelector instanceof HBasePyramidIOSelector) {
return;
}
_ioSelectorContainer.removeAll();
}
_ioSelector = new HBasePyramidIOSelector();
_ioSelectorContainer.add(_ioSelector.getPanel(), BorderLayout.CENTER);
break;
case SQLite:
if (null != _ioSelector) {
if (_ioSelector instanceof SQLitePyramidIOSelector) {
return;
}
_ioSelectorContainer.removeAll();
}
_ioSelector = new SQLitePyramidIOSelector();
_ioSelectorContainer.add(_ioSelector.getPanel(), BorderLayout.CENTER);
break;
case ZipStream:
if (null != _ioSelector) {
if (_ioSelector instanceof ZipFilePyramidIOSelector) {
return;
}
_ioSelectorContainer.removeAll();
}
_ioSelector = new ZipFilePyramidIOSelector(_fileChooser);
_ioSelectorContainer.add(_ioSelector.getPanel(), BorderLayout.CENTER);
break;
default:
if (null != _ioSelector) {
_ioSelectorContainer.removeAll();
_ioSelector = null;
}
}
// _layout.layoutContainer(_tileChooser);
_ioSelector.getPanel().addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange (PropertyChangeEvent event) {
if (PYRAMID_IO.equals(event.getPropertyName())) {
_pyramidIO = _ioSelector.getPyramidIO();
updateAvailableLevels();
}
}
});
_tileChooser.validate();
}
private void setPyramidId (String newId) {
if (!objectsEqual(newId, _pyramidId)) {
_pyramidId = newId;
// Notify change functions
updateAvailableLevels();
updatePyramidType();
}
}
private void updateAvailableLevels () {
if (null != _pyramidIO && null != _pyramidId && !_pyramidId.isEmpty()) {
try {
String rawMetaData = _pyramidIO.readMetaData(_pyramidId);
PyramidMetaData metaData = new PyramidMetaData(rawMetaData);
_levelField.removeAll();
_xField.removeAll();
_yField.removeAll();
List<Integer> levels = metaData.getValidZoomLevels();
for (Integer level: levels) {
_levelField.addItem(level);
}
_levelField.setSelectedIndex(0);
return;
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error getting level metadata for "
+ _pyramidId);
}
}
_levelField.removeAllItems();
_xField.removeAll();
_yField.removeAll();
}
private void updatePyramidType () {
_pyramid = null;
try {
String rawMetaData = _pyramidIO.readMetaData(_pyramidId);
PyramidMetaData metaData = new PyramidMetaData(rawMetaData);
_pyramid = metaData.getTilePyramid();
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error getting level metadata for "
+ _pyramidId);
}
if (null == _pyramid) {
_pyramidField.setSelectedIndex(-1);
_pyramidDesc.setText("");
} else if (_pyramid instanceof WebMercatorTilePyramid) {
_pyramidField.setSelectedItem(PyramidEnum.Geographic);
_pyramidDesc.setText("");
} else if (_pyramid instanceof AOITilePyramid) {
_pyramidField.setSelectedItem(PyramidEnum.AreaOfInterest);
Rectangle2D bounds = _pyramid.getTileBounds(new TileIndex(0, 0, 0));
_pyramidDesc.setText(String.format("bounds: [%.4f, %.4f] to [%.4f, %.4f]",
bounds.getMinX(), bounds.getMinY(),
bounds.getMaxX(), bounds.getMaxY()));
}
}
private void updateAvailableCoordinates () {
Integer level = (Integer) _levelField.getSelectedItem();
_xField.removeAllItems();
_yField.removeAllItems();
if (null == level) return;
int pow2 = 1 << level;
for (int i=0; i<pow2; ++i) {
_xField.addItem(i);
_yField.addItem(i);
}
}
private void showCurrentTile () {
if (null == _pyramidId) return;
if (null == _pyramidIO) return;
if (null == _levelField.getSelectedItem()) return;
if (null == _xField.getSelectedItem()) return;
if (null == _yField.getSelectedItem()) return;
TileIndex index = new TileIndex((Integer) _levelField.getSelectedItem(),
(Integer) _xField.getSelectedItem(),
(Integer) _yField.getSelectedItem());
try {
InputStream tileStream = _pyramidIO.getTileStream(_pyramidId, null, index);
_tile = AvroJSONConverter.convert(tileStream);
if (null == _tile) {
_tileVis.setText("");
} else {
_tileVis.setText(_tile.toString(2));
}
} catch (Exception e) {
_tileVis.setText("");
}
}
private class IOFieldUpdate implements ActionListener {
@Override
public void actionPerformed (ActionEvent event) {
setIOType((IOEnum) _ioField.getSelectedItem());
}
}
private class IDUpdate implements DocumentListener {
@Override
public void changedUpdate (DocumentEvent event) {
setPyramidId(_idField.getText());
}
@Override
public void insertUpdate (DocumentEvent e) {
setPyramidId(_idField.getText());
}
@Override
public void removeUpdate (DocumentEvent e) {
setPyramidId(_idField.getText());
}
}
private class LevelUpdate implements ActionListener {
@Override
public void actionPerformed (ActionEvent event) {
updateAvailableCoordinates();
}
}
private class ShowTile implements ActionListener {
@Override
public void actionPerformed (ActionEvent e) {
showCurrentTile();
}
}
private static boolean objectsEqual (Object a, Object b) {
if (null == a) return null == b;
return a.equals(b);
}
}