aboutsummaryrefslogtreecommitdiff
path: root/engine/src/desktop/jme3tools/navigation/MapModel2D.java
blob: a1a08c1c5e7d879be8baa46958e3de32cf181b96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
package jme3tools.navigation;
import java.awt.Point;
import java.text.DecimalFormat;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


/**
 * A representation of the actual map in terms of lat/long and x,y co-ordinates.
 * The Map class contains various helper methods such as methods for determining
 * the pixel positions for lat/long co-ordinates and vice versa.
 *
 * @author Cormac Gebruers
 * @author Benjamin Jakobus
 * @version 1.0
 * @since 1.0
 */
public class MapModel2D {

    /* The number of radians per degree */
    private final static double RADIANS_PER_DEGREE = 57.2957;

    /* The number of degrees per radian */
    private final static double DEGREES_PER_RADIAN = 0.0174532925;

    /* The map's width in longitude */
    public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;

    /* The top right hand corner of the map */
    private Position centre;

    /* The x and y co-ordinates for the viewport's centre */
    private int xCentre;
    private int yCentre;

    /* The width (in pixels) of the viewport holding the map */
    private int viewportWidth;

    /* The viewport height in pixels */
    private int viewportHeight;

    /* The number of minutes that one pixel represents */
    private double minutesPerPixel;

    /**
     * Constructor
     * @param viewportWidth the pixel width of the viewport (component) in which
     *        the map is displayed
     * @since 1.0
     */
    public MapModel2D(int viewportWidth) {
        try {
            this.centre = new Position(0, 0);
        } catch (InvalidPositionException e) {
            e.printStackTrace();
        }

        this.viewportWidth = viewportWidth;

        // Calculate the number of minutes that one pixel represents along the longitude
        calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE);

        // Calculate the viewport height based on its width and the number of degrees (85)
        // in our map
        viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2;
//        viewportHeight = viewportWidth; // REMOVE!!!
        // Determine the map's x,y centre
        xCentre = viewportWidth / 2;
        yCentre = viewportHeight / 2;
    }

    /**
     * Returns the height of the viewport in pixels
     * @return the height of the viewport in pixels
     * @since 0.1
     */
    public int getViewportPixelHeight() {
        return viewportHeight;
    }

    /**
     * Calculates the number of minutes per pixels using a given
     * map width in longitude
     * @param mapWidthInLongitude
     * @since 1.0
     */
    public void calculateMinutesPerPixel(double mapWidthInLongitude) {
        minutesPerPixel = (mapWidthInLongitude * 60) / (double) viewportWidth;
    }

    /**
     * Returns the width of the viewport in pixels
     * @return the width of the viewport in pixels
     * @since 0.1
     */
    public int getViewportPixelWidth() {
        return viewportWidth;
    }

    public void setViewportWidth(int viewportWidth) {
        this.viewportWidth = viewportWidth;
    }

    public void setViewportHeight(int viewportHeight) {
        this.viewportHeight = viewportHeight;
    }

    public void setCentre(Position centre) {
        this.centre = centre;
    }

    /**
     * Returns the number of minutes there are per pixel
     * @return the number of minutes per pixel
     * @since 1.0
     */
    public double getMinutesPerPixel() {
        return minutesPerPixel;
    }

    public double getMetersPerPixel() {
        return 1853 * minutesPerPixel;
    }

    public void setMinutesPerPixel(double minutesPerPixel) {
        this.minutesPerPixel = minutesPerPixel;
    }

    /**
     * Converts a latitude/longitude position into a pixel co-ordinate
     * @param position the position to convert
     * @return {@code Point} a pixel co-ordinate
     * @since 1.0
     */
    public Point toPixel(Position position) {
        // Get the distance between position and the centre for calculating
        // the position's longitude translation
        double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
                position.getLongitude());

        // Use the distance from the centre to calculate the pixel x co-ordinate
        double distanceInPixels = (distance / minutesPerPixel);

        // Use the difference in meridional parts to calculate the pixel y co-ordinate
        double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
                position.getLatitude());

        int x = 0;
        int y = 0;

        if (centre.getLatitude() == position.getLatitude()) {
            y = yCentre;
        }
        if (centre.getLongitude() == position.getLongitude()) {
            x = xCentre;
        }

        // Distinguish between northern and southern hemisphere for latitude calculations
        if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
            // Centre is north. Position is north of centre
            y = yCentre + (int) ((dmp) / minutesPerPixel);
        } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
            // Centre is north. Position is south of centre
            y = yCentre - (int) ((dmp) / minutesPerPixel);
        } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
            // Centre is south. Position is north of centre
            y = yCentre + (int) ((dmp) / minutesPerPixel);
        } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
            // Centre is south. Position is south of centre
            y = yCentre - (int) ((dmp) / minutesPerPixel);
        } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
            // Centre is at the equator. Position is north of the equator
            y = yCentre + (int) ((dmp) / minutesPerPixel);
        } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
            // Centre is at the equator. Position is south of the equator
            y = yCentre - (int) ((dmp) / minutesPerPixel);
        }

        // Distinguish between western and eastern hemisphere for longitude calculations
        if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
            // Centre is west. Position is west of centre
            x = xCentre - (int) distanceInPixels;
        } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
            // Centre is west. Position is south of centre
            x = xCentre + (int) distanceInPixels;
        } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
            // Centre is east. Position is west of centre
            x = xCentre - (int) distanceInPixels;
        } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
            // Centre is east. Position is east of centre
            x = xCentre + (int) distanceInPixels;
        } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
            // Centre is at the equator. Position is east of centre
            x = xCentre + (int) distanceInPixels;
        } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
            // Centre is at the equator. Position is west of centre
            x = xCentre - (int) distanceInPixels;
        }

        // Distinguish between northern and souterhn hemisphere for longitude calculations
        return new Point(x, y);
    }

    /**
     * Converts a pixel position into a mercator position
     * @param p {@link Point} object that you wish to convert into
     *        longitude / latiude
     * @return the converted {@code Position} object
     * @since 1.0
     */
    public Position toPosition(Point p) {
        double lat, lon;
        Position pos = null;
        try {
            Point pixelCentre = toPixel(new Position(0, 0));

            // Get the distance between position and the centre
            double xDistance = distance(xCentre, p.getX());
            double yDistance = distance(pixelCentre.getY(), p.getY());
            double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60;
            double mp = (yDistance * minutesPerPixel);
            // If we are zoomed in past a certain point, then use linear search.
            // Otherwise use binary search
            if (getMinutesPerPixel() < 0.05) {
                lat = findLat(mp, getCentre().getLatitude());
                if (lat == -1000) {
                    System.out.println("lat: " + lat);
                }
            } else {
                lat = findLat(mp, 0.0, 85.0);
            }
            lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
                    : centre.getLongitude() + lonDistanceInDegrees);

            if (p.getY() > pixelCentre.getY()) {
                lat = -1 * lat;
            }
            if (lat == -1000 || lon == -1000) {
                return pos;
            }
            pos = new Position(lat, lon);
        } catch (InvalidPositionException ipe) {
            ipe.printStackTrace();
        }
        return pos;
    }

    /**
     * Calculates distance between two points on the map in pixels
     * @param a
     * @param b
     * @return distance the distance between a and b in pixels
     * @since 1.0
     */
    private double distance(double a, double b) {
        return Math.abs(a - b);
    }

    /**
     * Defines the centre of the map in pixels
     * @param p <code>Point</code> object denoting the map's new centre
     * @since 1.0
     */
    public void setCentre(Point p) {
        try {
            Position newCentre = toPosition(p);
            if (newCentre != null) {
                centre = newCentre;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the map's xCentre
     * @param xCentre
     * @since 1.0
     */
    public void setXCentre(int xCentre) {
        this.xCentre = xCentre;
    }

    /**
     * Sets the map's yCentre
     * @param yCentre
     * @since 1.0
     */
    public void setYCentre(int yCentre) {
        this.yCentre = yCentre;
    }

    /**
     * Returns the pixel (x,y) centre of the map
     * @return {@link Point) object marking the map's (x,y) centre
     * @since 1.0
     */
    public Point getPixelCentre() {
        return new Point(xCentre, yCentre);
    }

    /**
     * Returns the {@code Position} centre of the map
     * @return {@code Position} object marking the map's (lat, long) centre
     * @since 1.0
     */
    public Position getCentre() {
        return centre;
    }

    /**
     * Uses binary search to find the latitude of a given MP.
     *
     * @param mp maridian part
     * @param low
     * @param high
     * @return the latitude of the MP value
     * @since 1.0
     */
    private double findLat(double mp, double low, double high) {
        DecimalFormat form = new DecimalFormat("#.####");
        mp = Math.round(mp);
        double midLat = (low + high) / 2.0;
        // ctr is used to make sure that with some
        // numbers which can't be represented exactly don't inifitely repeat
        double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);

        while (low <= high) {
            if (guessMP == mp) {
                return midLat;
            } else {
                if (guessMP > mp) {
                    high = midLat - 0.0001;
                } else {
                    low = midLat + 0.0001;
                }
            }

            midLat = Double.valueOf(form.format(((low + high) / 2.0)));
            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
            guessMP = Math.round(guessMP);
        }
        return -1000;
    }

    /**
     * Uses linear search to find the latitude of a given MP
     * @param mp the meridian part for which to find the latitude
     * @param previousLat the previous latitude. Used as a upper / lower bound
     * @return the latitude of the MP value
     */
    private double findLat(double mp, double previousLat) {
        DecimalFormat form = new DecimalFormat("#.#####");
        mp = Double.parseDouble(form.format(mp));
        double guessMP;
        for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
            guessMP = Double.parseDouble(form.format(guessMP));
            if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) {
                return lat;
            }
        }
        return -1000;
    }
}