loc 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 +7 -0
- data/README.md +74 -0
- data/lib/loc.rb +5 -0
- data/lib/loc/location.rb +97 -0
- data/lib/loc/location_collection.rb +89 -0
- data/lib/loc/version.rb +3 -0
- data/spec/loc/location_collection_spec.rb +110 -0
- data/spec/loc/location_spec.rb +67 -0
- data/spec/spec_helper.rb +9 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 372cb2c5d886cc5369727d21a6e7a731fab38957
|
4
|
+
data.tar.gz: 29e686c1057bb8b7a10a0ac25848ffce3827404c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf63d7d19c2cfa2964a7c0fd319af6648febb9343a2e395130f1bb0003da7d2e6371828ddef14bf26af4de152841c41f325d8490752ca6eebd6a1d9e7038aa5c
|
7
|
+
data.tar.gz: 27819b8ac2f6bd74541332cfabf551083fe9a6d62a69b9caca7d0737757b30cf009326b2c5abea28188efae5072a31adce3bea451a9da9c43f09d4636e773b11
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Loc
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/loc)
|
4
|
+
[](https://circleci.com/gh/wayzup/loc)
|
5
|
+
|
6
|
+
Location handling & geographical computations. No external API or DB.
|
7
|
+
|
8
|
+
## Install
|
9
|
+
|
10
|
+
```
|
11
|
+
gem install loc
|
12
|
+
```
|
13
|
+
|
14
|
+
or in Bundler:
|
15
|
+
```
|
16
|
+
gem "loc"
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Location
|
22
|
+
|
23
|
+
```
|
24
|
+
require "loc"
|
25
|
+
|
26
|
+
loc1 = Loc::Location[49.1, 2]
|
27
|
+
|
28
|
+
loc1.lat_degrees_per_km
|
29
|
+
# 0.008993210126354604
|
30
|
+
|
31
|
+
loc1.lng_degrees_per_km
|
32
|
+
# 0.013735635666645289
|
33
|
+
|
34
|
+
loc2 = Loc::Location[50, 3]
|
35
|
+
|
36
|
+
loc1.distance_to(loc2)
|
37
|
+
# 123364.76538823603
|
38
|
+
|
39
|
+
loc1.bearing_to(loc2)
|
40
|
+
# 48.01278750418339
|
41
|
+
```
|
42
|
+
|
43
|
+
### LocationCollection
|
44
|
+
|
45
|
+
```
|
46
|
+
require "loc"
|
47
|
+
|
48
|
+
collection = Loc::LocationCollection[[1,2],[3,4]]
|
49
|
+
|
50
|
+
collection.distance
|
51
|
+
# 314402.95102362486
|
52
|
+
|
53
|
+
collection[0]
|
54
|
+
# <Loc::Location {:lat=>1, :lng=>2}>
|
55
|
+
|
56
|
+
collection.each { |l| puts l.to_a }
|
57
|
+
# <Loc::Location {:lat=>1, :lng=>2}>
|
58
|
+
# <Loc::Location {:lat=>3, :lng=>4}>
|
59
|
+
|
60
|
+
collection.map(&:to_a)
|
61
|
+
# [[1, 2], [3, 4]]
|
62
|
+
```
|
63
|
+
|
64
|
+
## Versioning
|
65
|
+
|
66
|
+
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/wayzup/loc/tags).
|
67
|
+
|
68
|
+
## Authors
|
69
|
+
|
70
|
+
See the list of [contributors](https://github.com/wayzup/loc/contributors) who participated in this project.
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
Please see LICENSE
|
data/lib/loc.rb
ADDED
data/lib/loc/location.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
module Loc
|
2
|
+
class Location
|
3
|
+
attr_reader :lat, :lng
|
4
|
+
|
5
|
+
def initialize(lat, lng)
|
6
|
+
@lat = lat
|
7
|
+
@lng = lng
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def from_array(a)
|
12
|
+
new(a[0], a[1])
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](*args)
|
16
|
+
from_array(args)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias from_a from_array
|
20
|
+
end
|
21
|
+
|
22
|
+
# Calculate the distance in meters betwen the object
|
23
|
+
# and another location using the 'Haversine' formula.
|
24
|
+
# Params :
|
25
|
+
# +other+:: Location
|
26
|
+
def distance_to(other)
|
27
|
+
dlat = to_radians(other.lat - lat)
|
28
|
+
dlon = to_radians(other.lng - lng)
|
29
|
+
a = Math.sin(dlat / 2) * Math.sin(dlat / 2) +
|
30
|
+
Math.cos(to_radians(lat)) * Math.cos(to_radians(other.lat)) *
|
31
|
+
Math.sin(dlon / 2) * Math.sin(dlon / 2)
|
32
|
+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
33
|
+
6371 * c * 1000
|
34
|
+
end
|
35
|
+
|
36
|
+
# Give the bearing from the object to the other.
|
37
|
+
# Params :
|
38
|
+
# +other+:: Location
|
39
|
+
def bearing_to(other)
|
40
|
+
dlat = to_radians(other.lat - lat)
|
41
|
+
dlng = to_radians(other.lng - lng)
|
42
|
+
bearing = Math.atan2(dlat, dlng)
|
43
|
+
(90 - to_degrees(bearing) + 360) % 360
|
44
|
+
end
|
45
|
+
|
46
|
+
# Give the number of latitude degrees
|
47
|
+
# for a kilometer distance at location
|
48
|
+
def lat_degrees_per_km
|
49
|
+
1 / 111.195
|
50
|
+
end
|
51
|
+
|
52
|
+
# Calculate the number of longitude degrees
|
53
|
+
# for kilometer distance at location
|
54
|
+
def lng_degrees_per_km
|
55
|
+
1 / (distance_to(Location[lat, lng + 1]) / 1000)
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
return true if other.equal?(self)
|
60
|
+
return false unless other.instance_of?(self.class)
|
61
|
+
lat == other.lat && lng == other.lng
|
62
|
+
end
|
63
|
+
|
64
|
+
def [](*args)
|
65
|
+
to_array[*args]
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_array
|
69
|
+
[lat, lng]
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_hash
|
73
|
+
{ lat: lat, lng: lng }
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"#{lat},#{lng}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"<#{self.class} #{{ lat: lat, lng: lng }}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
alias to_a to_array
|
85
|
+
alias to_h to_hash
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def to_radians(deg)
|
90
|
+
deg * Math::PI / 180
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_degrees(rad)
|
94
|
+
rad * 180 / Math::PI
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Loc
|
2
|
+
class LocationCollection
|
3
|
+
attr_reader :locations
|
4
|
+
|
5
|
+
def initialize(locations = [])
|
6
|
+
@locations = locations.map { |o| to_location(o) }
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def from_array(a)
|
11
|
+
new(a)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*args)
|
15
|
+
new(args)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias from_a from_array
|
19
|
+
end
|
20
|
+
|
21
|
+
# Give the distance in meters between ordered
|
22
|
+
# location points using the 'Haversine' formula
|
23
|
+
def distance
|
24
|
+
return nil unless @locations.size > 1
|
25
|
+
locations.each_cons(2).reduce(0) do |acc, (loc1, loc2)|
|
26
|
+
acc + loc1.distance_to(loc2)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def size
|
31
|
+
locations.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](*args)
|
35
|
+
locations[*args]
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
locations.each(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def map
|
43
|
+
locations.map { |l| yield l }
|
44
|
+
end
|
45
|
+
|
46
|
+
def shift
|
47
|
+
locations.shift
|
48
|
+
end
|
49
|
+
|
50
|
+
def pop
|
51
|
+
locations.pop
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
to_a == other.to_a
|
56
|
+
end
|
57
|
+
|
58
|
+
def eql?(other)
|
59
|
+
to_a.eql?(other.to_a)
|
60
|
+
end
|
61
|
+
|
62
|
+
def hash
|
63
|
+
to_a.hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_array
|
67
|
+
locations.map(&:to_a)
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect
|
71
|
+
"<#{self.class} #{{ locations: @locations }}>"
|
72
|
+
end
|
73
|
+
|
74
|
+
alias to_a to_array
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def to_location(object)
|
79
|
+
case object
|
80
|
+
when Location
|
81
|
+
object
|
82
|
+
when Array
|
83
|
+
Location.from_array(object)
|
84
|
+
else
|
85
|
+
raise "Unsupported type #{object.class.name} for locations"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/loc/version.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Loc::LocationCollection do
|
4
|
+
|
5
|
+
it "can be created from an Array" do
|
6
|
+
collection = described_class.from_array([[1, 2], [3, 4]])
|
7
|
+
expect(collection.locations[0]).to eq Loc::Location[1, 2]
|
8
|
+
expect(collection.locations[1]).to eq Loc::Location[3, 4]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can be created from array with [] syntax" do
|
12
|
+
collection = described_class[[1, 2], [3, 4]]
|
13
|
+
expect(collection.locations[0]).to eq Loc::Location[1, 2]
|
14
|
+
expect(collection.locations[1]).to eq Loc::Location[3, 4]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should understand [] syntax" do
|
18
|
+
collection = described_class.new([[1, 2], [3, 4]])
|
19
|
+
expect(collection[0]).to eq Loc::Location[1, 2]
|
20
|
+
expect(collection[1]).to eq Loc::Location[3, 4]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should give geodesic distance between ordered locations" do
|
24
|
+
locs = described_class.new([[1, 2], [2, 3], [3, 4]])
|
25
|
+
expect(locs.distance).to eq 314402.9838527136
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should understand equality" do
|
29
|
+
collection_1 = described_class.new([[1, 2], [3, 4]])
|
30
|
+
collection_2 = described_class.new([[1, 2], [3, 4]])
|
31
|
+
collection_3 = described_class.new([[5, 6], [7, 8]])
|
32
|
+
expect(collection_1).to eql collection_2
|
33
|
+
expect(collection_1).to eq collection_2
|
34
|
+
expect(collection_1).to_not eql collection_3
|
35
|
+
expect(collection_1).to_not eq collection_3
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#size" do
|
39
|
+
it "should give location collection size" do
|
40
|
+
collection = described_class.new(
|
41
|
+
[Loc::Location[1, 2], Loc::Location[3, 4]]
|
42
|
+
)
|
43
|
+
expect(collection.size).to eq 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#each" do
|
48
|
+
let(:array) { [Loc::Location[1, 2], Loc::Location[3, 4]] }
|
49
|
+
let(:collection) { described_class.new(array) }
|
50
|
+
it "should return enum" do
|
51
|
+
expect(collection.each).to contain_exactly *array
|
52
|
+
end
|
53
|
+
it "should execute block for all locations" do
|
54
|
+
each_location = []
|
55
|
+
collection.each { |location| each_location << location }
|
56
|
+
expect(each_location).to contain_exactly *array
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#map" do
|
61
|
+
it "should map each location" do
|
62
|
+
collection = described_class.new([[1,2],[3,4]])
|
63
|
+
expect(
|
64
|
+
collection.map{|l| l.class.name }
|
65
|
+
).to eq(["Loc::Location", "Loc::Location"])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#shift" do
|
70
|
+
let(:array) { [Loc::Location[1, 2], Loc::Location[3, 4]] }
|
71
|
+
let(:collection) { described_class.new(array) }
|
72
|
+
it "should give first element" do
|
73
|
+
expect(collection.shift).to eq Loc::Location[1, 2]
|
74
|
+
end
|
75
|
+
it "should remove first element" do
|
76
|
+
collection.shift
|
77
|
+
expect(collection.locations).to eq [Loc::Location[3, 4]]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#pop" do
|
82
|
+
let(:array) { [Loc::Location[1, 2], Loc::Location[3, 4]] }
|
83
|
+
let(:collection) { described_class.new(array) }
|
84
|
+
it "should give last element" do
|
85
|
+
expect(collection.pop).to eq Loc::Location[3, 4]
|
86
|
+
end
|
87
|
+
it "should remove last element" do
|
88
|
+
collection.pop
|
89
|
+
expect(collection.locations).to eq [Loc::Location[1, 2]]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#hash" do
|
94
|
+
it "should give hash based on collection" do
|
95
|
+
collection_1 = described_class.new([[1, 2], [3, 4]])
|
96
|
+
collection_2 = described_class.new([[1, 2], [3, 4]])
|
97
|
+
collection_3 = described_class.new([[5, 6], [7, 8]])
|
98
|
+
expect(collection_1.hash).to eq collection_2.hash
|
99
|
+
expect(collection_1.hash).to_not eq collection_3.hash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#to_array" do
|
104
|
+
it "should give an array of locations as array" do
|
105
|
+
array = [[1,2],[3,4]]
|
106
|
+
collection = described_class.new(array)
|
107
|
+
expect(collection.to_array).to eq array
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Loc::Location do
|
4
|
+
it "can be created from an Array" do
|
5
|
+
location = described_class.from_array([1, 2])
|
6
|
+
expect(location.lat).to eq 1
|
7
|
+
expect(location.lng).to eq 2
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can be created from array with [] syntax" do
|
11
|
+
location = described_class[1, 2]
|
12
|
+
expect(location.lat).to eq 1
|
13
|
+
expect(location.lng).to eq 2
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can be accessed with [] syntax" do
|
17
|
+
location = described_class[1, 2]
|
18
|
+
expect(location[0]).to eq 1
|
19
|
+
expect(location[1]).to eq 2
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should give the distance in meters using \"Haversine\" formula" do
|
23
|
+
loc1 = described_class.new(50, 2)
|
24
|
+
loc2 = described_class.new(49, 3)
|
25
|
+
expect(loc1.distance_to(loc2)).to eq 132584.3578090791
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should give bearing from object to another" do
|
29
|
+
loc1 = described_class.new(50, 2)
|
30
|
+
loc2 = described_class.new(51, 2)
|
31
|
+
expect(loc1.bearing_to(loc2)).to eq 0
|
32
|
+
loc2 = described_class.new(51, 3)
|
33
|
+
expect(loc1.bearing_to(loc2)).to eq 45.0
|
34
|
+
loc2 = described_class.new(50, 3)
|
35
|
+
expect(loc1.bearing_to(loc2)).to eq 90.0
|
36
|
+
loc2 = described_class.new(49, 2)
|
37
|
+
expect(loc1.bearing_to(loc2)).to eq 180.0
|
38
|
+
loc2 = described_class.new(50, 1)
|
39
|
+
expect(loc1.bearing_to(loc2)).to eq 270.0
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should give latitude degrees per kilometer" do
|
43
|
+
loc = described_class.new(50, 2)
|
44
|
+
expect(loc.lat_degrees_per_km.round(7)).to eq 0.0089932
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should give longitude degrees per kilometer" do
|
48
|
+
loc = described_class.new(50, 2)
|
49
|
+
expect(loc.lng_degrees_per_km.round(7)).to eq 0.0139911
|
50
|
+
end
|
51
|
+
|
52
|
+
it "can understand equality" do
|
53
|
+
loc1 = described_class.new(1, 2)
|
54
|
+
loc2 = described_class.new(1, 2)
|
55
|
+
expect(loc1).to eq(loc2)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can be converted to Hash" do
|
59
|
+
loc = described_class.new(1, 2)
|
60
|
+
expect(loc.to_hash).to eq(lat: 1, lng: 2)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can be converted to an Array" do
|
64
|
+
loc = described_class.new(1, 2)
|
65
|
+
expect(loc.to_a).to eq([1, 2])
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: loc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cyrille Courtière
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '9.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '9.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- cyrille@wayzup.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- lib/loc.rb
|
50
|
+
- lib/loc/location.rb
|
51
|
+
- lib/loc/location_collection.rb
|
52
|
+
- lib/loc/version.rb
|
53
|
+
- spec/loc/location_collection_spec.rb
|
54
|
+
- spec/loc/location_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: http://github.com/wayzup/loc
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.6.8
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Location handling & geographical computations
|
80
|
+
test_files:
|
81
|
+
- spec/loc/location_collection_spec.rb
|
82
|
+
- spec/loc/location_spec.rb
|
83
|
+
- spec/spec_helper.rb
|