loc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ module Loc
2
+ end
3
+
4
+ require "loc/location"
5
+ require "loc/location_collection"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Loc
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require "bundler/setup"
4
+ require "byebug"
5
+ require "loc"
6
+
7
+ RSpec.configure do |config|
8
+ # Config goes here
9
+ end
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