geoprojection 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8560b474f5efac958d7e5296dae0fc6c122d9fc6ea605a8ae406101717a46d06
4
- data.tar.gz: 66c955de7126b71538d6d019ad43d767cbed22719929b631bbd0987420070d96
3
+ metadata.gz: f5ed2077814688af2c2731b2c9a6d9320d112b7a08d7f77e983038f43492fdb8
4
+ data.tar.gz: 9fa634a0b969585b9d18a302b586912e6fe679fdf6e1bb1d1cced4d705d3aa6f
5
5
  SHA512:
6
- metadata.gz: efee6e93050df75436a04773199c6721fcfd8b39f979ecbc8092df3717ba5b88086e0351ebf5a8def6b21e0adbaf991002a33748f473134cb7e95361ffea4809
7
- data.tar.gz: 720fa6125c7ac803473e0c604c37ca6829ba68ca89bcb539821d5b660e7c81828c72f01726e1c21307b63552bb36e05477c4fc973999583e2e19629fd086631e
6
+ metadata.gz: aea7ad9252cfbfc652f9b8c9d2b7295e6e1b7f6ecb72748326a06dac21896b633e94e2f7aee49eeee1117d7819c852770ba881805ec19894ee684eb34a68c7b6
7
+ data.tar.gz: e2cf4b75a12eea14e1d40b83a279fb67294fb25449c5fc137b3c582e3e1668a4dd7c4962805b381a5d2ad19dda64d0136a18dea6cf852efc36d015e8352840ff
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geoprojection (0.1.1)
4
+ geoprojection (0.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Geoprojection
2
2
 
3
- Toolkit for translating distances (kilometers) from co-ordinates (latitude, longitude) onto geographically-precise (within reason) projections.
3
+ Toolkit for translating distances (kilometers) from co-ordinates (latitude, longitude) onto geographically-precise (within reason) projections and calculating distances between pairs of co-ordinates.
4
4
 
5
5
  ## Installation
6
6
 
@@ -21,12 +21,30 @@ Provide a center point (latitude, longitude) and a distance (kilometers) to calc
21
21
  The default number of points is `36`. This can be overridden with the `points` parameter to `Geoprojection::Ellipse.new`.
22
22
 
23
23
  ```ruby
24
- ellipse = Geoprojection::Ellipse.new(latitude: 50.8169, longitude: 0.1367, distance: 10, points: 36)
24
+ ellipse = Geoprojection::Ellipse.new(latitude: 50.8169, longitude: 0.1367, distance: 10, points: 6)
25
25
  ellipse.points
26
+
27
+ # => [[0.2788814046153052, 50.8169], [0.20779070230765262, 50.968049947019516], [0.0656092976923474, 50.968049947019516], [-0.0054814046153052465, 50.8169], [0.0656092976923473, 50.66575005298048], [0.20779070230765262, 50.66575005298048], [0.2788814046153052, 50.8169]]
26
28
  ```
27
29
 
28
30
  The actual returned points will be N+1, as the start and end point are identical to provide a complete polygon.
29
31
 
32
+ ### Distance
33
+
34
+ Calculate the [Vincenty distance](https://en.wikipedia.org/wiki/Vincenty%27s_formulae) between two pairs of co-ordinates (latitude, longitude) in miles or kilometers:
35
+
36
+ ```ruby
37
+ distance = Geoprojection::Distance.new(
38
+ { latitude: 50.8169, longitude: 0.1367 },
39
+ { latitude: 51.2341, longitude: 1.4231 }
40
+ )
41
+
42
+ distance.kilometers
43
+ # => 148.2691075520691
44
+ distance.miles
45
+ # => 92.13012362873673
46
+ ```
47
+
30
48
  ## Contributing
31
49
 
32
50
  Make changes, run `make test`, create a pull request.
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geoprojection
4
+ # Vincenty distance calculation for a pair of latitude/longitude co-ordinates.
5
+ class Distance
6
+ # rubocop:disable Metrics/AbcSize
7
+ def initialize(point1, point2)
8
+ @latitude1 = degrees_to_radians(point1.fetch(:latitude))
9
+ @longitude1 = degrees_to_radians(point1.fetch(:longitude))
10
+ @latitude2 = degrees_to_radians(point2.fetch(:latitude))
11
+ @longitude2 = degrees_to_radians(point2.fetch(:longitude))
12
+ @tan_geo_latitude1 = (1 - FLATTENING) * Math.tan(@longitude1)
13
+ @tan_geo_latitude2 = (1 - FLATTENING) * Math.tan(@longitude2)
14
+ @cos_geo_latitude1 = 1 / Math.sqrt(1 + (@tan_geo_latitude1**2))
15
+ @cos_geo_latitude2 = 1 / Math.sqrt(1 + (@tan_geo_latitude2**2))
16
+ @sin_geo_latitude1 = @tan_geo_latitude1 * @cos_geo_latitude1
17
+ @sin_geo_latitude2 = @tan_geo_latitude2 * @cos_geo_latitude2
18
+ end
19
+ # rubocop:enable Metrics/AbcSize
20
+
21
+ def miles
22
+ meters * METERS_PER_MILE
23
+ end
24
+
25
+ def kilometers
26
+ meters / 1000.0
27
+ end
28
+
29
+ def meters
30
+ distance(*computed_variables)
31
+ end
32
+
33
+ private
34
+
35
+ def degrees_to_radians(val)
36
+ val * DEGREES_PER_RADIAN
37
+ end
38
+
39
+ def distance(theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m)
40
+ r_square = reduced_square(cos_sq_alpha)
41
+ vertex_b = r_square / 1024 * (256 + (r_square * (-128 + (r_square * (74 - (47 * r_square))))))
42
+ WGS84_MINOR_AXIS * vertex_a(r_square) * (theta - delta_theta(vertex_b, sin_theta, cos2_theta_m, cos_theta))
43
+ end
44
+
45
+ def reduced_square(cos_sq_alpha)
46
+ cos_sq_alpha * ((WGS84_MAJOR_AXIS**2) - (WGS84_MINOR_AXIS**2)) / (WGS84_MINOR_AXIS**2)
47
+ end
48
+
49
+ def vertex_a(r_square)
50
+ 1 + (r_square / 16_384 * (4096 + (r_square * (-768 + (r_square * (320 - (175 * r_square)))))))
51
+ end
52
+
53
+ def sine_theta(lambda1)
54
+ Math.sqrt(
55
+ ((@cos_geo_latitude2 * Math.sin(lambda1))**2) +
56
+ (((@cos_geo_latitude1 * @sin_geo_latitude2) -
57
+ (@sin_geo_latitude1 * @cos_geo_latitude2 * Math.cos(lambda1)))**2)
58
+ )
59
+ end
60
+
61
+ def delta_theta(vertex_b, sin_theta, cos2_theta_m, cos_theta)
62
+ vertex_b * sin_theta * (
63
+ delta_theta1(cos2_theta_m, vertex_b, cos_theta) +
64
+ delta_theta2(cos2_theta_m, vertex_b) +
65
+ delta_theta3(sin_theta, cos2_theta_m)
66
+ )
67
+ end
68
+
69
+ def delta_theta1(cos2_theta_m, vertex_b, cos_theta)
70
+ cos2_theta_m + ((vertex_b / 4 * cos_theta * -1))
71
+ end
72
+
73
+ def delta_theta2(cos2_theta_m, vertex_b)
74
+ (2 * (cos2_theta_m**2)) - ((vertex_b / 6 * cos2_theta_m * -3))
75
+ end
76
+
77
+ def delta_theta3(sin_theta, cos2_theta_m)
78
+ (4 * (sin_theta**2) * -3) + (4 * (cos2_theta_m**2))
79
+ end
80
+
81
+ def computed_variables
82
+ lambda1 = latitude_distance
83
+
84
+ precision = VINCENTY_PRECISION * 2
85
+
86
+ while precision > VINCENTY_PRECISION
87
+ theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m, lambda1, precision = iteration_variables(lambda1)
88
+ end
89
+ [theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m]
90
+ end
91
+
92
+ def iteration_variables(lambda1)
93
+ sin_theta = sine_theta(lambda1)
94
+ cos_theta = calculated_cos_theta(lambda1)
95
+ theta = Math.atan2(sin_theta, cos_theta)
96
+ sin_alpha = @cos_geo_latitude1 * @cos_geo_latitude2 * Math.sin(lambda1) / sin_theta
97
+ cos_sq_alpha = 1 - (sin_alpha**2)
98
+ cos2_theta_m = calculated_cos2_theta_m(cos_theta, cos_sq_alpha)
99
+ lambda2 = iteration_lambda(sin_alpha, theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m)
100
+ [theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m, lambda2, (lambda2 - lambda1).abs]
101
+ end
102
+
103
+ # rubocop:disable Metrics/ParameterLists
104
+ def iteration_lambda(sin_alpha, theta, sin_theta, cos_theta, cos_sq_alpha, cos2_theta_m)
105
+ c = calculated_c(cos_sq_alpha)
106
+ latitude_distance + (
107
+ (1 - c) * FLATTENING * sin_alpha *
108
+ (theta + (c * sin_theta * (cos2_theta_m + c + ((cos_theta * -1) + (2 * (cos2_theta_m**2))))))
109
+ )
110
+ end
111
+ # rubocop:enable Metrics/ParameterLists
112
+
113
+ def calculated_cos_theta(lambda_)
114
+ (@sin_geo_latitude1 * @sin_geo_latitude2) + (@cos_geo_latitude1 * @cos_geo_latitude2 * Math.cos(lambda_))
115
+ end
116
+
117
+ def calculated_c(cos_sq_alpha)
118
+ (FLATTENING / 16) * cos_sq_alpha * (4 + (FLATTENING * (4 - (3 * cos_sq_alpha))))
119
+ end
120
+
121
+ def calculated_cos2_theta_m(cos_theta, cos_sq_alpha)
122
+ cos_theta - (2 * @sin_geo_latitude1 * @sin_geo_latitude2 / cos_sq_alpha)
123
+ end
124
+
125
+ def latitude_distance
126
+ @latitude_distance ||= @latitude2 - @latitude1
127
+ end
128
+ end
129
+ end
@@ -3,10 +3,6 @@
3
3
  module Geoprojection
4
4
  # Elliptical projection of a center point (lat, long) with a provided distance from the center.
5
5
  class Ellipse
6
- DEGREES_PER_RADIAN = Math::PI / 180
7
- EQUATOR_KM_PER_LATITUDE_RADIAN = 110.567 * DEGREES_PER_RADIAN
8
- EQUATOR_KM_PER_LONGITUDE_RADIAN = 111.321 * DEGREES_PER_RADIAN
9
-
10
6
  def initialize(latitude:, longitude:, distance:, points: 36)
11
7
  @latitude = latitude
12
8
  @longitude = longitude
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geoprojection
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
data/lib/geoprojection.rb CHANGED
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'geoprojection/version'
4
+ require_relative 'geoprojection/distance'
4
5
  require_relative 'geoprojection/ellipse'
5
6
 
6
7
  module Geoprojection
7
8
  class Error < StandardError; end
9
+
10
+ DEGREES_PER_RADIAN = Math::PI / 180
11
+ EQUATOR_KM_PER_LATITUDE_RADIAN = 110.567 * DEGREES_PER_RADIAN
12
+ EQUATOR_KM_PER_LONGITUDE_RADIAN = 111.321 * DEGREES_PER_RADIAN
13
+ METERS_PER_MILE = 0.000621371
14
+ WGS84_MAJOR_AXIS = 6_378_137.0
15
+ WGS84_MINOR_AXIS = 6_356_752.314245
16
+ FLATTENING = (WGS84_MAJOR_AXIS - WGS84_MINOR_AXIS) / WGS84_MAJOR_AXIS
17
+ VINCENTY_PRECISION = 1e-12
8
18
  end
data/test.js ADDED
@@ -0,0 +1,50 @@
1
+ const λ1 = 51.8169 * (Math.PI / 180);
2
+ const λ2 = 50.8169 * (Math.PI / 180);
3
+ const φ1 = -0.1367 * (Math.PI / 180);
4
+ const φ2 = -0.4367 * (Math.PI / 180);
5
+ const a = 6378137;
6
+ const b = 6356752.314245;
7
+ // const f = 1 / 298.257223563;
8
+ const f = (a - b) / a;
9
+ const L = λ2 - λ1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanφ.
10
+ const tanU1 = (1-f) * Math.tan(φ1), cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1 * cosU1;
11
+ const tanU2 = (1-f) * Math.tan(φ2), cosU2 = 1 / Math.sqrt((1 + tanU2*tanU2)), sinU2 = tanU2 * cosU2;
12
+
13
+ let λ = L, sinλ = null, cosλ = null; // λ = difference in longitude on an auxiliary sphere
14
+ let σ = null, sinσ = null, cosσ = null; // σ = angular distance P₁ P₂ on the sphere
15
+ let cos2σₘ = null; // σₘ = angular distance on the sphere from the equator to the midpoint of the line
16
+ let cosSqα = null; // α = azimuth of the geodesic at the equator
17
+
18
+ let λʹ = null;
19
+ do {
20
+ console.log(λ);
21
+ sinλ = Math.sin(λ);
22
+ cosλ = Math.cos(λ);
23
+ console.log(sinλ);
24
+ console.log(cosλ);
25
+ console.log((cosU2*sinλ) * (cosU2*sinλ))
26
+ console.log((cosU1*sinU2-sinU1*cosU2*cosλ)**2);
27
+ const sinSqσ = (cosU2*sinλ) * (cosU2*sinλ) + (cosU1*sinU2-sinU1*cosU2*cosλ)**2;
28
+ sinσ = Math.sqrt(sinSqσ);
29
+ cosσ = sinU1*sinU2 + cosU1*cosU2*cosλ;
30
+ σ = Math.atan2(sinσ, cosσ);
31
+ const sinα = cosU1 * cosU2 * sinλ / sinσ;
32
+ cosSqα = 1 - sinα*sinα;
33
+ cos2σₘ = cosσ - 2*sinU1*sinU2/cosSqα;
34
+ const C = f/16*cosSqα*(4+f*(4-3*cosSqα));
35
+ λʹ = λ;
36
+ λ = L + (1-C) * f * sinα * (σ + C*sinσ*(cos2σₘ+C*cosσ*(-1+2*cos2σₘ*cos2σₘ)));
37
+ } while (Math.abs(λ-λʹ) > 1e-12);
38
+
39
+ const uSq = cosSqα * (a*a - b*b) / (b*b);
40
+ const A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
41
+ const B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
42
+ const Δσ = B*sinσ*(cos2σₘ+B/4*(cosσ*(-1+2*cos2σₘ*cos2σₘ)-B/6*cos2σₘ*(-3+4*sinσ*sinσ)*(-3+4*cos2σₘ*cos2σₘ)));
43
+
44
+ const s = b*A*(σ-Δσ); // s = length of the geodesic
45
+
46
+ console.log(s);
47
+ const α1 = Math.atan2(cosU2*sinλ, cosU1*sinU2-sinU1*cosU2*cosλ); // initial bearing
48
+ const α2 = Math.atan2(cosU1*sinλ, -sinU1*cosU2+cosU1*sinU2*cosλ); // final bearin
49
+ console.log(α1)
50
+ console.log(α2 * (Math.PI / 180))
data/test2.js ADDED
@@ -0,0 +1,17 @@
1
+ const lat1 = 50.8169;
2
+ const lat2 = 51.8169;
3
+ const lon1 = -0.1367;
4
+ const lon2 = -0.4367;
5
+ const R = 6371e3; // metres
6
+ const φ1 = lat1 * Math.PI/180; // φ, λ in radians
7
+ const φ2 = lat2 * Math.PI/180;
8
+ const Δφ = (lat2-lat1) * Math.PI/180;
9
+ const Δλ = (lon2-lon1) * Math.PI/180;
10
+
11
+ const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
12
+ Math.cos(φ1) * Math.cos(φ2) *
13
+ Math.sin(Δλ/2) * Math.sin(Δλ/2);
14
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
15
+
16
+ const d = R * c;
17
+ console.log(d);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geoprojection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-11 00:00:00.000000000 Z
11
+ date: 2022-06-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Write a longer description or delete this line.
14
14
  email:
@@ -27,10 +27,13 @@ files:
27
27
  - Rakefile
28
28
  - doc/example.rb
29
29
  - lib/geoprojection.rb
30
+ - lib/geoprojection/distance.rb
30
31
  - lib/geoprojection/ellipse.rb
31
32
  - lib/geoprojection/version.rb
32
33
  - sig/geoprojection.rbs
34
+ - test.js
33
35
  - test.rb
36
+ - test2.js
34
37
  homepage: https://github.com/bobf/geoprojection
35
38
  licenses: []
36
39
  metadata: