geo_coord 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|