1 /* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #include <item_geofunc_internal.h>
24
handle_gis_exception(const char * funcname)25 void handle_gis_exception(const char *funcname)
26 {
27 try
28 {
29 throw;
30 }
31 catch (const boost::geometry::centroid_exception &)
32 {
33 my_error(ER_BOOST_GEOMETRY_CENTROID_EXCEPTION, MYF(0), funcname);
34 }
35 catch (const boost::geometry::overlay_invalid_input_exception &)
36 {
37 my_error(ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION, MYF(0),
38 funcname);
39 }
40 catch (const boost::geometry::turn_info_exception &)
41 {
42 my_error(ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION, MYF(0), funcname);
43 }
44 catch (const boost::geometry::detail::self_get_turn_points::self_ip_exception &)
45 {
46 my_error(ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION, MYF(0),
47 funcname);
48 }
49 catch (const boost::geometry::empty_input_exception &)
50 {
51 my_error(ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION, MYF(0), funcname);
52 }
53 catch (const boost::geometry::inconsistent_turns_exception &)
54 {
55 my_error(ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION, MYF(0));
56 }
57 catch (const boost::geometry::exception &)
58 {
59 my_error(ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION, MYF(0), funcname);
60 }
61 catch (const std::bad_alloc &e)
62 {
63 my_error(ER_STD_BAD_ALLOC_ERROR, MYF(0), e.what(), funcname);
64 }
65 catch (const std::domain_error &e)
66 {
67 my_error(ER_STD_DOMAIN_ERROR, MYF(0), e.what(), funcname);
68 }
69 catch (const std::length_error &e)
70 {
71 my_error(ER_STD_LENGTH_ERROR, MYF(0), e.what(), funcname);
72 }
73 catch (const std::invalid_argument &e)
74 {
75 my_error(ER_STD_INVALID_ARGUMENT, MYF(0), e.what(), funcname);
76 }
77 catch (const std::out_of_range &e)
78 {
79 my_error(ER_STD_OUT_OF_RANGE_ERROR, MYF(0), e.what(), funcname);
80 }
81 catch (const std::overflow_error &e)
82 {
83 my_error(ER_STD_OVERFLOW_ERROR, MYF(0), e.what(), funcname);
84 }
85 catch (const std::range_error &e)
86 {
87 my_error(ER_STD_RANGE_ERROR, MYF(0), e.what(), funcname);
88 }
89 catch (const std::underflow_error &e)
90 {
91 my_error(ER_STD_UNDERFLOW_ERROR, MYF(0), e.what(), funcname);
92 }
93 catch (const std::logic_error &e)
94 {
95 my_error(ER_STD_LOGIC_ERROR, MYF(0), e.what(), funcname);
96 }
97 catch (const std::runtime_error &e)
98 {
99 my_error(ER_STD_RUNTIME_ERROR, MYF(0), e.what(), funcname);
100 }
101 catch (const std::exception &e)
102 {
103 my_error(ER_STD_UNKNOWN_EXCEPTION, MYF(0), e.what(), funcname);
104 }
105 catch (...)
106 {
107 my_error(ER_GIS_UNKNOWN_EXCEPTION, MYF(0), funcname);
108 }
109 }
110
111
112 /**
113 Merge all components as appropriate so that the object contains only
114 components that don't overlap.
115
116 @tparam Coordsys Coordinate system type, specified using those defined in
117 boost::geometry::cs.
118 @param[out] pnull_value takes back null_value set during the operation.
119 */
120 template<typename Coordsys>
121 void BG_geometry_collection::
merge_components(my_bool * pnull_value)122 merge_components(my_bool *pnull_value)
123 {
124 if (is_comp_no_overlapped())
125 return;
126
127 POS pos = {{NULL, NULL}, {NULL, NULL}};
128 Item_func_spatial_operation ifso(pos, NULL, NULL,
129 Item_func_spatial_operation::op_union);
130 bool do_again= true;
131 uint32 last_composition[6]= {0}, num_unchanged_composition= 0;
132 size_t last_num_geos= 0;
133
134 /*
135 After each merge_one_run call, see whether the two indicators change:
136 1. total number of geometry components;
137 2. total number of each of the 6 types of geometries
138
139 If they don't change for N*N/4 times, break out of the loop. Here N is
140 the total number of geometry components.
141
142 There is the rationale:
143
144 Given a geometry collection, it's likely that one effective merge_one_run
145 merges a polygon P and the linestring that crosses it (L) to a
146 polygon P'(the same one) and another linestring L', the 2 indicators above
147 don't change but the merge is actually done. If we merge P'
148 and L' again, they should not be considered cross, but given certain data
149 BG somehow believes L` still crosses P` even the P and P` are valid, and
150 it will give us a L'' and P'' which is different from L' and P'
151 respectively, and L'' is still considered crossing P'',
152 hence the loop here never breaks out.
153
154 If the collection has N components, and we have X [multi]linestrings and
155 N-X polygons, the number of pairs that can be merged is Y = X * (N-X),
156 so the largest Y is N*N/4. If the above 2 indicators stay unchanged more
157 than N*N/4 times the loop runs, we believe all possible combinations in
158 the collection are enumerated and no effective merge is being done any more.
159
160 Note that the L'' and P'' above is different from L' and P' so we can't
161 compare GEOMETRY byte string, and geometric comparison is expensive and may
162 still compare unequal and we would still be stuck in the endless loop.
163 */
164 while (!*pnull_value && do_again)
165 {
166 do_again= merge_one_run<Coordsys>(&ifso, pnull_value);
167 if (!*pnull_value && do_again)
168 {
169 const size_t num_geos= m_geos.size();
170 uint32 composition[6]= {0};
171
172 for (size_t i= 0; i < num_geos; ++i)
173 composition[m_geos[i]->get_type() - 1]++;
174
175 if (num_geos != last_num_geos ||
176 memcmp(composition, last_composition, sizeof(composition)))
177 {
178 memcpy(last_composition, composition, sizeof(composition));
179 last_num_geos= num_geos;
180 num_unchanged_composition= 0;
181 }
182 else
183 num_unchanged_composition++;
184
185 if (num_unchanged_composition > (last_num_geos * last_num_geos / 4 + 2))
186 break;
187 }
188 }
189 }
190
191 // Explicit template instantiation
192 template
193 void
194 BG_geometry_collection::merge_components<boost::geometry::cs::cartesian>(char*);
195
196
197
198 template<typename Coordsys>
199 inline bool
linestring_overlaps_polygon_outerring(const Gis_line_string & ls,const Gis_polygon & plgn)200 linestring_overlaps_polygon_outerring(const Gis_line_string &ls,
201 const Gis_polygon &plgn)
202 {
203
204 Gis_polygon_ring &oring= plgn.outer();
205 Gis_line_string ls2(oring.get_ptr(), oring.get_nbytes(),
206 oring.get_flags(), oring.get_srid());
207 return boost::geometry::overlaps(ls, ls2);
208 }
209
210
211 template<typename Coordsys>
linear_areal_intersect_infinite(Geometry * g1,Geometry * g2,my_bool * pnull_value)212 bool linear_areal_intersect_infinite(Geometry *g1, Geometry *g2,
213 my_bool *pnull_value)
214 {
215 bool res= false;
216
217 /*
218 If crosses check succeeds, make sure g2 is a valid [multi]polygon, invalid
219 ones can be accepted by BG and the cross check would be considered true,
220 we should reject such result and return false in this case.
221 */
222 if (Item_func_spatial_rel::bg_geo_relation_check<Coordsys>
223 (g1, g2, Item_func::SP_CROSSES_FUNC, pnull_value) && !*pnull_value)
224 {
225 Geometry::wkbType g2_type= g2->get_type();
226 if (g2_type == Geometry::wkb_polygon)
227 {
228 Gis_polygon plgn(g2->get_data_ptr(),
229 g2->get_data_size(), g2->get_flags(), g2->get_srid());
230 res= bg::is_valid(plgn);
231 }
232 else if (g2_type == Geometry::wkb_multipolygon)
233 {
234 Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
235 g2->get_flags(), g2->get_srid());
236 res= bg::is_valid(mplgn);
237 }
238 else
239 assert(false);
240
241 return res;
242 }
243
244 if (*pnull_value)
245 return false;
246
247 if (g1->get_type() == Geometry::wkb_linestring)
248 {
249 Gis_line_string ls(g1->get_data_ptr(),
250 g1->get_data_size(), g1->get_flags(), g1->get_srid());
251 if (g2->get_type() == Geometry::wkb_polygon)
252 {
253 Gis_polygon plgn(g2->get_data_ptr(),
254 g2->get_data_size(), g2->get_flags(), g2->get_srid());
255 res= linestring_overlaps_polygon_outerring
256 <Coordsys>(ls, plgn);
257 }
258 else
259 {
260 Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
261 g2->get_flags(), g2->get_srid());
262 for (size_t i= 0; i < mplgn.size(); i++)
263 {
264 if (linestring_overlaps_polygon_outerring<Coordsys>
265 (ls, mplgn[i]))
266 return true;
267 }
268 }
269 }
270 else
271 {
272 Gis_multi_line_string mls(g1->get_data_ptr(), g1->get_data_size(),
273 g1->get_flags(), g1->get_srid());
274 if (g2->get_type() == Geometry::wkb_polygon)
275 {
276 Gis_polygon plgn(g2->get_data_ptr(),
277 g2->get_data_size(), g2->get_flags(), g2->get_srid());
278 for (size_t i= 0; i < mls.size(); i++)
279 {
280 if (linestring_overlaps_polygon_outerring<Coordsys>
281 (mls[i], plgn))
282 return true;
283 }
284 }
285 else
286 {
287 Gis_multi_polygon mplgn(g2->get_data_ptr(), g2->get_data_size(),
288 g2->get_flags(), g2->get_srid());
289 for (size_t j= 0; j < mls.size(); j++)
290 {
291 for (size_t i= 0; i < mplgn.size(); i++)
292 {
293 if (linestring_overlaps_polygon_outerring<Coordsys>
294 (mls[j], mplgn[i]))
295 return true;
296 }
297 }
298 }
299 }
300
301 return res;
302 }
303
304
305 /**
306 Create this class for exception safety --- destroy the objects referenced
307 by the pointers in the set when destroying the container.
308 */
309 template<typename T>
310 class Pointer_vector : public std::vector<T *>
311 {
312 typedef std::vector<T*> parent;
313 public:
~Pointer_vector()314 ~Pointer_vector()
315 {
316 for (typename parent::iterator i= this->begin(); i != this->end(); ++i)
317 delete (*i);
318 }
319 };
320
321
322 // A unary predicate to locate a target Geometry object pointer from a sequence.
323 class Is_target_geometry
324 {
325 Geometry *m_target;
326 public:
Is_target_geometry(Geometry * t)327 Is_target_geometry(Geometry *t) :m_target(t)
328 {
329 }
330
operator ()(Geometry * g)331 bool operator()(Geometry *g)
332 {
333 return g == m_target;
334 }
335 };
336
337
338 class Rtree_entry_compare
339 {
340 public:
Rtree_entry_compare()341 Rtree_entry_compare()
342 {
343 }
344
operator ()(const BG_rtree_entry & re1,const BG_rtree_entry & re2) const345 bool operator()(const BG_rtree_entry &re1, const BG_rtree_entry &re2) const
346 {
347 return re1.second < re2.second;
348 }
349 };
350
351
352 /**
353 One run of merging components.
354
355 @tparam Coordsys Coordinate system type, specified using those defined in
356 boost::geometry::cs.
357 @param ifso the Item_func_spatial_operation object, we here rely on it to
358 do union operation.
359 @param[out] pnull_value takes back null_value set during the operation.
360 @return whether need another call of this function.
361 */
362 template<typename Coordsys>
merge_one_run(Item_func_spatial_operation * ifso,my_bool * pnull_value)363 bool BG_geometry_collection::merge_one_run(Item_func_spatial_operation *ifso,
364 my_bool *pnull_value)
365 {
366 Geometry *gres= NULL;
367 bool has_new= false;
368 my_bool &null_value= *pnull_value;
369 Pointer_vector<Geometry> added;
370 std::vector<String> added_wkbbufs;
371
372 added.reserve(16);
373 added_wkbbufs.reserve(16);
374
375 Rtree_index rtree;
376 make_rtree(m_geos, &rtree);
377 Rtree_result rtree_result;
378
379 for (Geometry_list::iterator i= m_geos.begin(); i != m_geos.end(); ++i)
380 {
381 if (*i == NULL)
382 continue;
383
384 BG_box box;
385 make_bg_box(*i, &box);
386 if (!is_box_valid(box))
387 continue;
388
389 rtree_result.clear();
390 rtree.query(bgi::intersects(box), std::back_inserter(rtree_result));
391 /*
392 Normally the rtree should be non-empty because at least there is *i
393 itself. But if box has NaN coordinates, the rtree can be empty since
394 all coordinate comparisons with NaN numbers are false. also if the
395 min corner point have greater coordinates than the max corner point,
396 the box isn't valid and the rtree can be empty.
397 */
398 assert(rtree_result.size() != 0);
399
400 // Sort rtree_result by Rtree_entry::second in order to make
401 // components in fixed order.
402 Rtree_entry_compare rtree_entry_compare;
403 std::sort(rtree_result.begin(), rtree_result.end(), rtree_entry_compare);
404
405 // Used to stop the nested loop.
406 bool stop_it= false;
407
408 for (Rtree_result::iterator j= rtree_result.begin();
409 j != rtree_result.end(); ++j)
410 {
411 Geometry *geom2= m_geos[j->second];
412 if (*i == geom2 || geom2 == NULL)
413 continue;
414
415 // Equals is much easier and faster to check, so check it first.
416 if (Item_func_spatial_rel::bg_geo_relation_check<Coordsys>
417 (geom2, *i, Item_func::SP_EQUALS_FUNC, &null_value) && !null_value)
418 {
419 *i= NULL;
420 break;
421 }
422
423 if (null_value)
424 {
425 stop_it= true;
426 break;
427 }
428
429 if (Item_func_spatial_rel::bg_geo_relation_check<Coordsys>
430 (*i, geom2, Item_func::SP_WITHIN_FUNC, &null_value) && !null_value)
431 {
432 *i= NULL;
433 break;
434 }
435
436 if (null_value)
437 {
438 stop_it= true;
439 break;
440 }
441
442 if (Item_func_spatial_rel::bg_geo_relation_check<Coordsys>
443 (geom2, *i, Item_func::SP_WITHIN_FUNC, &null_value) && !null_value)
444 {
445 m_geos[j->second]= NULL;
446 continue;
447 }
448
449 if (null_value)
450 {
451 stop_it= true;
452 break;
453 }
454
455 /*
456 If *i and geom2 is a polygon and a linestring that intersect only
457 finite number of points, the union result is the same as the two
458 geometries, and we would be stuck in an infinite loop. So we must
459 detect and exclude this case. All other argument type combinations
460 always will get a geometry different from the two arguments.
461 */
462 char d11= (*i)->feature_dimension();
463 char d12= geom2->feature_dimension();
464 Geometry *geom_d1= NULL;
465 Geometry *geom_d2= NULL;
466 bool is_linear_areal= false;
467
468 if (((d11 == 1 && d12 == 2) || (d12 == 1 && d11 == 2)))
469 {
470 geom_d1= (d11 == 1 ? *i : geom2);
471 geom_d2= (d11 == 2 ? *i : geom2);
472 if (d11 != 1)
473 {
474 const char tmp_dim= d11;
475 d11= d12;
476 d12= tmp_dim;
477 }
478 is_linear_areal= true;
479 }
480
481 /*
482 As said above, if one operand is linear, the other is areal, then we
483 only proceed the union of them if they intersect infinite number of
484 points, i.e. L crosses A or L touches A's outer ring. Note that if L
485 touches some of A's inner rings, L must be crossing A, so not gonna
486 check the inner rings.
487 */
488 if ((!is_linear_areal &&
489 Item_func_spatial_rel::bg_geo_relation_check<Coordsys>
490 (*i, geom2, Item_func::SP_INTERSECTS_FUNC, &null_value) &&
491 !null_value) ||
492 (is_linear_areal && linear_areal_intersect_infinite
493 <Coordsys>(geom_d1, geom_d2, &null_value)))
494 {
495 String wkbres;
496
497 if (null_value)
498 {
499 stop_it= true;
500 break;
501 }
502
503 gres= ifso->bg_geo_set_op<Coordsys>(*i, geom2,
504 &wkbres);
505 null_value= ifso->null_value;
506
507 if (null_value)
508 {
509 if (gres != NULL && gres != *i && gres != geom2)
510 delete gres;
511 stop_it= true;
512 break;
513 }
514
515 if (gres != *i)
516 *i= NULL;
517 if (gres != geom2)
518 m_geos[j->second]= NULL;
519 if (gres != NULL && gres != *i && gres != geom2)
520 {
521 added.push_back(gres);
522 String tmp_wkbbuf;
523 added_wkbbufs.push_back(tmp_wkbbuf);
524 added_wkbbufs.back().takeover(wkbres);
525 has_new= true;
526 gres= NULL;
527 }
528 /*
529 Done with *i, it's either adopted, or removed or merged to a new
530 geometry.
531 */
532 break;
533 } // intersects
534
535 if (null_value)
536 {
537 stop_it= true;
538 break;
539 }
540
541 } // for (*j)
542
543 if (stop_it)
544 break;
545
546 } // for (*i)
547
548 // Remove deleted Geometry object pointers, then append new components if any.
549 Is_target_geometry pred(NULL);
550 Geometry_list::iterator jj= std::remove_if(m_geos.begin(),
551 m_geos.end(), pred);
552 m_geos.resize(jj - m_geos.begin());
553
554 for (Pointer_vector<Geometry>::iterator i= added.begin();
555 i != added.end(); ++i)
556 {
557 /*
558 Fill rather than directly use *i for consistent memory management.
559 The objects pointed by pointers in added will be automatically destroyed.
560 */
561 fill(*i);
562 }
563
564 // The added and added_wkbbufs arrays are destroyed and the Geometry objects
565 // in 'added' are freed, and memory buffers in added_wkbbufs are freed too.
566 return has_new;
567 }
568
569
reassemble_geometry(Geometry * g)570 inline void reassemble_geometry(Geometry *g)
571 {
572 Geometry::wkbType gtype= g->get_geotype();
573 if (gtype == Geometry::wkb_polygon)
574 down_cast<Gis_polygon *>(g)->to_wkb_unparsed();
575 else if (gtype == Geometry::wkb_multilinestring)
576 down_cast<Gis_multi_line_string *>(g)->reassemble();
577 else if (gtype == Geometry::wkb_multipolygon)
578 down_cast<Gis_multi_polygon *>(g)->reassemble();
579 }
580
581
582 template <typename BG_geotype>
post_fix_result(BG_result_buf_mgr * resbuf_mgr,BG_geotype & geout,String * res)583 bool post_fix_result(BG_result_buf_mgr *resbuf_mgr,
584 BG_geotype &geout, String *res)
585 {
586 assert(geout.has_geom_header_space());
587 reassemble_geometry(&geout);
588
589 // Such objects returned by BG never have overlapped components.
590 if (geout.get_type() == Geometry::wkb_multilinestring ||
591 geout.get_type() == Geometry::wkb_multipolygon)
592 geout.set_components_no_overlapped(true);
593 if (geout.get_ptr() == NULL)
594 return true;
595 if (res)
596 {
597 const char *resptr= geout.get_cptr() - GEOM_HEADER_SIZE;
598 size_t len= geout.get_nbytes();
599
600 /*
601 The resptr buffer is now owned by resbuf_mgr and used by res, resptr
602 will be released properly by resbuf_mgr.
603 */
604 resbuf_mgr->add_buffer(const_cast<char *>(resptr));
605 /*
606 Pass resptr as const pointer so that the memory space won't be reused
607 by res object. Reuse is forbidden because the memory comes from BG
608 operations and will be freed upon next same val_str call.
609 */
610 res->set(resptr, len + GEOM_HEADER_SIZE, &my_charset_bin);
611
612 // Prefix the GEOMETRY header.
613 write_geometry_header(const_cast<char *>(resptr), geout.get_srid(),
614 geout.get_geotype());
615
616 /*
617 Give up ownership because the buffer may have to live longer than
618 the object.
619 */
620 geout.set_ownmem(false);
621 }
622
623 return false;
624 }
625
626
627 // Explicit template instantiation
628 template
629 bool post_fix_result<Gis_line_string>(BG_result_buf_mgr*,
630 Gis_line_string&, String*);
631 template
632 bool post_fix_result<Gis_multi_line_string>(BG_result_buf_mgr*,
633 Gis_multi_line_string&, String*);
634 template
635 bool post_fix_result<Gis_multi_point>(BG_result_buf_mgr*,
636 Gis_multi_point&, String*);
637 template
638 bool post_fix_result<Gis_multi_polygon>(BG_result_buf_mgr*,
639 Gis_multi_polygon&, String*);
640 template
641 bool post_fix_result<Gis_point>(BG_result_buf_mgr*, Gis_point&, String*);
642 template
643 bool post_fix_result<Gis_polygon>(BG_result_buf_mgr*, Gis_polygon&, String*);
644
645
646
647 class Is_empty_geometry : public WKB_scanner_event_handler
648 {
649 public:
650 bool is_empty;
651
Is_empty_geometry()652 Is_empty_geometry() :is_empty(true)
653 {
654 }
655
on_wkb_start(Geometry::wkbByteOrder bo,Geometry::wkbType geotype,const void * wkb,uint32 len,bool has_hdr)656 virtual void on_wkb_start(Geometry::wkbByteOrder bo,
657 Geometry::wkbType geotype,
658 const void *wkb, uint32 len, bool has_hdr)
659 {
660 if (is_empty && geotype != Geometry::wkb_geometrycollection)
661 is_empty= false;
662 }
663
on_wkb_end(const void * wkb)664 virtual void on_wkb_end(const void *wkb)
665 {
666 }
667
continue_scan() const668 virtual bool continue_scan() const
669 {
670 return is_empty;
671 }
672 };
673
674
is_empty_geocollection(const Geometry * g)675 bool is_empty_geocollection(const Geometry *g)
676 {
677 if (g->get_geotype() != Geometry::wkb_geometrycollection)
678 return false;
679
680 uint32 num= uint4korr(g->get_cptr());
681 if (num == 0)
682 return true;
683
684 Is_empty_geometry checker;
685 uint32 len= g->get_data_size();
686 wkb_scanner(g->get_cptr(), &len, Geometry::wkb_geometrycollection,
687 false, &checker);
688 return checker.is_empty;
689
690 }
691
692
is_empty_geocollection(const String & wkbres)693 bool is_empty_geocollection(const String &wkbres)
694 {
695 if (wkbres.ptr() == NULL)
696 return true;
697
698 uint32 geotype= uint4korr(wkbres.ptr() + SRID_SIZE + 1);
699
700 if (geotype != static_cast<uint32>(Geometry::wkb_geometrycollection))
701 return false;
702
703 if (uint4korr(wkbres.ptr() + SRID_SIZE + WKB_HEADER_SIZE) == 0)
704 return true;
705
706 Is_empty_geometry checker;
707 uint32 len= static_cast<uint32>(wkbres.length()) - GEOM_HEADER_SIZE;
708 wkb_scanner(wkbres.ptr() + GEOM_HEADER_SIZE, &len,
709 Geometry::wkb_geometrycollection, false, &checker);
710 return checker.is_empty;
711 }
712
713
714