Statistics
| Revision:

svn-gvsig-desktop / trunk / libraries / libFMap / src / com / iver / cit / gvsig / fmap / tools / geo / Geo.java @ 9820

History | View | Annotate | Download (30.9 KB)

1 9820 caballero
/*
2
 *                     RESTRICTED RIGHTS LEGEND
3
 *
4
 *                        BBNT Solutions LLC
5
 *                        A Verizon Company
6
 *                        10 Moulton Street
7
 *                       Cambridge, MA 02138
8
 *                         (617) 873-3000
9
 *
10
 * Copyright BBNT Solutions LLC 2001, 2002 All Rights Reserved
11
 *
12
 */
13
14
package com.iver.cit.gvsig.fmap.tools.geo;
15
16
import java.util.ArrayList;
17
import java.util.Enumeration;
18
import java.util.Iterator;
19
20
21
/**
22
 * A class that represents a point on the Earth as a three dimensional unit
23
 * length vector, rather than latitude and longitude. For the theory and an
24
 * efficient implementation using partial evaluation see:
25
 * http://openmap.bbn.com/~kanderso/lisp/performing-lisp/essence.ps
26
 *
27
 * This implementation matches the theory carefully, but does not use partial
28
 * evaluation.
29
 *
30
 * <p>
31
 * For the area calculation see: http://math.rice.edu/~pcmi/sphere/
32
 *
33
 * @author Ken Anderson
34
 * @author Sachin Date
35
 * @author Ben Lubin
36
 * @author Michael Thome
37
 * @version $Revision$ on $Date$
38
 */
39
public class Geo {
40
41
    /***************************************************************************
42
     * Constants for the shape of the earth. see
43
     * http://www.gfy.ku.dk/%7Eiag/HB2000/part4/groten.htm
44
     **************************************************************************/
45
    // Replaced by Length constants.
46
    // public static final double radiusKM = 6378.13662; // in KM.
47
    // public static final double radiusNM = 3443.9182; // in NM.
48
    // Replaced with WGS 84 constants
49
    // public static final double flattening = 1.0/298.25642;
50
    public static final double flattening = 1.0 / 298.257223563;
51
    public static final double FLATTENING_C = (1.0 - flattening)
52
            * (1.0 - flattening);
53
54
    public static final double METERS_PER_NM = 1852;
55
    private static final double NPD_LTERM1 = 111412.84 / METERS_PER_NM;
56
    private static final double NPD_LTERM2 = -93.5 / METERS_PER_NM;
57
    private static final double NPD_LTERM3 = 0.118 / METERS_PER_NM;
58
59
    private double x;
60
    private double y;
61
    private double z;
62
63
    public static void main(String[] args) {
64
            ArrayList geos=new ArrayList();
65
            geos.add( new Geo(-7,40));
66
            geos.add( new Geo(-3,45));
67
            geos.add( new Geo(-5,20));
68
            double area=area(geos.iterator());
69
            double areaM=km(area);
70
            System.out.println("area en Metros = "+areaM);
71
        }
72
    /**
73
     * Compute nautical miles per degree at a specified latitude (in degrees).
74
     * Calculation from NIMA: http://pollux.nss.nima.mil/calc/degree.html
75
     */
76
    public final static double npdAtLat(double latdeg) {
77
        double lat = (latdeg * Math.PI) / 180.0;
78
        return (NPD_LTERM1 * Math.cos(lat) + NPD_LTERM2 * Math.cos(3 * lat) + NPD_LTERM3
79
                * Math.cos(5 * lat));
80
    }
81
82
    /** Convert from geographic to geocentric latitude (radians) */
83
    public static double geocentricLatitude(double geographicLatitude) {
84
        return Math.atan((Math.tan(geographicLatitude) * FLATTENING_C));
85
    }
86
87
    /** Convert from geocentric to geographic latitude (radians) */
88
    public static double geographicLatitude(double geocentricLatitude) {
89
        return Math.atan(Math.tan(geocentricLatitude) / FLATTENING_C);
90
    }
91
92
    /** Convert from degrees to radians. */
93
    public static double radians(double degrees) {
94
        return Length.DECIMAL_DEGREE.toRadians(degrees);
95
    }
96
97
    /** Convert from radians to degrees. */
98
    public static double degrees(double radians) {
99
        return Length.DECIMAL_DEGREE.fromRadians(radians);
100
    }
101
102
    /** Convert radians to kilometers. * */
103
    public static double km(double radians) {
104
        return Length.KM.fromRadians(radians);
105
    }
106
    /** Convert radians to Meters. * */
107
    public static double meters(double radians) {
108
        return Length.METER.fromRadians(radians);
109
    }
110
    /** Convert kilometers to radians. * */
111
    public static double kmToAngle(double km) {
112
        return Length.KM.toRadians(km);
113
    }
114
115
    /** Convert radians to nauticalMiles. * */
116
//    public static double nm(double radians) {
117
//        return Length.NM.fromRadians(radians);
118
//    }
119
120
    /** Convert nautical miles to radians. * */
121
//    public static double nmToAngle(double nm) {
122
//        return Length.NM.toRadians(nm);
123
//    }
124
125
    /**
126
     * Construct a Geo from its latitude and longitude.
127
     *
128
     * @param lat latitude in decimal degrees.
129
     * @param lon longitude in decimal degrees.
130
     */
131
    public Geo(double lat, double lon) {
132
        initialize(lat, lon);
133
    }
134
135
    /**
136
     * Construct a Geo from its latitude and longitude.
137
     *
138
     * @param lat latitude.
139
     * @param lon longitude.
140
     * @param isDegrees should be true if the lat/lon are specified in decimal
141
     *        degrees, false if they are radians.
142
     */
143
    public Geo(double lat, double lon, boolean isDegrees) {
144
        if (isDegrees) {
145
            initialize(lat, lon);
146
        } else {
147
            initializeRadians(lat, lon);
148
        }
149
    }
150
151
    /** Construct a Geo from its parts. */
152
    public Geo(double x, double y, double z) {
153
        this.x = x;
154
        this.y = y;
155
        this.z = z;
156
    }
157
158
    /** Construct a Geo from another Geo. */
159
    public Geo(Geo geo) {
160
        this(geo.x, geo.y, geo.z);
161
    }
162
163
    public static final Geo makeGeoRadians(double latr, double lonr) {
164
        double rlat = geocentricLatitude(latr);
165
        double c = Math.cos(rlat);
166
        return new Geo(c * Math.cos(lonr), c * Math.sin(lonr), Math.sin(rlat));
167
    }
168
169
    public static final Geo makeGeoDegrees(double latd, double lond) {
170
        return makeGeoRadians(radians(latd), radians(lond));
171
    }
172
173
    public static final Geo makeGeo(double x, double y, double z) {
174
        return new Geo(x, y, z);
175
    }
176
177
    public static final Geo makeGeo(Geo p) {
178
        return new Geo(p.x, p.y, p.z);
179
    }
180
181
    /**
182
     * Initialize this Geo to match another.
183
     *
184
     * @param g
185
     */
186
    public void initialize(Geo g) {
187
        x = g.x;
188
        y = g.y;
189
        z = g.z;
190
    }
191
192
    /**
193
     * Initialize this Geo with new parameters.
194
     *
195
     * @param x
196
     * @param y
197
     * @param z
198
     */
199
    public void initialize(double x, double y, double z) {
200
        this.x = x;
201
        this.y = y;
202
        this.z = z;
203
    }
204
205
    /**
206
     * Initialize this Geo with to represent coordinates.
207
     *
208
     * @param lat latitude in decimal degrees.
209
     * @param lon longitude in decimal degrees.
210
     */
211
    public void initialize(double lat, double lon) {
212
        initializeRadians(radians(lat), radians(lon));
213
    }
214
215
    /**
216
     * Initialize this Geo with to represent coordinates.
217
     *
218
     * @param lat latitude in radians.
219
     * @param lon longitude in radians.
220
     */
221
    public void initializeRadians(double lat, double lon) {
222
        double rlat = geocentricLatitude(lat);
223
        double c = Math.cos(rlat);
224
        x = c * Math.cos(lon);
225
        y = c * Math.sin(lon);
226
        z = Math.sin(rlat);
227
    }
228
229
    /**
230
     * Find the midpoint Geo between this one and another on a Great Circle line
231
     * between the two. The result is undefined of the two points are antipodes.
232
     *
233
     * @param g2
234
     * @return midpoint Geo.
235
     */
236
    public Geo midPoint(Geo g2) {
237
        return add(g2).normalize();
238
    }
239
240
    public Geo interpolate(Geo g2, double x) {
241
        return scale(x).add(g2.scale(1 - x)).normalize();
242
    }
243
244
    public String toString() {
245
        return "Geo[" + getLatitude() + "," + getLongitude() + "]";
246
    }
247
248
    public double getLatitude() {
249
        return degrees(geographicLatitude(Math.atan2(z,
250
                Math.sqrt(x * x + y * y))));
251
    }
252
253
    public double getLongitude() {
254
        return degrees(Math.atan2(y, x));
255
    }
256
257
    public double getLatitudeRadians() {
258
        return geographicLatitude(Math.atan2(z, Math.sqrt(x * x + y * y)));
259
    }
260
261
    public double getLongitudeRadians() {
262
        return Math.atan2(y, x);
263
    }
264
265
    /**
266
     * Reader for x, in internal axis representation (positive to the right side
267
     * of screen).
268
     *
269
     * @return
270
     */
271
    public final double x() {
272
        return this.x;
273
    }
274
275
    /**
276
     * Reader for y in internal axis reprensentation (positive into screen).
277
     *
278
     * @return
279
     */
280
    public final double y() {
281
        return this.y;
282
    }
283
284
    /**
285
     * Reader for z in internal axis representation (positive going to top of
286
     * screen).
287
     *
288
     * @return
289
     */
290
    public final double z() {
291
        return this.z;
292
    }
293
294
    public void setLength(double r) {
295
        // It's tempting to call getLatitudeRadians() here, but it changes the
296
        // angle. I think we want to keep the angles the same, and just extend
297
        // x, y, z, and then let the latitudes get refigured out for the
298
        // ellipsoid when they are asked for.
299
        double rlat = Math.atan2(z, Math.sqrt(x * x + y * y));
300
        double rlon = getLongitudeRadians();
301
302
        double c = r * Math.cos(rlat);
303
        x = c * Math.cos(rlon);
304
        y = c * Math.sin(rlon);
305
        z = r * Math.sin(rlat);
306
    }
307
308
    /** North pole. */
309
    public static final Geo north = new Geo(0.0, 0.0, 1.0);
310
311
    /** Dot product. */
312
    public double dot(Geo b) {
313
        return (this.x() * b.x() + this.y() * b.y() + this.z() * b.z());
314
    }
315
316
    /** Dot product. */
317
    public static double dot(Geo a, Geo b) {
318
        return (a.x() * b.x() + a.y() * b.y() + a.z() * b.z());
319
    }
320
321
    /** Euclidian length. */
322
    public double length() {
323
        return Math.sqrt(this.dot(this));
324
    }
325
326
    /** Multiply this by s. * */
327
    public Geo scale(double s) {
328
        return new Geo(this.x() * s, this.y() * s, this.z() * s);
329
    }
330
331
    /** Returns a unit length vector parallel to this. */
332
    public Geo normalize() {
333
        return this.scale(1.0 / this.length());
334
    }
335
336
    /** Vector cross product. */
337
    public Geo cross(Geo b) {
338
        return new Geo(this.y() * b.z() - this.z() * b.y(), this.z() * b.x()
339
                - this.x() * b.z(), this.x() * b.y() - this.y() * b.x());
340
    }
341
342
    /** Eqvivalent to this.cross(b).length(). */
343
    public double crossLength(Geo b) {
344
        double x = this.y() * b.z() - this.z() * b.y();
345
        double y = this.z() * b.x() - this.x() * b.z();
346
        double z = this.x() * b.y() - this.y() * b.x();
347
        return Math.sqrt(x * x + y * y + z * z);
348
    }
349
350
    /** Eqvivalent to <code>this.cross(b).normalize()</code>. */
351
    public Geo crossNormalize(Geo b) {
352
        double x = this.y() * b.z() - this.z() * b.y();
353
        double y = this.z() * b.x() - this.x() * b.z();
354
        double z = this.x() * b.y() - this.y() * b.x();
355
        double L = Math.sqrt(x * x + y * y + z * z);
356
        return new Geo(x / L, y / L, z / L);
357
    }
358
359
    /** Eqvivalent to <code>this.cross(b).normalize()</code>. */
360
    public static Geo crossNormalize(Geo a, Geo b) {
361
        return a.crossNormalize(b);
362
    }
363
364
    /** Returns this + b. */
365
    public Geo add(Geo b) {
366
        return new Geo(this.x() + b.x(), this.y() + b.y(), this.z() + b.z());
367
    }
368
369
    /** Returns this - b. */
370
    public Geo subtract(Geo b) {
371
        return new Geo(this.x() - b.x(), this.y() - b.y(), this.z() - b.z());
372
    }
373
374
    public boolean equals(Geo v2) {
375
        return this.x == v2.x && this.y == v2.y && this.z == v2.z;
376
    }
377
378
    /** Angular distance, in radians between this and v2. */
379
    public double distance(Geo v2) {
380
        return Math.atan2(v2.crossLength(this), v2.dot(this));
381
    }
382
383
    /** Angular distance, in radians between v1 and v2. */
384
    public static double distance(Geo v1, Geo v2) {
385
        return v1.distance(v2);
386
    }
387
388
    /** Angular distance, in radians between the two lat lon points. */
389
    public static double distance(double lat1, double lon1, double lat2,
390
                                  double lon2) {
391
        return Geo.distance(new Geo(lat1, lon1), new Geo(lat2, lon2));
392
    }
393
394
    /** Distance in kilometers. * */
395
    public double distanceKM(Geo v2) {
396
        return km(distance(v2));
397
    }
398
399
    /** Distance in kilometers. * */
400
    public static double distanceKM(Geo v1, Geo v2) {
401
        return v1.distanceKM(v2);
402
    }
403
404
    /** Distance in kilometers. * */
405
    public static double distanceKM(double lat1, double lon1, double lat2,
406
                                    double lon2) {
407
        return Geo.distanceKM(new Geo(lat1, lon1), new Geo(lat2, lon2));
408
    }
409
410
    /** Distance in nautical miles. * */
411
//    public double distanceNM(Geo v2) {
412
//        return nm(distance(v2));
413
//    }
414
415
    /** Distance in nautical miles. * */
416
//    public static double distanceNM(Geo v1, Geo v2) {
417
//        return v1.distanceNM(v2);
418
//    }
419
420
    /** Distance in nautical miles. * */
421
//    public static double distanceNM(double lat1, double lon1, double lat2,
422
//                                    double lon2) {
423
//        return Geo.distanceNM(new Geo(lat1, lon1), new Geo(lat2, lon2));
424
//    }
425
426
    /** Azimuth in radians from this to v2. */
427
    public double azimuth(Geo v2) {
428
        /*
429
         * n1 is the great circle representing the meridian of this. n2 is the
430
         * great circle between this and v2. The azimuth is the angle between
431
         * them but we specialized the cross product.
432
         */
433
        // Geo n1 = north.cross(this);
434
        // Geo n2 = v2.cross(this);
435
        // crossNormalization is needed to geos of different length.
436
        Geo n1 = north.crossNormalize(this);
437
        Geo n2 = v2.crossNormalize(this);
438
        double az = Math.atan2(-north.dot(n2), n1.dot(n2));
439
        return (az > 0.0) ? az : 2.0 * Math.PI + az;
440
    }
441
442
    /**
443
     * Given 3 points on a sphere, p0, p1, p2, return the angle between them in
444
     * radians.
445
     */
446
    public static double angle(Geo p0, Geo p1, Geo p2) {
447
        return Math.PI - p0.cross(p1).distance(p1.cross(p2));
448
    }
449
450
    /**
451
     * Computes the area of a polygon on the surface of a unit sphere given an
452
     * enumeration of its point.. For a non unit sphere, multiply this by the
453
     * radius of sphere squared.
454
     */
455
    public static double area(Iterator vs) {
456
        int count = 0;
457
        double area = 0;
458
        Geo v0 = (Geo) vs.next();
459
        Geo v1 = (Geo) vs.next();
460
        Geo p0 = v0;
461
        Geo p1 = v1;
462
        Geo p2 = null;
463
        while (vs.hasNext()) {
464
            count = count + 1;
465
            p2 = (Geo) vs.next();
466
            area = area + angle(p0, p1, p2);
467
            p0 = p1;
468
            p1 = p2;
469
        }
470
471
        count = count + 1;
472
        p2 = v0;
473
        area = area + angle(p0, p1, p2);
474
        p0 = p1;
475
        p1 = p2;
476
477
        count = count + 1;
478
        p2 = v1;
479
        area = area + angle(p0, p1, p2);
480
481
        return area - (count - 2) * Math.PI;
482
    }
483
484
    /**
485
     * Is the point, p, within radius radians of the great circle segment
486
     * between this and v2?
487
     */
488
    public boolean isInside(Geo v2, double radius, Geo p) {
489
        /*
490
         * gc is a unit vector perpendicular to the plane defined by v1 and v2
491
         */
492
        Geo gc = this.crossNormalize(v2);
493
494
        /*
495
         * |gc . p| is the size of the projection of p onto gc (the normal of
496
         * v1,v2) cos(pi/2-r) is effectively the size of the projection of a
497
         * vector along gc of the radius length. If the former is larger than
498
         * the latter, than p is further than radius from arc, so must not be
499
         * isInside
500
         */
501
        if (Math.abs(gc.dot(p)) > Math.cos((Math.PI / 2.0) - radius))
502
            return false;
503
504
        /*
505
         * If p is within radius of either endpoint, then we know it isInside
506
         */
507
        if (this.distance(p) <= radius || v2.distance(p) <= radius)
508
            return true;
509
510
        /* d is the vector from the v2 to v1 */
511
        Geo d = v2.subtract(this);
512
513
        /* L is the length of the vector d */
514
        double L = d.length();
515
516
        /* n is the d normalized to length=1 */
517
        Geo n = d.normalize();
518
519
        /* dp is the vector from p to v1 */
520
        Geo dp = p.subtract(this);
521
522
        /* size is the size of the projection of dp onto n */
523
        double size = n.dot(dp);
524
525
        /* p is inside iff size>=0 and size <= L */
526
        return (0 <= size && size <= L);
527
    }
528
529
    /**
530
     * do the segments v1-v2 and p1-p2 come within radius (radians) of each
531
     * other?
532
     */
533
    public static boolean isInside(Geo v1, Geo v2, double radius, Geo p1, Geo p2) {
534
        return v1.isInside(v2, radius, p1) || v1.isInside(v2, radius, p2)
535
                || p1.isInside(p2, radius, v1) || p1.isInside(p2, radius, v2);
536
    }
537
538
    /**
539
     * Static version of isInside uses conventional (decimal degree)
540
     * coordinates.
541
     */
542
    public static boolean isInside(double lat1, double lon1, double lat2,
543
                                   double lon2, double radius, double lat3,
544
                                   double lon3) {
545
        return (new Geo(lat1, lon1)).isInside(new Geo(lat2, lon2),
546
                radius,
547
                new Geo(lat3, lon3));
548
    }
549
550
    /**
551
     * Is Geo p inside the time bubble along the great circle segment from this
552
     * to v2 looking forward forwardRadius and backward backwardRadius.
553
     */
554
    public boolean inBubble(Geo v2, double forwardRadius, double backRadius,
555
                            Geo p) {
556
        return distance(p) <= ((v2.subtract(this)
557
                .normalize()
558
                .dot(p.subtract(this)) > 0.0) ? forwardRadius : backRadius);
559
    }
560
561
    /** Returns the point opposite this point on the earth. */
562
    public Geo antipode() {
563
        return this.scale(-1.0);
564
    }
565
566
    /**
567
     * Find the intersection of the great circle between this and q and the
568
     * great circle normal to r.
569
     * <p>
570
     *
571
     * That is, find the point, y, lying between this and q such that
572
     *
573
     * <pre>
574
     *
575
     *                        y = [x*this + (1-x)*q]*c
576
     *                        where c = 1/y.dot(y) is a factor for normalizing y.
577
     *                        y.dot(r) = 0
578
     *                        substituting:
579
     *                        [x*this + (1-x)*q]*c.dot(r) = 0 or
580
     *                        [x*this + (1-x)*q].dot(r) = 0
581
     *                        x*this.dot(r) + (1-x)*q.dot(r) = 0
582
     *                        x*a + (1-x)*b = 0
583
     *                        x = -b/(a - b)
584
     *
585
     * </pre>
586
     *
587
     * We assume that this and q are less than 180 degrees appart. When this and
588
     * q are 180 degrees appart, the point -y is also a valid intersection.
589
     * <p>
590
     * Alternatively the intersection point, y, satisfies y.dot(r) = 0
591
     * y.dot(this.crossNormalize(q)) = 0 which is satisfied by y =
592
     * r.crossNormalize(this.crossNormalize(q));
593
     *
594
     */
595
    public Geo intersect(Geo q, Geo r) {
596
        double a = this.dot(r);
597
        double b = q.dot(r);
598
        double x = -b / (a - b);
599
        return this.scale(x).add(q.scale(1.0 - x)).normalize();
600
    }
601
602
    /** alias for computeCorridor(path, radius, radians(10), true) * */
603
//    public static Geo[] computeCorridor(Geo[] path, double radius) {
604
//        return computeCorridor(path, radius, radians(10.0), true);
605
//    }
606
607
    /**
608
     * Wrap a fixed-distance corridor around an (open) path, as specified by an
609
     * array of Geo.
610
     *
611
     * @param path Open path, must not have repeated points or consecutive
612
     *        antipodes.
613
     * @param radius Distance from path to widen corridor, in angular radians.
614
     * @param err maximum angle of rounded edges, in radians. If 0, will
615
     *        directly cut outside bends.
616
     * @param capp iff true, will round end caps
617
     * @return a closed polygon representing the specified corridor around the
618
     *         path.
619
     *
620
     */
621
//    public static Geo[] computeCorridor(Geo[] path, double radius, double err,
622
//                                        boolean capp) {
623
//        if (path == null || radius <= 0.0) {
624
//            return new Geo[] {};
625
//        }
626
//        // assert path!=null;
627
//        // assert radius > 0.0;
628
//
629
//        int pl = path.length;
630
//        if (pl < 2)
631
//            return null;
632
//
633
//        // final polygon will be right[0],...,right[n],left[m],...,left[0]
634
//        ArrayList right = new ArrayList((int) (pl * 1.5));
635
//        ArrayList left = new ArrayList((int) (pl * 1.5));
636
//
637
//        Geo g0 = null; // previous point
638
//        Geo n0 = null; // previous normal vector
639
//        Geo l0 = null;
640
//        Geo r0 = null;
641
//
642
//        Geo g1 = path[0]; // current point
643
//
644
//        for (int i = 1; i < pl; i++) {
645
//            Geo g2 = path[i]; // next point
646
//            Geo n1 = g1.crossNormalize(g2); // n is perpendicular to the vector
647
//            // from g1 to g2
648
//            n1 = n1.scale(radius); // normalize to radius
649
//            // these are the offsets on the g2 side at g1
650
//            Geo r1b = g1.add(n1);
651
//            Geo l1b = g1.subtract(n1);
652
//
653
//            if (n0 == null) {
654
//                if (capp && err > 0) {
655
//                    // start cap
656
//                    Geo[] arc = approximateArc(g1, l1b, r1b, err);
657
//                    for (int j = arc.length - 1; j >= 0; j--) {
658
//                        right.add(arc[j]);
659
//                    }
660
//                } else {
661
//                    // no previous point - we'll just be square
662
//                    right.add(l1b);
663
//                    left.add(r1b);
664
//                }
665
//                // advance normals
666
//                l0 = l1b;
667
//                r0 = r1b;
668
//            } else {
669
//                // otherwise, compute a more complex shape
670
//
671
//                // these are the right and left on the g0 side of g1
672
//                Geo r1a = g1.add(n0);
673
//                Geo l1a = g1.subtract(n0);
674
//
675
//                double handed = g0.cross(g1).dot(g2); // right or left handed
676
//                // divergence
677
//                if (handed > 0) { // left needs two points, right needs 1
678
//                    if (err > 0) {
679
//                        Geo[] arc = approximateArc(g1, l1b, l1a, err);
680
//                        for (int j = arc.length - 1; j >= 0; j--) {
681
//                            right.add(arc[j]);
682
//                        }
683
//                    } else {
684
//                        right.add(l1a);
685
//                        right.add(l1b);
686
//                    }
687
//                    l0 = l1b;
688
//
689
//                    Geo ip = Intersection.segmentsIntersect(r0,
690
//                            r1a,
691
//                            r1b,
692
//                            g2.add(n1));
693
//                    // if they intersect, take the intersection, else use the
694
//                    // points and punt
695
//                    if (ip != null) {
696
//                        left.add(ip);
697
//                    } else {
698
//                        left.add(r1a);
699
//                        left.add(r1b);
700
//                    }
701
//                    r0 = ip;
702
//                } else {
703
//                    Geo ip = Intersection.segmentsIntersect(l0,
704
//                            l1a,
705
//                            l1b,
706
//                            g2.subtract(n1));
707
//                    // if they intersect, take the intersection, else use the
708
//                    // points and punt
709
//                    if (ip != null) {
710
//                        right.add(ip);
711
//                    } else {
712
//                        right.add(l1a);
713
//                        right.add(l1b);
714
//                    }
715
//                    l0 = ip;
716
//                    if (err > 0) {
717
//                        Geo[] arc = approximateArc(g1, r1a, r1b, err);
718
//                        for (int j = 0; j < arc.length; j++) {
719
//                            left.add(arc[j]);
720
//                        }
721
//                    } else {
722
//                        left.add(r1a);
723
//                        left.add(r1b);
724
//                    }
725
//                    r0 = r1b;
726
//                }
727
//            }
728
//
729
//            // advance points
730
//            g0 = g1;
731
//            n0 = n1;
732
//            g1 = g2;
733
//        }
734
//
735
//        // finish it off
736
//        Geo rn = g1.subtract(n0);
737
//        Geo ln = g1.add(n0);
738
//        if (capp && err > 0) {
739
//            // end cap
740
//            Geo[] arc = approximateArc(g1, ln, rn, err);
741
//            for (int j = arc.length - 1; j >= 0; j--) {
742
//                right.add(arc[j]);
743
//            }
744
//        } else {
745
//            right.add(rn);
746
//            left.add(ln);
747
//        }
748
//
749
//        int ll = right.size();
750
//        int rl = left.size();
751
//        Geo[] result = new Geo[ll + rl];
752
//        for (int i = 0; i < ll; i++) {
753
//            result[i] = (Geo) right.get(i);
754
//        }
755
//        int j = ll;
756
//        for (int i = rl - 1; i >= 0; i--) {
757
//            result[j++] = (Geo) left.get(i);
758
//        }
759
//        return result;
760
//    }
761
762
    /** simple vector angle (not geocentric!) */
763
    static double simpleAngle(Geo p1, Geo p2) {
764
        return Math.acos(p1.dot(p2) / (p1.length() * p2.length()));
765
    }
766
767
    /**
768
     * compute a polygonal approximation of an arc centered at pc, beginning at
769
     * p0 and ending at p1, going clockwise and including the two end points.
770
     *
771
     * @param pc center point
772
     * @param p0 starting point
773
     * @param p1 ending point
774
     * @param err The maximum angle between approximates allowed, in radians.
775
     *        Smaller values will look better but will result in more returned
776
     *        points.
777
     * @return
778
     */
779
//    public static final Geo[] approximateArc(Geo pc, Geo p0, Geo p1, double err) {
780
//
781
//        double theta = angle(p0, pc, p1);
782
//        // if the rest of the code is undefined in this situation, just skip it.
783
//        if (Double.isNaN(theta)) {
784
//            return new Geo[] { p0, p1 };
785
//        }
786
//
787
//        int n = (int) (2.0 + Math.abs(theta / err)); // number of points
788
//        // (counting the end
789
//        // points)
790
//        Geo[] result = new Geo[n];
791
//        result[0] = p0;
792
//        double dtheta = theta / (n - 1);
793
//
794
//        double rho = 0.0; // angle starts at 0 (directly at p0)
795
//
796
//        for (int i = 1; i < n - 1; i++) {
797
//            rho += dtheta;
798
//            // Rotate p0 around this so it has the right azimuth.
799
//            result[i] = (new Rotation(pc, 2.0 * Math.PI - rho)).rotate(p0);
800
//        }
801
//        result[n - 1] = p1;
802
//
803
//        return result;
804
//    }
805
806
//    public final Geo[] approximateArc(Geo p0, Geo p1, double err) {
807
//        return approximateArc(this, p0, p1, err);
808
//    }
809
810
    /** @deprecated use </b>#offset(double, double) */
811
//    public Geo geoAt(double distance, double azimuth) {
812
//        return offset(distance, azimuth);
813
//    }
814
815
    /**
816
     * Returns a Geo that is distance (radians), and azimuth (radians) away from
817
     * this.
818
     *
819
     * @param distance distance of this to the target point in radians.
820
     * @param azimuth Direction of target point from this, in radians, clockwise
821
     *        from north.
822
     * @note this is undefined at the north pole, at which point "azimuth" is
823
     *       undefined.
824
     */
825
//    public Geo offset(double distance, double azimuth) {
826
//        // m is normal the the meridian through this.
827
//        Geo m = this.crossNormalize(north);
828
//        // p is a point on the meridian distance <tt>distance</tt> from this.
829
//        Geo p = (new Rotation(m, distance)).rotate(this);
830
//        // Rotate p around this so it has the right azimuth.
831
//        return (new Rotation(this, 2.0 * Math.PI - azimuth)).rotate(p);
832
//    }
833
834
//    public static Geo offset(Geo origin, double distance, double azimuth) {
835
//        return origin.offset(distance, azimuth);
836
//    }
837
838
    /*
839
     * //same as offset, except using trig instead of vector mathematics public
840
     * Geo trig_offset(double distance, double azimuth) { double latr =
841
     * getLatitudeRadians(); double lonr = getLongitudeRadians();
842
     *
843
     * double coslat = Math.cos(latr); double sinlat = Math.sin(latr); double
844
     * cosaz = Math.cos(azimuth); double sinaz = Math.sin(azimuth); double sind =
845
     * Math.sin(distance); double cosd = Math.cos(distance);
846
     *
847
     * return makeGeoRadians(Math.asin(sinlat * cosd + coslat * sind * cosaz),
848
     * Math.atan2(sind * sinaz, coslat * cosd - sinlat * sind * cosaz) + lonr); }
849
     */
850
851
    //
852
    // Follows are a series of Geo array operations as useful utilities
853
    //
854
    /**
855
     * convert a String containing space-separated pairs of comma-separated
856
     * decimal lat-lon pairs into a Geo array.
857
     */
858
    public static Geo[] posToGa(String coords) {
859
        return posToGa(coords.split(" "));
860
    }
861
862
    /**
863
     * Convert an array of strings with comma-separated decimal lat,lon pairs
864
     * into a Geo array
865
     */
866
    public static Geo[] posToGa(String[] coords) {
867
        // convert to floating lat/lon degrees
868
        Geo[] ga = new Geo[coords.length];
869
        for (int i = 0; i < coords.length; i++) {
870
            String[] ll = coords[i].split(",");
871
            ga[i] = Geo.makeGeoDegrees(Double.parseDouble(ll[0]),
872
                    Double.parseDouble(ll[1]));
873
        }
874
        return ga;
875
    }
876
877
    /**
878
     * Convert a Geo array into a floating point lat lon array (alternating lat
879
     * and lon values)
880
     */
881
    public static float[] GaToLLa(Geo[] ga) {
882
        float[] lla = new float[2 * ga.length];
883
        for (int i = 0; i < ga.length; i++) {
884
            Geo g = ga[i];
885
            lla[i * 2] = (float) g.getLatitude();
886
            lla[i * 2 + 1] = (float) g.getLongitude();
887
        }
888
        return lla;
889
    }
890
891
    /**
892
     * Return a Geo array with the duplicates removed. May arbitrarily mutate
893
     * the input array.
894
     */
895
    public static Geo[] removeDups(Geo[] ga) {
896
        Geo[] r = new Geo[ga.length];
897
        int p = 0;
898
        for (int i = 0; i < ga.length; i++) {
899
            if (p == 0 || !(r[p - 1].equals(ga[i]))) {
900
                r[p] = ga[i];
901
                p++;
902
            }
903
        }
904
        if (p != ga.length) {
905
            Geo[] x = new Geo[p];
906
            System.arraycopy(r, 0, x, 0, p);
907
            return x;
908
        } else {
909
            return ga;
910
        }
911
    }
912
913
    /**
914
     * Convert a float array of alternating lat and lon pairs into a Geo array.
915
     */
916
    public static Geo[] LLaToGa(float[] lla) {
917
        Geo[] r = new Geo[lla.length / 2];
918
        for (int i = 0; i < lla.length / 2; i++) {
919
            r[i] = Geo.makeGeoDegrees(lla[i * 2], lla[i * 2 + 1]);
920
        }
921
        return r;
922
    }
923
924
    /**
925
     * return a float array of alternating lat lon pairs where the first and
926
     * last pair are the same, thus closing the path, by adding a point if
927
     * needed. Does not mutate the input.
928
     */
929
    public static float[] closeLLa(float[] lla) {
930
        int l = lla.length;
931
        int s = (l / 2) - 1;
932
        if (lla[0] == lla[s * 2] && lla[1] == lla[s * 2 + 1]) {
933
            return lla;
934
        } else {
935
            float[] llx = new float[l + 2];
936
            for (int i = 0; i < l; i++) {
937
                llx[i] = lla[i];
938
            }
939
            llx[l] = lla[0];
940
            llx[l + 1] = lla[1];
941
            return llx;
942
        }
943
    }
944
945
    /**
946
     * return a Geo array where the first and last elements are the same, thus
947
     * closing the path, by adding a point if needed. Does not mutate the input.
948
     */
949
    public static Geo[] closeGa(Geo[] ga) {
950
        int l = ga.length;
951
        if (ga[0].equals(ga[l - 1])) {
952
            return ga;
953
        } else {
954
            Geo[] x = new Geo[l + 1];
955
            System.arraycopy(ga, 0, x, 0, l);
956
            x[l] = ga[0];
957
            return x;
958
        }
959
    }
960
}