geoprojection 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: