/* * 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 com.oculusinfo.binning.TileData; import com.oculusinfo.binning.TileIndex; import com.oculusinfo.binning.TilePyramid; import com.oculusinfo.binning.impl.AOITilePyramid; import com.oculusinfo.binning.impl.DenseTileSliceView; import com.oculusinfo.binning.impl.WebMercatorTilePyramid; import com.oculusinfo.binning.io.PyramidIO; import com.oculusinfo.binning.io.serialization.TileSerializer; import com.oculusinfo.binning.io.serialization.impl.KryoSerializer; import com.oculusinfo.binning.io.serialization.impl.PrimitiveArrayAvroSerializer; import com.oculusinfo.binning.io.serialization.impl.PrimitiveAvroSerializer; import com.oculusinfo.binning.metadata.PyramidMetaData; import com.oculusinfo.binning.util.TypeDescriptor; import org.apache.avro.file.CodecFactory; import javax.swing.*; import javax.swing.GroupLayout.Alignment; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * A class, used mostly for testing, that reads and displays a tile. * * @author Nathan Kronenfeld */ @SuppressWarnings("deprecation") public class BinVisualizer 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 BinVisualizer().setVisible(true); } private static enum IOEnum { File, HBase, SQLite, ZipStream } private static enum PyramidEnum { Geographic, AreaOfInterest } private static enum SerializerEnum { Avro, AvroBucketted, Kryo, KryoBucketted, Legacy } private Image _image; private JLabel _imageVis; private PyramidIO _pyramidIO; private TilePyramid _pyramid; private TileSerializer<Double> _serializer; private TileSerializer<List<Double>> _bucketSerializer; 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 JComboBox<SerializerEnum> _serializerField; private JTextField _idField; private JComboBox<Integer> _levelField; private JComboBox<Integer> _xField; private JComboBox<Integer> _yField; private JButton _show; private JCheckBox _showText; public BinVisualizer () { setupMenus(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocation(200, 50); setSize(1200, 1000); _image = null; _imageVis = new JLabel(); _pyramidIO = null; _pyramid = null; _serializer = null; _bucketSerializer = null; _pyramidId = null; _fileChooser = new JFileChooser(); createTileChooser(); JSplitPane split = new JSplitPane(); split.setResizeWeight(0.8); split.setLeftComponent(_imageVis); 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 serializerLabel = new JLabel("Serializer type:"); serializerLabel.setHorizontalAlignment(SwingConstants.RIGHT); _serializerField = new JComboBox<>(SerializerEnum.values()); _serializerField.addActionListener(new SerializerUpdate()); 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(serializerLabel, 0, pref, max).addComponent(_serializerField)) .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(serializerLabel).addComponent(_serializerField)) .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, _serializerField, _idField, _levelField, _xField, _yField); _tileChooser = chooser; _ioField.setSelectedIndex(0); _pyramidField.setSelectedIndex(0); _serializerField.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 setSerializer (SerializerEnum type) { boolean changed = false; switch (type) { case Avro: if (null == _serializer || !(_serializer instanceof PrimitiveAvroSerializer)) { _serializer = new PrimitiveAvroSerializer<Double>(Double.class, CodecFactory.bzip2Codec()); _bucketSerializer = null; changed = true; } break; case AvroBucketted: if (null == _bucketSerializer|| !(_bucketSerializer instanceof PrimitiveArrayAvroSerializer)) { _bucketSerializer = new PrimitiveArrayAvroSerializer<>(Double.class, CodecFactory.bzip2Codec()); _serializer = null; changed = true; } break; case Kryo: if (null == _serializer || !(_serializer instanceof KryoSerializer)) { _serializer = new KryoSerializer<Double>(new TypeDescriptor(Double.class)); _bucketSerializer = null; changed = true; } break; case KryoBucketted: if (null == _bucketSerializer|| !(_bucketSerializer instanceof KryoSerializer)) { _bucketSerializer = new KryoSerializer<>(new TypeDescriptor(List.class, new TypeDescriptor(Double.class)), KryoSerializer.Codec.GZIP); _serializer = null; changed = true; } break; case Legacy: if (null == _serializer || !(_serializer instanceof com.oculusinfo.binning.io.serialization.impl.BackwardCompatibilitySerializer)) { _serializer = new com.oculusinfo.binning.io.serialization.impl.BackwardCompatibilitySerializer(); changed = true; } } if (changed) { updateAvailableLevels(); } } 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 == _pyramid) return; if (null == _pyramidId) return; if (null == _pyramidIO) return; if (null == _serializer && null == _bucketSerializer) 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 { List<TileData<Double>> data = null; if (null != _serializer) { data = _pyramidIO.readTiles(_pyramidId, _serializer, Collections.singleton(index)); } else { List<TileData<List<Double>>> rawData = _pyramidIO.readTiles(_pyramidId, _bucketSerializer, Collections.singleton(index)); data = new ArrayList<>(); for (TileData<List<Double>> tile: rawData) { data.add(new DenseTileSliceView<Double>(tile, 0)); } } if (1 == data.size()) { TileData<Double> tile = data.get(0); showTile(tile); } } catch (Exception e) { e.printStackTrace(); } } private <T extends Number> void showTile(final TileData<T> tile) { if (null == tile) { _image = null; _imageVis.setIcon(null); _imageVis.repaint(); return; } TileIndex index = tile.getDefinition(); int xBins = index.getXBins(); int yBins = index.getYBins(); // Draw the tile data into the image. BufferedImage tileImage = new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB); Graphics2D g = tileImage.createGraphics(); double maxValue = Double.MIN_VALUE; for (int x=0; x<xBins; ++x) { for (int y=0; y<yBins; ++y) { Number value = tile.getBin(x, y); if (null != value && value.doubleValue() > maxValue) maxValue = value.doubleValue(); } } for (int x=0; x<xBins; ++x) { for (int y=0; y<yBins; ++y) { int minX = (int) Math.round(1024*((double) x) / ((double) xBins)); int maxX = (int) Math.round(1024*((double) x+1) / ((double) xBins)); int minY = (int) Math.round(1024*((double) y) / ((double) yBins)); int maxY = (int) Math.round(1024*((double) y+1) / ((double) yBins)); Number binValue = tile.getBin(x, y); float alpha = 0.0f; float value = 0.0f; if (null != binValue && binValue.doubleValue() > 0) { value = (float) Math.sqrt(binValue.doubleValue() / maxValue); alpha = 1.0f; } Color c = new Color(1.0f, 1.0f-value, 1.0f-value, alpha); g.setColor(c); g.fillRect(minX, minY, maxX-minX, maxY-minY); if (_showText.isSelected() && null != binValue) { g.setColor(Color.BLACK); g.drawString(binValue.toString(), minX + (maxX - minX) / 2f, minY + (maxY - minY) / 2f); } } } g.dispose(); BufferedImage displayImage = tileImage; if (_pyramid instanceof WebMercatorTilePyramid) { Image mapImage = getMapImage(tile.getDefinition()); displayImage = new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB); g = displayImage.createGraphics(); g.drawImage(mapImage, 0, 0, new ImageObserver() { @Override public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height) { if (ImageObserver.ALLBITS == (ImageObserver.ALLBITS&infoflags)) { showTile(tile); return false; } return true; } }); g.drawImage(tileImage, 0, 0, null); g.dispose(); } _image = displayImage; _imageVis.setIcon(new ImageIcon(_image)); _imageVis.repaint(); } private Image getMapImage (TileIndex tile) { WebMercatorTilePyramid mercator = new WebMercatorTilePyramid(); Rectangle2D bounds = mercator.getEPSG_900913Bounds(tile, null); String url = String.format("http://129.206.228.72/cached/osm?LAYERS=osm_auto:all&STYLES=&" + "SRS=EPSG%%3A900913&FORMAT=image%%2Fpng&SERVICE=WMS&VERSION=1.1.1" + "&REQUEST=GetMap&BBOX=%.3f,%.3f,%.3f,%.3f&WIDTH=1024&HEIGHT=1024", bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()); try { return getToolkit().getImage(new URL(url)); } catch (MalformedURLException e) { return null; } } private class IOFieldUpdate implements ActionListener { @Override public void actionPerformed (ActionEvent event) { setIOType((IOEnum) _ioField.getSelectedItem()); } } private class SerializerUpdate implements ActionListener { @Override public void actionPerformed (ActionEvent event) { setSerializer((SerializerEnum) _serializerField.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); } }