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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +20 -2
- data/lib/geoprojection/distance.rb +129 -0
- data/lib/geoprojection/ellipse.rb +0 -4
- data/lib/geoprojection/version.rb +1 -1
- data/lib/geoprojection.rb +10 -0
- data/test.js +50 -0
- data/test2.js +17 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5ed2077814688af2c2731b2c9a6d9320d112b7a08d7f77e983038f43492fdb8
|
4
|
+
data.tar.gz: 9fa634a0b969585b9d18a302b586912e6fe679fdf6e1bb1d1cced4d705d3aa6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aea7ad9252cfbfc652f9b8c9d2b7295e6e1b7f6ecb72748326a06dac21896b633e94e2f7aee49eeee1117d7819c852770ba881805ec19894ee684eb34a68c7b6
|
7
|
+
data.tar.gz: e2cf4b75a12eea14e1d40b83a279fb67294fb25449c5fc137b3c582e3e1668a4dd7c4962805b381a5d2ad19dda64d0136a18dea6cf852efc36d015e8352840ff
|
data/Gemfile.lock
CHANGED
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:
|
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
|
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.
|
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-
|
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:
|