loc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/loc.svg)](https://badge.fury.io/rb/loc)
|
4
|
+
[![CircleCI](https://circleci.com/gh/wayzup/loc.svg?style=shield&circle-token=:circle-token)](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
|