Commit 9bdb41583b1b49ea725ca84cd764ee5c3bf0a0a2

Authored by will-r
1 parent 40bee0a0

ankane readme and test improvements, other tidying up

README.md
... ... @@ -834,7 +834,7 @@ product = Product.first
834 834 product.similar(fields: ["name"], where: {size: "12 oz"})
835 835 ```
836 836  
837   -### Simple Geospatial Indexing: geo_points
  837 +### Geospatial Searches
838 838  
839 839 If your data consists of point values, searchkick offers a useful shorthand:
840 840  
... ... @@ -883,7 +883,7 @@ City.search "san", boost_by_distance: {field: :location, origin: {lat: 37, lon:
883 883 ```
884 884  
885 885  
886   -### Complex Geospatial Indexing: geo_shapes
  886 +### Geo Shapes
887 887  
888 888 You can also pass through complex or varied shapes as GeoJSON objects.
889 889  
... ... @@ -911,28 +911,23 @@ end
911 911  
912 912 The `geo_shapes` hash is passed through to elasticsearch without modification. Please see the [geo_shape data type documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for options.
913 913  
914   -Any geospatial data type can be used in the index or in the search. It is up to you to ensure that it is a valid geoJSON representation. The possible shapes are:
  914 +Any geospatial data type can be held in the index or give as a search query. It is up to you to ensure that it is a valid geoJSON representation. The possible shapes are:
915 915  
916 916 * **point**: single lat/lon pair
917 917 * **multipoint**: array of points
918 918 * **linestring**: array of at least two lat/lon pairs
919 919 * **multilinestring**: array of lines
920   -* **polygon**: an array of paths, each being an array of at least four lat/lon pairs whose first and last points are the same. Paths after the first represent exclusions.
  920 +* **polygon**: an array of paths, each being an array of at least four lat/lon pairs whose first and last points are the same. Paths after the first represent exclusions. Elasticsearch will return an error if a polygon contains two consecutive identical points, intersects itself or is not closed.
921 921 * **multipolygon**: array of polygons
922 922 * **envelope**: a bounding box defined by top left and bottom right points
923 923 * **circle**: a bounding circle defined by center point and radius
924 924 * **geometrycollection**: an array of separate geoJSON objects possibly of various types
925 925  
926   -See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for details. GeoJSON coordinates are usually given as an array of `[lon, lat]` points but this often causes swapping errors so searchkick can also take objects with `lon` and `lat` keys.
  926 +See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for details. GeoJSON coordinates are usually given as an array of `[lon, lat]` points but searchkick can also take objects with `lon` and `lat` keys.
927 927  
928   -Elasticsearch is sensitive about geo_shape validity. For example it will throw an exception if a polygon contains two consecutive identical points, intersects itself or is not properly closed.
  928 +Once a geo_shape index is established, you can include a geo_shape filter in any search. This also takes a geoJSON shape, and will return a list of items based on their overlap with that shape.
929 929  
930   -
931   -### Geospatial searching
932   -
933   -Once a geo_shape index is established, you can apply a geo_shape filter to any search. This also takes a geoJSON shape, and will return a list of items based on their overlap with that shape.
934   -
935   -Intersecting with the query shape:
  930 +Find shapes (of any kind) intersecting with the query shape:
936 931  
937 932 ```ruby
938 933 City.search "san", where: {bounds: {geo_shape: {type: "polygon", coordinates: [[{lat: 38, lon: -123}, ...]]}}}
... ... @@ -941,22 +936,21 @@ City.search "san", where: {bounds: {geo_shape: {type: "polygon", coordinates: [[
941 936 Falling entirely within the query shape:
942 937  
943 938 ```ruby
944   -City.search "san", where: {relation: "within", bounds: {geo_shape: {type: "circle", coordinates: [{lat: 38, lon: -123}], radius: "1km"}}}
  939 +City.search "san", where: {bounds: {geo_shape: {type: "circle", relation: "within", coordinates: [{lat: 38, lon: -123}], radius: "1km"}}}
945 940 ```
946 941  
947 942 Not touching the query shape:
948 943  
949 944 ```ruby
950   -City.search "san", where: {relation: "disjoint", bounds: {geo_shape: {type: "envelope", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
  945 +City.search "san", where: {bounds: {geo_shape: {type: "envelope", relation: "disjoint", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
951 946 ```
952 947  
953   -Envelope is a special case. For consistency, searchkick also understands coordinates given as top_left and bottom_right:
  948 +Containing the query shape (ElasticSearch 2.2+):
954 949  
955 950 ```ruby
956   -City.search "san", where: {relation: "within", bounds: {geo_shape: {type: "envelope", top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}}}}
  951 +City.search "san", where: {bounds: {geo_shape: {type: "envelope", relation: "contains", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
957 952 ```
958 953  
959   -
960 954 ### Routing
961 955  
962 956 Searchkick supports [Elasticsearchโ€™s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
... ...
lib/searchkick/query.rb
... ... @@ -809,15 +809,7 @@ module Searchkick
809 809 }
810 810 }
811 811 when :geo_shape
812   - if op_value[:type] == "envelope" && op_value[:top_left].present? && op_value[:bottom_right].present?
813   - op_value[:coordinates] = [coordinate_array(op_value[:top_left]), coordinate_array(op_value[:bottom_right])]
814   - op_value.delete(:top_left)
815   - op_value.delete(:bottom_right)
816   - elsif op_value[:type] == "circle"
817   - op_value[:coordinates] = coordinate_array(op_value[:coordinates] || [])
818   - else
819   - op_value[:coordinates] = (op_value[:coordinates] || []).map { |loc| coordinate_array(loc) }
820   - end
  812 + op_value[:coordinates] = coordinate_array(op_value[:coordinates]) if op_value[:coordinates]
821 813 relation = op_value.delete(:relation) || 'intersects'
822 814 filters << {
823 815 geo_shape: {
... ... @@ -943,9 +935,7 @@ module Searchkick
943 935 end
944 936  
945 937 # Recursively descend through nesting of arrays until we reach either a lat/lon object or an array of numbers,
946   - # eventually returning the same structure with all values transformed to [lon, lat]. Question: should we reverse
947   - # the array order so that arguments can be given as [lat, lon], as happens elsewhere in searchkick? We are moving
948   - # GeoJSON around so it seems better to stick to that specification, though the lat/lon objects are already a deviation.
  938 + # eventually returning the same structure with all values transformed to [lon, lat].
949 939 #
950 940 def coordinate_array(value)
951 941 if value.is_a?(Hash)
... ...
test/geo_shape_test.rb
1   -require "pp"
2 1 require_relative "test_helper"
3 2  
4 3 class GeoShapeTest < Minitest::Test
5   -
6   - def test_geo_shape
7   - regions = [
  4 + def setup
  5 + super
  6 + store [
8 7 {name: "Region A", text: "The witch had a cat", territory: "30,40,35,45,40,40,40,30,30,30,30,40"},
9 8 {name: "Region B", text: "and a very tall hat", territory: "50,60,55,65,60,60,60,50,50,50,50,60"},
10   - {name: "Region C", text: "and long ginger hair which she wore in a plait.", territory: "10,20,15,25,20,20,20,10,10,10,10,20"},
11   - ]
12   - store regions, Region
  9 + {name: "Region C", text: "and long ginger hair which she wore in a plait", territory: "10,20,15,25,20,20,20,10,10,10,10,20"}
  10 + ], Region
  11 + end
13 12  
14   - # circle
  13 + def test_circle
15 14 assert_search "*", ["Region A"], {
16 15 where: {
17 16 territory: {
... ... @@ -23,8 +22,9 @@ class GeoShapeTest &lt; Minitest::Test
23 22 }
24 23 }
25 24 }, Region
  25 + end
26 26  
27   - # envelope
  27 + def test_envelope
28 28 assert_search "*", ["Region A"], {
29 29 where: {
30 30 territory: {
... ... @@ -35,21 +35,9 @@ class GeoShapeTest &lt; Minitest::Test
35 35 }
36 36 }
37 37 }, Region
  38 + end
38 39  
39   - # envelope as corners
40   - assert_search "*", ["Region A"], {
41   - where: {
42   - territory: {
43   - geo_shape: {
44   - type: "envelope",
45   - top_left: {lat: 42.0, lon: 28.0},
46   - bottom_right: {lat: 38.0, lon: 32.0}
47   - }
48   - }
49   - }
50   - }, Region
51   -
52   - # polygon
  40 + def test_polygon
53 41 assert_search "*", ["Region A"], {
54 42 where: {
55 43 territory: {
... ... @@ -60,8 +48,9 @@ class GeoShapeTest &lt; Minitest::Test
60 48 }
61 49 }
62 50 }, Region
  51 + end
63 52  
64   - # multipolygon
  53 + def test_multipolygon
65 54 assert_search "*", ["Region A", "Region B"], {
66 55 where: {
67 56 territory: {
... ... @@ -75,8 +64,9 @@ class GeoShapeTest &lt; Minitest::Test
75 64 }
76 65 }
77 66 }, Region
  67 + end
78 68  
79   - # disjoint
  69 + def test_disjoint
80 70 assert_search "*", ["Region B", "Region C"], {
81 71 where: {
82 72 territory: {
... ... @@ -88,8 +78,9 @@ class GeoShapeTest &lt; Minitest::Test
88 78 }
89 79 }
90 80 }, Region
  81 + end
91 82  
92   - # within
  83 + def test_within
93 84 assert_search "*", ["Region A"], {
94 85 where: {
95 86 territory: {
... ... @@ -101,8 +92,9 @@ class GeoShapeTest &lt; Minitest::Test
101 92 }
102 93 }
103 94 }, Region
  95 + end
104 96  
105   - # with search
  97 + def test_search_math
106 98 assert_search "witch", ["Region A"], {
107 99 where: {
108 100 territory: {
... ... @@ -113,7 +105,9 @@ class GeoShapeTest &lt; Minitest::Test
113 105 }
114 106 }
115 107 }, Region
  108 + end
116 109  
  110 + def test_search_no_match
117 111 assert_search "ginger hair", [], {
118 112 where: {
119 113 territory: {
... ... @@ -126,20 +120,18 @@ class GeoShapeTest &lt; Minitest::Test
126 120 }, Region
127 121 end
128 122  
129   - def test_geo_shape_contains
  123 + def test_contains
130 124 skip if elasticsearch_below22?
131   -
132   - assert_search "*", ["Region A"], {
  125 + assert_search "*", ["Region C"], {
133 126 where: {
134 127 territory: {
135 128 geo_shape: {
136 129 type: "envelope",
137 130 relation: "contains",
138   - coordinates: [[32, 33], [33, 32]]
  131 + coordinates: [[12, 13], [13,12]]
139 132 }
140 133 }
141 134 }
142 135 }, Region
143   -
144 136 end
145 137 end
... ...
test/test_helper.rb
... ... @@ -435,6 +435,7 @@ class Minitest::Test
435 435 Store.destroy_all
436 436 Animal.destroy_all
437 437 Speaker.destroy_all
  438 + Region.destroy_all
438 439 end
439 440  
440 441 protected
... ...