/************************************************************* * Spring 2003, Visualization Class (Professor Yap) * Hw4 Solution by * Zubin Girglani (zg220@nyu.edu), * Marianna Shvartsapel (m_shvartsapel@yahoo.com), * Evelina Khukhashvili (ek401@nyu.edu) * Extensions added by * Chee Yap (yap@cs.nyu.edu) * Sam Berlin (sab275@nyu.edu) * * This display shows the map of Manhattan (lines only) * It supports zooming (buttons/sliding), panning, * resizing, and printing to a certain format. * It requires a Postgresql server to serve map information. * * Separately, there is a Reader program to enter the * Tiger data into a Postgresql Database. * * BUGS: * -- (do not open to cover whole screen when started) * *************************************************************/ import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import java.sql.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import org.postgresql.geometric.*; public class MapDisplay extends JFrame implements ActionListener, ChangeListener, ComponentListener { String DEFAULT_HOSTNAME = "slinky.cs.nyu.edu"; String DEFAULT_PORT = "5432"; String DEFAULT_DATABASE = "kzm"; String DEFAULT_USERNAME = "visclass"; String DEFAULT_PASSWORD = "visclass"; String DEFAULT_TABLENAME = "rt12"; boolean FULLSIZE = true; // start display in full screen size? int DEF_WIDTH = 750; // default screen width int DEF_HEIGHT = 450; // default screen height Connection con; Statement stmt; ResultSet rs; Dimension screenSize; Insets screenInsets; Dimension frameSize; Dimension mapSize; Point pressedPoint; Point translatedPoint; BufferedImage mapImage; Graphics2D mapGraphics; Container contentPane; JPanel mapPanel; JPanel controlPanel; JSlider zoomSlider; // zoom with a slider JButton minusButton; // zoom out JButton plusButton; // zoom in JButton printButton; // print Xfig JButton quitButton; // quit Object[] tigerLines; int count = 0; // debug counter String printFile = "output.fig"; // name of Xfig output file double minLong; double minLat; double cosMinLat; // projection scale factor double maxLong; double maxLat; double scale; boolean shouldRepaint; boolean isRepainting = false; double tick_factor; public MapDisplay() { // Moved from paintComponent: // mapImage = new BufferedImage( mapSize.width, mapSize.height, BufferedImage.TYPE_INT_ARGB ); System.out.println("opening connection"); openConnection(); System.out.println("retrieving data."); retrieveData(); System.out.println("closing connection."); closeConnection(); System.out.println("showing frame."); showFrame(); } public void openConnection() { try { Class.forName( "org.postgresql.Driver" ); con = DriverManager.getConnection( "jdbc:postgresql://" + DEFAULT_HOSTNAME + ":" + DEFAULT_PORT + "/" + DEFAULT_DATABASE, DEFAULT_USERNAME, DEFAULT_PASSWORD ); stmt = con.createStatement(); } catch( Exception e ) { e.printStackTrace(); } } public void retrieveData() { screenSize = Toolkit.getDefaultToolkit().getScreenSize(); screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration() ); if (FULLSIZE) { frameSize = new Dimension( ( screenSize.width -screenInsets.left -screenInsets.right), ( screenSize.height -screenInsets.top -screenInsets.bottom)); } else { frameSize = new Dimension(DEF_WIDTH, DEF_HEIGHT); } pressedPoint = new Point( 0, 0 ); translatedPoint = new Point( 0, 0 ); ArrayList al = new ArrayList(); try { System.out.println("executing first query"); rs = stmt.executeQuery( "SELECT min(startpt[0]) FROM " + DEFAULT_TABLENAME ); while ( rs.next() ) { minLong = rs.getDouble( 1 ); } System.out.println("executing second query"); rs = stmt.executeQuery( "SELECT min(startpt[1]) FROM " + DEFAULT_TABLENAME ); while ( rs.next() ) { minLat = rs.getDouble( 1 ); cosMinLat = Math.cos(Math.toRadians(((double)minLat)/1000000.0)); } System.out.println("executing third query"); rs = stmt.executeQuery( "SELECT max(startpt[0]) FROM " + DEFAULT_TABLENAME ); while ( rs.next() ) { maxLong = rs.getDouble( 1 ); } System.out.println("executing fourth query"); rs = stmt.executeQuery( "SELECT max(startpt[1]) FROM " + DEFAULT_TABLENAME ); while ( rs.next() ) { maxLat = rs.getDouble( 1 ); } System.out.println("executing fifth query"); rs = stmt.executeQuery( "SELECT details FROM " + DEFAULT_TABLENAME ); scale = frameSize.width / ( maxLong - minLong ); tick_factor = 50 / scale; // so 50 is the center scale while ( rs.next() ) { PGpoint[] lines = ( ( PGpolygon ) rs.getObject( 1 ) ).points; for ( int j = 0; j < lines.length; j++ ) { lines[ j ].x = ( lines[ j ].x - minLong ); lines[ j ].y = Math.abs( ( lines[ j ].y - maxLat ) ); } al.add( new PGpolygon( lines) ); } tigerLines = al.toArray(); // Is this a bug? Shouldn't we multiply the Longitude by Cosine(Lat)? mapSize = new Dimension( ( int ) ( ( maxLong - minLong ) * scale ), ( int ) ( ( maxLat - minLat ) * scale ) ); } catch( Exception e ) { e.printStackTrace(); } } public void closeConnection() { try { rs.close(); stmt.close(); con.close(); } catch( Exception e ) { e.printStackTrace(); } } public void showFrame() { contentPane = getContentPane(); addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { dispose(); System.exit( 0 ); } } ); setLocation( 0, 0 ); setSize( frameSize ); setResizable( true ); contentPane.setLayout( new BorderLayout() ); resizeMap(); mapPanel = new JPanel() { public void paintComponent( Graphics g ) { Graphics2D g2 = ( Graphics2D ) g; mapGraphics = mapImage.createGraphics(); mapGraphics.setPaint( Color.BLUE ); for ( int i = 0; i < tigerLines.length; i++ ) { PGpoint[] lines = ( ( PGpolygon ) tigerLines[ i ] ).points; GeneralPath gp = new GeneralPath(); gp.moveTo( (float)(lines[ 0 ].x * scale * cosMinLat) , (float)(lines[ 0 ].y * scale) ); for ( int j = 0; j < lines.length ; j++ ) { gp.lineTo( (float)(lines[ j ].x * scale * cosMinLat) , (float)(lines[ j ].y * scale) ); } mapGraphics.draw( gp ); } g2.drawImage( mapImage, translatedPoint.x, translatedPoint.y, mapSize.width, mapSize.height, this ); } }; mapPanel.setBorder( BorderFactory.createLineBorder( Color.GREEN ) ); mapPanel.addMouseListener( new MouseAdapter() { public void mousePressed( MouseEvent e ) { Point p = e.getPoint(); pressedPoint.x = p.x - translatedPoint.x; pressedPoint.y = p.y - translatedPoint.y; } } ); mapPanel.addMouseMotionListener( new MouseMotionAdapter() { public void mouseDragged( MouseEvent e ) { Point p = e.getPoint(); p.x = p.x - pressedPoint.x; p.y = p.y - pressedPoint.y; translatedPoint = p; repaint(); } } ); controlPanel = new JPanel(); controlPanel.setBorder( BorderFactory.createLineBorder( Color.GREEN ) ); zoomSlider = new JSlider(); zoomSlider.addChangeListener(this); controlPanel.add( zoomSlider ); minusButton = new JButton( "-" ); minusButton.addActionListener( this ); controlPanel.add( minusButton ); plusButton = new JButton( "+" ); plusButton.addActionListener( this ); controlPanel.add( plusButton ); // Chee's addition: printButton = new JButton( "Print" ); printButton.addActionListener( this ); controlPanel.add( printButton ); quitButton = new JButton( "Quit" ); quitButton.addActionListener( this ); controlPanel.add( quitButton ); contentPane.add( mapPanel, "Center" ); contentPane.add( controlPanel, "South" ); addComponentListener(this); setVisible( true ); } public void componentResized(ComponentEvent e) { int oldWidth = frameSize.width; int oldHeight = frameSize.height; frameSize = getSize(); translatedPoint.x -= ( oldWidth - frameSize.width); translatedPoint.y -= ( oldHeight - frameSize.height); } public void componentShown(ComponentEvent e) {} public void componentMoved(ComponentEvent e) {} public void componentHidden(ComponentEvent e) {} public void actionPerformed( ActionEvent e ) { if ( e.getSource() == minusButton ) { zoomSlider.setValue( (int) (scale * .6 * tick_factor) ); } else if ( e.getSource() == plusButton ) { zoomSlider.setValue( (int) (scale * 1.7 * tick_factor) ); } else if ( e.getSource() == printButton ) { printToFile(); } else if ( e.getSource() == quitButton ) { System.exit(0); } } public void stateChanged( ChangeEvent e ) { if( e.getSource() == zoomSlider ) { scale = zoomSlider.getValue() / tick_factor; resizeMap(); repaint(); } } public void resizeMap() { int oldWidth = mapSize.width; int oldHeight = mapSize.height; mapSize.width = ( int ) ( ( maxLong - minLong ) * scale ); mapSize.height = ( int ) ( ( maxLat - minLat ) * scale ); translatedPoint.x += ( oldWidth - mapSize.width ) / 2; translatedPoint.y += ( oldHeight - mapSize.height) / 2; mapImage = new BufferedImage( mapSize.width, mapSize.height, BufferedImage.TYPE_INT_ARGB ); } public void printToFile() { try { File out = new File(printFile); FileWriter fw = new FileWriter(out); fw.write("#Generated via MapDisplay.printToFile\n"); fw.write("Portrait\n"); fw.write("Center\n"); fw.write("Inches\n"); fw.write("Letter\n"); fw.write("100.00\n"); fw.write("Single\n"); fw.write("-2\n"); fw.write("1200 2\n"); for ( int i = 0; i < tigerLines.length; i++ ) { PGpoint[] lines = ( ( PGpolygon ) tigerLines[ i ] ).points; fw.write("2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 " + lines.length + "\n\t"); for ( int j = 0; j < lines.length ; j++ ) { // USE THIS COMMENTED OUT VERSION IF .fig SUPPORTS doubles //fw.write(" " + lines[j].x * scale * cosMinLat + " " + lines[j].y * scale); fw.write(" " + (int)(lines[j].x * scale * cosMinLat) + " " + (int)(lines[j].y * scale)); } fw.write("\n"); } fw.close(); } catch(IOException e) { e.printStackTrace(); } } /** * This ensures that we only repaint once every 10 milliseconds, and if we were supposed to repaint * while sleeping, that we do it again after waking up. */ public void repaint() { shouldRepaint = true; if( isRepainting ) return; isRepainting = true; super.repaint(); try { Thread.sleep(10); } catch(InterruptedException ignored) {} if(shouldRepaint) super.repaint(); isRepainting = false; shouldRepaint = false; // this fixes a bug } public static void main( String[] args ) { new MapDisplay(); } }