No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BasicDrawing.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. package org.insa.graphics.drawing.components;
  2. import java.awt.BasicStroke;
  3. import java.awt.Color;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.Image;
  7. import java.awt.event.ActionEvent;
  8. import java.awt.event.ActionListener;
  9. import java.awt.event.MouseAdapter;
  10. import java.awt.event.MouseEvent;
  11. import java.awt.geom.AffineTransform;
  12. import java.awt.geom.NoninvertibleTransformException;
  13. import java.awt.geom.Point2D;
  14. import java.awt.image.BufferedImage;
  15. import java.io.IOException;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.Iterator;
  19. import java.util.List;
  20. import javax.swing.JPanel;
  21. import org.insa.graph.Arc;
  22. import org.insa.graph.Graph;
  23. import org.insa.graph.GraphStatistics.BoundingBox;
  24. import org.insa.graph.Node;
  25. import org.insa.graph.Path;
  26. import org.insa.graph.Point;
  27. import org.insa.graphics.drawing.BasicGraphPalette;
  28. import org.insa.graphics.drawing.Drawing;
  29. import org.insa.graphics.drawing.DrawingClickListener;
  30. import org.insa.graphics.drawing.GraphPalette;
  31. import org.insa.graphics.drawing.MercatorProjection;
  32. import org.insa.graphics.drawing.overlays.MarkerOverlay;
  33. import org.insa.graphics.drawing.overlays.MarkerUtils;
  34. import org.insa.graphics.drawing.overlays.Overlay;
  35. import org.insa.graphics.drawing.overlays.PathOverlay;
  36. import org.insa.graphics.drawing.overlays.PointSetOverlay;
  37. /**
  38. * Cette implementation de la classe Dessin produit vraiment un affichage (au
  39. * contraire de la classe DessinInvisible).
  40. */
  41. public class BasicDrawing extends JPanel implements Drawing {
  42. /**
  43. *
  44. */
  45. private static final long serialVersionUID = 96779785877771827L;
  46. private abstract class BasicOverlay implements Overlay {
  47. // Visible?
  48. protected boolean visible;
  49. // Color
  50. protected Color color;
  51. public BasicOverlay(Color color) {
  52. this.visible = true;
  53. this.color = color;
  54. }
  55. @Override
  56. public void setColor(Color color) {
  57. this.color = color;
  58. }
  59. @Override
  60. public Color getColor() {
  61. return this.color;
  62. }
  63. @Override
  64. public void setVisible(boolean visible) {
  65. this.visible = visible;
  66. BasicDrawing.this.repaint();
  67. }
  68. @Override
  69. public boolean isVisible() {
  70. return this.visible;
  71. }
  72. @Override
  73. public void delete() {
  74. synchronized (overlays) {
  75. BasicDrawing.this.overlays.remove(this);
  76. }
  77. BasicDrawing.this.repaint();
  78. }
  79. /**
  80. * Draw the given overlay.
  81. */
  82. public void draw(Graphics2D g) {
  83. if (this.visible) {
  84. drawImpl(g);
  85. }
  86. }
  87. public abstract void drawImpl(Graphics2D g);
  88. public void redraw() {
  89. BasicDrawing.this.repaint();
  90. }
  91. };
  92. private class BasicMarkerOverlay extends BasicOverlay implements MarkerOverlay {
  93. // Marker width and height
  94. public static final int MARKER_WIDTH = 30, MARKER_HEIGHT = 60;
  95. // Point of the marker.
  96. private Point point;
  97. // Image to draw
  98. private Image image;
  99. public BasicMarkerOverlay(Point point, Color color) {
  100. super(color);
  101. this.point = point;
  102. this.color = color;
  103. this.image = MarkerUtils.getMarkerForColor(color);
  104. }
  105. @Override
  106. public Point getPoint() {
  107. return point;
  108. }
  109. @Override
  110. public void setColor(Color color) {
  111. super.setColor(color);
  112. this.image = MarkerUtils.getMarkerForColor(color);
  113. }
  114. @Override
  115. public void moveTo(Point point) {
  116. this.point = point;
  117. BasicDrawing.this.repaint();
  118. }
  119. @Override
  120. public void drawImpl(Graphics2D graphics) {
  121. int px = projection.longitudeToPixelX(getPoint().getLongitude());
  122. int py = projection.latitudeToPixelY(getPoint().getLatitude());
  123. graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
  124. MARKER_HEIGHT, BasicDrawing.this);
  125. }
  126. };
  127. private class BasicPathOverlay extends BasicOverlay implements PathOverlay {
  128. // List of points
  129. private final List<Point> points;
  130. // Origin / Destination markers.
  131. private BasicMarkerOverlay origin, destination;
  132. public BasicPathOverlay(List<Point> points, Color color, BasicMarkerOverlay origin,
  133. BasicMarkerOverlay destination) {
  134. super(color);
  135. this.points = points;
  136. this.origin = origin;
  137. this.destination = destination;
  138. this.color = color;
  139. }
  140. @Override
  141. public void setColor(Color color) {
  142. super.setColor(color);
  143. this.origin.setColor(color);
  144. this.destination.setColor(color);
  145. }
  146. @Override
  147. public void drawImpl(Graphics2D graphics) {
  148. if (!points.isEmpty()) {
  149. graphics.setStroke(new BasicStroke(2));
  150. graphics.setColor(getColor());
  151. Iterator<Point> itPoint = points.iterator();
  152. Point prev = itPoint.next();
  153. while (itPoint.hasNext()) {
  154. Point curr = itPoint.next();
  155. int x1 = projection.longitudeToPixelX(prev.getLongitude());
  156. int x2 = projection.longitudeToPixelX(curr.getLongitude());
  157. int y1 = projection.latitudeToPixelY(prev.getLatitude());
  158. int y2 = projection.latitudeToPixelY(curr.getLatitude());
  159. graphics.drawLine(x1, y1, x2, y2);
  160. prev = curr;
  161. }
  162. }
  163. if (this.origin != null) {
  164. this.origin.draw(graphics);
  165. }
  166. if (this.destination != null) {
  167. this.destination.draw(graphics);
  168. }
  169. }
  170. };
  171. private class BasicPointSetOverlay extends BasicOverlay implements PointSetOverlay {
  172. // Default point width
  173. private static final int DEFAULT_POINT_WIDTH = 5;
  174. // Image for path / points
  175. private final BufferedImage image;
  176. private final Graphics2D graphics;
  177. private int width = DEFAULT_POINT_WIDTH;
  178. public BasicPointSetOverlay() {
  179. super(Color.BLACK);
  180. this.image = new BufferedImage(BasicDrawing.this.width, BasicDrawing.this.height,
  181. BufferedImage.TYPE_4BYTE_ABGR);
  182. this.graphics = image.createGraphics();
  183. this.graphics.setBackground(new Color(0, 0, 0, 0));
  184. }
  185. @Override
  186. public void setColor(Color color) {
  187. super.setColor(color);
  188. this.graphics.setColor(color);
  189. }
  190. @Override
  191. public void setWidth(int width) {
  192. this.width = Math.max(2, width);
  193. }
  194. @Override
  195. public void setWidthAndColor(int width, Color color) {
  196. setWidth(width);
  197. setColor(color);
  198. }
  199. @Override
  200. public void addPoint(Point point) {
  201. int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
  202. int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
  203. this.graphics.fillOval(x, y, this.width, this.width);
  204. BasicDrawing.this.repaint();
  205. }
  206. @Override
  207. public void addPoint(Point point, int width) {
  208. setWidth(width);
  209. addPoint(point);
  210. }
  211. @Override
  212. public void addPoint(Point point, Color color) {
  213. setColor(color);
  214. addPoint(point);
  215. }
  216. @Override
  217. public void addPoint(Point point, int width, Color color) {
  218. setWidth(width);
  219. setColor(color);
  220. addPoint(point);
  221. }
  222. @Override
  223. public void drawImpl(Graphics2D g) {
  224. g.drawImage(this.image, 0, 0, BasicDrawing.this);
  225. }
  226. }
  227. // Default path color.
  228. public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
  229. // Default palette.
  230. public static final GraphPalette DEFAULT_PALETTE = new BasicGraphPalette();
  231. // Maximum width for the drawing (in pixels).
  232. private static final int MAXIMUM_DRAWING_WIDTH = 2000;
  233. private MercatorProjection projection;
  234. // Width and height of the image
  235. private int width, height;
  236. // Zoom controls
  237. private MapZoomControls zoomControls;
  238. private ZoomAndPanListener zoomAndPanListener;
  239. //
  240. private Image graphImage = null;
  241. private Graphics2D graphGraphics = null;
  242. // List of image for markers
  243. private List<BasicOverlay> overlays = Collections
  244. .synchronizedList(new ArrayList<BasicOverlay>());
  245. // Mapping DrawingClickListener -> MouseEventListener
  246. private List<DrawingClickListener> drawingClickListeners = new ArrayList<>();
  247. /**
  248. * Create a new BasicDrawing.
  249. *
  250. */
  251. public BasicDrawing() {
  252. setLayout(null);
  253. this.setBackground(new Color(240, 240, 240));
  254. this.zoomAndPanListener = new ZoomAndPanListener(this,
  255. ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2);
  256. // Try...
  257. try {
  258. this.zoomControls = new MapZoomControls(this, 0,
  259. ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20);
  260. this.zoomControls.addZoomInListener(new ActionListener() {
  261. @Override
  262. public void actionPerformed(ActionEvent e) {
  263. zoomAndPanListener.zoomIn();
  264. }
  265. });
  266. this.zoomControls.addZoomOutListener(new ActionListener() {
  267. @Override
  268. public void actionPerformed(ActionEvent e) {
  269. zoomAndPanListener.zoomOut();
  270. }
  271. });
  272. }
  273. catch (IOException e) {
  274. e.printStackTrace();
  275. }
  276. this.addMouseListener(new MouseAdapter() {
  277. @Override
  278. public void mouseClicked(MouseEvent evt) {
  279. if (zoomControls.contains(evt.getPoint())) {
  280. return;
  281. }
  282. Point lonlat = null;
  283. try {
  284. lonlat = getLongitudeLatitude(evt);
  285. }
  286. catch (NoninvertibleTransformException e) {
  287. return;
  288. }
  289. for (DrawingClickListener listener: drawingClickListeners) {
  290. listener.mouseClicked(lonlat);
  291. }
  292. }
  293. });
  294. }
  295. @Override
  296. public void paintComponent(Graphics g1) {
  297. super.paintComponent(g1);
  298. Graphics2D g = (Graphics2D) g1;
  299. AffineTransform sTransform = g.getTransform();
  300. g.setColor(this.getBackground());
  301. g.fillRect(0, 0, getWidth(), getHeight());
  302. g.setTransform(zoomAndPanListener.getCoordTransform());
  303. if (graphImage != null) {
  304. // Draw graph
  305. g.drawImage(graphImage, 0, 0, this);
  306. }
  307. // Draw markers
  308. synchronized (overlays) {
  309. for (BasicOverlay overlay: overlays) {
  310. overlay.draw(g);
  311. }
  312. }
  313. g.setTransform(sTransform);
  314. if (this.zoomControls != null) {
  315. this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel());
  316. this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20,
  317. this.getHeight() - this.zoomControls.getHeight() - 10, this);
  318. }
  319. }
  320. /*
  321. * (non-Javadoc)
  322. * @see org.insa.graphics.drawing.Drawing#clear()
  323. */
  324. @Override
  325. public void clear() {
  326. if (this.graphGraphics != null) {
  327. this.graphGraphics.clearRect(0, 0, this.width, this.height);
  328. }
  329. synchronized (overlays) {
  330. this.overlays.clear();
  331. }
  332. this.repaint();
  333. }
  334. /*
  335. * (non-Javadoc)
  336. * @see org.insa.graphics.drawing.Drawing#clearOverlays()
  337. */
  338. @Override
  339. public void clearOverlays() {
  340. synchronized (overlays) {
  341. this.overlays.clear();
  342. }
  343. this.repaint();
  344. }
  345. /**
  346. * @return The current ZoomAndPanListener associated with this drawing.
  347. */
  348. public ZoomAndPanListener getZoomAndPanListener() {
  349. return this.zoomAndPanListener;
  350. }
  351. /**
  352. * Return the longitude and latitude corresponding to the given position of the
  353. * MouseEvent.
  354. *
  355. * @param event MouseEvent from which longitude/latitude should be retrieved.
  356. *
  357. * @return Point representing the projection of the MouseEvent position in the
  358. * graph/map.
  359. *
  360. * @throws NoninvertibleTransformException if the actual transformation is
  361. * invalid.
  362. */
  363. protected Point getLongitudeLatitude(MouseEvent event) throws NoninvertibleTransformException {
  364. // Get the point using the inverse transform of the Zoom/Pan object, this gives
  365. // us
  366. // a point within the drawing box (between [0, 0] and [width, height]).
  367. Point2D ptDst = this.zoomAndPanListener.getCoordTransform()
  368. .inverseTransform(event.getPoint(), null);
  369. // Inverse the "projection" on x/y to get longitude and latitude.
  370. return new Point(projection.pixelXToLongitude(ptDst.getX()),
  371. projection.pixelYToLatitude(ptDst.getY()));
  372. }
  373. /*
  374. * (non-Javadoc)
  375. * @see
  376. * org.insa.graphics.drawing.Drawing#addDrawingClickListener(org.insa.graphics.
  377. * drawing.DrawingClickListener)
  378. */
  379. @Override
  380. public void addDrawingClickListener(DrawingClickListener listener) {
  381. this.drawingClickListeners.add(listener);
  382. }
  383. /*
  384. * (non-Javadoc)
  385. * @see org.insa.graphics.drawing.Drawing#removeDrawingClickListener(org.insa.
  386. * graphics.drawing.DrawingClickListener)
  387. */
  388. @Override
  389. public void removeDrawingClickListener(DrawingClickListener listener) {
  390. this.drawingClickListeners.remove(listener);
  391. }
  392. public BasicMarkerOverlay createMarker(Point point, Color color) {
  393. return new BasicMarkerOverlay(point, color);
  394. }
  395. @Override
  396. public MarkerOverlay drawMarker(Point point, Color color) {
  397. BasicMarkerOverlay marker = createMarker(point, color);
  398. synchronized (overlays) {
  399. this.overlays.add(marker);
  400. }
  401. this.repaint();
  402. return marker;
  403. }
  404. @Override
  405. public PointSetOverlay createPointSetOverlay() {
  406. BasicPointSetOverlay ps = new BasicPointSetOverlay();
  407. synchronized (overlays) {
  408. this.overlays.add(ps);
  409. }
  410. return ps;
  411. }
  412. @Override
  413. public PointSetOverlay createPointSetOverlay(int width, Color color) {
  414. PointSetOverlay ps = createPointSetOverlay();
  415. ps.setWidthAndColor(width, color);
  416. return ps;
  417. }
  418. /**
  419. * Draw the given arc.
  420. *
  421. * @param arc Arc to draw.
  422. * @param palette Palette to use to retrieve color and width for arc, or null to
  423. * use current settings.
  424. */
  425. protected void drawArc(Arc arc, GraphPalette palette, boolean repaint) {
  426. List<Point> pts = arc.getPoints();
  427. if (!pts.isEmpty()) {
  428. if (palette != null) {
  429. this.graphGraphics.setColor(palette.getColorForArc(arc));
  430. this.graphGraphics.setStroke(new BasicStroke(palette.getWidthForArc(arc)));
  431. }
  432. Iterator<Point> it1 = pts.iterator();
  433. Point prev = it1.next();
  434. while (it1.hasNext()) {
  435. Point curr = it1.next();
  436. int x1 = projection.longitudeToPixelX(prev.getLongitude());
  437. int x2 = projection.longitudeToPixelX(curr.getLongitude());
  438. int y1 = projection.latitudeToPixelY(prev.getLatitude());
  439. int y2 = projection.latitudeToPixelY(curr.getLatitude());
  440. graphGraphics.drawLine(x1, y1, x2, y2);
  441. prev = curr;
  442. }
  443. }
  444. if (repaint) {
  445. this.repaint();
  446. }
  447. }
  448. /**
  449. * Initialize the drawing for the given graph.
  450. *
  451. * @param graph
  452. */
  453. protected void initialize(Graph graph) {
  454. // Clear everything.
  455. this.clear();
  456. BoundingBox box = graph.getGraphInformation().getBoundingBox();
  457. // Find minimum/maximum longitude and latitude.
  458. float minLon = box.getTopLeftPoint().getLongitude(),
  459. maxLon = box.getBottomRightPoint().getLongitude(),
  460. minLat = box.getBottomRightPoint().getLatitude(),
  461. maxLat = box.getTopLeftPoint().getLatitude();
  462. // Add a little delta to avoid drawing on the edge...
  463. float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
  464. float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
  465. // Create the projection and retrieve width and height for the box.
  466. projection = new MercatorProjection(box.extend(deltaLon, deltaLat, deltaLon, deltaLat),
  467. MAXIMUM_DRAWING_WIDTH);
  468. this.width = (int) projection.getImageWidth();
  469. this.height = (int) projection.getImageHeight();
  470. // Create the image
  471. BufferedImage img = new BufferedImage(this.width, this.height,
  472. BufferedImage.TYPE_3BYTE_BGR);
  473. this.graphImage = img;
  474. this.graphGraphics = img.createGraphics();
  475. this.graphGraphics.setBackground(this.getBackground());
  476. this.graphGraphics.clearRect(0, 0, this.width, this.height);
  477. // Set the zoom and pan listener
  478. double scale = 1 / Math.max(this.width / (double) this.getWidth(),
  479. this.height / (double) this.getHeight());
  480. this.zoomAndPanListener.setCoordTransform(this.graphGraphics.getTransform());
  481. this.zoomAndPanListener.getCoordTransform().translate(
  482. (this.getWidth() - this.width * scale) / 2,
  483. (this.getHeight() - this.height * scale) / 2);
  484. this.zoomAndPanListener.getCoordTransform().scale(scale, scale);
  485. this.zoomAndPanListener.setZoomLevel(0);
  486. this.zoomControls.setZoomLevel(0);
  487. // Repaint
  488. this.repaint();
  489. }
  490. @Override
  491. public void drawGraph(Graph graph, GraphPalette palette) {
  492. int repaintModulo = graph.getNodes().size() / 100;
  493. // Initialize the buffered image
  494. this.initialize(graph);
  495. // Remove zoom and pan listener
  496. this.removeMouseListener(zoomAndPanListener);
  497. this.removeMouseMotionListener(zoomAndPanListener);
  498. this.removeMouseWheelListener(zoomAndPanListener);
  499. for (Node node: graph.getNodes()) {
  500. for (Arc arc: node.getSuccessors()) {
  501. // Draw arcs only if there are one-way arcs or if origin is lower than
  502. // destination, avoid drawing two-ways arc twice.
  503. if (arc.getRoadInformation().isOneWay()
  504. || arc.getOrigin().compareTo(arc.getDestination()) < 0) {
  505. drawArc(arc, palette, false);
  506. }
  507. }
  508. if (node.getId() % repaintModulo == 0) {
  509. this.repaint();
  510. }
  511. }
  512. this.repaint();
  513. // Re-add zoom and pan listener
  514. this.addMouseListener(zoomAndPanListener);
  515. this.addMouseMotionListener(zoomAndPanListener);
  516. this.addMouseWheelListener(zoomAndPanListener);
  517. }
  518. @Override
  519. public void drawGraph(Graph graph) {
  520. drawGraph(graph, DEFAULT_PALETTE);
  521. }
  522. @Override
  523. public PathOverlay drawPath(Path path, Color color, boolean markers) {
  524. List<Point> points = new ArrayList<Point>();
  525. if (!path.isEmpty()) {
  526. points.add(path.getOrigin().getPoint());
  527. for (Arc arc: path.getArcs()) {
  528. Iterator<Point> itPoint = arc.getPoints().iterator();
  529. // Discard origin each time
  530. itPoint.next();
  531. while (itPoint.hasNext()) {
  532. points.add(itPoint.next());
  533. }
  534. }
  535. }
  536. BasicMarkerOverlay origin = null, destination = null;
  537. if (markers && !path.isEmpty()) {
  538. origin = createMarker(path.getOrigin().getPoint(), color);
  539. destination = createMarker(path.getDestination().getPoint(), color);
  540. }
  541. BasicPathOverlay overlay = new BasicPathOverlay(points, color, origin, destination);
  542. synchronized (overlays) {
  543. this.overlays.add(overlay);
  544. }
  545. this.repaint();
  546. return overlay;
  547. }
  548. @Override
  549. public PathOverlay drawPath(Path path, Color color) {
  550. return drawPath(path, color, true);
  551. }
  552. @Override
  553. public PathOverlay drawPath(Path path) {
  554. return drawPath(path, DEFAULT_PATH_COLOR);
  555. }
  556. @Override
  557. public PathOverlay drawPath(Path path, boolean markers) {
  558. return drawPath(path, DEFAULT_PATH_COLOR, markers);
  559. }
  560. }