Browse Source

Use a better projection for BasicDrawing.

Holt59 3 years ago
parent
commit
f3964f1958

+ 30
- 0
src/main/org/insa/graph/GraphStatistics.java View File

@@ -51,6 +51,36 @@ public class GraphStatistics {
51 51
             return topLeft;
52 52
         }
53 53
 
54
+        /**
55
+         * Create a new bounding box by extending the current one according to the given
56
+         * value for each side.
57
+         * 
58
+         * @param left Extra size to add to the left of the box.
59
+         * @param top Extra size to add to the top of the box.
60
+         * @param right Extra size to add to the right of the box.
61
+         * @param bottom Extra size to add to the bottom of the box.
62
+         * 
63
+         * @return New bounding box corresponding to an extension of the current one.
64
+         */
65
+        public BoundingBox extend(float left, float top, float right, float bottom) {
66
+            return new BoundingBox(
67
+                    new Point(this.topLeft.getLongitude() - left, this.topLeft.getLatitude() + top),
68
+                    new Point(this.bottomRight.getLongitude() + right,
69
+                            this.bottomRight.getLatitude() - bottom));
70
+        }
71
+
72
+        /**
73
+         * Create a new bounding box by extending the current one according by the given
74
+         * value on each side.
75
+         * 
76
+         * @param size Extra size to add to each side of this box.
77
+         * 
78
+         * @return New bounding box corresponding to an extension of the current one.
79
+         */
80
+        public BoundingBox extend(float size) {
81
+            return this.extend(size, size, size, size);
82
+        }
83
+
54 84
     }
55 85
 
56 86
     // Bounding box for this graph.

+ 144
- 0
src/main/org/insa/graphics/drawing/MercatorProjection.java View File

@@ -0,0 +1,144 @@
1
+package org.insa.graphics.drawing;
2
+
3
+import java.awt.Dimension;
4
+
5
+import org.insa.graph.GraphStatistics.BoundingBox;
6
+
7
+public class MercatorProjection {
8
+
9
+    public static final double MAX_LATITUDE = 82;
10
+
11
+    public static final double MIN_LATITUDE = -MAX_LATITUDE;
12
+
13
+    // From Wikipedia... for the above max/min latitude.
14
+    private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746;
15
+
16
+    private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE);
17
+    private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE);
18
+
19
+    // Bounding box
20
+    private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
21
+
22
+    // Projection of min and max latitude.
23
+    private final double minLatitudeProj, maxLatitudeProj;
24
+
25
+    // Dimension of the image
26
+    private final double width, height;
27
+
28
+    /**
29
+     * Create a new MercatorProjection corresponding to the given BoundingBox and
30
+     * maxSize.
31
+     * 
32
+     * @param boundingBox Box for this projection.
33
+     * @param maxSize Maximum size of any side (width / height) of the image to
34
+     *        which this projection should draw.
35
+     */
36
+    public MercatorProjection(BoundingBox boundingBox, int maxSize) {
37
+        // Find minimum/maximum longitude and latitude.
38
+        this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
39
+        this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
40
+        this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
41
+        this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
42
+
43
+        // Compute projection
44
+        this.minLatitudeProj = projectY(this.minLatitude);
45
+        this.maxLatitudeProj = projectY(this.maxLatitude);
46
+
47
+        Dimension imageDimension = computeImageSize(maxSize);
48
+        this.width = imageDimension.getWidth();
49
+        this.height = imageDimension.getHeight();
50
+    }
51
+
52
+    /**
53
+     * @return Image width for this projection to work properly.
54
+     */
55
+    public double getImageWidth() {
56
+        return this.width;
57
+    }
58
+
59
+    /**
60
+     * @return Image weight for this projection to work properly.
61
+     */
62
+    public double getImageHeight() {
63
+        return this.height;
64
+    }
65
+
66
+    /**
67
+     * Compute the projection (without scaling) of the given latitude.
68
+     * 
69
+     * @param latitude Latitude to project.
70
+     * 
71
+     * @return Projection of the given latitude (without scaling).
72
+     */
73
+    private static double projectY(double latitude) {
74
+        double sinLatitude = Math.sin(latitude * Math.PI / 180.0);
75
+        return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2;
76
+    }
77
+
78
+    /**
79
+     * Compute the dimension required for drawing a projection of the given box on
80
+     * an image, ensuring that none of the side of image is greater than maxSize.
81
+     * 
82
+     * @param maxSize Maximum side of any side of the image.
83
+     * 
84
+     * @return Dimension corresponding to the preferred size for the image.
85
+     */
86
+    protected Dimension computeImageSize(int maxSize) {
87
+        double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0;
88
+        double propHeight = (this.maxLatitudeProj - this.minLatitudeProj)
89
+                / (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT;
90
+
91
+        return propWidth < propHeight
92
+                ? new Dimension((int) (maxSize * propWidth / propHeight), maxSize)
93
+                : new Dimension(maxSize, (int) (maxSize * propHeight / propWidth));
94
+    }
95
+
96
+    /**
97
+     * Project the given latitude on the image.
98
+     * 
99
+     * @param latitude Latitude to project.
100
+     * 
101
+     * @return Projected position of the latitude on the image.
102
+     */
103
+    public int latitudeToPixelY(float latitude) {
104
+        return (int) ((this.maxLatitudeProj - projectY(latitude))
105
+                / (this.maxLatitudeProj - this.minLatitudeProj) * this.height);
106
+    }
107
+
108
+    /**
109
+     * Project the given longitude on the image.
110
+     * 
111
+     * @param longitude Longitude to project.
112
+     * 
113
+     * @return Projected position of the longitude on the image.
114
+     */
115
+    public int longitudeToPixelX(float longitude) {
116
+        return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude));
117
+    }
118
+
119
+    /**
120
+     * Retrieve the latitude associated to the given projected point.
121
+     * 
122
+     * @param py Projected y-position for which latitude should be retrieved.
123
+     * 
124
+     * @return The original latitude of the point.
125
+     */
126
+    public float pixelYToLatitude(double py) {
127
+        float y = (float) (this.maxLatitudeProj
128
+                - (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj));
129
+        return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI);
130
+    }
131
+
132
+    /**
133
+     * Retrieve the longitude associated to the given projected point.
134
+     * 
135
+     * @param px Projected x-position for which longitude should be retrieved.
136
+     * 
137
+     * @return The original longitude of the point.
138
+     */
139
+    public float pixelXToLongitude(double px) {
140
+        return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude)
141
+                + this.minLongitude);
142
+    }
143
+
144
+}

+ 25
- 55
src/main/org/insa/graphics/drawing/components/BasicDrawing.java View File

@@ -31,6 +31,7 @@ import org.insa.graphics.drawing.BasicGraphPalette;
31 31
 import org.insa.graphics.drawing.Drawing;
32 32
 import org.insa.graphics.drawing.DrawingClickListener;
33 33
 import org.insa.graphics.drawing.GraphPalette;
34
+import org.insa.graphics.drawing.MercatorProjection;
34 35
 import org.insa.graphics.drawing.overlays.MarkerOverlay;
35 36
 import org.insa.graphics.drawing.overlays.MarkerUtils;
36 37
 import org.insa.graphics.drawing.overlays.Overlay;
@@ -146,8 +147,8 @@ public class BasicDrawing extends JPanel implements Drawing {
146 147
         @Override
147 148
         public void drawImpl(Graphics2D graphics) {
148 149
 
149
-            int px = BasicDrawing.this.projx(getPoint().getLongitude());
150
-            int py = BasicDrawing.this.projy(getPoint().getLatitude());
150
+            int px = projection.longitudeToPixelX(getPoint().getLongitude());
151
+            int py = projection.latitudeToPixelY(getPoint().getLatitude());
151 152
 
152 153
             graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
153 154
                     MARKER_HEIGHT, BasicDrawing.this);
@@ -193,10 +194,10 @@ public class BasicDrawing extends JPanel implements Drawing {
193 194
                 while (itPoint.hasNext()) {
194 195
                     Point curr = itPoint.next();
195 196
 
196
-                    int x1 = BasicDrawing.this.projx(prev.getLongitude());
197
-                    int x2 = BasicDrawing.this.projx(curr.getLongitude());
198
-                    int y1 = BasicDrawing.this.projy(prev.getLatitude());
199
-                    int y2 = BasicDrawing.this.projy(curr.getLatitude());
197
+                    int x1 = projection.longitudeToPixelX(prev.getLongitude());
198
+                    int x2 = projection.longitudeToPixelX(curr.getLongitude());
199
+                    int y1 = projection.latitudeToPixelY(prev.getLatitude());
200
+                    int y2 = projection.latitudeToPixelY(curr.getLatitude());
200 201
 
201 202
                     graphics.drawLine(x1, y1, x2, y2);
202 203
 
@@ -253,8 +254,8 @@ public class BasicDrawing extends JPanel implements Drawing {
253 254
 
254 255
         @Override
255 256
         public void addPoint(Point point) {
256
-            int x = BasicDrawing.this.projx(point.getLongitude()) - this.width / 2;
257
-            int y = BasicDrawing.this.projy(point.getLatitude()) - this.width / 2;
257
+            int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
258
+            int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
258 259
             this.graphics.fillOval(x, y, this.width, this.width);
259 260
             BasicDrawing.this.repaint();
260 261
         }
@@ -294,7 +295,7 @@ public class BasicDrawing extends JPanel implements Drawing {
294 295
     // Maximum width for the drawing (in pixels).
295 296
     private static final int MAXIMUM_DRAWING_WIDTH = 2000;
296 297
 
297
-    private double long1, long2, lat1, lat2;
298
+    private MercatorProjection projection;
298 299
 
299 300
     // Width and height of the image
300 301
     private int width, height;
@@ -430,22 +431,6 @@ public class BasicDrawing extends JPanel implements Drawing {
430 431
     }
431 432
 
432 433
     /**
433
-     * @param lon
434
-     * @return
435
-     */
436
-    private int projx(double lon) {
437
-        return (int) (width * (lon - this.long1) / (this.long2 - this.long1));
438
-    }
439
-
440
-    /**
441
-     * @param lat
442
-     * @return
443
-     */
444
-    private int projy(double lat) {
445
-        return (int) (height * (1 - (lat - this.lat1) / (this.lat2 - this.lat1)));
446
-    }
447
-
448
-    /**
449 434
      * Return the longitude and latitude corresponding to the given position of the
450 435
      * MouseEvent.
451 436
      * 
@@ -465,13 +450,8 @@ public class BasicDrawing extends JPanel implements Drawing {
465 450
                 .inverseTransform(event.getPoint(), null);
466 451
 
467 452
         // Inverse the "projection" on x/y to get longitude and latitude.
468
-        double lon = ptDst.getX();
469
-        double lat = ptDst.getY();
470
-        lon = (lon / this.width) * (this.long2 - this.long1) + this.long1;
471
-        lat = (1 - lat / this.height) * (this.lat2 - this.lat1) + this.lat1;
472
-
473
-        // Return a new point.
474
-        return new Point((float) lon, (float) lat);
453
+        return new Point(projection.pixelXToLongitude(ptDst.getX()),
454
+                projection.pixelYToLatitude(ptDst.getY()));
475 455
     }
476 456
 
477 457
     /*
@@ -546,10 +526,10 @@ public class BasicDrawing extends JPanel implements Drawing {
546 526
             while (it1.hasNext()) {
547 527
                 Point curr = it1.next();
548 528
 
549
-                int x1 = this.projx(prev.getLongitude());
550
-                int x2 = this.projx(curr.getLongitude());
551
-                int y1 = this.projy(prev.getLatitude());
552
-                int y2 = this.projy(curr.getLatitude());
529
+                int x1 = projection.longitudeToPixelX(prev.getLongitude());
530
+                int x2 = projection.longitudeToPixelX(curr.getLongitude());
531
+                int y1 = projection.latitudeToPixelY(prev.getLatitude());
532
+                int y2 = projection.latitudeToPixelY(curr.getLatitude());
553 533
 
554 534
                 graphGraphics.drawLine(x1, y1, x2, y2);
555 535
                 prev = curr;
@@ -573,30 +553,20 @@ public class BasicDrawing extends JPanel implements Drawing {
573 553
         BoundingBox box = graph.getGraphInformation().getBoundingBox();
574 554
 
575 555
         // Find minimum/maximum longitude and latitude.
576
-        double minLon = box.getTopLeftPoint().getLongitude(),
556
+        float minLon = box.getTopLeftPoint().getLongitude(),
577 557
                 maxLon = box.getBottomRightPoint().getLongitude(),
578 558
                 minLat = box.getBottomRightPoint().getLatitude(),
579 559
                 maxLat = box.getTopLeftPoint().getLatitude();
580 560
 
581 561
         // Add a little delta to avoid drawing on the edge...
582
-        double diffLon = maxLon - minLon, diffLat = maxLat - minLat;
583
-        double deltaLon = 0.01 * diffLon, deltaLat = 0.01 * diffLat;
584
-
585
-        this.long1 = minLon - deltaLon;
586
-        this.long2 = maxLon + deltaLon;
587
-        this.lat1 = minLat - deltaLat;
588
-        this.lat2 = maxLat + deltaLat;
589
-
590
-        // Compute width/height for the image
591
-
592
-        if (diffLat < diffLon) {
593
-            this.width = MAXIMUM_DRAWING_WIDTH;
594
-            this.height = (int) (this.width * diffLat / diffLon);
595
-        }
596
-        else {
597
-            this.height = MAXIMUM_DRAWING_WIDTH;
598
-            this.width = (int) (this.height * diffLon / diffLat);
599
-        }
562
+        float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
563
+        float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
564
+
565
+        // Create the projection and retrieve width and height for the box.
566
+        projection = new MercatorProjection(box.extend(deltaLon, deltaLat, deltaLon, deltaLat),
567
+                MAXIMUM_DRAWING_WIDTH);
568
+        this.width = (int) projection.getImageWidth();
569
+        this.height = (int) projection.getImageHeight();
600 570
 
601 571
         // Create the image
602 572
         BufferedImage img = new BufferedImage(this.width, this.height,

Loading…
Cancel
Save