
// GeomsViewer.java
// Andrew Davison, ad@coe.psu.ac.th, March 2023

/* A window that displays JTS geometries as shapes inside a
   JPanel. 

   The main drawback of this approach is that the y-axis runs
   down the window from the top, and so shapes are drawn
   'reflected'. For example, this means that a geometry with
   clockwise ordered coordinates appears to be drawn in the
   anti-clockwise direction.

   Clicking (or dragging) inside the window shows the coordinate
   and WKT info for any geometries under the cursor in a popup
   window.

   A geometry is assigned a color from the colors[] array, or can
   be allocated one in the call to add(). A geometry can also
   be assigned a label, which is included in the popup window.

   GeomsViewer operations:
     * add()    // a geometry
     * remove()

     * clear()   // delete all the geometries

     * setTitle()  // text that appears at the top-left of the JPanel

     * close()  // the window
   
     * getWidth(), getHeight()  // of the window
*/

import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;


import org.locationtech.jts.geom.*;
import org.locationtech.jts.awt.*;


public class GeomsViewer extends JPanel
             implements MouseListener, MouseMotionListener
{
  private static final int PWIDTH = 500;
  private static final int PHEIGHT = 500;

  private static final int MAX_LABEL_LEN = 80;
        // max length of a line in the popup window

  private static final int PT_SIZE = 3;
        // enlarge the size of drawn points

  private static final Color[] colors = {
      new Color(0xFF0000), new Color(0xFF8000), 
      new Color(0xFFFF00), // new Color(0x80FF00), 
      new Color(0x00FF00), // new Color(0x00FF80), 
      new Color(0x00FFFF), // new Color(0x0080FF), 
      new Color(0x0000FF), new Color(0x7F00FF), 
      new Color(0xFF00FF), new Color(0xFF007F), 
      new Color(0x808080), new Color(0x000000)
    };



  private GeometryFactory gf = new GeometryFactory();
  private ShapeWriter sw = new ShapeWriter();


  private Font titleFont = new Font("TimesRoman", Font.BOLD, 30);
  private String title = "";

  private ArrayList<Geometry> geoms;
  private ArrayList<Color> geomColors;

  private Popup coordsPopup;  // for showing shape info



  public GeomsViewer()
  {
    geoms = new ArrayList<Geometry>();
    geomColors = new ArrayList<Color>();

    addMouseListener(this);
    addMouseMotionListener(this);
  }  // end of GeomsViewer()



  public void add(Geometry geom)
  {  add(null, geom, null);  }

  public void add(String label, Geometry geom)
  {  add(label, geom, null);  }

  public void add(Geometry geom, Color c)
  {  add(null, geom, c);  }

   
  public void add(String label, Geometry geom, Color c)
  {
    if (label != null)
      geom.setUserData(label);
    geoms.add(geom);
    // System.out.println("Added " + geom.getGeometryType());

    if (c != null)
      geomColors.add(c);
    else if ( geom.getGeometryType().equals("Point") || 
              geom.getGeometryType().equals("MultiPoint") )
      geomColors.add(Color.BLACK);
    else
      geomColors.add(
          colors[(geomColors.size())%colors.length]);

    repaint();
  } // end of add()

   
  public void remove(Geometry g)
  {
    for (int i=0; i < geoms.size(); i++) {
      Geometry geom = geoms.get(i);
      if (g.equalsExact(geom)) {
        geoms.remove(i);
        geomColors.remove(i);
        // System.out.println("Removed " + geom.toString());
        repaint();
        return;
      }
    }
    System.out.println("Could not find " + g.toString());
  }  // end of remove()


  public void clear()
  {
    geoms.clear();
    geomColors.clear();
    System.out.println("Cleared Viewer");
    repaint();
  }  // end of clear()


  public void close()
  {
    JFrame f = (JFrame) SwingUtilities.windowForComponent(this);
    if (f == null)
      System.out.println("Unable to access viewer window");
    else
      f.dispose();
  } // end of close()


  public void setTitle(String s)
  {  title = s;  }

   
  public java.awt.Dimension getPreferredSize()
  {   return new java.awt.Dimension(PWIDTH, PHEIGHT); }


  public int getWidth()
  {  return PWIDTH;  }

  public int getHeight()
  {  return PHEIGHT;  }



  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);

    g2d.setStroke(new BasicStroke(4));  

    for (int i=0; i < geoms.size(); i++) {
      Geometry geom = geoms.get(i);
      Shape shp = sw.toShape(geom);

      Color c = geomColors.get(i);
      g2d.setColor(c);

      if (geom.getGeometryType().equals("Point")) {
        Coordinate coord = geom.getCoordinate();
        if (coord != null)
          g2d.drawOval((int)coord.x-PT_SIZE, (int)coord.y-PT_SIZE, 
                               2*PT_SIZE, 2*PT_SIZE);
      }
      else
        g2d.draw(shp);

      if (!(geom.getGeometryType().equals("LineString") ||
            geom.getGeometryType().equals("MultiLineString") ||
            geom.getGeometryType().equals("Point")) ) {
        g2d.setColor(Utils.colorAlpha(c, 0.125));
        g2d.fill(shp);
      }
    }

    g2d.setColor(Color.BLACK);
    g2d.setFont(titleFont);
    g2d.drawString(title, 20,30);
  } /* paintComponent */



  // ------- MouseListener methods --------

  public void mousePressed(MouseEvent e)
  { 
    coordsPopup = PopupFactory.getSharedInstance().
                  getPopup(e.getComponent(), 
                     listGeoms(e.getX(), e.getY()), 
                     e.getXOnScreen()+15, e.getYOnScreen());
    coordsPopup.show();
  }  // end of mousePressed()



  private JLabel listGeoms(int x, int y)
  {
    // System.out.println("(" + x + ", " + y + ")");
    Geometry square = makeSquare(x,y);

    String geomsStr = "";
    for (Geometry geom : geoms) {
      try {
        if (square.intersects(geom)) {
          String label = (String)geom.getUserData();
          if (label != null) 
            geomsStr += "<br>  " + label + ": " + shorten(geom);
          else 
            geomsStr += "<br>  " + shorten(geom);
        }
      }
      catch(UnsupportedOperationException ex)
      // deals with multi-geometry WTK forms
      { System.out.println("Cannot test " + geom.getGeometryType());  }
    }

    return new JLabel("<html><p>(" +  x + "," + y + ") " + 
                                         geomsStr + "</p></html>");
  }  // end of listGeoms()



  private static String shorten(Geometry g)
  {
    String geomStr = Utils.simplify(g);
    if (geomStr.length() > MAX_LABEL_LEN)
      geomStr = geomStr.substring(0, MAX_LABEL_LEN) + "...";
    return geomStr;
  }  // end of shorten()


  private Geometry  makeSquare(int x, int y)
  // a bigger point to improve intersection
  {
    GeometryFactory gf = new GeometryFactory();
    Coordinate[] coords = new Coordinate[] {
      new Coordinate(x-2, y-2),
      new Coordinate(x+2, y-2),
      new Coordinate(x+2, y+2),
      new Coordinate(x-2, y+2),
      new Coordinate(x-2, y-2)
    };

    return gf.createPolygon(coords);
  }  // end of makeSquare()



  public void mouseReleased( MouseEvent e)
  { 
    if (coordsPopup != null)
      coordsPopup.hide();
  }

  public void mouseClicked(MouseEvent e) { }
  public void mouseEntered(MouseEvent e) { }
  public void mouseExited(MouseEvent e) { }



  // ------- MouseMotionListener methods --------


  public void mouseDragged( MouseEvent e)
  {
   if (coordsPopup != null)
     coordsPopup.hide();

   coordsPopup = PopupFactory.getSharedInstance().
                 getPopup(e.getComponent(), 
                    listGeoms(e.getX(), e.getY()), 
                    e.getXOnScreen()+15, e.getYOnScreen());
   coordsPopup.show();
  }  // end of mouseDragged()


  public void mouseMoved(MouseEvent e) {}

}  // end of GeomsViewer class