cacheable_kdtree 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0df08204eb2b5ccf4cb9495a44a329573ca4af04
4
+ data.tar.gz: 90c8dd11173baa5aa4ec78808d42b461da2bd04d
5
+ SHA512:
6
+ metadata.gz: 34040472bdfe529124bb27e14ac6da865c3ee79335787d02b973fcd79c546fe4d34ed66035ec1da40f73ee168c38a9a8e2bc821ea8faae345407e700fb53d685
7
+ data.tar.gz: 80a110414a30f66a8ad4ad7ea162d832c99fdc1896346a482222629068627fcb4801ce607a24da151f86cd4082d2d904d0820ff6d6162554f292020fd5630b8b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,491 @@
1
+ # Common configuration.
2
+ AllCops:
3
+ # Include gemspec and Rakefile
4
+ Include:
5
+ - '**/*.gemspec'
6
+ - '**/Rakefile'
7
+ Exclude:
8
+ - 'vendor/**/*'
9
+ - 'features/**/*'
10
+ - 'Gemfile'
11
+ - 'bin/**'
12
+ - 'tmp/**/*'
13
+ - 'db/**/*.rb'
14
+ # By default, the rails cops are not run. Override in project or home
15
+ # directory .rubocop.yml files, or by giving the -R/--rails option.
16
+
17
+ # Indent private/protected/public as deep as method definitions
18
+ Style/AccessModifierIndentation:
19
+ EnforcedStyle: indent
20
+ SupportedStyles:
21
+ - outdent
22
+ - indent
23
+
24
+ # Align the elements of a hash literal if they span more than one line.
25
+ Style/AlignHash:
26
+ # Alignment of entries using hash rocket as separator. Valid values are:
27
+ #
28
+ # key - left alignment of keys
29
+ # 'a' => 2
30
+ # 'bb' => 3
31
+ # separator - alignment of hash rockets, keys are right aligned
32
+ # 'a' => 2
33
+ # 'bb' => 3
34
+ # table - left alignment of keys, hash rockets, and values
35
+ # 'a' => 2
36
+ # 'bb' => 3
37
+ EnforcedHashRocketStyle: key
38
+ # Alignment of entries using colon as separator. Valid values are:
39
+ #
40
+ # key - left alignment of keys
41
+ # a: 0
42
+ # bb: 1
43
+ # separator - alignment of colons, keys are right aligned
44
+ # a: 0
45
+ # bb: 1
46
+ # table - left alignment of keys and values
47
+ # a: 0
48
+ # bb: 1
49
+ EnforcedColonStyle: key
50
+ # Select whether hashes that are the last argument in a method call should be
51
+ # inspected? Valid values are:
52
+ #
53
+ # always_inspect - Inspect both implicit and explicit hashes.
54
+ # Registers an offense for:
55
+ # function(a: 1,
56
+ # b: 2)
57
+ # Registers an offense for:
58
+ # function({a: 1,
59
+ # b: 2})
60
+ # always_ignore - Ignore both implicit and explicit hashes.
61
+ # Accepts:
62
+ # function(a: 1,
63
+ # b: 2)
64
+ # Accepts:
65
+ # function({a: 1,
66
+ # b: 2})
67
+ # ignore_implicit - Ignore only implicit hashes.
68
+ # Accepts:
69
+ # function(a: 1,
70
+ # b: 2)
71
+ # Registers an offense for:
72
+ # function({a: 1,
73
+ # b: 2})
74
+ # ignore_explicit - Ignore only explicit hashes.
75
+ # Accepts:
76
+ # function({a: 1,
77
+ # b: 2})
78
+ # Registers an offense for:
79
+ # function(a: 1,
80
+ # b: 2)
81
+ EnforcedLastArgumentHashStyle: always_inspect
82
+ SupportedLastArgumentHashStyles:
83
+ - always_inspect
84
+ - always_ignore
85
+ - ignore_implicit
86
+ - ignore_explicit
87
+
88
+ Style/AlignParameters:
89
+ # Alignment of parameters in multi-line method calls.
90
+ #
91
+ # The `with_first_parameter` style aligns the following lines along the same column
92
+ # as the first parameter.
93
+ #
94
+ # method_call(a,
95
+ # b)
96
+ #
97
+ # The `with_fixed_indentation` style aligns the following lines with one
98
+ # level of indentation relative to the start of the line with the method call.
99
+ #
100
+ # method_call(a,
101
+ # b)
102
+ EnforcedStyle: with_first_parameter
103
+ SupportedStyles:
104
+ - with_first_parameter
105
+ - with_fixed_indentation
106
+
107
+ Metrics/BlockNesting:
108
+ Max: 3
109
+
110
+ Style/BracesAroundHashParameters:
111
+ EnforcedStyle: no_braces
112
+ SupportedStyles:
113
+ - braces
114
+ - no_braces
115
+
116
+ # Indentation of `when`.
117
+ Style/CaseIndentation:
118
+ IndentWhenRelativeTo: case
119
+ SupportedStyles:
120
+ - case
121
+ - end
122
+ IndentOneStep: false
123
+
124
+ Style/ClassAndModuleChildren:
125
+ # Checks the style of children definitions at classes and modules.
126
+ #
127
+ # Basically there are two different styles:
128
+ #
129
+ # `nested` - have each child on a separate line
130
+ # class Foo
131
+ # class Bar
132
+ # end
133
+ # end
134
+ #
135
+ # `compact` - combine definitions as much as possible
136
+ # class Foo::Bar
137
+ # end
138
+ #
139
+ # The compact style is only forced, for classes / modules with one child.
140
+ Enabled: false
141
+ EnforcedStyle: compact
142
+ SupportedStyles:
143
+ - nested
144
+ - compact
145
+
146
+ Style/ClassCheck:
147
+ EnforcedStyle: is_a?
148
+ SupportedStyles:
149
+ - is_a?
150
+ - kind_of?
151
+
152
+ Metrics/ClassLength:
153
+ CountComments: false # count full line comments?
154
+ Max: 100
155
+
156
+ # Align with the style guide.
157
+ Style/CollectionMethods:
158
+ # Mapping from undesired method to desired_method
159
+ # e.g. to use `detect` over `find`:
160
+ #
161
+ # CollectionMethods:
162
+ # PreferredMethods:
163
+ # find: detect
164
+ PreferredMethods:
165
+ collect: 'map'
166
+ collect!: 'map!'
167
+ inject: 'reduce'
168
+ detect: 'find'
169
+ find_all: 'select'
170
+
171
+ # Checks formatting of special comments
172
+ Style/CommentAnnotation:
173
+ Keywords:
174
+ - TODO
175
+ - FIXME
176
+ - OPTIMIZE
177
+ - HACK
178
+ - REVIEW
179
+
180
+ # Avoid complex methods.
181
+ Metrics/CyclomaticComplexity:
182
+ Max: 7
183
+
184
+ # Multi-line method chaining should be done with leading dots.
185
+ Style/DotPosition:
186
+ EnforcedStyle: leading
187
+ SupportedStyles:
188
+ - leading
189
+ - trailing
190
+
191
+ # Use empty lines between defs.
192
+ Style/EmptyLineBetweenDefs:
193
+ # If true, this parameter means that single line method definitions don't
194
+ # need an empty line between them.
195
+ AllowAdjacentOneLineDefs: false
196
+
197
+ # Checks whether the source file has a utf-8 encoding comment or not
198
+ Style/Encoding:
199
+ EnforcedStyle: always
200
+ SupportedStyles:
201
+ - when_needed
202
+ - always
203
+
204
+ Style/FileName:
205
+ Exclude:
206
+ - '**/Rakefile'
207
+ - '**/Gemfile'
208
+ - '**/Capfile'
209
+
210
+ # Checks use of for or each in multiline loops.
211
+ Style/For:
212
+ EnforcedStyle: each
213
+ SupportedStyles:
214
+ - for
215
+ - each
216
+
217
+ # Enforce the method used for string formatting.
218
+ Style/FormatString:
219
+ EnforcedStyle: format
220
+ SupportedStyles:
221
+ - format
222
+ - sprintf
223
+ - percent
224
+
225
+ # Built-in global variables are allowed by default.
226
+ Style/GlobalVars:
227
+ AllowedVariables: []
228
+
229
+ # `MinBodyLength` defines the number of lines of the a body of an if / unless
230
+ # needs to have to trigger this cop
231
+ Style/GuardClause:
232
+ MinBodyLength: 1
233
+
234
+ Style/HashSyntax:
235
+ EnforcedStyle: ruby19
236
+ SupportedStyles:
237
+ - ruby19
238
+ - hash_rockets
239
+
240
+ Style/IfUnlessModifier:
241
+ MaxLineLength: 80
242
+
243
+ # Checks the indentation of the first key in a hash literal.
244
+ Style/IndentHash:
245
+ # The value `special_inside_parentheses` means that hash literals with braces
246
+ # that have their opening brace on the same line as a surrounding opening
247
+ # round parenthesis, shall have their first key indented relative to the
248
+ # first position inside the parenthesis.
249
+ # The value `consistent` means that the indentation of the first key shall
250
+ # always be relative to the first position of the line where the opening
251
+ # brace is.
252
+ EnforcedStyle: special_inside_parentheses
253
+ SupportedStyles:
254
+ - special_inside_parentheses
255
+ - consistent
256
+
257
+ Style/LambdaCall:
258
+ EnforcedStyle: call
259
+ SupportedStyles:
260
+ - call
261
+ - braces
262
+
263
+ Metrics/LineLength:
264
+ Max: 80
265
+ AllowURI: true
266
+
267
+ Style/Next:
268
+ # With `always` all conditions at the end of an iteration needs to be
269
+ # replace by next - with `skip_modifier_ifs` the modifier if like this one
270
+ # are ignored: [1, 2].each { |a| return 'yes' if a == 1 }
271
+ EnforcedStyle: skip_modifier_ifs
272
+ SupportedStyles:
273
+ - skip_modifier_ifs
274
+ - always
275
+
276
+ Style/NonNilCheck:
277
+ # With `IncludeSemanticChanges` set to `true`, this cop reports offenses for
278
+ # `!x.nil?` and autocorrects that and `x != nil` to solely `x`, which is
279
+ # **usually** OK, but might change behavior.
280
+ #
281
+ # With `IncludeSemanticChanges` set to `false`, this cop does not report
282
+ # offenses for `!x.nil?` and does no changes that might change behavior.
283
+ IncludeSemanticChanges: false
284
+
285
+ Style/MethodDefParentheses:
286
+ EnforcedStyle: require_parentheses
287
+ SupportedStyles:
288
+ - require_parentheses
289
+ - require_no_parentheses
290
+
291
+ Metrics/MethodLength:
292
+ CountComments: false # count full line comments?
293
+ Max: 10
294
+ Exclude:
295
+ - 'db/migrate/*.rb'
296
+
297
+ Style/MethodName:
298
+ EnforcedStyle: snake_case
299
+ SupportedStyles:
300
+ - snake_case
301
+ - camelCase
302
+
303
+ Style/NumericLiterals:
304
+ MinDigits: 5
305
+
306
+ Metrics/ParameterLists:
307
+ Max: 5
308
+ CountKeywordArgs: true
309
+
310
+ # Allow safe assignment in conditions.
311
+ Style/ParenthesesAroundCondition:
312
+ AllowSafeAssignment: true
313
+
314
+ Style/PercentLiteralDelimiters:
315
+ PreferredDelimiters:
316
+ '%': ()
317
+ '%i': ()
318
+ '%q': ()
319
+ '%Q': ()
320
+ '%r': '{}'
321
+ '%s': ()
322
+ '%w': ()
323
+ '%W': ()
324
+ '%x': ()
325
+
326
+ Style/PredicateName:
327
+ NamePrefixBlacklist:
328
+ - is_
329
+ - has_
330
+ - have_
331
+
332
+ Style/RaiseArgs:
333
+ EnforcedStyle: exploded
334
+ SupportedStyles:
335
+ - compact # raise Exception.new(msg)
336
+ - exploded # raise Exception, msg
337
+
338
+
339
+ Style/RedundantReturn:
340
+ # When true allows code like `return x, y`.
341
+ AllowMultipleReturnValues: false
342
+
343
+ Style/Semicolon:
344
+ # Allow ; to separate several expressions on the same line.
345
+ AllowAsExpressionSeparator: false
346
+
347
+ Style/SignalException:
348
+ EnforcedStyle: semantic
349
+ SupportedStyles:
350
+ - only_raise
351
+ - only_fail
352
+ - semantic
353
+
354
+
355
+ Style/SingleLineBlockParams:
356
+ Methods:
357
+ - reduce:
358
+ - a
359
+ - e
360
+ - inject:
361
+ - a
362
+ - e
363
+
364
+ Style/SingleLineMethods:
365
+ AllowIfMethodIsEmpty: true
366
+
367
+ Style/StringLiterals:
368
+ EnforcedStyle: single_quotes
369
+ SupportedStyles:
370
+ - single_quotes
371
+ - double_quotes
372
+
373
+ Style/SpaceAroundEqualsInParameterDefault:
374
+ EnforcedStyle: space
375
+ SupportedStyles:
376
+ - space
377
+ - no_space
378
+
379
+ Style/SpaceBeforeBlockBraces:
380
+ EnforcedStyle: space
381
+ SupportedStyles:
382
+ - space
383
+ - no_space
384
+
385
+ Style/SpaceInsideBlockBraces:
386
+ EnforcedStyle: space
387
+ SupportedStyles:
388
+ - space
389
+ - no_space
390
+ # Valid values are: space, no_space
391
+ EnforcedStyleForEmptyBraces: no_space
392
+ # Space between { and |. Overrides EnforcedStyle if there is a conflict.
393
+ SpaceBeforeBlockParameters: true
394
+
395
+ Style/SpaceInsideHashLiteralBraces:
396
+ EnforcedStyle: space
397
+ EnforcedStyleForEmptyBraces: no_space
398
+ SupportedStyles:
399
+ - space
400
+ - no_space
401
+
402
+ Style/TrailingBlankLines:
403
+ EnforcedStyle: final_newline
404
+ SupportedStyles:
405
+ - final_newline
406
+ - final_blank_line
407
+
408
+ # TrivialAccessors doesn't require exact name matches and doesn't allow
409
+ # predicated methods by default.
410
+ Style/TrivialAccessors:
411
+ ExactNameMatch: false
412
+ AllowPredicates: false
413
+ # Allows trivial writers that don't end in an equal sign. e.g.
414
+ #
415
+ # def on_exception(action)
416
+ # @on_exception=action
417
+ # end
418
+ # on_exception :restart
419
+ #
420
+ # Commonly used in DSLs
421
+ AllowDSLWriters: false
422
+ Whitelist:
423
+ - to_ary
424
+ - to_a
425
+ - to_c
426
+ - to_enum
427
+ - to_h
428
+ - to_hash
429
+ - to_i
430
+ - to_int
431
+ - to_io
432
+ - to_open
433
+ - to_path
434
+ - to_proc
435
+ - to_r
436
+ - to_regexp
437
+ - to_str
438
+ - to_s
439
+ - to_sym
440
+
441
+ Style/VariableName:
442
+ EnforcedStyle: snake_case
443
+ SupportedStyles:
444
+ - snake_case
445
+ - camelCase
446
+
447
+ Style/WhileUntilModifier:
448
+ MaxLineLength: 80
449
+
450
+ Style/WordArray:
451
+ MinSize: 0
452
+
453
+ ##################### Lint ##################################
454
+
455
+ # Allow safe assignment in conditions.
456
+ Lint/AssignmentInCondition:
457
+ AllowSafeAssignment: true
458
+
459
+ # Align ends correctly.
460
+ Lint/EndAlignment:
461
+ # The value `keyword` means that `end` should be aligned with the matching
462
+ # keyword (if, while, etc.).
463
+ # The value `variable` means that in assignments, `end` should be aligned
464
+ # with the start of the variable on the left hand side of `=`. In all other
465
+ # situations, `end` should still be aligned with the keyword.
466
+ AlignWith: keyword
467
+ SupportedStyles:
468
+ - keyword
469
+ - variable
470
+
471
+ Lint/DefEndAlignment:
472
+ # The value `def` means that `end` should be aligned with the def keyword.
473
+ # The value `start_of_line` means that `end` should be aligned with method
474
+ # calls like `private`, `public`, etc, if present in front of the `def`
475
+ # keyword on the same line.
476
+ AlignWith: start_of_line
477
+ SupportedStyles:
478
+ - start_of_line
479
+ - def
480
+
481
+ LineLength:
482
+ Max: 150
483
+
484
+ Documentation:
485
+ Enabled: false
486
+
487
+ EndOfLine:
488
+ Enabled: false
489
+
490
+ Metrics/AbcSize:
491
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.2.2
5
+ script:
6
+ - bundle exec rubocop -Dc .rubocop.yml
7
+ - bundle exec rake test
8
+ deploy:
9
+ provider: rubygems
10
+ api_key:
11
+ secure: TfNKvb7Qzqch5zzbaXv3Qv0m4amPSMQI8rlucf5nW2YxdNBIxGAx9sFeD495V592bzgcnYoeew5OFPOSxVd5YeT+VVu43cJv5gvI+Eouro7r4AKTuJjLcMRvmZvhy5RZrPhph1QwbDHbEcGTpqKcbXka8aURKgsnZzdsf66/iezp30/+q3gkWvUz94b4bBkcHMELsnwvlUX5XukVPP8Zh73YYzUy1Dh6UZiV/Reio2fQb+giSd1FafyptI5LXgmnNhnuLGwOajFgbEYr4ZFg8SfvlQ1SKZzHvH3tsFNUrCcsklowcbks/G/Hb/lOjZpqQH9M4BFrVwYNDEGN8pkmB6mBO56nK6JW4qJwh38MpAhhrLScy8IJw+D9tajkGMyjezb2svqWvMWQ6bdyBeeEsVF51oBqIXD/tadIAVY41ngCvcFw+zzi9568W27CL/9idFHi7rSB1Q/RLNIF2/uT0lt4eOk8Gr8Zyh/YImb3ktXSu4JwWQEfj4m+xugFXL1F/XkkOi/cSNq1tBf2J2LUCe+DxCogGWz5LbiY/aU1sfM4TJmp6awPP6JBv+qujJvVCJblKX/HIhIkfW0o1q1e639JYMTq/6DlIJQk+D9hXzY4X/lrsLlH7zYRie+HCxgxSg1tKTiZkfmohY3GOLLy8W/LfaR/ma622MLWjIIyUVM=
12
+ gem: kdtree
13
+ on:
14
+ tags: true
15
+ repo: aaronFranssell/kdtree
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kdtree.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ [![Build Status](https://travis-ci.org/aaronFranssell/kdtree.svg?branch=master)](https://travis-ci.org/aaronFranssell/kdtree)
2
+ [![Code Climate](https://codeclimate.com/github/aaronFranssell/kdtree/badges/gpa.svg)](https://codeclimate.com/github/aaronFranssell/kdtree)
3
+ [![Test Coverage](https://codeclimate.com/github/aaronFranssell/kdtree/badges/coverage.svg)](https://codeclimate.com/github/aaronFranssell/kdtree/coverage)
4
+
5
+ # Kd-tree
6
+
7
+ My Ruby implementation of a [Kd-tree](https://en.wikipedia.org/wiki/K-d_tree). Kd-trees allow for fast nearest-neighbor searches. This implementation will also allow the Kd-tree to be cached using ```Rails.cache``` methods. For now, this only supports 2d latitude/longitude searches.
8
+
9
+ ## Getting Started
10
+
11
+ Add the gem:
12
+
13
+ ```ruby
14
+ gem 'cacheable_kdtree'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ A Kd-tree is made up of multiple nodes. A single node contains the data associated with the latitude/longitude:
20
+
21
+ ```ruby
22
+ Kdtree::LatitudeLongitudeNode.new(your_data_here, latitude_of_your_data, longitude_of_your_data)
23
+ ```
24
+
25
+ Once you have an array of nodes, you can create a Kd-tree:
26
+ ```ruby
27
+ nodes = [Kdtree::LatitudeLongitudeNode.new(...), Kdtree::LatitudeLongitudeNode.new(...)]
28
+ my_tree = Kdtree::LatitudeLongitudeTree.new(nodes)
29
+ ```
30
+
31
+ You can query your tree and return the closest nodes:
32
+ ```ruby
33
+ # The 4th parameter may be :miles or :kilometers
34
+ all_my_nodes = my_tree.closest(my_lat, my_long, distance, :kilometers)
35
+ ```
36
+
37
+ ## Contributing
38
+
39
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aaronFranssell/kdtree.
40
+
41
+ Please make sure all tests pass and that [Rubocop](https://github.com/bbatsov/rubocop) is happy:
42
+ ```ruby
43
+ rake test
44
+ rubocop -Dac .rubocop.yml
45
+ ```
46
+
47
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'kdtree'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/kdtree.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kdtree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'cacheable_kdtree'
8
+ spec.version = Kdtree::VERSION
9
+ spec.authors = ['Aaron Franssell']
10
+ spec.email = ['aaron.franssell@gmail.com']
11
+
12
+ spec.summary = 'My implementation of a 2d k-d tree'
13
+ spec.homepage = 'https://github.com/aaronFranssell/kdtree'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)/) }
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(/^exe/) { |f| File.basename(f) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.10'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'minitest'
23
+ spec.add_development_dependency 'rubocop', '0.38.0'
24
+ spec.add_development_dependency 'simplecov', '~> 0.10.0'
25
+ spec.add_development_dependency 'simplecov-rcov', '~> 0.2.3'
26
+ spec.add_development_dependency 'mocha', '~> 1.1.0'
27
+ end
@@ -0,0 +1,33 @@
1
+ class Kdtree::LatitudeLongitudeNode
2
+ attr_accessor :left, :right, :data, :latitude, :longitude, :region
3
+
4
+ def initialize(node_data, node_lat, node_long)
5
+ @data = node_data
6
+ @latitude = node_lat
7
+ @longitude = node_long
8
+ end
9
+
10
+ def to_s
11
+ "region: #{region ? region.to_s : region}"
12
+ end
13
+
14
+ def self.create_or_merge_regions(n1, n2)
15
+ return Kdtree::LatitudeLongitudeRegion.new(n1.latitude, n1.longitude, n2.latitude, n2.longitude) if n1.region.nil? && n2.region.nil?
16
+ return n1.region.merge_point(n2.latitude, n2.longitude) if n1.region && n2.region.nil?
17
+ return n2.region.merge_point(n1.latitude, n1.longitude) if n2.region && n1.region.nil?
18
+ n1.region.merge_region(n2.region)
19
+ end
20
+
21
+ def self.sort_node_list(node_list, depth)
22
+ use_latitude?(depth) ? node_list.sort_by(&:latitude) : node_list.sort_by(&:longitude)
23
+ end
24
+
25
+ def self.partition_node_list(node_list)
26
+ midpoint = node_list.length / 2
27
+ [node_list.slice(0, node_list.length / 2), node_list[midpoint], node_list.slice(midpoint + 1..-1)]
28
+ end
29
+
30
+ def self.use_latitude?(depth)
31
+ depth.even?
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ class Kdtree::LatitudeLongitudeRegion
2
+ attr_accessor :max_latitude, :max_longitude, :min_latitude, :min_longitude
3
+ def initialize(lat1, long1, lat2, long2)
4
+ @min_latitude, @max_latitude = [lat1, lat2].minmax
5
+ @min_longitude, @max_longitude = [long1, long2].minmax
6
+ end
7
+
8
+ def merge_point(lat, long)
9
+ new_min_lat, new_max_lat = [lat, @min_latitude, @max_latitude].minmax
10
+ new_min_long, new_max_long = [long, @min_longitude, @max_longitude].minmax
11
+ self.class.new(new_min_lat, new_min_long, new_max_lat, new_max_long)
12
+ end
13
+
14
+ def merge_region(region)
15
+ new_min_lat, new_max_lat = [region.min_latitude, region.max_latitude, @min_latitude, @max_latitude].minmax
16
+ new_min_long, new_max_long = [region.min_longitude, region.max_longitude, @min_longitude, @max_longitude].minmax
17
+ self.class.new(new_min_lat, new_min_long, new_max_lat, new_max_long)
18
+ end
19
+
20
+ def point_in_region?(latitude, longitude)
21
+ latitude.between?(min_latitude, max_latitude) && longitude.between?(min_longitude, max_longitude)
22
+ end
23
+
24
+ def to_s
25
+ "min lat/long: (#{min_latitude}, #{min_longitude}) max lat/long (#{max_latitude}, #{max_longitude})"
26
+ end
27
+
28
+ def self.regions_intersect?(region1, region2)
29
+ region1.min_latitude <= region2.max_latitude && region1.max_latitude >= region2.min_latitude &&
30
+ region1.min_longitude <= region2.max_longitude && region1.max_longitude >= region2.min_longitude
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ class Kdtree::LatitudeLongitudeTree
2
+ attr_accessor :root
3
+
4
+ def initialize(node_list)
5
+ @root = create_tree(node_list)
6
+ find_regions(@root)
7
+ end
8
+
9
+ def closest(lat, long, distance, units = :miles)
10
+ fail 'Input must be numeric.' unless lat.is_a?(Numeric) && long.is_a?(Numeric) && distance.is_a?(Numeric)
11
+ fail 'Units must be either :kilometers or :miles.' unless %i(miles kilometers).include?(units)
12
+ bounding_box = units == :miles ? Kdtree::Util.bounding_box_miles(lat, long, distance) : Kdtree::Util.bounding_box_kilometers(lat, long, distance)
13
+ nearest_nodes(bounding_box)
14
+ end
15
+
16
+ private
17
+
18
+ def nearest_nodes(bounding_box, node = @root, result = [])
19
+ return result if node.nil?
20
+ result << node.data if bounding_box.point_in_region?(node.latitude, node.longitude)
21
+ nearest_nodes(bounding_box, node.left, result) if search_child?(node.left, bounding_box)
22
+ nearest_nodes(bounding_box, node.right, result) if search_child?(node.right, bounding_box)
23
+ result
24
+ end
25
+
26
+ def search_child?(child, bounding_box)
27
+ child &&
28
+ (bounding_box.point_in_region?(child.latitude, child.longitude) ||
29
+ (child.region && Kdtree::LatitudeLongitudeRegion.regions_intersect?(child.region, bounding_box)))
30
+ end
31
+
32
+ def find_regions(node)
33
+ return unless node
34
+ left_region = find_regions(node.left)
35
+ left_region = Kdtree::LatitudeLongitudeNode.create_or_merge_regions(node, node.left) if node.left
36
+ right_region = find_regions(node.right)
37
+ right_region = Kdtree::LatitudeLongitudeNode.create_or_merge_regions(node, node.right) if node.right
38
+ node.region = merge_regions(left_region, right_region)
39
+ node.region
40
+ end
41
+
42
+ def merge_regions(region1, region2)
43
+ return region1 unless region2
44
+ return region2 unless region1
45
+ region1.merge_region(region2)
46
+ end
47
+
48
+ def create_tree(node_list, depth = 0)
49
+ return if node_list.empty?
50
+ return node_list.first if node_list.length == 1
51
+ sorted_list = Kdtree::LatitudeLongitudeNode.sort_node_list(node_list, depth)
52
+ lower_half, midpoint, greater_half = Kdtree::LatitudeLongitudeNode.partition_node_list(sorted_list)
53
+ midpoint.left = create_tree(lower_half, depth + 1)
54
+ midpoint.right = create_tree(greater_half, depth + 1)
55
+ midpoint
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ class Kdtree::Util
2
+ EARTH_MILES = 3959.0
3
+ EARTH_KILOMETERS = 6371.0
4
+
5
+ # I pulled the algorithm for the bounding_box calculation from here:
6
+ # http://stackoverflow.com/questions/1689096/calculating-bounding-box-a-certain-distance-away-from-a-lat-long-coordinate-in-j
7
+
8
+ def self.bounding_box_miles(lat, long, distance_in_miles)
9
+ bounding_box(lat, long, distance_in_miles, EARTH_MILES)
10
+ end
11
+
12
+ def self.bounding_box_kilometers(lat, long, distance_in_miles)
13
+ bounding_box(lat, long, distance_in_miles, EARTH_MILES)
14
+ end
15
+
16
+ def self.bounding_box(lat, long, radius_distance, sphere_radius)
17
+ lat_amount = radians_to_degrees(radius_distance / sphere_radius)
18
+ lat1 = lat + lat_amount
19
+ lat2 = lat - lat_amount
20
+ long_amount = radians_to_degrees(radius_distance / sphere_radius / Math.cos(degrees_to_radians(lat)))
21
+ long1 = long - long_amount
22
+ long2 = long + long_amount
23
+ Kdtree::LatitudeLongitudeRegion.new(lat1, long1, lat2, long2)
24
+ end
25
+
26
+ def self.degrees_to_radians(degrees)
27
+ degrees * 3.1416 / 180
28
+ end
29
+
30
+ def self.radians_to_degrees(radians)
31
+ radians * 180 / 3.1416
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Kdtree
2
+ VERSION = '1.0.0'.freeze
3
+ end
data/lib/kdtree.rb ADDED
@@ -0,0 +1,7 @@
1
+ %w(
2
+ version
3
+ latitude_longitude_node
4
+ latitude_longitude_region
5
+ latitude_longitude_tree
6
+ util
7
+ ).each { |name| require "kdtree/#{name}" }
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cacheable_kdtree
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Franssell
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.38.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.38.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov-rcov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: mocha
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.1.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.1.0
111
+ description:
112
+ email:
113
+ - aaron.franssell@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rubocop.yml"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - README.md
123
+ - Rakefile
124
+ - bin/console
125
+ - bin/setup
126
+ - kdtree.gemspec
127
+ - lib/kdtree.rb
128
+ - lib/kdtree/latitude_longitude_node.rb
129
+ - lib/kdtree/latitude_longitude_region.rb
130
+ - lib/kdtree/latitude_longitude_tree.rb
131
+ - lib/kdtree/util.rb
132
+ - lib/kdtree/version.rb
133
+ homepage: https://github.com/aaronFranssell/kdtree
134
+ licenses: []
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.4.7
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: My implementation of a 2d k-d tree
156
+ test_files: []