View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.samples.geometry;
23  
24  import java.awt.BorderLayout;
25  import java.awt.Color;
26  import java.awt.Component;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.ActionListener;
29  import java.awt.geom.Point2D;
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  import javax.swing.BorderFactory;
34  import javax.swing.JButton;
35  import javax.swing.JComponent;
36  import javax.swing.JPanel;
37  import javax.swing.JSplitPane;
38  
39  import org.hipparchus.geometry.enclosing.Encloser;
40  import org.hipparchus.geometry.enclosing.EnclosingBall;
41  import org.hipparchus.geometry.enclosing.WelzlEncloser;
42  import org.hipparchus.geometry.euclidean.twod.DiskGenerator;
43  import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
44  import org.hipparchus.geometry.euclidean.twod.Segment;
45  import org.hipparchus.geometry.euclidean.twod.Vector2D;
46  import org.hipparchus.geometry.euclidean.twod.hull.ConvexHull2D;
47  import org.hipparchus.geometry.euclidean.twod.hull.ConvexHullGenerator2D;
48  import org.hipparchus.geometry.euclidean.twod.hull.MonotoneChain;
49  import org.hipparchus.random.MersenneTwister;
50  import org.hipparchus.random.RandomGenerator;
51  import org.hipparchus.samples.ExampleUtils;
52  import org.hipparchus.samples.ExampleUtils.ExampleFrame;
53  import org.hipparchus.util.FastMath;
54  import org.hipparchus.util.SinCos;
55  import org.piccolo2d.PCamera;
56  import org.piccolo2d.PCanvas;
57  import org.piccolo2d.PNode;
58  import org.piccolo2d.event.PBasicInputEventHandler;
59  import org.piccolo2d.event.PInputEvent;
60  import org.piccolo2d.event.PMouseWheelZoomEventHandler;
61  import org.piccolo2d.nodes.PPath;
62  import org.piccolo2d.nodes.PText;
63  
64  /**
65   * Simple example illustrating some parts of the geometry package.
66   *
67   * TODO:
68   *  - select tolerance level
69   *  - allow editing of the point set
70   */
71  //CHECKSTYLE: stop HideUtilityClassConstructor
72  public class GeometryExample {
73  
74      /** Tooltip. */
75      private static final String TOOLTIP = "tooltip";
76  
77      /** Empty constructor.
78       * <p>
79       * This constructor is not strictly necessary, but it prevents spurious
80       * javadoc warnings with JDK 18 and later.
81       * </p>
82       * @since 3.0
83       */
84      public GeometryExample() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
85          // nothing to do
86      }
87  
88      /** Create a list of random points.
89       * @param size number of points
90       * @return random points
91       */
92      public static List<Vector2D> createRandomPoints(int size) {
93          RandomGenerator random = new MersenneTwister();
94  
95          // create the cloud container
96          List<Vector2D> points = new ArrayList<Vector2D>(size);
97          // fill the cloud with a random distribution of points
98          for (int i = 0; i < size; i++) {
99              points.add(new Vector2D(FastMath.round(random.nextDouble() * 400 + 100),
100                     FastMath.round(random.nextDouble() * 400 + 100)));
101         }
102 
103         return points;
104     }
105 
106     /** Create a circle sprite.
107      * @param samples number of points
108      * @return vectors describing the sprite
109      */
110     public static List<Vector2D> createCircle(int samples) {
111         List<Vector2D> points = new ArrayList<Vector2D>();
112         final Vector2D center = new Vector2D(300, 300);
113         double range = 2.0 * FastMath.PI;
114         double step = range / (samples + 1);
115         for (double angle = 0; angle < range; angle += step) {
116             Vector2D circle = buildVector(angle);
117             points.add(circle.scalarMultiply(200).add(center));
118         }
119 
120         return points;
121     }
122 
123     /** Create a cross sprite.
124      * @return vectors describing the sprite
125      */
126     public static List<Vector2D> createCross() {
127         List<Vector2D> points = new ArrayList<Vector2D>();
128 
129         for (int i = 100; i < 500; i += 10) {
130             points.add(new Vector2D(300, i));
131             points.add(new Vector2D(i, 300));
132         }
133 
134         return points;
135     }
136 
137     /** Create a canvas
138      * @return canvas
139      */
140     public static PCanvas createCanvas() {
141         final PCanvas canvas = new PCanvas();
142         final PCamera camera = canvas.getCamera();
143 
144         final PText tooltipNode = new PText();
145         tooltipNode.setPickable(false);
146         camera.addChild(tooltipNode);
147 
148         camera.addInputEventListener(new PBasicInputEventHandler() {
149             public void mouseMoved(final PInputEvent event) {
150                 updateToolTip(event);
151             }
152 
153             public void mouseDragged(final PInputEvent event) {
154                 updateToolTip(event);
155             }
156 
157             public void updateToolTip(final PInputEvent event) {
158                 final PNode n = event.getPickedNode();
159                 final Object object = (Object) n.getAttribute(TOOLTIP);
160                 if (object != null) {
161                     final String tooltipString = object.toString();
162                     final Point2D p = event.getCanvasPosition();
163 
164                     event.getPath().canvasToLocal(p, camera);
165 
166                     tooltipNode.setText(tooltipString);
167                     tooltipNode.setOffset(p.getX() + 8, p.getY() - 8);
168                 } else {
169                     tooltipNode.setText(null);
170                 }
171             }
172         });
173 
174         // uninstall default zoom event handler
175         canvas.removeInputEventListener(canvas.getZoomEventHandler());
176 
177         // install mouse wheel zoom event handler
178         final PMouseWheelZoomEventHandler mouseWheelZoomEventHandler = new PMouseWheelZoomEventHandler();
179         canvas.addInputEventListener(mouseWheelZoomEventHandler);
180 
181         return canvas;
182     }
183 
184     /**
185      * Build the 2D vector corresponding to the given angle.
186      * @param alpha angle
187      * @return the corresponding 2D vector
188      */
189     private static Vector2D buildVector(final double alpha) {
190         final SinCos sc = FastMath.sinCos(alpha);
191         return new Vector2D(sc.cos(), sc.sin());
192     }
193 
194     /** Main frame for geometry examples.
195      */
196     @SuppressWarnings("serial")
197     public static class Display extends ExampleFrame {
198 
199         /** Points to plot. */
200         private List<Vector2D> points;
201 
202         /** Canvas for plotting. */
203         private PCanvas canvas;
204 
205         /** Container. */
206         private JComponent container;
207 
208         /** Simple constructor.
209          */
210         public Display() {
211             setTitle("Hipparchus: Geometry Examples");
212             setSize(800, 700);
213 
214             container = new JPanel(new BorderLayout());
215             canvas = createCanvas();
216             container.add(canvas);
217             container.setBorder(BorderFactory.createLineBorder(Color.black, 1));
218 
219             JComponent controlPanel = new JPanel();
220             JButton random = new JButton("Randomize");
221             controlPanel.add(random);
222 
223             random.addActionListener(new ActionListener() {
224 
225 //                @Override
226                 public void actionPerformed(ActionEvent e) {
227                     canvas.getLayer().removeAllChildren();
228 
229                     points = createRandomPoints(1000);
230                     paintConvexHull();
231                 }
232             });
233 
234             JButton circle = new JButton("Circle");
235             controlPanel.add(circle);
236 
237             circle.addActionListener(new ActionListener() {
238 
239 //                @Override
240                 public void actionPerformed(ActionEvent e) {
241                     canvas.getLayer().removeAllChildren();
242 
243                     points = createCircle(100);
244                     paintConvexHull();
245                 }
246             });
247 
248             JButton cross = new JButton("Cross");
249             controlPanel.add(cross);
250 
251             cross.addActionListener(new ActionListener() {
252 
253 //                @Override
254                 public void actionPerformed(ActionEvent e) {
255                     canvas.getLayer().removeAllChildren();
256 
257                     points = createCross();
258                     paintConvexHull();
259                 }
260             });
261 
262             JSplitPane splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, container,
263                                                   controlPanel);
264             splitpane.setDividerLocation(600);
265 
266             add(splitpane);
267 
268             points = createRandomPoints(1000);
269             paintConvexHull();
270         }
271 
272         /** {@inheritDoc} */
273         @Override
274         public Component getMainPanel() {
275             return container;
276         }
277 
278         /** Paint a convex hull.
279          */
280         public void paintConvexHull() {
281             PNode pointSet = new PNode();
282             for (Vector2D point : points) {
283                 final PNode node = PPath.createEllipse(point.getX() - 1, point.getY() - 1, 2, 2);
284                 node.addAttribute(TOOLTIP, point);
285                 node.setPaint(Color.gray);
286                 pointSet.addChild(node);
287             }
288 
289             canvas.getLayer().addChild(pointSet);
290 
291             ConvexHullGenerator2D generator = new MonotoneChain(true, 1e-6);
292             ConvexHull2D hull = generator.generate(points); //AklToussaintHeuristic.reducePoints(points));
293 
294             PNode hullNode = new PNode();
295             for (Vector2D vertex : hull.getVertices()) {
296                 final PPath node = PPath.createEllipse(vertex.getX() - 1, vertex.getY() - 1, 2, 2);
297                 node.addAttribute(TOOLTIP, vertex);
298                 node.setPaint(Color.red);
299                 node.setStrokePaint(Color.red);
300                 hullNode.addChild(node);
301             }
302 
303             for (Segment line : hull.getLineSegments()) {
304                 final PPath node = PPath.createLine(line.getStart().getX(), line.getStart().getY(),
305                                                     line.getEnd().getX(), line.getEnd().getY());
306                 node.setPickable(false);
307                 node.setPaint(Color.red);
308                 node.setStrokePaint(Color.red);
309                 hullNode.addChild(node);
310             }
311 
312             canvas.getLayer().addChild(hullNode);
313 
314             Encloser<Euclidean2D, Vector2D> encloser =
315                     new WelzlEncloser<Euclidean2D, Vector2D>(1e-10, new DiskGenerator());
316             EnclosingBall<Euclidean2D, Vector2D> ball = encloser.enclose(points);
317 
318             final double radius = ball.getRadius();
319             PPath ballCenter =
320                     PPath.createEllipse(ball.getCenter().getX() - 1, ball.getCenter().getY() - 1, 2, 2);
321             ballCenter.setStrokePaint(Color.blue);
322             ballCenter.setPaint(Color.blue);
323             canvas.getLayer().addChild(0, ballCenter);
324 
325             PPath ballNode =
326                     PPath.createEllipse(ball.getCenter().getX() - radius, ball.getCenter().getY() - radius,
327                                         radius * 2, radius * 2);
328             ballNode.setTransparency(1.0f);
329             ballNode.setStrokePaint(Color.blue);
330             canvas.getLayer().addChild(0, ballNode);
331         }
332     }
333 
334     /** Main entry point.
335      * @param argv program arguments (unused here)
336      */
337     public static void main(final String[] argv) {
338         ExampleUtils.showExampleFrame(new Display());
339     }
340 
341 }