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.
@@ -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