geo_coord 0.0.1 → 0.1.0
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 +5 -5
- data/CHANGELOG.md +12 -0
- data/README.md +77 -60
- data/geo_coord.gemspec +7 -11
- data/lib/geo/coord.rb +101 -70
- data/lib/geo/coord/version.rb +1 -1
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 36feeb545afb9bf13f5d330302ba226b9c9fc9ce28aa1b223ad0c796ca1e2e37
|
4
|
+
data.tar.gz: 57ea4296740735b6c8606738b38498225130620a43056832ae7ee3a96c8b3b2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45c9aa30f9d38d1ea2d96cd89fefe0f09ff77a1d18d515402ba6ccb2d37b92a5b8d3ef1903c619cc8c655f4787750db1dae1f457330ac58d2c84ec086ed8962b
|
7
|
+
data.tar.gz: 77ffed86527f6752f4885c0e22173aa84214c8074a7793fc0026ce8d7c78ee43bc3c6d6e1b62a43d77de03619dcc097dde576531c176d149e115b0bf235532f3
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Geo::Coord changelog
|
2
|
+
|
3
|
+
## 0.1.0 - Feb 3, 2018
|
4
|
+
|
5
|
+
* Switch to `BigDecimal` for internal values storage;
|
6
|
+
* More friendly `#inspect` & `#to_s` format;
|
7
|
+
* Rename `#to_a` to `#latlng` & `#lnglat`;
|
8
|
+
* Fix `#lats` formula bug.
|
9
|
+
|
10
|
+
## 0.0.1 - Jun 06, 2016
|
11
|
+
|
12
|
+
Initial release as a gem.
|
data/README.md
CHANGED
@@ -40,74 +40,29 @@ I still have a small hope it would be part of stdlib once, that's why I
|
|
40
40
|
preserve the style of specs (outdated rspec, but compatible with mspec used
|
41
41
|
for standard library) and docs (yard in RDoc-compatibility mode).
|
42
42
|
|
43
|
-
## Design decisions
|
44
|
-
|
45
|
-
While designing `Geo` library, my reference point was standard `Time`
|
46
|
-
class (and, to lesser extent, `Date`/`DateTime`). It has these
|
47
|
-
responsibilities:
|
48
|
-
* stores data in simple internal form;
|
49
|
-
* helps to parse and format data to and from strings;
|
50
|
-
* provides easy access to logical components of data;
|
51
|
-
* allows most simple and unambiguous calculations.
|
52
|
-
|
53
|
-
**Namespace name**: The gem takes pretty short and generic top-level
|
54
|
-
namespace name `Geo`, but creates only one class inside it: `Geo::Coord`.
|
55
|
-
|
56
|
-
**Main type name**: as far as I can see, there's no good singular name
|
57
|
-
for `(lat, lng)` pair concept. In different libraries, there can be seen
|
58
|
-
names like `LatLng`, or `Location`, or `Point`; and in natural language
|
59
|
-
just "coordinates" used frequently. I propose the name `Coord`, which
|
60
|
-
is pretty short, easy to remember, demonstrates intentions (and looks
|
61
|
-
like singular, so you can have "one coord object" and "array of coords",
|
62
|
-
which is not 100% linguistically correct, yet convenient). Alternative
|
63
|
-
`Point` name seems to be too ambiguous, being used in many contexts.
|
64
|
-
|
65
|
-
`Geo::Coord` object is **immutable**, there's no semantical sense in
|
66
|
-
`location.latitude = ...` or something like this.
|
67
|
-
|
68
|
-
**Units**: `Geo` calculations (just like `Time` calculations) provide
|
69
|
-
no units options, just returning numbers measured in "default" units:
|
70
|
-
metres for distances (as they are SI unit) and degrees for azimuth.
|
71
|
-
Latitude and longitude are stored in degrees, but radians values accessors
|
72
|
-
are provided (being widely used in geodesy math).
|
73
|
-
|
74
|
-
All coordinates and calculations are thought to be in
|
75
|
-
[WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System#A_new_World_Geodetic_System:_WGS_84)
|
76
|
-
coordinates reference system, being current standard for maps and GPS.
|
77
|
-
|
78
|
-
There's introduced **a concept of globe** used internally for calculations.
|
79
|
-
Only generic (sphere) and Earth globes are implemented, but for 2016 I
|
80
|
-
feel like the current design of basic types should take in consideration
|
81
|
-
possibility of writing Ruby scripts for Mars maps analysis. Only one
|
82
|
-
geodesy formula is implemented (Vincenty, generally considered one of
|
83
|
-
the most precise), as for standard library class it considered
|
84
|
-
unnecessary to provide a user with geodesy formulae options.
|
85
|
-
|
86
|
-
No **map projection** math was added into the current gem, but it
|
87
|
-
may be a good direction for further work. No **elevation** data considered
|
88
|
-
either.
|
89
|
-
|
90
43
|
## Installation
|
91
44
|
|
92
45
|
Now when it is a gem, just do your usual `gem install geo_coord` or add
|
93
|
-
`gem "geo_coord"` to your Gemfile.
|
46
|
+
`gem "geo_coord", require: "geo/coord"` to your Gemfile.
|
94
47
|
|
95
48
|
## Usage
|
96
49
|
|
97
50
|
### Creation
|
98
51
|
|
99
52
|
```ruby
|
53
|
+
require 'geo/coord'
|
54
|
+
|
100
55
|
# From lat/lng pair:
|
101
56
|
g = Geo::Coord.new(50.004444, 36.231389)
|
102
|
-
# => #<Geo::Coord 50
|
57
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
103
58
|
|
104
59
|
# Or using keyword arguments form:
|
105
60
|
g = Geo::Coord.new(lat: 50.004444, lng: 36.231389)
|
106
|
-
# => #<Geo::Coord 50
|
61
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
107
62
|
|
108
63
|
# Keyword arguments also allow creation of Coord from components:
|
109
64
|
g = Geo::Coord.new(latd: 50, latm: 0, lats: 16, lath: 'N', lngd: 36, lngm: 13, lngs: 53, lngh: 'E')
|
110
|
-
# => #<Geo::Coord 50
|
65
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
111
66
|
```
|
112
67
|
|
113
68
|
For parsing API responses you'd like to use `from_h`,
|
@@ -116,7 +71,7 @@ and knows synonyms (lng/lon/longitude):
|
|
116
71
|
|
117
72
|
```ruby
|
118
73
|
g = Geo::Coord.from_h('LAT' => 50.004444, 'LON' => 36.231389)
|
119
|
-
# => #<Geo::Coord 50
|
74
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
120
75
|
```
|
121
76
|
|
122
77
|
For math, you'd probably like to be able to initialize
|
@@ -124,7 +79,7 @@ Coord with radians rather than degrees:
|
|
124
79
|
|
125
80
|
```ruby
|
126
81
|
g = Geo::Coord.from_rad(0.8727421884291233, 0.6323570306208558)
|
127
|
-
# => #<Geo::Coord 50
|
82
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
128
83
|
```
|
129
84
|
|
130
85
|
There's also family of string parsing methods, with different
|
@@ -133,21 +88,21 @@ applicability:
|
|
133
88
|
```ruby
|
134
89
|
# Tries to parse (lat, lng) pair:
|
135
90
|
g = Geo::Coord.parse_ll('50.004444, 36.231389')
|
136
|
-
# => #<Geo::Coord 50
|
91
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
137
92
|
|
138
93
|
# Tries to parse degrees/minutes/seconds:
|
139
94
|
g = Geo::Coord.parse_dms('50° 0′ 16″ N, 36° 13′ 53″ E')
|
140
|
-
# => #<Geo::Coord 50
|
95
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
141
96
|
|
142
97
|
# Tries to do best guess:
|
143
98
|
g = Geo::Coord.parse('50.004444, 36.231389')
|
144
|
-
# => #<Geo::Coord 50
|
99
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
145
100
|
g = Geo::Coord.parse('50° 0′ 16″ N, 36° 13′ 53″ E')
|
146
|
-
# => #<Geo::Coord 50
|
101
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
147
102
|
|
148
103
|
# Allows user to provide pattern:
|
149
104
|
g = Geo::Coord.strpcoord('50.004444, 36.231389', '%lat, %lng')
|
150
|
-
# => #<Geo::Coord 50
|
105
|
+
# => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
151
106
|
```
|
152
107
|
|
153
108
|
[Pattern language description](http://www.rubydoc.info/gems/geo_coord/Geo/Coord#strpcoord-class_method)
|
@@ -170,7 +125,8 @@ g.latdms # => [50, 0, 15.998400000011316, "N"]
|
|
170
125
|
### Formatting and converting
|
171
126
|
|
172
127
|
```ruby
|
173
|
-
g.to_s
|
128
|
+
g.to_s # => "50°0'16\"N 36°13'53\"E"
|
129
|
+
g.to_s(dms: false) # => "50.004444,36.231389"
|
174
130
|
g.strfcoord('%latd°%latm′%lats″%lath %lngd°%lngm′%lngs″%lngh')
|
175
131
|
# => "50°0′16″N 36°13′53″E"
|
176
132
|
|
@@ -185,11 +141,72 @@ kyiv = Geo::Coord.new(50.45, 30.523333)
|
|
185
141
|
|
186
142
|
kharkiv.distance(kyiv) # => 410211.22377421556
|
187
143
|
kharkiv.azimuth(kyiv) # => 279.12614358262067
|
188
|
-
kharkiv.endpoint(410_211, 280) # => #<Geo::Coord 50
|
144
|
+
kharkiv.endpoint(410_211, 280) # => #<Geo::Coord 50°30'22"N 30°31'53"E>
|
189
145
|
```
|
190
146
|
|
191
147
|
[Full API Docs](http://www.rubydoc.info/gems/geo_coord)
|
192
148
|
|
149
|
+
## Design decisions
|
150
|
+
|
151
|
+
While designing `Geo` library, my reference point was standard `Time`
|
152
|
+
class (and, to lesser extent, `Date`/`DateTime`). It has these
|
153
|
+
responsibilities:
|
154
|
+
|
155
|
+
* stores data in simple internal form;
|
156
|
+
* helps to parse and format data to and from strings;
|
157
|
+
* provides easy access to logical components of data;
|
158
|
+
* allows most simple and unambiguous calculations.
|
159
|
+
|
160
|
+
**Namespace name**: The gem takes pretty short and generic top-level
|
161
|
+
namespace name `Geo`, but creates only one class inside it: `Geo::Coord`.
|
162
|
+
|
163
|
+
**Main type name**: as far as I can see, there's no good singular name
|
164
|
+
for `(lat, lng)` pair concept. In different libraries, there can be seen
|
165
|
+
names like `LatLng`, or `Location`, or `Point`; and in natural language
|
166
|
+
just "coordinates" used frequently. I propose the name `Coord`, which
|
167
|
+
is pretty short, easy to remember, demonstrates intentions (and looks
|
168
|
+
like singular, so you can have "one coord object" and "array of coords",
|
169
|
+
which is not 100% linguistically correct, yet convenient). Alternative
|
170
|
+
`Point` name seems to be too ambiguous, being used in many contexts.
|
171
|
+
|
172
|
+
`Geo::Coord` object is **immutable**, there's no semantical sense in
|
173
|
+
`location.latitude = ...` or something like this.
|
174
|
+
|
175
|
+
**Units**: `Geo` calculations (just like `Time` calculations) provide
|
176
|
+
no units options, just returning numbers measured in "default" units:
|
177
|
+
metres for distances (as they are SI unit) and degrees for azimuth.
|
178
|
+
Latitude and longitude are stored in degrees, but radians values accessors
|
179
|
+
are provided (being widely used in geodesy math).
|
180
|
+
|
181
|
+
**Internal storage**: Since ver 0.0.2, latitude and longitude stored
|
182
|
+
internally as an instances of `BigDecimal`. While having some memory
|
183
|
+
and performance downsides, this datatype provides _correctness_ of
|
184
|
+
conversions between floating point & deg-min-sec representations:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
# 33.3 should be 33°18'00"
|
188
|
+
# Float:
|
189
|
+
33.3 * 60 % 60 # => 17.999999999999773 minutes
|
190
|
+
# BigDecimal
|
191
|
+
BigDecimal(33.3, 10) * 60 % 60 # => 0.18e2
|
192
|
+
```
|
193
|
+
|
194
|
+
All coordinates and calculations are thought to be in
|
195
|
+
[WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System#A_new_World_Geodetic_System:_WGS_84)
|
196
|
+
coordinates reference system, being current standard for maps and GPS.
|
197
|
+
|
198
|
+
There's introduced **a concept of globe** used internally for calculations.
|
199
|
+
Only generic (sphere) and Earth globes are implemented, but for 2010th I
|
200
|
+
feel like the current design of basic types should take in consideration
|
201
|
+
possibility of writing Ruby scripts for Mars maps analysis. Only one
|
202
|
+
geodesy formula is implemented (Vincenty, generally considered one of
|
203
|
+
the most precise), as for standard library class it considered
|
204
|
+
unnecessary to provide a user with geodesy formulae options.
|
205
|
+
|
206
|
+
No **map projection** math was added into the current gem, but it
|
207
|
+
may be a good direction for further work. No **elevation** data considered
|
208
|
+
either.
|
209
|
+
|
193
210
|
## Author
|
194
211
|
|
195
212
|
[Victor Shepelev](https://zverok.github.io)
|
data/geo_coord.gemspec
CHANGED
@@ -8,22 +8,17 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.homepage = 'https://github.com/zverok/geo_coord'
|
9
9
|
|
10
10
|
s.summary = 'Geo::Coord class'
|
11
|
-
s.description = <<-
|
12
|
-
|
11
|
+
s.description = <<-DESC
|
12
|
+
DESC
|
13
13
|
s.licenses = ['MIT']
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split($RS).reject do |file|
|
16
|
-
file =~
|
17
|
-
|
18
|
-
|Gemfile
|
19
|
-
|Rakefile
|
20
|
-
|\.rspec
|
21
|
-
|\.gitignore
|
22
|
-
|\.rubocop.yml
|
16
|
+
file =~ %r{^(?: spec\/.* |Gemfile |Rakefile
|
17
|
+
|\.rspec |\.gitignore |\.rubocop.yml
|
23
18
|
|\.travis.yml
|
24
|
-
)
|
19
|
+
)$}x
|
25
20
|
end
|
26
|
-
s.require_paths = [
|
21
|
+
s.require_paths = ['lib']
|
27
22
|
|
28
23
|
s.required_ruby_version = '>= 2.1.0'
|
29
24
|
|
@@ -35,4 +30,5 @@ Gem::Specification.new do |s|
|
|
35
30
|
s.add_development_dependency 'rubygems-tasks'
|
36
31
|
s.add_development_dependency 'yard'
|
37
32
|
s.add_development_dependency 'coveralls'
|
33
|
+
s.add_development_dependency 'dokaz'
|
38
34
|
end
|
data/lib/geo/coord.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
1
3
|
# Geo::Coord is Ruby's library for handling [lat, lng] pairs of
|
2
4
|
# geographical coordinates. It provides most of basic functionality
|
3
5
|
# you may expect (storing and representing coordinate pair), as well
|
@@ -19,48 +21,48 @@ module Geo
|
|
19
21
|
#
|
20
22
|
# # From lat/lng pair:
|
21
23
|
# g = Geo::Coord.new(50.004444, 36.231389)
|
22
|
-
# # => #<Geo::Coord 50
|
24
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
23
25
|
#
|
24
26
|
# # Or using keyword arguments form:
|
25
27
|
# g = Geo::Coord.new(lat: 50.004444, lng: 36.231389)
|
26
|
-
# # => #<Geo::Coord 50
|
28
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
27
29
|
#
|
28
30
|
# # Keyword arguments also allow creation of Coord from components:
|
29
31
|
# g = Geo::Coord.new(latd: 50, latm: 0, lats: 16, lath: 'N', lngd: 36, lngm: 13, lngs: 53, lngh: 'E')
|
30
|
-
# # => #<Geo::Coord 50
|
32
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
31
33
|
#
|
32
34
|
# For parsing API responses you'd like to use +from_h+,
|
33
35
|
# which accepts String and Symbol keys, any letter case,
|
34
36
|
# and knows synonyms (lng/lon/longitude):
|
35
37
|
#
|
36
38
|
# g = Geo::Coord.from_h('LAT' => 50.004444, 'LON' => 36.231389)
|
37
|
-
# # => #<Geo::Coord 50
|
39
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
38
40
|
#
|
39
41
|
# For math, you'd probably like to be able to initialize
|
40
42
|
# Coord with radians rather than degrees:
|
41
43
|
#
|
42
44
|
# g = Geo::Coord.from_rad(0.8727421884291233, 0.6323570306208558)
|
43
|
-
# # => #<Geo::Coord 50
|
45
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
44
46
|
#
|
45
47
|
# There's also family of parsing methods, with different applicability:
|
46
48
|
#
|
47
49
|
# # Tries to parse (lat, lng) pair:
|
48
50
|
# g = Geo::Coord.parse_ll('50.004444, 36.231389')
|
49
|
-
# # => #<Geo::Coord 50
|
51
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
50
52
|
#
|
51
53
|
# # Tries to parse degrees/minutes/seconds:
|
52
54
|
# g = Geo::Coord.parse_dms('50° 0′ 16″ N, 36° 13′ 53″ E')
|
53
|
-
# # => #<Geo::Coord 50
|
55
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
54
56
|
#
|
55
57
|
# # Tries to do best guess:
|
56
58
|
# g = Geo::Coord.parse('50.004444, 36.231389')
|
57
|
-
# # => #<Geo::Coord 50
|
59
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
58
60
|
# g = Geo::Coord.parse('50° 0′ 16″ N, 36° 13′ 53″ E')
|
59
|
-
# # => #<Geo::Coord 50
|
61
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
60
62
|
#
|
61
|
-
# # Allows user to provide pattern
|
63
|
+
# # Allows user to provide pattern:
|
62
64
|
# g = Geo::Coord.strpcoord('50.004444, 36.231389', '%lat, %lng')
|
63
|
-
# # => #<Geo::Coord 50
|
65
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
64
66
|
#
|
65
67
|
# Having Coord object, you can get its properties:
|
66
68
|
#
|
@@ -117,7 +119,7 @@ module Geo
|
|
117
119
|
# and longitude ("lng", "lon", "long", "longitude").
|
118
120
|
#
|
119
121
|
# g = Geo::Coord.from_h('LAT' => 50.004444, longitude: 36.231389)
|
120
|
-
# # => #<Geo::Coord 50
|
122
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
121
123
|
#
|
122
124
|
def from_h(hash)
|
123
125
|
h = hash.map { |k, v| [k.to_s.downcase.to_sym, v] }.to_h
|
@@ -132,7 +134,7 @@ module Geo
|
|
132
134
|
# Creates Coord from φ and λ (latitude and longitude in radians).
|
133
135
|
#
|
134
136
|
# g = Geo::Coord.from_rad(0.8727421884291233, 0.6323570306208558)
|
135
|
-
# # => #<Geo::Coord 50
|
137
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
136
138
|
#
|
137
139
|
def from_rad(phi, la)
|
138
140
|
new(phi * 180 / Math::PI, la * 180 / Math::PI)
|
@@ -150,7 +152,7 @@ module Geo
|
|
150
152
|
# @private
|
151
153
|
DEG_PATTERN = '[ °d]'.freeze # :nodoc:
|
152
154
|
# @private
|
153
|
-
MIN_PATTERN = "['
|
155
|
+
MIN_PATTERN = "['′’m]".freeze # :nodoc:
|
154
156
|
# @private
|
155
157
|
SEC_PATTERN = '["″s]'.freeze # :nodoc:
|
156
158
|
|
@@ -158,24 +160,31 @@ module Geo
|
|
158
160
|
LL_PATTERN = /^(#{FLOAT_PATTERN})\s*[,; ]\s*(#{FLOAT_PATTERN})$/ # :nodoc:
|
159
161
|
|
160
162
|
# @private
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
163
|
+
DMS_LATD_P = "(?<latd>#{INT_PATTERN})#{DEG_PATTERN}".freeze # :nodoc:
|
164
|
+
# @private
|
165
|
+
DMS_LATM_P = "(?<latm>#{UINT_PATTERN})#{MIN_PATTERN}".freeze # :nodoc:
|
166
|
+
# @private
|
167
|
+
DMS_LATS_P = "(?<lats>#{UFLOAT_PATTERN})#{SEC_PATTERN}".freeze # :nodoc:
|
168
|
+
# @private
|
169
|
+
DMS_LAT_P = "#{DMS_LATD_P}\\s*#{DMS_LATM_P}\\s*#{DMS_LATS_P}\\s*(?<lath>[NS])".freeze # :nodoc:
|
170
|
+
|
171
|
+
# @private
|
172
|
+
DMS_LNGD_P = "(?<lngd>#{INT_PATTERN})#{DEG_PATTERN}".freeze # :nodoc:
|
173
|
+
# @private
|
174
|
+
DMS_LNGM_P = "(?<lngm>#{UINT_PATTERN})#{MIN_PATTERN}".freeze # :nodoc:
|
175
|
+
# @private
|
176
|
+
DMS_LNGS_P = "(?<lngs>#{UFLOAT_PATTERN})#{SEC_PATTERN}".freeze # :nodoc:
|
177
|
+
# @private
|
178
|
+
DMS_LNG_P = "#{DMS_LNGD_P}\\s*#{DMS_LNGM_P}\\s*#{DMS_LNGS_P}\\s*(?<lngh>[EW])".freeze # :nodoc:
|
179
|
+
|
180
|
+
# @private
|
181
|
+
DMS_PATTERN = /^\s*#{DMS_LAT_P}\s*[,; ]\s*#{DMS_LNG_P}\s*$/x # :nodoc:
|
173
182
|
|
174
183
|
# Parses Coord from string containing float latitude and longitude.
|
175
184
|
# Understands several types of separators/spaces between values.
|
176
185
|
#
|
177
186
|
# Geo::Coord.parse_ll('-50.004444 +36.231389')
|
178
|
-
# # => #<Geo::Coord
|
187
|
+
# # => #<Geo::Coord 50°0'16"S 36°13'53"E>
|
179
188
|
#
|
180
189
|
# If parse_ll is not wise enough to understand your data, consider
|
181
190
|
# using ::strpcoord.
|
@@ -193,7 +202,7 @@ module Geo
|
|
193
202
|
# explicit hemisphere and no-hemisphere (signed degrees) formats.
|
194
203
|
#
|
195
204
|
# Geo::Coord.parse_dms('50°0′16″N 36°13′53″E')
|
196
|
-
# # => #<Geo::Coord 50
|
205
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
197
206
|
#
|
198
207
|
# If parse_dms is not wise enough to understand your data, consider
|
199
208
|
# using ::strpcoord.
|
@@ -212,9 +221,9 @@ module Geo
|
|
212
221
|
# known form).
|
213
222
|
#
|
214
223
|
# Geo::Coord.parse('-50.004444 +36.231389')
|
215
|
-
# # => #<Geo::Coord
|
224
|
+
# # => #<Geo::Coord 50°0'16"S 36°13'53"E>
|
216
225
|
# Geo::Coord.parse('50°0′16″N 36°13′53″E')
|
217
|
-
# # => #<Geo::Coord 50
|
226
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
218
227
|
#
|
219
228
|
# If you know exact form in which coordinates are
|
220
229
|
# provided, it may be wider to consider parse_ll, parse_dms or
|
@@ -268,12 +277,9 @@ module Geo
|
|
268
277
|
pattern = PARSE_PATTERNS.inject(pattern) do |memo, (pfrom, pto)|
|
269
278
|
memo.gsub(pfrom, pto)
|
270
279
|
end
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
else
|
275
|
-
raise ArgumentError, "Coordinates str #{str} can't be parsed by pattern #{pattern}"
|
276
|
-
end
|
280
|
+
match = Regexp.new('^' + pattern).match(str)
|
281
|
+
raise ArgumentError, "Coordinates str #{str} can't be parsed by pattern #{pattern}" unless match
|
282
|
+
new(match.names.map { |n| [n.to_sym, _extract_match(match, n)] }.to_h)
|
277
283
|
end
|
278
284
|
|
279
285
|
private
|
@@ -299,19 +305,19 @@ module Geo
|
|
299
305
|
# key +lat+ and partial longitude keys +lngd+, +lngm+ and so on.
|
300
306
|
#
|
301
307
|
# g = Geo::Coord.new(50.004444, 36.231389)
|
302
|
-
# # => #<Geo::Coord 50
|
308
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
303
309
|
#
|
304
310
|
# # Or using keyword arguments form:
|
305
311
|
# g = Geo::Coord.new(lat: 50.004444, lng: 36.231389)
|
306
|
-
# # => #<Geo::Coord 50
|
312
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
307
313
|
#
|
308
314
|
# # Keyword arguments also allow creation of Coord from components:
|
309
315
|
# g = Geo::Coord.new(latd: 50, latm: 0, lats: 16, lath: 'N', lngd: 36, lngm: 13, lngs: 53, lngh: 'E')
|
310
|
-
# # => #<Geo::Coord 50
|
316
|
+
# # => #<Geo::Coord 50°0'16"N 36°13'53"E>
|
311
317
|
#
|
312
318
|
# # Providing defaults:
|
313
319
|
# g = Geo::Coord.new(lat: 50.004444)
|
314
|
-
# # => #<Geo::Coord 50
|
320
|
+
# # => #<Geo::Coord 50°0'16"N 0°0'0"W>
|
315
321
|
#
|
316
322
|
def initialize(lat = nil, lng = nil, **opts)
|
317
323
|
@globe = Globes::Earth.instance
|
@@ -352,7 +358,7 @@ module Geo
|
|
352
358
|
|
353
359
|
# Returns latitude seconds (unsigned float).
|
354
360
|
def lats
|
355
|
-
(lat.abs * 3600) %
|
361
|
+
(lat.abs * 3600) % 60
|
356
362
|
end
|
357
363
|
|
358
364
|
# Returns latitude hemisphere (upcase letter 'N' or 'S').
|
@@ -386,14 +392,14 @@ module Geo
|
|
386
392
|
# # Nothern hemisphere:
|
387
393
|
# g = Geo::Coord.new(50.004444, 36.231389)
|
388
394
|
#
|
389
|
-
# g.latdms # => [50, 0, 15.
|
390
|
-
# g.latdms(true) # => [50, 0, 15.
|
395
|
+
# g.latdms # => [50, 0, 15.9984, "N"]
|
396
|
+
# g.latdms(true) # => [50, 0, 15.9984]
|
391
397
|
#
|
392
398
|
# # Southern hemisphere:
|
393
399
|
# g = Geo::Coord.new(-50.004444, 36.231389)
|
394
400
|
#
|
395
|
-
# g.latdms # => [50, 0, 15.
|
396
|
-
# g.latdms(true) # => [-50, 0, 15.
|
401
|
+
# g.latdms # => [50, 0, 15.9984, "S"]
|
402
|
+
# g.latdms(true) # => [-50, 0, 15.9984]
|
397
403
|
#
|
398
404
|
def latdms(nohemisphere = false)
|
399
405
|
nohemisphere ? [latsign * latd, latm, lats] : [latd, latm, lats, lath]
|
@@ -405,14 +411,14 @@ module Geo
|
|
405
411
|
# # Eastern hemisphere:
|
406
412
|
# g = Geo::Coord.new(50.004444, 36.231389)
|
407
413
|
#
|
408
|
-
# g.lngdms # => [36, 13, 53.
|
409
|
-
# g.lngdms(true) # => [36, 13, 53.
|
414
|
+
# g.lngdms # => [36, 13, 53.0004, "E"]
|
415
|
+
# g.lngdms(true) # => [36, 13, 53.0004]
|
410
416
|
#
|
411
417
|
# # Western hemisphere:
|
412
418
|
# g = Geo::Coord.new(50.004444, 36.231389)
|
413
419
|
#
|
414
|
-
# g.lngdms # => [36, 13, 53.
|
415
|
-
# g.lngdms(true) # => [-36, 13, 53.
|
420
|
+
# g.lngdms # => [36, 13, 53.0004, "E"]
|
421
|
+
# g.lngdms(true) # => [-36, 13, 53.0004]
|
416
422
|
#
|
417
423
|
def lngdms(nohemisphere = false)
|
418
424
|
nohemisphere ? [lngsign * lngd, lngm, lngs] : [lngd, lngm, lngs, lngh]
|
@@ -440,25 +446,35 @@ module Geo
|
|
440
446
|
# g.inspect # => "#<Geo::Coord 50.004444,36.231389>"
|
441
447
|
#
|
442
448
|
def inspect
|
443
|
-
|
449
|
+
strfcoord(%{#<#{self.class.name} %latd°%latm'%lats"%lath %lngd°%lngm'%lngs"%lngh>})
|
444
450
|
end
|
445
451
|
|
446
452
|
# Returns a string representing coordinates.
|
447
453
|
#
|
448
|
-
# g.to_s
|
454
|
+
# g.to_s # => "50°0'16\"N 36°13'53\"E"
|
455
|
+
# g.to_s(dms: false) # => "50.004444,36.231389"
|
449
456
|
#
|
450
|
-
def to_s
|
451
|
-
'%
|
457
|
+
def to_s(dms: true)
|
458
|
+
format = dms ? %{%latd°%latm'%lats"%lath %lngd°%lngm'%lngs"%lngh} : '%lat,%lng'
|
459
|
+
strfcoord(format)
|
452
460
|
end
|
453
461
|
|
454
462
|
# Returns a two-element array of latitude and longitude.
|
455
463
|
#
|
456
|
-
# g.
|
464
|
+
# g.latlng # => [50.004444, 36.231389]
|
457
465
|
#
|
458
|
-
def
|
466
|
+
def latlng
|
459
467
|
[lat, lng]
|
460
468
|
end
|
461
469
|
|
470
|
+
# Returns a two-element array of longitude and latitude (reverse order to +latlng+).
|
471
|
+
#
|
472
|
+
# g.lnglat # => [36.231389, 50.004444]
|
473
|
+
#
|
474
|
+
def lnglat
|
475
|
+
[lng, lat]
|
476
|
+
end
|
477
|
+
|
462
478
|
# Returns hash of latitude and longitude. You can provide your keys
|
463
479
|
# if you want:
|
464
480
|
#
|
@@ -480,17 +496,17 @@ module Geo
|
|
480
496
|
|
481
497
|
# @private
|
482
498
|
DIRECTIVES = { # :nodoc:
|
499
|
+
/%(#{FLOATUFLAGS})?lats/ => proc { |m| "%<lats>#{m[1] || '.0'}f" },
|
500
|
+
'%latm' => '%<latm>i',
|
483
501
|
/%(#{INTFLAGS})?latds/ => proc { |m| "%<latds>#{m[1]}i" },
|
484
502
|
'%latd' => '%<latd>i',
|
485
|
-
'%latm' => '%<latm>i',
|
486
|
-
/%(#{FLOATUFLAGS})?lats/ => proc { |m| "%<lats>#{m[1] || '.0'}f" },
|
487
503
|
'%lath' => '%<lath>s',
|
488
504
|
/%(#{FLOATFLAGS})?lat/ => proc { |m| "%<lat>#{m[1]}f" },
|
489
505
|
|
506
|
+
/%(#{FLOATUFLAGS})?lngs/ => proc { |m| "%<lngs>#{m[1] || '.0'}f" },
|
507
|
+
'%lngm' => '%<lngm>i',
|
490
508
|
/%(#{INTFLAGS})?lngds/ => proc { |m| "%<lngds>#{m[1]}i" },
|
491
509
|
'%lngd' => '%<lngd>i',
|
492
|
-
'%lngm' => '%<lngm>i',
|
493
|
-
/%(#{FLOATUFLAGS})?lngs/ => proc { |m| "%<lngs>#{m[1] || '.0'}f" },
|
494
510
|
'%lngh' => '%<lngh>s',
|
495
511
|
/%(#{FLOATFLAGS})?lng/ => proc { |m| "%<lng>#{m[1]}f" }
|
496
512
|
}.freeze
|
@@ -529,13 +545,23 @@ module Geo
|
|
529
545
|
# g.strfcoord("%latd°%latm'%lath -- %lngd°%lngm'%lngh")
|
530
546
|
# # => "50°0'N -- 36°13'E"
|
531
547
|
#
|
548
|
+
# +strfcoord+ handles seconds rounding implicitly:
|
549
|
+
#
|
550
|
+
# pos = Geo::Coord.new(0.033333, 91.333333)
|
551
|
+
# pos.lats # => 0.599988e2
|
552
|
+
# pos.strfcoord('%latd %latm %.05lats') # => "0 1 59.99880"
|
553
|
+
# pos.strfcoord('%latd %latm %lats') # => "0 2 0"
|
554
|
+
#
|
532
555
|
def strfcoord(formatstr)
|
533
556
|
h = full_hash
|
534
557
|
|
535
558
|
DIRECTIVES.reduce(formatstr) do |memo, (from, to)|
|
536
559
|
memo.gsub(from) do
|
537
560
|
to = to.call(Regexp.last_match) if to.is_a?(Proc)
|
538
|
-
to % h
|
561
|
+
res = to % h
|
562
|
+
res, carrymin = guard_seconds(to, res)
|
563
|
+
h[carrymin] += 1 if carrymin
|
564
|
+
res
|
539
565
|
end
|
540
566
|
end
|
541
567
|
end
|
@@ -570,7 +596,7 @@ module Geo
|
|
570
596
|
#
|
571
597
|
# kharkiv = Geo::Coord.new(50.004444, 36.231389)
|
572
598
|
# kharkiv.endpoint(410_211, 280)
|
573
|
-
# # => #<Geo::Coord 50
|
599
|
+
# # => #<Geo::Coord 50°30'22"N 30°31'53"E>
|
574
600
|
#
|
575
601
|
def endpoint(distance, azimuth)
|
576
602
|
phi2, la2 = @globe.direct(phi, la, distance, deg2rad(azimuth))
|
@@ -579,17 +605,15 @@ module Geo
|
|
579
605
|
|
580
606
|
private
|
581
607
|
|
582
|
-
|
583
|
-
|
584
|
-
lng = lng.to_f
|
608
|
+
LAT_RANGE_ERROR = 'Expected latitude to be between -90 and 90, %p received'.freeze
|
609
|
+
LNG_RANGE_ERROR = 'Expected longitude to be between -180 and 180, %p received'.freeze
|
585
610
|
|
586
|
-
|
587
|
-
|
588
|
-
|
611
|
+
def _init(lat, lng)
|
612
|
+
lat = BigDecimal(lat.to_f, 10)
|
613
|
+
lng = BigDecimal(lng.to_f, 10)
|
589
614
|
|
590
|
-
unless (-
|
591
|
-
|
592
|
-
end
|
615
|
+
raise ArgumentError, LAT_RANGE_ERROR % lat unless (-90..90).cover?(lat)
|
616
|
+
raise ArgumentError, LNG_RANGE_ERROR % lng unless (-180..180).cover?(lng)
|
593
617
|
|
594
618
|
@lat = lat
|
595
619
|
@lng = lng
|
@@ -620,6 +644,13 @@ module Geo
|
|
620
644
|
raise ArgumentError, "Unidentified hemisphere: #{h}"
|
621
645
|
end
|
622
646
|
|
647
|
+
def guard_seconds(pattern, result)
|
648
|
+
m = pattern.match(/<(lat|lng)s>/)
|
649
|
+
return result unless m && result.start_with?('60')
|
650
|
+
carry = "#{m[1]}m".to_sym
|
651
|
+
[pattern % {lats: 0, lngs: 0}, carry]
|
652
|
+
end
|
653
|
+
|
623
654
|
def latsign
|
624
655
|
lat <=> 0
|
625
656
|
end
|
data/lib/geo/coord/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geo_coord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Shepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubocop
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: dokaz
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: ''
|
126
140
|
email: zverok.offline@gmail.com
|
127
141
|
executables: []
|
@@ -129,6 +143,7 @@ extensions: []
|
|
129
143
|
extra_rdoc_files: []
|
130
144
|
files:
|
131
145
|
- ".yardopts"
|
146
|
+
- CHANGELOG.md
|
132
147
|
- LICENSE.txt
|
133
148
|
- README.md
|
134
149
|
- StdlibProposal.md
|
@@ -156,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
171
|
version: '0'
|
157
172
|
requirements: []
|
158
173
|
rubyforge_project:
|
159
|
-
rubygems_version: 2.4
|
174
|
+
rubygems_version: 2.7.4
|
160
175
|
signing_key:
|
161
176
|
specification_version: 4
|
162
177
|
summary: Geo::Coord class
|