geokdtree 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,135 @@
1
+ /*
2
+ This file is part of ``kdtree'', a library for working with kd-trees.
3
+ Copyright (C) 2007-2011 John Tsiombikas <nuclear@member.fsf.org>
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+ 3. The name of the author may not be used to endorse or promote products
14
+ derived from this software without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ OF SUCH DAMAGE.
26
+ */
27
+
28
+ /*
29
+ Feb 2013
30
+ included in the ruby-ffi-kdtree gem by Colin Surprenant <colin.surprenant@gmail.com>
31
+ original source code from https://code.google.com/p/kdtree/
32
+ using version 0.5.6 dated Nov 2011
33
+ */
34
+
35
+ #ifndef _KDTREE_H_
36
+ #define _KDTREE_H_
37
+
38
+ #ifdef __cplusplus
39
+ extern "C" {
40
+ #endif
41
+
42
+ struct kdtree;
43
+ struct kdres;
44
+
45
+ #define GEO_UNITS_MI 0
46
+ #define GEO_UNITS_KM 1
47
+
48
+ /* create a kd-tree for "k"-dimensional data */
49
+ struct kdtree *kd_create(int k);
50
+
51
+ /* free the struct kdtree */
52
+ void kd_free(struct kdtree *tree);
53
+
54
+ /* remove all the elements from the tree */
55
+ void kd_clear(struct kdtree *tree);
56
+
57
+ /* if called with non-null 2nd argument, the function provided
58
+ * will be called on data pointers (see kd_insert) when nodes
59
+ * are to be removed from the tree.
60
+ */
61
+ void kd_data_destructor(struct kdtree *tree, void (*destr)(void*));
62
+
63
+ /* insert a node, specifying its position, and optional data */
64
+ int kd_insert(struct kdtree *tree, const double *pos, void *data);
65
+ int kd_insert2(struct kdtree *tree, double x, double y, void *data);
66
+ int kd_insert3(struct kdtree *tree, double x, double y, double z, void *data);
67
+
68
+ /* Find the nearest node from a given point.
69
+ *
70
+ * This function returns a pointer to a result set with at most one element.
71
+ */
72
+ struct kdres *kd_nearest(struct kdtree *tree, const double *pos);
73
+ struct kdres *kd_nearest2(struct kdtree *tree, double x, double y);
74
+ struct kdres *kd_nearest3(struct kdtree *tree, double x, double y, double z);
75
+
76
+ /* Find the N nearest nodes from a given point.
77
+ *
78
+ * This function returns a pointer to a result set, with at most N elements,
79
+ * which can be manipulated with the kd_res_* functions.
80
+ * The returned pointer can be null as an indication of an error. Otherwise
81
+ * a valid result set is always returned which may contain 0 or more elements.
82
+ * The result set must be deallocated with kd_res_free after use.
83
+ */
84
+ /*
85
+ struct kdres *kd_nearest_n(struct kdtree *tree, const double *pos, int num);
86
+ struct kdres *kd_nearest_nf(struct kdtree *tree, const float *pos, int num);
87
+ struct kdres *kd_nearest_n3(struct kdtree *tree, double x, double y, double z);
88
+ struct kdres *kd_nearest_n3f(struct kdtree *tree, float x, float y, float z);
89
+ */
90
+
91
+ /* Find any nearest nodes from a given point within a range.
92
+ *
93
+ * This function returns a pointer to a result set, which can be manipulated
94
+ * by the kd_res_* functions.
95
+ * The returned pointer can be null as an indication of an error. Otherwise
96
+ * a valid result set is always returned which may contain 0 or more elements.
97
+ * The result set must be deallocated with kd_res_free after use.
98
+ */
99
+ struct kdres *kd_nearest_range(struct kdtree *tree, const double *pos, const double range);
100
+ struct kdres *kd_nearest_range2(struct kdtree *tree, double x, double y, double range);
101
+ struct kdres *kd_nearest_range3(struct kdtree *tree, double x, double y, double z, double range);
102
+ struct kdres *kd_nearest_geo_range(struct kdtree *kd, double lat, double lng, double range, int units);
103
+
104
+ /* frees a result set returned by kd_nearest_range() */
105
+ void kd_res_free(struct kdres *set);
106
+
107
+ /* returns the size of the result set (in elements) */
108
+ int kd_res_size(struct kdres *set);
109
+
110
+ /* rewinds the result set iterator */
111
+ void kd_res_rewind(struct kdres *set);
112
+
113
+ /* returns non-zero if the set iterator reached the end after the last element */
114
+ int kd_res_end(struct kdres *set);
115
+
116
+ /* advances the result set iterator, returns non-zero on success, zero if
117
+ * there are no more elements in the result set.
118
+ */
119
+ int kd_res_next(struct kdres *set);
120
+
121
+ /* returns the data pointer (can be null) of the current result set item
122
+ * and optionally sets its position to the pointers(s) if not null.
123
+ */
124
+ void *kd_res_item(struct kdres *set, double *pos);
125
+ void *kd_res_item3(struct kdres *set, double *x, double *y, double *z);
126
+
127
+ /* equivalent to kd_res_item(set, 0) */
128
+ void *kd_res_item_data(struct kdres *set);
129
+
130
+
131
+ #ifdef __cplusplus
132
+ }
133
+ #endif
134
+
135
+ #endif /* _KDTREE_H_ */
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'geokdtree/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "geokdtree"
8
+ gem.version = Geokdtree::VERSION
9
+ gem.authors = ["Colin Surprenant"]
10
+ gem.email = ["colin.surprenant@gmail.com"]
11
+ gem.description = "Ruby & JRuby gem with a fast k-d tree C implementation using FFI bindings with support for latitude/longitude and geo distance range search"
12
+ gem.summary = "Ruby & JRuby FFI gem with a fast k-d tree C implementation with geo support"
13
+ gem.homepage = "http://github.com/colinsurprenant/geokdtree"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.extensions = ["ext/geokdtree/extconf.rb"]
20
+
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_runtime_dependency "ffi"
23
+ end
@@ -0,0 +1,13 @@
1
+ require 'ffi'
2
+
3
+ module Geokdtree
4
+ module C
5
+ extend FFI::Library
6
+
7
+ ffi_lib File.dirname(__FILE__) + "/" + (FFI::Platform.mac? ? "geokdtree.bundle" : FFI.map_library_name("geokdtree"))
8
+ end
9
+ end
10
+
11
+ require 'geokdtree/version'
12
+ require 'geokdtree/tree_ffi'
13
+ require 'geokdtree/tree'
@@ -0,0 +1,139 @@
1
+ module Geokdtree
2
+
3
+ ResultPoint = Struct.new(:point, :data)
4
+
5
+ RADIUS_MI = 3958.760
6
+ RADIUS_KM = 6371.0
7
+ KM_PER_MI = 1.609
8
+
9
+ GEO_UNITS_MI = 0
10
+ GEO_UNITS_KM = 1
11
+
12
+ class KdtreePointer < FFI::AutoPointer
13
+
14
+ def self.release(ptr)
15
+ # ptr is the original pointer you passed in to create VectorPointer
16
+ C::kd_free(ptr)
17
+ end
18
+ end
19
+
20
+ class Tree
21
+
22
+ def initialize(dimensions)
23
+ @dimensions = dimensions
24
+ @tree = KdtreePointer.new(C::kd_create(dimensions))
25
+ @data_cache = {}
26
+ @data_id = 0
27
+ end
28
+
29
+ # insert a point of n dimensions and arbritary associated object
30
+ # @param point [Array<Float>] array of n dimensions of numeric coordinates
31
+ # @param data [Object] arbritary object stored with point
32
+ # @return [Tree] self
33
+ def insert(point, data = nil)
34
+ # here we don't actually pass a pointer to our data but the object numeric key encoded into the pointer
35
+ r = case point.size
36
+ when 2 then C::kd_insert2(@tree, point[0].to_f, point[1].to_f, cache(data))
37
+ when 3 then C::kd_insert3(@tree, point[0].to_f, point[1].to_f, point[2].to_f, cache(data))
38
+ else C::kd_insert(@tree, Tree.allocate_doubles(point), cache(data))
39
+ end
40
+ raise("error on Tree#insert, code=#{r}") unless r.zero?
41
+ self
42
+ end
43
+
44
+ # find the given point nearest neighbor
45
+ # @param point [Array<Float>] array of n dimensions of numeric coordinates
46
+ # @return [ResultPoint] the nearest neighbor, nil if no neighbor
47
+ def nearest(point)
48
+ set = case point.size
49
+ when 2 then C::kd_nearest2(@tree, point[0].to_f, point[1].to_f)
50
+ when 3 then C::kd_nearest3(@tree, point[0].to_f, point[1].to_f, point[2].to_f)
51
+ else C::kd_nearest(@tree, Tree.allocate_doubles(point))
52
+ end
53
+ return nil if set.null?
54
+ retrieve_results(set).first
55
+ end
56
+
57
+ # find all the given point neighbors within the given Euclidean range
58
+ # @param point [Array<Float>] array of n dimensions of numeric coordinates
59
+ # @param range [Float] range units expressed as a Euclidean distance
60
+ # @return [Array<ResultPoint>] the point neighbors within range, [] if none
61
+ def nearest_range(point, range)
62
+ set = case point.size
63
+ when 2 then C::kd_nearest_range2(@tree, point[0].to_f, point[1].to_f, range.to_f)
64
+ when 3 then C::kd_nearest_range3(@tree, point[0].to_f, point[1].to_f, point[2].to_f, range.to_f)
65
+ else C::kd_nearest_range(@tree, Tree.allocate_doubles(point), range.to_f)
66
+ end
67
+ return [] if set.null?
68
+ retrieve_results(set)
69
+ end
70
+
71
+ # find all the given point neighbors within the given geo range
72
+ # @param point [Array<Float>] [latitude, longitude] array
73
+ # @param range [Float] range units in miles or kms, default in miles
74
+ # @param units [Symbol] :mi or :km, default :mi
75
+ # @return [Array<ResultPoint>] the point neighbors within range, [] if none
76
+ def nearest_geo_range(point, range, units = :mi)
77
+ raise("must have 2 dimensions for geo methods") unless @dimensions == 2
78
+ set = C::kd_nearest_geo_range(@tree, point[0].to_f, point[1].to_f, range.to_f, units == :mi ? GEO_UNITS_MI : GEO_UNITS_KM)
79
+ return [] if set.null?
80
+ retrieve_results(set)
81
+ end
82
+
83
+ # compute the Euclidean distance between 2 points of n dimensions
84
+ # @param pointa [Array<Float>] array of n dimensions of numeric coordinates
85
+ # @param pointb [Array<Float>] array of n dimensions of numeric coordinates
86
+ # @return [Float] the Euclidean distance between the 2 points
87
+ def self.distance(pointa, pointb)
88
+ case k = pointa.size
89
+ when 2 then C::euclidean_distance2(pointa[0].to_f, pointa[1].to_f, pointb[0].to_f, pointb[1].to_f)
90
+ else C::euclidean_distance(allocate_doubles(pointa), allocate_doubles(pointb), k)
91
+ end
92
+ end
93
+
94
+ # compute the geo distance between 2 points of latitude, longitude
95
+ # @param pointa [Array<Float>] [latitude, longitude] array
96
+ # @param pointb [Array<Float>] [latitude, longitude] array
97
+ # @param units [Symbol] :mi or :km, default :mi
98
+ # @return [Float] the geo distance between the 2 points
99
+ def self.geo_distance(pointa, pointb, units = :mi)
100
+ C::slc_distance2(pointa[0].to_f, pointa[1].to_f, pointb[0].to_f, pointb[1].to_f, units == :mi ? RADIUS_MI : RADIUS_KM)
101
+ end
102
+
103
+ private
104
+
105
+ # store data in cache hash and return pointer encoded numeric object key
106
+ # @param data [Object] arbritary object
107
+ # @return [FFI::Pointer] pointer encoded numeric object key
108
+ def cache(data)
109
+ return nil unless data
110
+ @data_cache[@data_id += 1] = data
111
+ FFI::Pointer.new(@data_id)
112
+ end
113
+
114
+ # allocate managed memory pointer for given doubles array
115
+ # @param doubles [Array<Fixnum>] array doubles numbers
116
+ # @return [FFI::MemoryPointer] pointer to allocated & copied doubles
117
+ def self.allocate_doubles(doubles)
118
+ ptr = FFI::MemoryPointer.new(:double, doubles.size)
119
+ ptr.write_array_of_double(doubles)
120
+ ptr
121
+ end
122
+
123
+ # walk a result set and transpose into result array
124
+ # @param set [FFI::Pointer] the result set from tree search functions
125
+ # @return [Array<ResultPoint>] transposed result items
126
+ def retrieve_results(set)
127
+ results = []
128
+ while C::kd_res_end(set) == 0
129
+ point_ptr = FFI::MemoryPointer.new(:double, @dimensions) # to hold point result
130
+ data_ptr = C::kd_res_item(set, point_ptr) # fetch single result item
131
+ results << ResultPoint.new(point_ptr.read_array_of_double(@dimensions), data_ptr.null? ? nil : @data_cache[data_ptr.address])
132
+ C::kd_res_next(set)
133
+ end
134
+ C::kd_res_free(set)
135
+ results
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,92 @@
1
+ require 'ffi'
2
+
3
+ module Geokdtree::C
4
+ # /* create a kd-tree for "k"-dimensional data */
5
+ # struct kdtree *kd_create(int k);
6
+ attach_function "kd_create", "kd_create", [:int], :pointer
7
+
8
+ # /* free the struct kdtree */
9
+ # void kd_free(struct kdtree *tree);
10
+ attach_function "kd_free", "kd_free", [:pointer], :void
11
+
12
+ # /* remove all the elements from the tree */
13
+ # void kd_clear(struct kdtree *tree);
14
+ attach_function "kd_clear", "kd_clear", [:pointer], :void
15
+
16
+ # /* insert a node, specifying its position, and optional data */
17
+ # int kd_insert(struct kdtree *tree, const double *pos, void *data);
18
+ attach_function "kd_insert", "kd_insert", [:pointer, :pointer, :pointer], :int
19
+
20
+ # int kd_insert2(struct kdtree *tree, double x, double y, void *data);
21
+ attach_function "kd_insert2", "kd_insert2", [:pointer, :double, :double, :pointer], :int
22
+
23
+ # int kd_insert3(struct kdtree *tree, double x, double y, double z, void *data);
24
+ attach_function "kd_insert3", "kd_insert3", [:pointer, :double, :double, :double, :pointer], :int
25
+
26
+ # /* Find the nearest node from a given point.
27
+ # *
28
+ # * This function returns a pointer to a result set with at most one element.
29
+ # */
30
+ # struct kdres *kd_nearest(struct kdtree *tree, const double *pos);
31
+ attach_function "kd_nearest", "kd_nearest", [:pointer, :pointer], :pointer
32
+
33
+ # struct kdres *kd_nearest2(struct kdtree *tree, double x, double y);
34
+ attach_function "kd_nearest2", "kd_nearest2", [:pointer, :double, :double], :pointer
35
+
36
+ # struct kdres *kd_nearest2(struct kdtree *tree, double x, double y, double z);
37
+ attach_function "kd_nearest3", "kd_nearest3", [:pointer, :double, :double, :double], :pointer
38
+
39
+ # /* Find any nearest nodes from a given point within a range.
40
+ # *
41
+ # * This function returns a pointer to a result set, which can be manipulated
42
+ # * by the kd_res_* functions.
43
+ # * The returned pointer can be null as an indication of an error. Otherwise
44
+ # * a valid result set is always returned which may contain 0 or more elements.
45
+ # * The result set must be deallocated with kd_res_free after use.
46
+ # */
47
+ # struct kdres *kd_nearest_range(struct kdtree *tree, const double *pos, double range);
48
+ attach_function "kd_nearest_range", "kd_nearest_range", [:pointer, :pointer, :double], :pointer
49
+
50
+ # struct kdres *kd_nearest_range2(struct kdtree *tree, double x, double y, double range);
51
+ attach_function "kd_nearest_range2", "kd_nearest_range2", [:pointer, :double, :double, :double], :pointer
52
+
53
+ # struct kdres *kd_nearest_range3(struct kdtree *tree, double x, double y, double z, double range);
54
+ attach_function "kd_nearest_range3", "kd_nearest_range3", [:pointer, :double, :double, :double, :double], :pointer
55
+
56
+ # struct kdres *kd_nearest_geo_range(struct kdtree *kd, double lat, double lng, double range, int units)
57
+ attach_function "kd_nearest_geo_range", "kd_nearest_geo_range", [:pointer, :double, :double, :double, :int], :pointer
58
+
59
+ # void *kd_res_item(struct kdres *set, double *pos);
60
+ attach_function "kd_res_item", "kd_res_item", [:pointer, :pointer], :pointer
61
+
62
+ # /* frees a result set returned by kd_nearest_range() */
63
+ # void kd_res_free(struct kdres *set);
64
+ attach_function "kd_res_free", "kd_res_free", [:pointer], :void
65
+
66
+ # /* advances the result set iterator, returns non-zero on success, zero if
67
+ # * there are no more elements in the result set.
68
+ # */
69
+ # int kd_res_next(struct kdres *set);
70
+ attach_function "kd_res_next", "kd_res_next", [:pointer], :int
71
+
72
+ # /* returns non-zero if the set iterator reached the end after the last element */
73
+ # int kd_res_end(struct kdres *set);
74
+ attach_function "kd_res_end", "kd_res_end", [:pointer], :int
75
+
76
+ # /* return the Euclidean distance from two points in k dimensions */
77
+ # double euclidean_distance( double *a, double *b, int k );
78
+ attach_function "euclidean_distance", "euclidean_distance", [:pointer, :pointer, :int], :double
79
+
80
+ # /* return the Euclidean distance from two points in two dimensions */
81
+ # double euclidean_distance2(double ax, double ay, double bx, double by);
82
+ attach_function "euclidean_distance2", "euclidean_distance2", [:double, :double, :double, :double], :double
83
+
84
+ # /* return the geo distance from two lat/lng points using the spherical law of cosines */
85
+ # double slc_distance(double *a, double *b, double radius) {
86
+ attach_function "slc_distance", "slc_distance", [:pointer, :pointer, :double], :double
87
+
88
+ # /* return the geo distance from two lat/lng points using the spherical law of cosines */
89
+ # double slc_distance2(double lat1, double lng1, double lat2, double lng2, double radius) {
90
+ attach_function "slc_distance2", "slc_distance2", [:double, :double, :double, :double, :double], :double
91
+
92
+ end
@@ -0,0 +1,3 @@
1
+ module Geokdtree
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,209 @@
1
+ require 'rspec'
2
+ require 'geokdtree'
3
+
4
+ describe Geokdtree::Tree do
5
+
6
+ before do
7
+ @points = {
8
+ :top_left => [-1, -1],
9
+ :mid_upper_left => [-0.5, -0.5],
10
+ :top_right => [1, -1],
11
+ :mid_upper_right => [0.5, -0.5],
12
+ :center => [0, 0],
13
+ :mid_lower_left => [-0.5, 0.5],
14
+ :bottom_left => [-1, 1],
15
+ :mid_lower_right => [0.5, 0.5],
16
+ :bottom_right => [1, 1],
17
+ }
18
+
19
+ @places = {
20
+ :seattle => [47.6, -122.3],
21
+ :portland => [45.5, -122.8],
22
+ :new_york => [40.7, -74.0],
23
+ :los_angeles => [34.1, -118.2],
24
+ :montreal => [45.50, -73.55],
25
+ }
26
+ @insert_places = [:seattle, :portland, :new_york]
27
+ end
28
+
29
+ it "should initialize" do
30
+ tree = Geokdtree::Tree.new(2)
31
+ tree.should_not be_nil
32
+ end
33
+
34
+ it "should find neighbors in a 2D tree without data" do
35
+ # this spec inspired by https://github.com/sdeming/ffi-kdtree/blob/master/spec/find_nearest_spec.rb
36
+ tree = Geokdtree::Tree.new(2)
37
+ @points.each{|k, p| tree.insert(p)}
38
+
39
+ result = tree.nearest([0, 0])
40
+ result.point.should == [0, 0]
41
+ result.data.should be_nil
42
+
43
+ result = tree.nearest([0.3, 0.3])
44
+ result.point.should == [0.5, 0.5]
45
+ result.data.should be_nil
46
+
47
+ result = tree.nearest([-0.3, -0.3])
48
+ result.point.should == [-0.5, -0.5]
49
+ result.data.should be_nil
50
+
51
+ result = tree.nearest([0.1,0.1])
52
+ result.point.should == [0, 0]
53
+ result.data.should be_nil
54
+
55
+ results = tree.nearest_range([0, 0], 2)
56
+ results.size.should == 9
57
+
58
+ results = tree.nearest_range([0, 0], 1)
59
+ results.size.should == 5 # center + mids
60
+
61
+ tree.nearest_range([10, 10], 1).should == []
62
+ end
63
+
64
+ it "should find neighbors in a 2D tree using data" do
65
+ # this spec inspired by https://github.com/sdeming/ffi-kdtree/blob/master/spec/find_nearest_spec.rb
66
+ tree = Geokdtree::Tree.new(2)
67
+
68
+ @points.each{|k, p| tree.insert(p, k)}
69
+
70
+ result = tree.nearest([0, 0])
71
+ result.point.should == [0, 0]
72
+ result.data.should == :center
73
+
74
+ result = tree.nearest([0.3, 0.3])
75
+ result.point.should == [0.5, 0.5]
76
+ result.data.should == :mid_lower_right
77
+
78
+ result = tree.nearest([-0.3, -0.3])
79
+ result.point.should == [-0.5, -0.5]
80
+ result.data.should == :mid_upper_left
81
+
82
+ result = tree.nearest([0.1,0.1])
83
+ result.point.should == [0, 0]
84
+ result.data.should == :center
85
+
86
+ results = tree.nearest_range([0, 0], 2)
87
+ results.size.should == 9
88
+ results.map(&:data).sort.should == @points.to_a.map(&:first).sort
89
+
90
+ results = tree.nearest_range([0, 0], 1)
91
+ results.map(&:data).sort.should == [:mid_upper_left, :mid_upper_right, :center, :mid_lower_left, :mid_lower_right].sort
92
+
93
+ results = tree.nearest_range([10, 10], 1).should == []
94
+ end
95
+
96
+ it "should not find any neighbor in an empty tree" do
97
+ tree = Geokdtree::Tree.new(2)
98
+ tree.nearest([0, 0]).should be_nil
99
+ tree = Geokdtree::Tree.new(3)
100
+ tree.nearest([0, 0, 0]).should be_nil
101
+ end
102
+
103
+ it "should find nearest by latlong" do
104
+ tree = Geokdtree::Tree.new(2)
105
+ @insert_places.each{|p| tree.insert(@places[p], p)}
106
+
107
+ tree.nearest(@places[:los_angeles]).data.should == :portland
108
+ tree.nearest(@places[:montreal]).data.should == :new_york
109
+ end
110
+
111
+ it "should find nearest geo range by latlong" do
112
+ tree = Geokdtree::Tree.new(2)
113
+ @insert_places.each{|p| tree.insert(@places[p], p)}
114
+
115
+ tree.nearest_geo_range(@places[:los_angeles], 0).size.should == 0
116
+ tree.nearest_geo_range(@places[:los_angeles], 800).size.should == 0
117
+
118
+ tree.nearest_geo_range(@places[:los_angeles], 900).size.should == 1
119
+ tree.nearest_geo_range(@places[:los_angeles], 900).first.data.should == :portland
120
+ tree.nearest_geo_range(@places[:los_angeles], 900, :mi).size.should == 1
121
+ tree.nearest_geo_range(@places[:los_angeles], 900, :mi).first.data.should == :portland
122
+
123
+ tree.nearest_geo_range(@places[:los_angeles], 900, :km).size.should == 0
124
+ tree.nearest_geo_range(@places[:los_angeles], 1448, :km).size.should == 1
125
+ tree.nearest_geo_range(@places[:los_angeles], 1448, :km).first.data.should == :portland
126
+
127
+ tree.nearest_geo_range(@places[:los_angeles], 957).size.should == 2
128
+ tree.nearest_geo_range(@places[:los_angeles], 957).map(&:data).sort.should == [:portland, :seattle].sort
129
+ tree.nearest_geo_range(@places[:los_angeles], 957, :mi).size.should == 2
130
+ tree.nearest_geo_range(@places[:los_angeles], 957, :mi).map(&:data).sort.should == [:portland, :seattle].sort
131
+ tree.nearest_geo_range(@places[:los_angeles], 1540, :km).size.should == 2
132
+ tree.nearest_geo_range(@places[:los_angeles], 1540, :km).map(&:data).sort.should == [:portland, :seattle].sort
133
+
134
+ tree.nearest_geo_range(@places[:los_angeles], 2500).size.should == 3
135
+ tree.nearest_geo_range(@places[:los_angeles], 2500).map(&:data).sort.should == [:portland, :seattle, :new_york].sort
136
+ tree.nearest_geo_range(@places[:los_angeles], 2500, :mi).size.should == 3
137
+ tree.nearest_geo_range(@places[:los_angeles], 2500, :mi).map(&:data).sort.should == [:portland, :seattle, :new_york].sort
138
+ tree.nearest_geo_range(@places[:los_angeles], 4023, :km).size.should == 3
139
+ tree.nearest_geo_range(@places[:los_angeles], 4023, :km).map(&:data).sort.should == [:portland, :seattle, :new_york].sort
140
+
141
+ tree.insert(@places[:los_angeles], :los_angeles)
142
+
143
+ tree.nearest_geo_range(@places[:los_angeles], 0).size.should == 1
144
+ tree.nearest_geo_range(@places[:los_angeles], 0).first.data.should == :los_angeles
145
+ tree.nearest_geo_range(@places[:los_angeles], 0, :mi).size.should == 1
146
+ tree.nearest_geo_range(@places[:los_angeles], 0, :mi).first.data.should == :los_angeles
147
+ tree.nearest_geo_range(@places[:los_angeles], 0, :km).size.should == 1
148
+ tree.nearest_geo_range(@places[:los_angeles], 0, :km).first.data.should == :los_angeles
149
+
150
+ tree.nearest_geo_range(@places[:montreal], 900).size.should == 1
151
+ tree.nearest_geo_range(@places[:montreal], 900).first.data.should == :new_york
152
+ tree.nearest_geo_range(@places[:montreal], 900, :mi).size.should == 1
153
+ tree.nearest_geo_range(@places[:montreal], 900, :mi).first.data.should == :new_york
154
+ tree.nearest_geo_range(@places[:montreal], 1448, :km).size.should == 1
155
+ tree.nearest_geo_range(@places[:montreal], 1448, :km).first.data.should == :new_york
156
+ end
157
+
158
+ it "should find nearest using random points" do
159
+ tree = Geokdtree::Tree.new(2)
160
+ random_points = (0...1000).map{|i| [[rand_coord, rand_coord], i]}
161
+ random_points.each{|p| tree.insert(p.first, p.last)}
162
+
163
+ 100.times do
164
+ random_point = [rand_coord, rand_coord]
165
+
166
+ # tree search
167
+ result = tree.nearest(random_point)
168
+ kd_nearest_point = random_points[result.data].first
169
+
170
+ # sort search
171
+ sort_nearest_point = random_points.sort_by{|i| Geokdtree::Tree.distance(i.first, random_point)}.first.first
172
+
173
+ Geokdtree::Tree.distance(sort_nearest_point, random_point).should == Geokdtree::Tree.distance(kd_nearest_point, random_point)
174
+ end
175
+ end
176
+
177
+ it "should compute geo distance" do
178
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:portland]).round(0).should == 824
179
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:seattle]).round(0).should == 957
180
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:new_york]).round(0).should == 2442
181
+ Geokdtree::Tree.geo_distance(@places[:montreal], @places[:new_york]).round(0).should == 332
182
+
183
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:portland], :mi).round(0).should == 824
184
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:seattle], :mi).round(0).should == 957
185
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:new_york], :mi).round(0).should == 2442
186
+ Geokdtree::Tree.geo_distance(@places[:montreal], @places[:new_york],:mi).round(0).should == 332
187
+
188
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:portland], :km).round(0).should == 1327
189
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:seattle], :km).round(0).should == 1540
190
+ Geokdtree::Tree.geo_distance(@places[:los_angeles], @places[:new_york], :km).round(0).should == 3931
191
+ Geokdtree::Tree.geo_distance(@places[:montreal], @places[:new_york], :km).round(0).should == 535
192
+ end
193
+
194
+ it "should compute distance" do
195
+ Geokdtree::Tree.distance([-1, 1], [1, 1]).should == 2
196
+ Geokdtree::Tree.distance([-1, 1], [-1, -1]).should == 2
197
+ Geokdtree::Tree.distance([1, 1], [1, 0]).should == 1
198
+ Geokdtree::Tree.distance([0, 0], [-1, 0]).should == 1
199
+ Geokdtree::Tree.distance([0, 0], [0, -1]).should == 1
200
+ Geokdtree::Tree.distance([-0.5, -0.5], [0.5, -0.5]).should == 1
201
+ Geokdtree::Tree.distance([-0.5, -0.5], [0, -0.5]).should == 0.5
202
+ end
203
+
204
+ end
205
+
206
+ def rand_coord
207
+ rand(0) * 10 - 5
208
+ end
209
+