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