forked from trdtnguyen/mysql-plnvm
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathitem_geofunc_buffer.cc
674 lines (597 loc) · 21 KB
/
item_geofunc_buffer.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
/**
@file
@brief
This file defines ST_Buffer function.
*/
#include "my_config.h"
#include "item_geofunc.h"
#include "sql_class.h" // THD
#include "item_geofunc_internal.h"
#include "gis_bg_traits.h"
static const char *const buffer_strategy_names []=
{
"invalid_strategy",
"end_round",
"end_flat",
"join_round",
"join_miter",
"point_circle",
"point_square"
};
template <typename Char_type>
inline int char_icmp(const Char_type a, const Char_type b)
{
const int a1= std::tolower(a);
const int b1= std::tolower(b);
return a1 > b1 ? 1 : (a1 < b1 ? -1 : 0);
}
/**
Case insensitive comparison of two ascii strings.
@param a '\0' ended string.
@param b '\0' ended string.
*/
template <typename Char_type>
int str_icmp(const Char_type *a, const Char_type *b)
{
int ret= 0, i;
for (i= 0; a[i] != 0 && b[i] != 0; i++)
if ((ret= char_icmp(a[i], b[i])))
return ret;
if (a[i] == 0 && b[i] != 0)
return -1;
if (a[i] != 0 && b[i] == 0)
return 1;
return 0;
}
/*
Convert strategies stored in String objects into Strategy_setting objects.
*/
void Item_func_buffer::set_strategies()
{
for (int i= 0; i < num_strats; i++)
{
String *pstr= strategies[i];
const uchar *pstrat= pointer_cast<const uchar *>(pstr->ptr());
uint32 snum= 0;
if (pstr->length() != 12 ||
!((snum= uint4korr(pstrat)) > invalid_strategy && snum <= max_strategy))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value= true;
return;
}
const enum_buffer_strategies strat= (enum_buffer_strategies)snum;
double value;
float8get(&value, pstrat + 4);
enum_buffer_strategy_types strategy_type= invalid_strategy_type;
switch (strat)
{
case end_round:
case end_flat:
strategy_type= end_strategy;
break;
case join_round:
case join_miter:
strategy_type= join_strategy;
break;
case point_circle:
case point_square:
strategy_type= point_strategy;
break;
default:
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value= true;
return;
break;
}
// Each strategy option can be set no more than once for every ST_Buffer()
// call.
if (settings[strategy_type].strategy != invalid_strategy)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "st_buffer");
null_value= true;
return;
}
else
{
settings[strategy_type].strategy= (enum_buffer_strategies)snum;
settings[strategy_type].value= value;
}
}
}
Item_func_buffer_strategy::
Item_func_buffer_strategy(const POS &pos, PT_item_list *ilist)
:Item_str_func(pos, ilist)
{
// Here we want to use the String::set(const char*, ..) version.
const char *pbuf= tmp_buffer;
tmp_value.set(pbuf, 0, NULL);
}
void Item_func_buffer_strategy::fix_length_and_dec()
{
collation.set(&my_charset_bin);
decimals=0;
max_length= 16;
maybe_null= 1;
}
String *Item_func_buffer_strategy::val_str(String * /* str_arg */)
{
String str;
String *strat_name= args[0]->val_str_ascii(&str);
if ((null_value= args[0]->null_value))
{
DBUG_ASSERT(maybe_null);
return NULL;
}
// Get the NULL-terminated ascii string.
const char *pstrat_name= strat_name->c_ptr_safe();
bool found= false;
tmp_value.set_charset(&my_charset_bin);
// The tmp_value is supposed to always stores a {uint32,double} pair,
// and it uses a char tmp_buffer[16] array data member.
uchar *result_buf= const_cast<uchar *>(pointer_cast<const uchar *>
(tmp_value.ptr()));
// Although the result of this item node is never persisted, we still have to
// use portable endianess access otherwise unaligned access will crash
// on sparc CPUs.
for (uint32 i= 0; i <= Item_func_buffer::max_strategy; i++)
{
// The above var_str_ascii() call makes the strat_name an ascii string so
// we can do below comparison.
if (str_icmp(pstrat_name, buffer_strategy_names[i]) != 0)
continue;
int4store(result_buf, i);
result_buf+= 4;
Item_func_buffer::enum_buffer_strategies istrat=
static_cast<Item_func_buffer::enum_buffer_strategies>(i);
/*
The end_flat and point_square strategies must have no more arguments;
The rest strategies must have 2nd parameter which must be a positive
numeric value, and we will store it as a double.
We use float8store to ensure that the value is independent of endianness.
*/
if (istrat != Item_func_buffer::end_flat &&
istrat != Item_func_buffer::point_square)
{
if (arg_count != 2)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
double val= args[1]->val_real();
if ((null_value= args[1]->null_value))
{
DBUG_ASSERT(maybe_null);
return NULL;
}
if (val <= 0)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
if (istrat != Item_func_buffer::join_miter &&
val > current_thd->variables.max_points_in_geometry)
{
my_error(ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED, MYF(0),
"points_per_circle",
current_thd->variables.max_points_in_geometry,
func_name());
return error_str();
}
float8store(result_buf, val);
}
else if (arg_count != 1)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
else
float8store(result_buf, 0.0);
found= true;
break;
}
// Unrecognized strategy names, report error.
if (!found)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
return error_str();
}
tmp_value.length(12);
return &tmp_value;
}
#define CALL_BG_BUFFER(result, geom, geom_out, dist_strategy, side_strategy,\
join_strategy, end_strategy, point_strategy) do {\
(result)= false;\
switch ((geom)->get_type())\
{\
case Geometry::wkb_point:\
{\
BG_models<bgcs::cartesian>::Point\
bg((geom)->get_data_ptr(), (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
case Geometry::wkb_multipoint:\
{\
BG_models<bgcs::cartesian>::Multipoint\
bg((geom)->get_data_ptr(), (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
case Geometry::wkb_linestring:\
{\
BG_models<bgcs::cartesian>::Linestring\
bg((geom)->get_data_ptr(), (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
case Geometry::wkb_multilinestring:\
{\
BG_models<bgcs::cartesian>::Multilinestring\
bg((geom)->get_data_ptr(), (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
case Geometry::wkb_polygon:\
{\
const void *data_ptr= (geom)->normalize_ring_order();\
if (data_ptr == NULL)\
{\
my_error(ER_GIS_INVALID_DATA, MYF(0), "st_buffer");\
(result)= true;\
break; \
}\
BG_models<bgcs::cartesian>::Polygon\
bg(data_ptr, (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
case Geometry::wkb_multipolygon:\
{\
const void *data_ptr= (geom)->normalize_ring_order();\
if (data_ptr == NULL)\
{\
my_error(ER_GIS_INVALID_DATA, MYF(0), "st_buffer");\
(result)= true;\
break; \
}\
BG_models<bgcs::cartesian>::Multipolygon\
bg(data_ptr, (geom)->get_data_size(),\
(geom)->get_flags(), (geom)->get_srid());\
bg::buffer(bg, (geom_out), (dist_strategy), (side_strategy),\
(join_strategy), (end_strategy), (point_strategy));\
break;\
}\
default:\
DBUG_ASSERT(false);\
break;\
}\
} while(0)
Item_func_buffer::Item_func_buffer(const POS &pos, PT_item_list *ilist)
:Item_geometry_func(pos, ilist)
{
num_strats= 0;
memset(settings, 0, sizeof(settings));
memset(strategies, 0, sizeof(strategies));
}
namespace bgst= boost::geometry::strategy::buffer;
String *Item_func_buffer::val_str(String *str_value_arg)
{
DBUG_ENTER("Item_func_buffer::val_str");
DBUG_ASSERT(fixed == 1);
String strat_bufs[side_strategy + 1];
String *obj= args[0]->val_str(&tmp_value);
double dist= args[1]->val_real();
Geometry_buffer buffer;
Geometry *geom;
String *str_result= str_value_arg;
null_value= false;
bg_resbuf_mgr.free_result_buffer();
if (!obj || args[0]->null_value || args[1]->null_value)
DBUG_RETURN(error_str());
// Reset the two arrays, set_strategies() requires the settings array to
// be brand new on every ST_Buffer() call.
memset(settings, 0, sizeof(settings));
memset(strategies, 0, sizeof(strategies));
// Strategies options start from 3rd argument, the 1st two arguments are
// never strategies: the 1st is input geometry, and the 2nd is distance.
num_strats= arg_count - 2;
for (uint i= 2; i < arg_count; i++)
{
strategies[i - 2]= args[i]->val_str(&strat_bufs[i]);
if (strategies[i - 2] == NULL || args[i]->null_value)
DBUG_RETURN(error_str());
}
/*
Do this before simplify_multi_geometry() in order to exclude invalid
WKB/WKT data.
*/
if (!(geom= Geometry::construct(&buffer, obj)))
{
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
DBUG_RETURN(error_str());
}
/*
If the input geometry is a multi-geometry or geometry collection that has
only one component, extract that component as input argument.
*/
Geometry::wkbType geom_type= geom->get_type();
if (geom_type == Geometry::wkb_multipoint ||
geom_type == Geometry::wkb_multipolygon ||
geom_type == Geometry::wkb_multilinestring ||
geom_type == Geometry::wkb_geometrycollection)
{
/*
Make a copy of the geometry byte string argument to work on it,
don't modify the original one since it is assumed to be stable.
Simplifying the argument is worth the effort because buffer computation
is less expensive with simplified geometry.
The copy's buffer may be directly returned as result so it has to be a
data member.
Here we assume that if obj->is_alloced() is false, obj's referring to some
geometry data stored somewhere else so here we cache the simplified
version into m_tmp_geombuf without modifying obj's original referred copy;
otherwise we believe the geometry data
is solely owned by obj and that each call of this ST_Buffer() is given
a valid GEOMETRY byte string, i.e. it is structually valid and if it was
simplified before, the obj->m_length was correctly set to the new length
after the simplification operation.
*/
const bool use_buffer= !obj->is_alloced();
if (simplify_multi_geometry(obj, (use_buffer ? &m_tmp_geombuf : NULL)) &&
use_buffer)
obj= &m_tmp_geombuf;
if (!(geom= Geometry::construct(&buffer, obj)))
{
my_error(ER_GIS_INVALID_DATA, MYF(0), func_name());
DBUG_RETURN(error_str());
}
}
/*
If distance passed to ST_Buffer is too small, then we return the
original geometry as its buffer. This is needed to avoid division
overflow in buffer calculation, as well as for performance purposes.
*/
if (std::abs(dist) <= GIS_ZERO || is_empty_geocollection(geom))
{
null_value= 0;
str_result= obj;
DBUG_RETURN(str_result);
}
Geometry::wkbType gtype= geom->get_type();
if (dist < 0 && gtype != Geometry::wkb_polygon &&
gtype != Geometry::wkb_multipolygon &&
gtype != Geometry::wkb_geometrycollection)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
DBUG_RETURN(error_str());
}
set_strategies();
if (null_value)
DBUG_RETURN(error_str());
/*
str_result will refer to BG object's memory directly if any, here we remove
last call's remainings so that if this call doesn't produce any result,
this call won't note down last address(already freed above) and
next call won't free already free'd memory.
*/
str_result->set(NullS, 0, &my_charset_bin);
bool had_except= false;
try
{
Strategy_setting ss1= settings[end_strategy];
Strategy_setting ss2= settings[join_strategy];
Strategy_setting ss3= settings[point_strategy];
const bool is_pts= (gtype == Geometry::wkb_point ||
gtype == Geometry::wkb_multipoint);
const bool is_plygn= (gtype == Geometry::wkb_polygon ||
gtype == Geometry::wkb_multipolygon);
const bool is_ls= (gtype == Geometry::wkb_linestring ||
gtype == Geometry::wkb_multilinestring);
/*
Some strategies can be applied to only part of the geometry types and
coordinate systems. For now we only have cartesian coordinate system
so no check for them.
*/
if ((is_pts && (ss1.strategy != invalid_strategy ||
ss2.strategy != invalid_strategy)) ||
(is_plygn && (ss1.strategy != invalid_strategy ||
ss3.strategy != invalid_strategy)) ||
(is_ls && ss3.strategy != invalid_strategy))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
DBUG_RETURN(error_str());
}
bgst::distance_symmetric<double> dist_strat(dist);
bgst::side_straight side_strat;
bgst::end_round bgst_end_round(ss1.strategy ==
invalid_strategy ? 32 : ss1.value);
bgst::end_flat bgst_end_flat;
bgst::join_round bgst_join_round(ss2.strategy ==
invalid_strategy ? 32 : ss2.value);
bgst::join_miter bgst_join_miter(ss2.value);
bgst::point_circle bgst_point_circle(ss3.strategy ==
invalid_strategy ? 32 : ss3.value);
bgst::point_square bgst_point_square;
/*
Default strategies if not specified:
end_round(32), join_round(32), point_circle(32)
The order of enum items in enum enum_buffer_strategies is crucial for
this setting to be correct, don't modify it.
Although point strategy isn't needed for linear and areal geometries,
we have to specify it because of bg::buffer interface, and BG will
silently ignore it. Similarly for other strategies.
*/
int strats_combination= 0;
if (ss1.strategy == end_flat)
strats_combination|= 1;
if (ss2.strategy == join_miter)
strats_combination|= 2;
if (ss3.strategy == point_square)
strats_combination|= 4;
BG_models<bgcs::cartesian>::Multipolygon result;
result.set_srid(geom->get_srid());
if (geom->get_type() != Geometry::wkb_geometrycollection)
{
bool ret= false;
switch (strats_combination)
{
case 0:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_circle);
break;
case 1:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_circle);
break;
case 2:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_circle);
break;
case 3:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_circle);
break;
case 4:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_square);
break;
case 5:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_square);
break;
case 6:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_square);
break;
case 7:
CALL_BG_BUFFER(ret, geom, result, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_square);
break;
default:
DBUG_ASSERT(false);
break;
}
if (ret)
DBUG_RETURN(error_str());
if (result.size() == 0)
{
str_result->reserve(GEOM_HEADER_SIZE + 4);
write_geometry_header(str_result, geom->get_srid(),
Geometry::wkb_geometrycollection, 0);
DBUG_RETURN(str_result);
}
else if (post_fix_result(&bg_resbuf_mgr, result, str_result))
DBUG_RETURN(error_str());
bg_resbuf_mgr.set_result_buffer(const_cast<char *>(str_result->ptr()));
}
else
{
// Compute buffer for a geometry collection(GC). We first compute buffer
// for each component of the GC, and put the buffer polygons into another
// collection, finally merge components of the collection.
BG_geometry_collection bggc, bggc2;
bggc.fill(geom);
for (BG_geometry_collection::Geometry_list::iterator
i= bggc.get_geometries().begin();
i != bggc.get_geometries().end(); ++i)
{
BG_models<bgcs::cartesian>::Multipolygon res;
String temp_result;
res.set_srid((*i)->get_srid());
Geometry::wkbType gtype= (*i)->get_type();
if (dist < 0 && gtype != Geometry::wkb_multipolygon &&
gtype != Geometry::wkb_polygon)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), func_name());
DBUG_RETURN(error_str());
}
bool ret= false;
switch (strats_combination)
{
case 0:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_circle);
break;
case 1:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_circle);
break;
case 2:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_circle);
break;
case 3:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_circle);
break;
case 4:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_round, bgst_point_square);
break;
case 5:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_round, bgst_end_flat, bgst_point_square);
break;
case 6:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_round, bgst_point_square);
break;
case 7:
CALL_BG_BUFFER(ret, *i, res, dist_strat, side_strat,
bgst_join_miter, bgst_end_flat, bgst_point_square);
break;
default:
DBUG_ASSERT(false);
break;
}
if (ret)
DBUG_RETURN(error_str());
if (res.size() == 0)
continue;
if (post_fix_result(&bg_resbuf_mgr, res, &temp_result))
DBUG_RETURN(error_str());
// A single component's buffer is computed above and stored here.
bggc2.fill(&res);
}
// Merge the accumulated polygons because they may overlap.
bggc2.merge_components<bgcs::cartesian>(&null_value);
Gis_geometry_collection *gc= bggc2.as_geometry_collection(str_result);
delete gc;
}
/*
If the result geometry is a multi-geometry or geometry collection that has
only one component, extract that component as result.
*/
simplify_multi_geometry(str_result, NULL);
}
catch (...)
{
had_except= true;
handle_gis_exception("st_buffer");
}
if (had_except)
DBUG_RETURN(error_str());
DBUG_RETURN(str_result);
}