geokdtree 0.1.0

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