cacheable_kdtree 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []