1 /**********************************************************************
2  *
3  * rttopo - topology library
4  * http://git.osgeo.org/gitea/rttopo/librttopo
5  *
6  * rttopo is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * rttopo is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  **********************************************************************
20  *
21  * Copyright (C) 2001-2006 Refractions Research Inc.
22  *
23  **********************************************************************/
24 
25 
26 
27 #include "rttopo_config.h"
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include "librttopo_geom_internal.h"
32 #include "rtgeom_log.h"
33 
34 
35 #define CHECK_RTGEOM_ZM 1
36 
37 void
rtcollection_release(const RTCTX * ctx,RTCOLLECTION * rtcollection)38 rtcollection_release(const RTCTX *ctx, RTCOLLECTION *rtcollection)
39 {
40   rtgeom_release(ctx, rtcollection_as_rtgeom(ctx, rtcollection));
41 }
42 
43 
44 RTCOLLECTION *
rtcollection_construct(const RTCTX * ctx,uint8_t type,int srid,RTGBOX * bbox,uint32_t ngeoms,RTGEOM ** geoms)45 rtcollection_construct(const RTCTX *ctx, uint8_t type, int srid, RTGBOX *bbox,
46                        uint32_t ngeoms, RTGEOM **geoms)
47 {
48   RTCOLLECTION *ret;
49   int hasz, hasm;
50 #ifdef CHECK_RTGEOM_ZM
51   char zm;
52   uint32_t i;
53 #endif
54 
55   RTDEBUGF(ctx, 2, "rtcollection_construct called with %d, %d, %p, %d, %p.", type, srid, bbox, ngeoms, geoms);
56 
57   if( ! rttype_is_collection(ctx, type) )
58     rterror(ctx, "Non-collection type specified in collection constructor!");
59 
60   hasz = 0;
61   hasm = 0;
62   if ( ngeoms > 0 )
63   {
64     hasz = RTFLAGS_GET_Z(geoms[0]->flags);
65     hasm = RTFLAGS_GET_M(geoms[0]->flags);
66 #ifdef CHECK_RTGEOM_ZM
67     zm = RTFLAGS_GET_ZM(geoms[0]->flags);
68 
69     RTDEBUGF(ctx, 3, "rtcollection_construct type[0]=%d", geoms[0]->type);
70 
71     for (i=1; i<ngeoms; i++)
72     {
73       RTDEBUGF(ctx, 3, "rtcollection_construct type=[%d]=%d", i, geoms[i]->type);
74 
75       if ( zm != RTFLAGS_GET_ZM(geoms[i]->flags) )
76         rterror(ctx, "rtcollection_construct: mixed dimension geometries: %d/%d", zm, RTFLAGS_GET_ZM(geoms[i]->flags));
77     }
78 #endif
79   }
80 
81 
82   ret = rtalloc(ctx, sizeof(RTCOLLECTION));
83   ret->type = type;
84   ret->flags = gflags(ctx, hasz,hasm,0);
85   RTFLAGS_SET_BBOX(ret->flags, bbox?1:0);
86   ret->srid = srid;
87   ret->ngeoms = ngeoms;
88   ret->maxgeoms = ngeoms;
89   ret->geoms = geoms;
90   ret->bbox = bbox;
91 
92   return ret;
93 }
94 
95 RTCOLLECTION *
rtcollection_construct_empty(const RTCTX * ctx,uint8_t type,int srid,char hasz,char hasm)96 rtcollection_construct_empty(const RTCTX *ctx, uint8_t type, int srid, char hasz, char hasm)
97 {
98   RTCOLLECTION *ret;
99   if( ! rttype_is_collection(ctx, type) )
100     rterror(ctx, "Non-collection type specified in collection constructor!");
101 
102   ret = rtalloc(ctx, sizeof(RTCOLLECTION));
103   ret->type = type;
104   ret->flags = gflags(ctx, hasz,hasm,0);
105   ret->srid = srid;
106   ret->ngeoms = 0;
107   ret->maxgeoms = 1; /* Allocate room for sub-members, just in case. */
108   ret->geoms = rtalloc(ctx, ret->maxgeoms * sizeof(RTGEOM*));
109   ret->bbox = NULL;
110 
111   return ret;
112 }
113 
114 RTGEOM *
rtcollection_getsubgeom(const RTCTX * ctx,RTCOLLECTION * col,int gnum)115 rtcollection_getsubgeom(const RTCTX *ctx, RTCOLLECTION *col, int gnum)
116 {
117   return (RTGEOM *)col->geoms[gnum];
118 }
119 
120 /**
121  * @brief Clone #RTCOLLECTION object. #RTPOINTARRAY are not copied.
122  *       Bbox is cloned if present in input.
123  */
124 RTCOLLECTION *
rtcollection_clone(const RTCTX * ctx,const RTCOLLECTION * g)125 rtcollection_clone(const RTCTX *ctx, const RTCOLLECTION *g)
126 {
127   uint32_t i;
128   RTCOLLECTION *ret = rtalloc(ctx, sizeof(RTCOLLECTION));
129   memcpy(ret, g, sizeof(RTCOLLECTION));
130   if ( g->ngeoms > 0 )
131   {
132     ret->geoms = rtalloc(ctx, sizeof(RTGEOM *)*g->ngeoms);
133     for (i=0; i<g->ngeoms; i++)
134     {
135       ret->geoms[i] = rtgeom_clone(ctx, g->geoms[i]);
136     }
137     if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
138   }
139   else
140   {
141     ret->bbox = NULL; /* empty collection */
142     ret->geoms = NULL;
143   }
144   return ret;
145 }
146 
147 /**
148 * @brief Deep clone #RTCOLLECTION object. #RTPOINTARRAY are copied.
149 */
150 RTCOLLECTION *
rtcollection_clone_deep(const RTCTX * ctx,const RTCOLLECTION * g)151 rtcollection_clone_deep(const RTCTX *ctx, const RTCOLLECTION *g)
152 {
153   uint32_t i;
154   RTCOLLECTION *ret = rtalloc(ctx, sizeof(RTCOLLECTION));
155   memcpy(ret, g, sizeof(RTCOLLECTION));
156   if ( g->ngeoms > 0 )
157   {
158     ret->geoms = rtalloc(ctx, sizeof(RTGEOM *)*g->ngeoms);
159     for (i=0; i<g->ngeoms; i++)
160     {
161       ret->geoms[i] = rtgeom_clone_deep(ctx, g->geoms[i]);
162     }
163     if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
164   }
165   else
166   {
167     ret->bbox = NULL; /* empty collection */
168     ret->geoms = NULL;
169   }
170   return ret;
171 }
172 
173 /**
174  * Ensure the collection can hold up at least ngeoms
175  */
rtcollection_reserve(const RTCTX * ctx,RTCOLLECTION * col,int ngeoms)176 void rtcollection_reserve(const RTCTX *ctx, RTCOLLECTION *col, int ngeoms)
177 {
178   if ( ngeoms <= col->maxgeoms ) return;
179 
180   /* Allocate more space if we need it */
181   do { col->maxgeoms *= 2; } while ( col->maxgeoms < ngeoms );
182   col->geoms = rtrealloc(ctx, col->geoms, sizeof(RTGEOM*) * col->maxgeoms);
183 }
184 
185 /**
186 * Appends geom to the collection managed by col. Does not copy or
187 * clone, simply takes a reference on the passed geom.
188 */
rtcollection_add_rtgeom(const RTCTX * ctx,RTCOLLECTION * col,const RTGEOM * geom)189 RTCOLLECTION* rtcollection_add_rtgeom(const RTCTX *ctx, RTCOLLECTION *col, const RTGEOM *geom)
190 {
191   if ( col == NULL || geom == NULL ) return NULL;
192 
193   if ( col->geoms == NULL && (col->ngeoms || col->maxgeoms) ) {
194     rterror(ctx, "Collection is in inconsistent state. Null memory but non-zero collection counts.");
195     return NULL;
196   }
197 
198   /* Check type compatibility */
199   if ( ! rtcollection_allows_subtype(ctx, col->type, geom->type) ) {
200     rterror(ctx, "%s cannot contain %s element", rttype_name(ctx, col->type), rttype_name(ctx, geom->type));
201     return NULL;
202   }
203 
204   /* In case this is a truly empty, make some initial space  */
205   if ( col->geoms == NULL )
206   {
207     col->maxgeoms = 2;
208     col->ngeoms = 0;
209     col->geoms = rtalloc(ctx, col->maxgeoms * sizeof(RTGEOM*));
210   }
211 
212   /* Allocate more space if we need it */
213   rtcollection_reserve(ctx, col, col->ngeoms + 1);
214 
215 #if PARANOIA_LEVEL > 1
216   /* See http://trac.osgeo.org/postgis/ticket/2933 */
217   /* Make sure we don't already have a reference to this geom */
218   {
219   int i = 0;
220   for ( i = 0; i < col->ngeoms; i++ )
221   {
222     if ( col->geoms[i] == geom )
223     {
224       RTDEBUGF(ctx, 4, "Found duplicate geometry in collection %p == %p", col->geoms[i], geom);
225       return col;
226     }
227   }
228   }
229 #endif
230 
231   col->geoms[col->ngeoms] = (RTGEOM*)geom;
232   col->ngeoms++;
233   return col;
234 }
235 
236 
237 RTCOLLECTION *
rtcollection_segmentize2d(const RTCTX * ctx,RTCOLLECTION * col,double dist)238 rtcollection_segmentize2d(const RTCTX *ctx, RTCOLLECTION *col, double dist)
239 {
240   uint32_t i;
241   RTGEOM **newgeoms;
242 
243   if ( ! col->ngeoms ) return rtcollection_clone(ctx, col);
244 
245   newgeoms = rtalloc(ctx, sizeof(RTGEOM *)*col->ngeoms);
246   for (i=0; i<col->ngeoms; i++)
247   {
248     newgeoms[i] = rtgeom_segmentize2d(ctx, col->geoms[i], dist);
249     if ( ! newgeoms[i] ) {
250       while (i--) rtgeom_free(ctx, newgeoms[i]);
251       rtfree(ctx, newgeoms);
252       return NULL;
253     }
254   }
255 
256   return rtcollection_construct(ctx, col->type, col->srid, NULL, col->ngeoms, newgeoms);
257 }
258 
259 /** @brief check for same geometry composition
260  *
261  */
262 char
rtcollection_same(const RTCTX * ctx,const RTCOLLECTION * c1,const RTCOLLECTION * c2)263 rtcollection_same(const RTCTX *ctx, const RTCOLLECTION *c1, const RTCOLLECTION *c2)
264 {
265   uint32_t i;
266 
267   RTDEBUG(ctx, 2, "rtcollection_same called");
268 
269   if ( c1->type != c2->type ) return RT_FALSE;
270   if ( c1->ngeoms != c2->ngeoms ) return RT_FALSE;
271 
272   for ( i = 0; i < c1->ngeoms; i++ )
273   {
274     if ( ! rtgeom_same(ctx, c1->geoms[i], c2->geoms[i]) )
275       return RT_FALSE;
276   }
277 
278   /* Former method allowed out-of-order equality between collections
279 
280     hit = rtalloc(ctx, sizeof(uint32_t)*c1->ngeoms);
281     memset(hit, 0, sizeof(uint32_t)*c1->ngeoms);
282 
283     for (i=0; i<c1->ngeoms; i++)
284     {
285       char found=0;
286       for (j=0; j<c2->ngeoms; j++)
287       {
288         if ( hit[j] ) continue;
289         if ( rtgeom_same(ctx, c1->geoms[i], c2->geoms[j]) )
290         {
291           hit[j] = 1;
292           found=1;
293           break;
294         }
295       }
296       if ( ! found ) return RT_FALSE;
297     }
298   */
299 
300   return RT_TRUE;
301 }
302 
rtcollection_ngeoms(const RTCTX * ctx,const RTCOLLECTION * col)303 int rtcollection_ngeoms(const RTCTX *ctx, const RTCOLLECTION *col)
304 {
305   int i;
306   int ngeoms = 0;
307 
308   if ( ! col )
309   {
310     rterror(ctx, "Null input geometry.");
311     return 0;
312   }
313 
314   for ( i = 0; i < col->ngeoms; i++ )
315   {
316     if ( col->geoms[i])
317     {
318       switch (col->geoms[i]->type)
319       {
320       case RTPOINTTYPE:
321       case RTLINETYPE:
322       case RTCIRCSTRINGTYPE:
323       case RTPOLYGONTYPE:
324         ngeoms += 1;
325         break;
326       case RTMULTIPOINTTYPE:
327       case RTMULTILINETYPE:
328       case RTMULTICURVETYPE:
329       case RTMULTIPOLYGONTYPE:
330         ngeoms += col->ngeoms;
331         break;
332       case RTCOLLECTIONTYPE:
333         ngeoms += rtcollection_ngeoms(ctx, (RTCOLLECTION*)col->geoms[i]);
334         break;
335       }
336     }
337   }
338   return ngeoms;
339 }
340 
rtcollection_free(const RTCTX * ctx,RTCOLLECTION * col)341 void rtcollection_free(const RTCTX *ctx, RTCOLLECTION *col)
342 {
343   int i;
344   if ( ! col ) return;
345 
346   if ( col->bbox )
347   {
348     rtfree(ctx, col->bbox);
349   }
350   for ( i = 0; i < col->ngeoms; i++ )
351   {
352     RTDEBUGF(ctx, 4,"freeing geom[%d]", i);
353     if ( col->geoms && col->geoms[i] )
354       rtgeom_free(ctx, col->geoms[i]);
355   }
356   if ( col->geoms )
357   {
358     rtfree(ctx, col->geoms);
359   }
360   rtfree(ctx, col);
361 }
362 
363 
364 /**
365 * Takes a potentially heterogeneous collection and returns a homogeneous
366 * collection consisting only of the specified type.
367 */
rtcollection_extract(const RTCTX * ctx,RTCOLLECTION * col,int type)368 RTCOLLECTION* rtcollection_extract(const RTCTX *ctx, RTCOLLECTION *col, int type)
369 {
370   int i = 0;
371   RTGEOM **geomlist;
372   RTCOLLECTION *outcol;
373   int geomlistsize = 16;
374   int geomlistlen = 0;
375   uint8_t outtype;
376 
377   if ( ! col ) return NULL;
378 
379   switch (type)
380   {
381   case RTPOINTTYPE:
382     outtype = RTMULTIPOINTTYPE;
383     break;
384   case RTLINETYPE:
385     outtype = RTMULTILINETYPE;
386     break;
387   case RTPOLYGONTYPE:
388     outtype = RTMULTIPOLYGONTYPE;
389     break;
390   default:
391     rterror(ctx, "Only POLYGON, LINESTRING and POINT are supported by rtcollection_extract. %s requested.", rttype_name(ctx, type));
392     return NULL;
393   }
394 
395   geomlist = rtalloc(ctx, sizeof(RTGEOM*) * geomlistsize);
396 
397   /* Process each sub-geometry */
398   for ( i = 0; i < col->ngeoms; i++ )
399   {
400     int subtype = col->geoms[i]->type;
401     /* Don't bother adding empty sub-geometries */
402     if ( rtgeom_is_empty(ctx, col->geoms[i]) )
403     {
404       continue;
405     }
406     /* Copy our sub-types into the output list */
407     if ( subtype == type )
408     {
409       /* We've over-run our buffer, double the memory segment */
410       if ( geomlistlen == geomlistsize )
411       {
412         geomlistsize *= 2;
413         geomlist = rtrealloc(ctx, geomlist, sizeof(RTGEOM*) * geomlistsize);
414       }
415       geomlist[geomlistlen] = rtgeom_clone(ctx, col->geoms[i]);
416       geomlistlen++;
417     }
418     /* Recurse into sub-collections */
419     if ( rttype_is_collection(ctx,  subtype ) )
420     {
421       int j = 0;
422       RTCOLLECTION *tmpcol = rtcollection_extract(ctx, (RTCOLLECTION*)col->geoms[i], type);
423       for ( j = 0; j < tmpcol->ngeoms; j++ )
424       {
425         /* We've over-run our buffer, double the memory segment */
426         if ( geomlistlen == geomlistsize )
427         {
428           geomlistsize *= 2;
429           geomlist = rtrealloc(ctx, geomlist, sizeof(RTGEOM*) * geomlistsize);
430         }
431         geomlist[geomlistlen] = tmpcol->geoms[j];
432         geomlistlen++;
433       }
434       rtfree(ctx, tmpcol);
435     }
436   }
437 
438   if ( geomlistlen > 0 )
439   {
440     RTGBOX gbox;
441     outcol = rtcollection_construct(ctx, outtype, col->srid, NULL, geomlistlen, geomlist);
442     rtgeom_calculate_gbox(ctx, (RTGEOM *) outcol, &gbox);
443     outcol->bbox = gbox_copy(ctx, &gbox);
444   }
445   else
446   {
447     rtfree(ctx, geomlist);
448     outcol = rtcollection_construct_empty(ctx, outtype, col->srid, RTFLAGS_GET_Z(col->flags), RTFLAGS_GET_M(col->flags));
449   }
450 
451   return outcol;
452 }
453 
454 RTGEOM*
rtcollection_remove_repeated_points(const RTCTX * ctx,const RTCOLLECTION * coll,double tolerance)455 rtcollection_remove_repeated_points(const RTCTX *ctx, const RTCOLLECTION *coll, double tolerance)
456 {
457   uint32_t i;
458   RTGEOM **newgeoms;
459 
460   newgeoms = rtalloc(ctx, sizeof(RTGEOM *)*coll->ngeoms);
461   for (i=0; i<coll->ngeoms; i++)
462   {
463     newgeoms[i] = rtgeom_remove_repeated_points(ctx, coll->geoms[i], tolerance);
464   }
465 
466   return (RTGEOM*)rtcollection_construct(ctx, coll->type,
467                                          coll->srid, coll->bbox ? gbox_copy(ctx, coll->bbox) : NULL,
468                                          coll->ngeoms, newgeoms);
469 }
470 
471 
472 RTCOLLECTION*
rtcollection_force_dims(const RTCTX * ctx,const RTCOLLECTION * col,int hasz,int hasm)473 rtcollection_force_dims(const RTCTX *ctx, const RTCOLLECTION *col, int hasz, int hasm)
474 {
475   RTCOLLECTION *colout;
476 
477   /* Return 2D empty */
478   if( rtcollection_is_empty(ctx, col) )
479   {
480     colout = rtcollection_construct_empty(ctx, col->type, col->srid, hasz, hasm);
481   }
482   else
483   {
484     int i;
485     RTGEOM **geoms = NULL;
486     geoms = rtalloc(ctx, sizeof(RTGEOM*) * col->ngeoms);
487     for( i = 0; i < col->ngeoms; i++ )
488     {
489       geoms[i] = rtgeom_force_dims(ctx, col->geoms[i], hasz, hasm);
490     }
491     colout = rtcollection_construct(ctx, col->type, col->srid, NULL, col->ngeoms, geoms);
492   }
493   return colout;
494 }
495 
rtcollection_is_empty(const RTCTX * ctx,const RTCOLLECTION * col)496 int rtcollection_is_empty(const RTCTX *ctx, const RTCOLLECTION *col)
497 {
498   int i;
499   if ( (col->ngeoms == 0) || (!col->geoms) )
500     return RT_TRUE;
501   for( i = 0; i < col->ngeoms; i++ )
502   {
503     if ( ! rtgeom_is_empty(ctx, col->geoms[i]) ) return RT_FALSE;
504   }
505   return RT_TRUE;
506 }
507 
508 
rtcollection_count_vertices(const RTCTX * ctx,RTCOLLECTION * col)509 int rtcollection_count_vertices(const RTCTX *ctx, RTCOLLECTION *col)
510 {
511   int i = 0;
512   int v = 0; /* vertices */
513   assert(col);
514   for ( i = 0; i < col->ngeoms; i++ )
515   {
516     v += rtgeom_count_vertices(ctx, col->geoms[i]);
517   }
518   return v;
519 }
520 
rtcollection_simplify(const RTCTX * ctx,const RTCOLLECTION * igeom,double dist,int preserve_collapsed)521 RTCOLLECTION* rtcollection_simplify(const RTCTX *ctx, const RTCOLLECTION *igeom, double dist, int preserve_collapsed)
522 {
523    int i;
524   RTCOLLECTION *out = rtcollection_construct_empty(ctx, igeom->type, igeom->srid, RTFLAGS_GET_Z(igeom->flags), RTFLAGS_GET_M(igeom->flags));
525 
526   if( rtcollection_is_empty(ctx, igeom) )
527     return out; /* should we return NULL instead ? */
528 
529   for( i = 0; i < igeom->ngeoms; i++ )
530   {
531     RTGEOM *ngeom = rtgeom_simplify(ctx, igeom->geoms[i], dist, preserve_collapsed);
532     if ( ngeom ) out = rtcollection_add_rtgeom(ctx, out, ngeom);
533   }
534 
535   return out;
536 }
537 
rtcollection_allows_subtype(const RTCTX * ctx,int collectiontype,int subtype)538 int rtcollection_allows_subtype(const RTCTX *ctx, int collectiontype, int subtype)
539 {
540   if ( collectiontype == RTCOLLECTIONTYPE )
541     return RT_TRUE;
542   if ( collectiontype == RTMULTIPOINTTYPE &&
543           subtype == RTPOINTTYPE )
544     return RT_TRUE;
545   if ( collectiontype == RTMULTILINETYPE &&
546           subtype == RTLINETYPE )
547     return RT_TRUE;
548   if ( collectiontype == RTMULTIPOLYGONTYPE &&
549           subtype == RTPOLYGONTYPE )
550     return RT_TRUE;
551   if ( collectiontype == RTCOMPOUNDTYPE &&
552           (subtype == RTLINETYPE || subtype == RTCIRCSTRINGTYPE) )
553     return RT_TRUE;
554   if ( collectiontype == RTCURVEPOLYTYPE &&
555           (subtype == RTCIRCSTRINGTYPE || subtype == RTLINETYPE || subtype == RTCOMPOUNDTYPE) )
556     return RT_TRUE;
557   if ( collectiontype == RTMULTICURVETYPE &&
558           (subtype == RTCIRCSTRINGTYPE || subtype == RTLINETYPE || subtype == RTCOMPOUNDTYPE) )
559     return RT_TRUE;
560   if ( collectiontype == RTMULTISURFACETYPE &&
561           (subtype == RTPOLYGONTYPE || subtype == RTCURVEPOLYTYPE) )
562     return RT_TRUE;
563   if ( collectiontype == RTPOLYHEDRALSURFACETYPE &&
564           subtype == RTPOLYGONTYPE )
565     return RT_TRUE;
566   if ( collectiontype == RTTINTYPE &&
567           subtype == RTTRIANGLETYPE )
568     return RT_TRUE;
569 
570   /* Must be a bad combination! */
571   return RT_FALSE;
572 }
573 
574 int
rtcollection_startpoint(const RTCTX * ctx,const RTCOLLECTION * col,RTPOINT4D * pt)575 rtcollection_startpoint(const RTCTX *ctx, const RTCOLLECTION* col, RTPOINT4D* pt)
576 {
577   if ( col->ngeoms < 1 )
578     return RT_FAILURE;
579 
580   return rtgeom_startpoint(ctx, col->geoms[0], pt);
581 }
582 
583 
rtcollection_grid(const RTCTX * ctx,const RTCOLLECTION * coll,const gridspec * grid)584 RTCOLLECTION* rtcollection_grid(const RTCTX *ctx, const RTCOLLECTION *coll, const gridspec *grid)
585 {
586   uint32_t i;
587   RTCOLLECTION *newcoll;
588 
589   newcoll = rtcollection_construct_empty(ctx, coll->type, coll->srid, rtgeom_has_z(ctx, (RTGEOM*)coll), rtgeom_has_m(ctx, (RTGEOM*)coll));
590 
591   for (i=0; i<coll->ngeoms; i++)
592   {
593     RTGEOM *g = rtgeom_grid(ctx, coll->geoms[i], grid);
594     if ( g )
595       rtcollection_add_rtgeom(ctx, newcoll, g);
596   }
597 
598   return newcoll;
599 }
600