xref: /6.0.3/geocouch/gc-couchbase/test/04-emit.t (revision 9c6fdfc8)
1#!/usr/bin/env escript
2%% -*- erlang -*-
3%%! -smp enable
4
5% Licensed under the Apache License, Version 2.0 (the "License"); you may not
6% use this file except in compliance with the License. You may obtain a copy of
7% the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing, software
12% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14% License for the specific language governing permissions and limitations under
15% the License.
16
17-include_lib("../include/couch_spatial.hrl").
18-include_lib("couch_set_view/include/couch_set_view.hrl").
19
20% from couch_db.hrl
21-define(MIN_STR, <<>>).
22-define(MAX_STR, <<255>>).
23-define(JSON_DECODE(V), ejson:decode(V)).
24-define(JSON_ENCODE(V), ejson:encode(V)).
25
26
27test_set_name() -> <<"couch_test_spatial_emit">>.
28num_set_partitions() -> 4.
29ddoc_id() -> <<"_design/test">>.
30
31
32main(_) ->
33    test_util:init_code_path(),
34
35    etap:plan(46),
36    case (catch test()) of
37        ok ->
38            etap:end_tests();
39        Other ->
40            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
41            etap:bail(Other)
42    end,
43    %init:stop(),
44    %receive after infinity -> ok end,
45    ok.
46
47
48test() ->
49    spatial_test_util:start_server(test_set_name()),
50
51    etap:diag("Testing emits in spatial views"),
52
53    test_spatial_emit_geom_only(),
54    test_spatial_emit_geom_and_value(),
55    test_spatial_emit_without_geometry_point(),
56    test_spatial_emit_without_geometry_range(),
57    test_spatial_emit_without_geometry_point_and_range(),
58    test_spatial_emit_multiple(),
59    test_spatial_emit_dups(),
60    test_spatial_emit_without_geometry_dups(),
61    test_spatial_emit_huge_geom(),
62
63
64    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
65    spatial_test_util:stop_server(),
66    ok.
67
68
69test_spatial_emit_geom_only() ->
70    etap:diag("Testing emits with a geometry only"),
71    setup_test(),
72    ok = configure_spatial_group(ddoc_id()),
73    Tests = [{[{105.0, 105.0}, {5.0, 5.0}],
74              [{<<"geoPoint">>,
75                [[105.0, 105.0], [5.0, 5.0]]}],
76              "Point geometry was returned"},
77             {[{200.0, 200.4}, {-1.0, 1.0}],
78              [{<<"geoLineString">>,
79                [[200.0, 201.0], [0.0, 2.0]]}],
80              "LineString geometry was returned"},
81             {[{88200.0, 88200.4}, {-881.0, 881.0}],
82              [{<<"geoLineStringWithBbox">>,
83                [[88200.0, 88201.0], [880.0, 882.0]]}],
84              "LineString geometry with bounding box was returned"},
85             {[{400.8, 401.0}, {0.8, 1.0}],
86              [{<<"geoPolygon">>,
87                [[400.0,401.0], [0.0, 1.0]]}],
88              "Polygon geometry was returned"},
89             {[{300.8, 301.0}, {0.8, 1.0}],
90              [{<<"geoPolygonWithHole">>,
91                [[300.0, 301.0], [0.0,1.0]]}],
92              "Polygon geometry (with hole) was returned"},
93             {[{500.1, 500.2}, {0.8, 1.5}],
94              [{<<"geoMultiPoint">>,
95                [[500.0, 501.0],[0.0, 1.0]]}],
96              "MultiPoint geometry was returned"},
97             {[{601.2, 601.6}, {1.3, 1.5}],
98              [{<<"geoMultiLineString">>,
99                [[600.0, 603.0], [0.0, 3.0]]}],
100              "MultiLineString geometry was returned"},
101             {[{701.2, 701.6}, {2.3, 3.5}],
102              [{<<"geoMultiPolygon">>,
103                [[700.0, 703.0], [0.0,3.0]]}],
104              "MultiPolygon geometry was returned"},
105             {[{802, 802}, {0, 0}],
106              [{<<"geoGeometryCollection">>,
107                [[800.0, 802.0],[0.0, 1.0]]}],
108              "GeometryCollection geometry was returned"},
109             {[{-55, 27}, {-35, 16}],
110              [{<<"geoLshapedPolygon">>,
111                [[-11.953125, 61.171875], [-11.6015625, 48.1640625]]}],
112              "L-shaped Polygon geometry was returned as range queries"
113              "only compare on a boundinx box level and not on the"
114              "geometry level"},
115             {[{400.8, 500.2}, {0.8, 2.0}],
116              [{<<"geoPolygon">>, [[400.0, 401.0], [0.0, 1.0]]},
117               {<<"geoMultiPoint">>, [[500.0, 501.0], [0.0, 1.0]]}],
118              "Polygon and MultiPoint geometry was returned"}
119            ],
120    lists:foreach(fun({Range, Expected, Message}) ->
121                          query_for_expected_result(
122                            <<"geomonly">>, Range, Expected, Message)
123                  end, Tests),
124    shutdown_group().
125
126
127test_spatial_emit_geom_and_value() ->
128    etap:diag("Testing emits with a geometry and an additional dimension"),
129    setup_test(),
130    ok = configure_spatial_group(ddoc_id()),
131    Tests = [{[{105.0, 105.0}, {5.0, 5.0}, {0, 10}],
132              [{<<"geoPoint">>,
133                [[105.0, 105.0], [5.0, 5.0], [1.0, 1.0]]}],
134              "Point geometry was returned"},
135             {[{200.0, 200.4}, {-1.0, 1.0}, {0, 10}],
136              [{<<"geoLineString">>,
137                [[200.0, 201.0], [0.0, 2.0], [2.0, 2.0]]}],
138              "LineString geometry was returned"},
139             {[{88200.0, 88200.4}, {-881.0, 881.0}, {0, 10}],
140              [{<<"geoLineStringWithBbox">>,
141                [[88200.0, 88201.0], [880.0, 882.0], [10.0, 10.0]]}],
142              "LineString geometry with boudning box was returned"},
143             {[{400.8, 401.0}, {0.8, 1.0}, {0, 10}],
144              [{<<"geoPolygon">>,
145                [[400.0, 401.0],[0.0,1.0], [3.0, 3.0]]}],
146              "Polygon geometry was returned"},
147             {[{300.8, 301.0}, {0.8, 1.0}, {0, 10}],
148              [{<<"geoPolygonWithHole">>,
149                [[300.0, 301.0], [0.0, 1.0], [5.0, 5.0]]}],
150              "Polygon geometry (with hole) was returned"},
151             {[{500.1, 500.2}, {0.8, 1.5}, {0, 10}],
152              [{<<"geoMultiPoint">>,
153                [[500.0, 501.0], [0.0, 1.0], [6.0, 6.0]]}],
154              "MultiPoint geometry was returned"},
155             {[{601.2, 601.6}, {1.3, 1.5}, {0, 10}],
156              [{<<"geoMultiLineString">>,
157                [[600.0, 603.0], [0.0, 3.0], [7.0, 7.0]]}],
158              "MultiLineString geometry was returned"},
159             {[{701.2, 701.6}, {2.3, 3.5}, {0, 10}],
160              [{<<"geoMultiPolygon">>,
161                [[700.0, 703.0], [0.0, 3.0], [8.0, 8.0]]}],
162              "MultiPolygon geometry was returned"},
163             {[{802, 802}, {0, 0}, {0, 10}],
164              [{<<"geoGeometryCollection">>,
165                [[800.0, 802.0], [0.0, 1.0], [9.0, 9.0]]}],
166              "GeometryCollection geometry was returned"},
167             {[{-55, 27}, {-35, 16}, {0, 10}],
168              [{<<"geoLshapedPolygon">>,
169                [[-11.953125, 61.171875], [-11.6015625, 48.1640625],
170                 [4.0, 4.0]]}],
171              "L-shaped Polygon geometry was returned as range queries"
172              "only compare on a boundinx box level and not on the"
173              "geometry level"},
174             {[{400.8, 500.2}, {0.8, 2.0}, {0, 10}],
175              [{<<"geoPolygon">>, [[400.0, 401.0],[0.0, 1.0], [3.0, 3.0]]},
176               {<<"geoMultiPoint">>, [[500.0, 501.0],[0.0, 1.0], [6.0, 6.0]]}],
177              "Polygon and MultiPoint geometry was returned"},
178             {[{0, 250}, {0, 230}, {0, 2.9}],
179              [{<<"geoPoint">>, [[105.0, 105.0], [5.0, 5.0], [1.0, 1.0]]},
180               {<<"geoLineString">>,
181                [[200.0, 201.0], [0.0, 2.0], [2.0, 2.0]]}],
182              "Point and Linestring geometry was returned"},
183             {[{400.8, 401.0}, {0.8, 1.0}, {-20, -10}],
184              [],
185              "Polygon geometry would've been returned, but wasn't "
186              "because of the 3rd dimension"},
187             {[{0, 250}, {0, 230}, {0, 1.6}],
188              [{<<"geoPoint">>, [[105.0, 105.0], [5.0, 5.0], [1.0, 1.0]]}],
189              "Linestring geometry would've been returned, but wasn't "
190              "because of the 3rd dimension"},
191             {[{0, 250}, {3, 8}, {0, 2.9}],
192              [{<<"geoPoint">>, [[105.0, 105.0], [5.0, 5.0], [1.0, 1.0]]}],
193              "Linestring geometry would've been returned, but wasn't "
194              "because of the 2nd dimension"},
195             {[{0, 150}, {0, 8}, {0, 2.9}],
196              [{<<"geoPoint">>, [[105.0, 105.0], [5.0, 5.0], [1.0, 1.0]]}],
197              "Linestring geometry would've been returned, but wasn't "
198              "because of the 1st dimension"}
199            ],
200    lists:foreach(fun({Range, Expected, Message}) ->
201                          query_for_expected_result(
202                            <<"geomandvalue">>, Range, Expected, Message)
203                  end, Tests),
204    shutdown_group().
205
206
207test_spatial_emit_without_geometry_point() ->
208    etap:diag("Testing emits without a geometry (point emit)"),
209    setup_test(),
210    ok = configure_spatial_group(ddoc_id()),
211    Tests = [{[{2.7, 3.4}, {4.1, 6.8}],
212              [{<<"item3">>, [[3, 3], [6, 6]]}],
213              "Correct item was returned"},
214             {[{0.3, 2.3}, {0.3, 4.1}],
215              [{<<"item1">>, [[1, 1], [2, 2]]},
216               {<<"item2">>, [[2, 2], [4, 4]]}],
217              "Correct items were returned"}
218            ],
219    lists:foreach(fun({Range, Expected, Message}) ->
220                          query_for_expected_result_no_geom(
221                            <<"withoutgeompoint">>, Range, Expected, Message)
222                  end, Tests),
223    shutdown_group().
224
225
226test_spatial_emit_without_geometry_range() ->
227    etap:diag("Testing emits without a geometry (range emit)"),
228    setup_test(),
229    ok = configure_spatial_group(ddoc_id()),
230    Tests = [{[{2.1, 2.4}, {4.1, 6.8}],
231              [{<<"item2">>, [[2, 4], [4, 12]]}],
232              "Correct item was returned"},
233             {[{0.3, 2.3}, {0.3, 4.1}],
234              [{<<"item1">>, [[1, 2], [2, 11]]},
235               {<<"item2">>, [[2, 4], [4, 12]]}],
236              "Correct items were returned"}
237            ],
238    etap:diag("Testing emits without a geometry (range emits)"),
239    lists:foreach(fun({Range, Expected, Message}) ->
240                          query_for_expected_result_no_geom(
241                            <<"withoutgeomrange">>, Range, Expected, Message)
242                  end, Tests),
243    shutdown_group().
244
245
246test_spatial_emit_without_geometry_point_and_range() ->
247    etap:diag("Testing emits without a geometry (point and range emit)"),
248    setup_test(),
249    ok = configure_spatial_group(ddoc_id()),
250    Tests = [{[{2.1, 2.4}, {8.7, 12.8}],
251              [{<<"item2">>, [[2, 4], [12, 12]]}],
252              "Correct item was returned"},
253             {[{0.3, 2.3}, {11.0, 24.1}],
254              [{<<"item1">>, [[1, 2], [11, 11]]},
255               {<<"item2">>, [[2, 4], [12, 12]]}],
256              "Correct items were returned"}
257            ],
258    lists:foreach(fun({Range, Expected, Message}) ->
259                          query_for_expected_result_no_geom(
260                            <<"withoutgeompointandrange">>, Range, Expected,
261                            Message)
262                  end, Tests),
263    shutdown_group().
264
265
266test_spatial_emit_multiple() ->
267    etap:diag("Testing multiple emits (point emit)"),
268    setup_test(),
269    ok = configure_spatial_group(ddoc_id()),
270    Tests = [{[{2.7, 3.4}],
271              [{<<"item3">>, [[2.9, 2.9]], 14, nil},
272               {<<"item3">>, [[3.1, 3.1]], 15, nil}],
273              "Correct items from multiple emit were returned (a)"},
274             {[{5, 7}],
275              [{<<"item3">>, [[6.0, 6.0]], 16, nil},
276               {<<"item5">>, [[5.1, 5.1]], 17, nil}],
277              "Correct items from multiple emit were returned (b)"},
278             {[{0.3, 2.3}],
279              [{<<"item1">>, [[0.9, 0.9]], 12, nil},
280               {<<"item1">>, [[1.1, 1.1]], 13, nil},
281               {<<"item1">>, [[2.0, 2.0]], 14, nil},
282               {<<"item2">>, [[1.9, 1.9]], 13, nil},
283               {<<"item2">>, [[2.1, 2.1]], 14, nil}],
284              "Correct items from multiple emit were returned (c)"},
285             {[{9.5, 10.5}],
286              [{<<"item5">>, [[10, 10]], 18, nil}],
287              "Correct items from multiple emit were returned (d)"}
288            ],
289    lists:foreach(fun({Range, Expected, Message}) ->
290                          query_for_expected_result_multi(
291                            <<"multiple">>, Range, Expected, Message)
292                  end, Tests),
293    shutdown_group().
294
295
296test_spatial_emit_dups() ->
297    etap:diag("Testing multiple emits with the same key (dups)"),
298    setup_test(),
299    ok = configure_spatial_group(ddoc_id()),
300    Tests = [{[{2.7, 3.4}, {2.7, 3.4}],
301              [{<<"item3">>, [[3.0, 3.0], [3.0, 3.0]], 14,
302                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[3.0,3.0]}]}},
303               {<<"item3">>, [[3.0, 3.0], [3.0, 3.0]], 15,
304                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[3.0,3.0]}]}}],
305              "Correct items from emitting items with the same key "
306              "were returned (a)"},
307             {[{5, 7}, {5, 7}],
308              [{<<"item3">>, [[6.0, 6.0], [6.0, 6.0]], 16,
309                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[6.0,6.0]}]}},
310               {<<"item5">>, [[5.0, 5.0], [5.0, 5.0]], 16,
311                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[5.0,5.0]}]}},
312               {<<"item5">>, [[5.0, 5.0], [5.0, 5.0]], 17,
313                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[5.0,5.0]}]}}],
314              "Correct items from emitting items with the same key "
315              "were returned (b)"},
316             {[{0.3, 2.3}, {0.3, 2.3}],
317              [{<<"item1">>, [[1.0, 1.0], [1.0, 1.0]], 12,
318                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[1.0,1.0]}]}},
319               {<<"item1">>, [[1.0, 1.0], [1.0, 1.0]], 13,
320                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[1.0,1.0]}]}},
321               {<<"item1">>, [[2.0, 2.0], [2.0, 2.0]], 14,
322                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[2.0,2.0]}]}},
323               {<<"item2">>, [[2.0, 2.0], [2.0, 2.0]], 13,
324                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[2.0,2.0]}]}},
325               {<<"item2">>, [[2.0, 2.0], [2.0, 2.0]], 14,
326                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[2.0,2.0]}]}}],
327              "Correct items from emitting items with the same key "
328              "were returned (c)"},
329             {[{9.5, 10.5}, {9.5, 10.5}],
330              [{<<"item5">>, [[10, 10], [10, 10]], 18,
331                {[{<<"type">>,<<"Point">>},{<<"coordinates">>,[10.0,10.0]}]}}],
332              "Correct items from emitting items with the same key "
333              "were returned (d)"}
334            ],
335    lists:foreach(fun({Range, Expected, Message}) ->
336                          query_for_expected_result_multi(
337                            <<"dups">>, Range, Expected, Message)
338                  end, Tests),
339    shutdown_group().
340
341
342test_spatial_emit_without_geometry_dups() ->
343    etap:diag(
344        "Testing multiple emits with the same key without a geometry (dups)"),
345    setup_test(),
346    ok = configure_spatial_group(ddoc_id()),
347    Tests = [{[{2.7, 3.4}],
348              [{<<"item3">>, [[3.0, 3.0]], 14, nil},
349               {<<"item3">>, [[3.0, 3.0]], 15, nil}],
350              "Correct items from emitting items without geometry and with "
351              "the same key were returned (a)"},
352             {[{5, 7}],
353              [{<<"item3">>, [[6.0, 6.0]], 16, nil},
354               {<<"item5">>, [[5.0, 5.0]], 16, nil},
355               {<<"item5">>, [[5.0, 5.0]], 17, nil}],
356              "Correct items from emitting items without geometry and with "
357              "the same key were returned (b)"},
358             {[{0.3, 2.3}],
359              [{<<"item1">>, [[1.0, 1.0]], 12, nil},
360               {<<"item1">>, [[1.0, 1.0]], 13, nil},
361               {<<"item1">>, [[2.0, 2.0]], 14, nil},
362               {<<"item2">>, [[2.0, 2.0]], 13, nil},
363               {<<"item2">>, [[2.0, 2.0]], 14, nil}],
364              "Correct items from emitting items without geometry and with "
365              "the same key were returned (c)"},
366             {[{9.5, 10.5}],
367              [{<<"item5">>, [[10, 10]], 18, nil}],
368              "Correct items from emitting items without geometry and with "
369              "the same key were returned (d)"}
370            ],
371    lists:foreach(fun({Range, Expected, Message}) ->
372                          query_for_expected_result_multi(
373                            <<"withoutgeomdups">>, Range, Expected, Message)
374                  end, Tests),
375    shutdown_group().
376
377
378test_spatial_emit_huge_geom() ->
379    etap:diag("Testing emit with a huge geometry (> 4KB) as key (MB-14401)"),
380    couch_set_view_test_util:delete_set_dbs(
381        test_set_name(), num_set_partitions()),
382    couch_set_view_test_util:create_set_dbs(
383        test_set_name(), num_set_partitions()),
384    DDoc = {[
385             {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
386             {<<"json">>,
387              {[{<<"spatial">>,
388                 {[{<<"geomonly">>,
389                    <<"function(doc, meta) {if (doc.geom) {"
390                      "emit([doc.geom], doc.value);}}">>}
391                  ]}}
392               ]}}
393            ]},
394    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
395
396    % These coordinates will lead to a geometry that is bigger than 4KB when
397    % serialized to JSON
398    Coords = lists:map(fun(I) -> [I, I + 10] end, lists:seq(0, 4000)),
399    Doc = {[
400            {<<"meta">>, {[{<<"id">>, <<"hugegeom">>}]}},
401            {<<"json">>,
402             {[{<<"value">>, 1},
403               {<<"geom">>,
404                {[{<<"type">>, <<"LineString">>},
405                  {<<"coordinates">>, Coords}]}}
406              ]}}
407           ]},
408
409    ok = couch_set_view_test_util:populate_set_sequentially(
410        test_set_name(),
411        lists:seq(0, 1),
412        [Doc]),
413    ok = configure_spatial_group(ddoc_id()),
414
415    ViewArgs = #spatial_query_args{},
416    {ok, Rows} = query_spatial_view(<<"geomonly">>, ViewArgs),
417    etap:is(Rows, [{[[0.0,4.0e3],[10.0,4010.0]], <<"hugegeom">>,
418                    {0,<<"1">>,
419                     {[{<<"type">>,<<"LineString">>},
420                       {<<"coordinates">>, Coords}]}
421                    }}],
422           "The document with the huge geometry got emitted"),
423    shutdown_group().
424
425
426query_for_expected_result(View, Range, Expected, Message) ->
427    ViewArgs = #spatial_query_args{range = Range},
428    {ok, Rows} = (catch query_spatial_view(View, ViewArgs)),
429    verify_rows(Rows, Expected, Message).
430
431verify_rows(Rows, Expected, Message) ->
432    ExpectedRows = lists:foldl(
433                      fun({DocId, {Val, Geom}}, Acc) ->
434                              case lists:keyfind(DocId, 1, Expected) of
435                                  false ->
436                                      Acc;
437                                  {DocId, Mbb} ->
438                                      GeomNoBbox =
439                                          lists:keydelete(<<"bbox">>, 1, Geom),
440                                      [{DocId, Mbb, Val, {GeomNoBbox}} | Acc]
441                              end
442                      end, [], geojson_docs()),
443    RowsWithoutPartId = [{DocId, Key, ?JSON_DECODE(Value), Geom} ||
444        {Key, DocId, {_PartId, Value, Geom}} <- Rows],
445    etap:is(lists:sort(RowsWithoutPartId), lists:sort(ExpectedRows), Message).
446
447
448query_for_expected_result_no_geom(View, Range, Expected, Message) ->
449    ViewArgs = #spatial_query_args{range = Range},
450    {ok, Rows} = (catch query_spatial_view(View, ViewArgs)),
451    verify_rows_no_geom(Rows, Expected, Message).
452
453verify_rows_no_geom(Rows, Expected, Message) ->
454    ExpectedRows = lists:foldl(
455                      fun({DocId, {_, _, Val}}, Acc) ->
456                              case lists:keyfind(DocId, 1, Expected) of
457                                  false ->
458                                      Acc;
459                                  {DocId, Mbb} ->
460                                     [{DocId, Mbb, Val} | Acc]
461                              end
462                      end, [], nogeom_docs()),
463    RowsWithoutPartId = [{DocId, Key, ?JSON_DECODE(Value)} ||
464        {Key, DocId, {_PartId, Value, nil}} <- Rows],
465    etap:is(lists:sort(RowsWithoutPartId), lists:sort(ExpectedRows), Message).
466
467
468query_for_expected_result_multi(View, Range, Expected, Message) ->
469    ViewArgs = #spatial_query_args{range = Range},
470    {ok, Rows} = (catch query_spatial_view(View, ViewArgs)),
471    RowsWithoutPartId = [{DocId, Key, ?JSON_DECODE(Value), Geom} ||
472        {Key, DocId, {_PartId, Value, Geom}} <- Rows],
473    etap:is(lists:sort(RowsWithoutPartId), lists:sort(Expected), Message).
474
475
476query_spatial_view(ViewName, ViewArgs) ->
477    etap:diag("Querying spatial view " ++ binary_to_list(ddoc_id()) ++ "/" ++
478        binary_to_list(ViewName)),
479    Req = #set_view_group_req{
480        stale = false
481    },
482    {ok, View, Group, _} = spatial_view:get_spatial_view(
483        test_set_name(), ddoc_id(), ViewName, Req),
484
485    FoldFun = fun({{Key, DocId}, Value}, Acc) ->
486        {ok, [{Key, DocId, Value} | Acc]}
487    end,
488    {ok, _, Rows} = couch_set_view:fold(Group, View, FoldFun, [], ViewArgs),
489    couch_set_view:release_group(Group),
490    {ok, lists:reverse(Rows)}.
491
492
493setup_test() ->
494    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
495    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
496
497    DDoc = {[
498        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
499        {<<"json">>, {[
500            {<<"spatial">>, {[
501                {<<"geomonly">>,
502                 <<"function(doc, meta) {if (doc.geom) {"
503                   "emit(doc.geom, doc.value);}}">>},
504                {<<"geomandvalue">>,
505                     <<"function(doc, meta) {if (doc.geom) {"
506                       "emit([doc.geom, doc.value], doc.value);}}">>},
507                {<<"withoutgeompoint">>,
508                     <<"function(doc, meta) {if (doc.geom === undefined) {"
509                       "emit([doc.value1, doc.value2], doc.value10);}}">>},
510                {<<"withoutgeomrange">>,
511                     <<"function(doc, meta) {if (doc.geom === undefined) {"
512                       "emit([[doc.value1, doc.value2], "
513                       "[doc.value2, doc.value10]], doc.value10);}}">>},
514                {<<"withoutgeompointandrange">>,
515                     <<"function(doc, meta) {if (doc.geom === undefined) {"
516                       "emit([[doc.value1, doc.value2], doc.value10], "
517                       "doc.value10);}}">>},
518                {<<"multiple">>,
519                     <<"function(doc, meta) {if (doc.geom === undefined) {"
520                       "emit([doc.value1 - 0.1], doc.value10 + 1);"
521                       "emit([doc.value1 + 0.1], doc.value10 + 2);"
522                       "emit([doc.value2], doc.value10 + 3);}}">>},
523                {<<"dups">>,
524                     <<"function(doc, meta) {if (doc.geom === undefined) {"
525                       "emit({\"type\":\"Point\",\"coordinates\":"
526                       "[doc.value1, doc.value1]}, doc.value10 + 1);"
527                       "emit({\"type\":\"Point\",\"coordinates\":"
528                       "[doc.value1, doc.value1]}, doc.value10 + 2);"
529                       "emit({\"type\":\"Point\",\"coordinates\":"
530                       "[doc.value2, doc.value2]}, doc.value10 + 3);}}">>},
531                {<<"withoutgeomdups">>,
532                     <<"function(doc, meta) {if (doc.geom === undefined) {"
533                       "emit([doc.value1], doc.value10 + 1);"
534                       "emit([doc.value1], doc.value10 + 2);"
535                       "emit([doc.value2], doc.value10 + 3);}}">>}
536            ]}}
537        ]}}
538    ]},
539    populate_set(DDoc).
540
541
542geojson_docs() ->
543% some geometries are based on the GeoJSON specification
544% http://geojson.org/geojson-spec.html (2010-08-17)
545[
546 {<<"geoPoint">>, {
547      1,
548      [{<<"type">>, <<"Point">>}, {<<"coordinates">>, [105.0, 5.0]}]}},
549 {<<"geoLineString">>, {
550      2,
551      [{<<"type">>, <<"LineString">>},
552       {<<"coordinates">>, [[200.0, 0.0], [201.0, 2.0]]}]}},
553 {<<"geoPolygon">>, {
554      3,
555      [{<<"type">>, <<"Polygon">>},
556       {<<"coordinates">>, [[[400.0, 0.0], [401.0, 0.0], [400.0, 1.0],
557                             [400.0, 0.0]]]}]}},
558 {<<"geoLshapedPolygon">>, {
559    4,
560    [{<<"type">>, <<"Polygon">>},
561     {<<"coordinates">>, [[[-11.25, 48.1640625], [-11.953125, 22.8515625],
562                           [35.859375, 21.4453125], [35.859375, -10.8984375],
563                           [61.171875, -11.6015625], [60.46875, 47.4609375],
564                           [60.46875, 46.0546875], [-11.25, 48.1640625]]]}]}},
565 {<<"geoPolygonWithHole">>, {
566    5,
567    [{<<"type">>, <<"Polygon">>},
568     {<<"coordinates">>,
569      [[ [300.0, 0.0], [301.0, 0.0], [300.0, 1.0], [300.0, 0.0] ],
570       [ [300.2, 0.2], [300.6, 0.2], [300.2, 0.6], [300.2, 0.2] ]]}]}},
571 {<<"geoMultiPoint">>, {
572    6,
573    [{<<"type">>, <<"MultiPoint">>},
574     {<<"coordinates">>,  [ [500.0, 0.0], [501.0, 1.0] ]}]}},
575 {<<"geoMultiLineString">>, {
576    7,
577    [{<<"type">>, <<"MultiLineString">>},
578     {<<"coordinates">>, [[ [600.0, 0.0], [601.0, 1.0] ],
579                          [ [602.0, 2.0], [603.0, 3.0] ]]}]}},
580 {<<"geoMultiPolygon">>, {
581    8,
582    [{<<"type">>, <<"MultiPolygon">>},
583     {<<"coordinates">>,
584      [
585       [
586        [[702.0, 2.0], [703.0, 2.0], [703.0, 3.0], [702.0, 3.0], [702.0, 2.0]]
587       ],
588       [
589        [[700.0, 0.0], [701.0, 0.0], [701.0, 1.0], [700.0, 1.0], [700.0, 0.0]],
590        [[700.2, 0.2], [700.8, 0.2], [700.8, 0.8], [700.2, 0.8], [700.2, 0.2]]
591       ]]}]}},
592 {<<"geoGeometryCollection">>, {
593    9,
594    [{<<"type">>, <<"GeometryCollection">>},
595     {<<"geometries">>,
596      [{[{<<"type">>, <<"Point">>}, {<<"coordinates">>, [800.0, 0.0] }]},
597       {[{<<"type">>, <<"LineString">>}, {<<"coordinates">>,
598                                      [ [801.0, 0.0], [802.0, 1.0] ]}]}]}]}},
599 {<<"geoLineStringWithBbox">>, {
600      10,
601      [{<<"type">>, <<"LineString">>},
602       {<<"coordinates">>, [[88200.0, 880.0], [88201.0, 882.0]]},
603       {<<"bbox">>, [88200.0, 880.0, 88201.0, 882.0]}]}}
604].
605
606nogeom_docs() ->
607    lists:map(
608      fun(Val) ->
609              {list_to_binary(["item" | integer_to_list(Val)]),
610               {Val, Val * 2, Val + 10}}
611      end, lists:seq(1, 5)).
612
613create_docs() ->
614    Geoms = lists:map(
615      fun({Id, {Val, Geom}}) ->
616            {[
617              {<<"meta">>, {[{<<"id">>, Id}]}},
618              {<<"json">>, {[
619                             {<<"value">>, Val},
620                             {<<"geom">>, {Geom}}
621                            ]}}
622            ]}
623      end, geojson_docs()),
624    Values = lists:map(
625      fun({Id, {Val1, Val2, Val3}}) ->
626            {[
627              {<<"meta">>, {[{<<"id">>, Id}]}},
628              {<<"json">>, {[
629                             {<<"value1">>, Val1},
630                             {<<"value2">>, Val2},
631                             {<<"value10">>, Val3}
632                            ]}}
633            ]}
634      end, nogeom_docs()),
635    Geoms ++ Values.
636
637
638populate_set(DDoc) ->
639    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
640        " databases with some GeoJSON documents"),
641    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
642    DocList = create_docs(),
643    ok = couch_set_view_test_util:populate_set_sequentially(
644        test_set_name(),
645        lists:seq(0, num_set_partitions() - 1),
646        DocList).
647
648
649configure_spatial_group(DDocId) ->
650    etap:diag("Configuring spatial view group"),
651    Params = #set_view_params{
652        max_partitions = num_set_partitions(),
653        active_partitions = lists:seq(0, num_set_partitions()-1),
654        passive_partitions = [],
655        use_replica_index = false
656    },
657    try
658        ok = couch_set_view:define_group(
659            spatial_view, test_set_name(), DDocId, Params)
660    catch _:Error ->
661        Error
662    end.
663
664
665shutdown_group() ->
666    GroupPid = couch_set_view:get_group_pid(
667        spatial_view, test_set_name(), ddoc_id(), prod),
668    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
669    MonRef = erlang:monitor(process, GroupPid),
670    receive
671    {'DOWN', MonRef, _, _, _} ->
672        ok
673    after 10000 ->
674        etap:bail("Timeout waiting for group shutdown")
675    end.
676