1
2 ///////////////////////////////////////////////////////////
3 // //
4 // SAGA //
5 // //
6 // System for Automated Geoscientific Analyses //
7 // //
8 // Application Programming Interface //
9 // //
10 // Library: SAGA_API //
11 // //
12 //-------------------------------------------------------//
13 // //
14 // shapes.cpp //
15 // //
16 // Copyright (C) 2005 by Olaf Conrad //
17 // //
18 //-------------------------------------------------------//
19 // //
20 // This file is part of 'SAGA - System for Automated //
21 // Geoscientific Analyses'. //
22 // //
23 // This library is free software; you can redistribute //
24 // it and/or modify it under the terms of the GNU Lesser //
25 // General Public License as published by the Free //
26 // Software Foundation, either version 2.1 of the //
27 // License, or (at your option) any later version. //
28 // //
29 // This library is distributed in the hope that it will //
30 // be useful, but WITHOUT ANY WARRANTY; without even the //
31 // implied warranty of MERCHANTABILITY or FITNESS FOR A //
32 // PARTICULAR PURPOSE. See the GNU Lesser General Public //
33 // License for more details. //
34 // //
35 // You should have received a copy of the GNU Lesser //
36 // General Public License along with this program; if //
37 // not, see <http://www.gnu.org/licenses/>. //
38 // //
39 //-------------------------------------------------------//
40 // //
41 // contact: Olaf Conrad //
42 // Institute of Geography //
43 // University of Goettingen //
44 // Goldschmidtstr. 5 //
45 // 37077 Goettingen //
46 // Germany //
47 // //
48 // e-mail: oconrad@saga-gis.org //
49 // //
50 ///////////////////////////////////////////////////////////
51
52 //---------------------------------------------------------
53
54
55 ///////////////////////////////////////////////////////////
56 // //
57 // //
58 // //
59 ///////////////////////////////////////////////////////////
60
61 //---------------------------------------------------------
62 #include "shapes.h"
63 #include "pointcloud.h"
64 #include "tool_library.h"
65
66
67 ///////////////////////////////////////////////////////////
68 // //
69 // //
70 // //
71 ///////////////////////////////////////////////////////////
72
73 //---------------------------------------------------------
SG_Get_ShapeType_Name(TSG_Shape_Type Type)74 CSG_String SG_Get_ShapeType_Name(TSG_Shape_Type Type)
75 {
76 switch( Type )
77 {
78 case SHAPE_TYPE_Point : return( _TL("Point" ) );
79 case SHAPE_TYPE_Points : return( _TL("Points" ) );
80 case SHAPE_TYPE_Line : return( _TL("Line" ) );
81 case SHAPE_TYPE_Polygon: return( _TL("Polygon" ) );
82 default : return( _TL("Undefined") );
83 }
84 }
85
86
87 ///////////////////////////////////////////////////////////
88 // //
89 // //
90 // //
91 ///////////////////////////////////////////////////////////
92
93 //---------------------------------------------------------
SG_Create_Shapes(void)94 CSG_Shapes * SG_Create_Shapes(void)
95 {
96 return( new CSG_Shapes );
97 }
98
99 //---------------------------------------------------------
SG_Create_Shapes(const CSG_Shapes & Shapes)100 CSG_Shapes * SG_Create_Shapes(const CSG_Shapes &Shapes)
101 {
102 switch( Shapes.Get_ObjectType() )
103 {
104 case SG_DATAOBJECT_TYPE_Shapes:
105 return( new CSG_Shapes(Shapes) );
106
107 case SG_DATAOBJECT_TYPE_PointCloud:
108 return( SG_Create_PointCloud(*((CSG_PointCloud *)&Shapes)) );
109
110 default:
111 return( NULL );
112 }
113 }
114
115 //---------------------------------------------------------
SG_Create_Shapes(const CSG_String & File_Name)116 CSG_Shapes * SG_Create_Shapes(const CSG_String &File_Name)
117 {
118 return( new CSG_Shapes(File_Name) );
119 }
120
121 //---------------------------------------------------------
SG_Create_Shapes(TSG_Shape_Type Type,const SG_Char * Name,CSG_Table * pStructure,TSG_Vertex_Type Vertex_Type)122 CSG_Shapes * SG_Create_Shapes(TSG_Shape_Type Type, const SG_Char *Name, CSG_Table *pStructure, TSG_Vertex_Type Vertex_Type)
123 {
124 return( new CSG_Shapes(Type, Name, pStructure, Vertex_Type) );
125 }
126
127 //---------------------------------------------------------
SG_Create_Shapes(CSG_Shapes * pTemplate)128 CSG_Shapes * SG_Create_Shapes(CSG_Shapes *pTemplate)
129 {
130 if( pTemplate )
131 {
132 switch( pTemplate->Get_ObjectType() )
133 {
134 case SG_DATAOBJECT_TYPE_Shapes:
135 return( new CSG_Shapes(pTemplate->Get_Type(), pTemplate->Get_Name(), pTemplate, pTemplate->Get_Vertex_Type()) );
136
137 case SG_DATAOBJECT_TYPE_PointCloud:
138 return( SG_Create_PointCloud((CSG_PointCloud *)pTemplate) );
139
140 default:
141 break;
142 }
143 }
144
145 return( new CSG_Shapes() );
146 }
147
148
149 ///////////////////////////////////////////////////////////
150 // //
151 // //
152 // //
153 ///////////////////////////////////////////////////////////
154
155 //---------------------------------------------------------
CSG_Shapes(void)156 CSG_Shapes::CSG_Shapes(void)
157 : CSG_Table()
158 {
159 _On_Construction();
160 }
161
162 //---------------------------------------------------------
CSG_Shapes(const CSG_Shapes & Shapes)163 CSG_Shapes::CSG_Shapes(const CSG_Shapes &Shapes)
164 : CSG_Table()
165 {
166 _On_Construction();
167
168 Create(Shapes);
169 }
170
171 //---------------------------------------------------------
CSG_Shapes(const CSG_String & File_Name)172 CSG_Shapes::CSG_Shapes(const CSG_String &File_Name)
173 : CSG_Table()
174 {
175 _On_Construction();
176
177 Create(File_Name);
178 }
179
180 //---------------------------------------------------------
CSG_Shapes(TSG_Shape_Type Type,const SG_Char * Name,CSG_Table * pStructure,TSG_Vertex_Type Vertex_Type)181 CSG_Shapes::CSG_Shapes(TSG_Shape_Type Type, const SG_Char *Name, CSG_Table *pStructure, TSG_Vertex_Type Vertex_Type)
182 : CSG_Table()
183 {
184 _On_Construction();
185
186 Create(Type, Name, pStructure, Vertex_Type);
187 }
188
189
190 ///////////////////////////////////////////////////////////
191 // //
192 // //
193 // //
194 ///////////////////////////////////////////////////////////
195
196 //---------------------------------------------------------
_On_Construction(void)197 void CSG_Shapes::_On_Construction(void)
198 {
199 CSG_Table::_On_Construction();
200
201 m_Type = SHAPE_TYPE_Undefined;
202 m_Vertex_Type = SG_VERTEX_TYPE_XY;
203
204 m_Encoding = SG_FILE_ENCODING_UTF8;
205 }
206
207
208 ///////////////////////////////////////////////////////////
209 // //
210 // //
211 // //
212 ///////////////////////////////////////////////////////////
213
214 //---------------------------------------------------------
Create(const CSG_Shapes & Shapes)215 bool CSG_Shapes::Create(const CSG_Shapes &Shapes)
216 {
217 return( Assign((CSG_Data_Object *)&Shapes) );
218 }
219
220 //---------------------------------------------------------
Create(const CSG_String & File_Name)221 bool CSG_Shapes::Create(const CSG_String &File_Name)
222 {
223 Destroy();
224
225 SG_UI_Msg_Add(CSG_String::Format("%s %s: %s...", _TL("Loading"), _TL("shapes"), File_Name.c_str()), true);
226
227 bool bResult = false;
228
229 //-----------------------------------------------------
230 if( File_Name.BeforeFirst(':').Cmp("PGSQL") == 0 ) // database source
231 {
232 CSG_String s(File_Name);
233
234 s = s.AfterFirst(':'); CSG_String Host (s.BeforeFirst(':'));
235 s = s.AfterFirst(':'); CSG_String Port (s.BeforeFirst(':'));
236 s = s.AfterFirst(':'); CSG_String DBName(s.BeforeFirst(':'));
237 s = s.AfterFirst(':'); CSG_String Table (s.BeforeFirst(':'));
238
239 CSG_Tool *pTool = SG_Get_Tool_Library_Manager().Create_Tool("db_pgsql", 0, true); // CGet_Connections
240
241 if( pTool != NULL )
242 {
243 SG_UI_ProgressAndMsg_Lock(true);
244
245 //---------------------------------------------
246 CSG_Table Connections;
247 CSG_String Connection = DBName + " [" + Host + ":" + Port + "]";
248
249 pTool->Set_Manager(NULL);
250 pTool->On_Before_Execution();
251
252 if( SG_TOOL_PARAMETER_SET("CONNECTIONS", &Connections) && pTool->Execute() ) // CGet_Connections
253 {
254 for(int i=0; !bResult && i<Connections.Get_Count(); i++)
255 {
256 if( !Connection.Cmp(Connections[i].asString(0)) )
257 {
258 bResult = true;
259 }
260 }
261 }
262
263 SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
264
265 //---------------------------------------------
266 if( bResult && (bResult = (pTool = SG_Get_Tool_Library_Manager().Create_Tool("db_pgsql", 20, true)) != NULL) == true ) // CPGIS_Shapes_Load
267 {
268 pTool->Set_Manager(NULL);
269 pTool->On_Before_Execution();
270
271 bResult = SG_TOOL_PARAMETER_SET("CONNECTION", Connection)
272 && SG_TOOL_PARAMETER_SET("TABLES" , Table)
273 && SG_TOOL_PARAMETER_SET("SHAPES" , this)
274 && pTool->Execute();
275
276 SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
277 }
278
279 SG_UI_ProgressAndMsg_Lock(false);
280 }
281 }
282 else
283 {
284 bResult = _Load_ESRI(File_Name) || _Load_GDAL(File_Name);
285 }
286
287 //-----------------------------------------------------
288 if( bResult )
289 {
290 Set_Modified(false);
291 Set_Update_Flag();
292
293 SG_UI_Process_Set_Ready();
294 SG_UI_Msg_Add(_TL("okay"), false, SG_UI_MSG_STYLE_SUCCESS);
295
296 return( true );
297 }
298
299 //-----------------------------------------------------
300 for(int iShape=Get_Count()-1; iShape>=0; iShape--) // be kind, keep at least those shapes that have been loaded successfully
301 {
302 if( !Get_Shape(iShape)->is_Valid() )
303 {
304 Del_Shape(iShape);
305 }
306 }
307
308 if( Get_Count() <= 0 )
309 {
310 Destroy();
311 }
312
313 //-----------------------------------------------------
314 SG_UI_Process_Set_Ready();
315 SG_UI_Msg_Add(_TL("failed"), false, SG_UI_MSG_STYLE_FAILURE);
316
317 return( false );
318 }
319
320 //---------------------------------------------------------
Create(TSG_Shape_Type Type,const SG_Char * Name,CSG_Table * pStructure,TSG_Vertex_Type Vertex_Type)321 bool CSG_Shapes::Create(TSG_Shape_Type Type, const SG_Char *Name, CSG_Table *pStructure, TSG_Vertex_Type Vertex_Type)
322 {
323 Destroy();
324
325 CSG_Table::Create(pStructure);
326
327 if( Name )
328 {
329 Set_Name(CSG_String(Name));
330 }
331
332 m_Type = Type;
333 m_Vertex_Type = Vertex_Type;
334
335 return( true );
336 }
337
338
339 ///////////////////////////////////////////////////////////
340 // //
341 // //
342 // //
343 ///////////////////////////////////////////////////////////
344
345 //---------------------------------------------------------
~CSG_Shapes(void)346 CSG_Shapes::~CSG_Shapes(void)
347 {
348 Destroy();
349 }
350
351 //---------------------------------------------------------
Destroy(void)352 bool CSG_Shapes::Destroy(void)
353 {
354 return( CSG_Table::Destroy() );
355 }
356
357
358 ///////////////////////////////////////////////////////////
359 // //
360 // //
361 // //
362 ///////////////////////////////////////////////////////////
363
364 //---------------------------------------------------------
Assign(CSG_Data_Object * pObject)365 bool CSG_Shapes::Assign(CSG_Data_Object *pObject)
366 {
367 if( pObject && pObject->is_Valid() && (pObject->Get_ObjectType() == SG_DATAOBJECT_TYPE_Shapes || pObject->Get_ObjectType() == SG_DATAOBJECT_TYPE_PointCloud) )
368 {
369 CSG_Shapes *pShapes = (CSG_Shapes *)pObject;
370
371 Create(pShapes->Get_Type(), pShapes->Get_Name(), pShapes, pShapes->Get_Vertex_Type());
372
373 Get_History() = pShapes->Get_History();
374
375 Get_Projection().Create(pShapes->Get_Projection());
376
377 for(int iShape=0; iShape<pShapes->Get_Count() && SG_UI_Process_Get_Okay(); iShape++)
378 {
379 Add_Shape(pShapes->Get_Shape(iShape));
380 }
381
382 return( true );
383 }
384
385 return( false );
386 }
387
388
389 ///////////////////////////////////////////////////////////
390 // //
391 // //
392 // //
393 ///////////////////////////////////////////////////////////
394
395 //---------------------------------------------------------
396 static TSG_Shape_File_Format gSG_Shape_File_Format_Default = SHAPE_FILE_FORMAT_ESRI;
397
398 //---------------------------------------------------------
SG_Shapes_Set_File_Format_Default(int Format)399 bool SG_Shapes_Set_File_Format_Default (int Format)
400 {
401 switch( Format )
402 {
403 case SHAPE_FILE_FORMAT_ESRI :
404 case SHAPE_FILE_FORMAT_GeoPackage:
405 case SHAPE_FILE_FORMAT_GeoJSON :
406 gSG_Shape_File_Format_Default = (TSG_Shape_File_Format)Format;
407 return( true );
408 }
409
410 return( false );
411 }
412
413 //---------------------------------------------------------
SG_Shapes_Get_File_Format_Default(void)414 TSG_Shape_File_Format SG_Shapes_Get_File_Format_Default (void)
415 {
416 return( gSG_Shape_File_Format_Default );
417 }
418
419 //---------------------------------------------------------
SG_Shapes_Get_File_Extension_Default(void)420 CSG_String SG_Shapes_Get_File_Extension_Default (void)
421 {
422 switch( gSG_Shape_File_Format_Default )
423 {
424 default:
425 case SHAPE_FILE_FORMAT_ESRI : return( "shp" );
426 case SHAPE_FILE_FORMAT_GeoPackage: return( "gpkg" );
427 case SHAPE_FILE_FORMAT_GeoJSON : return( "geojson" );
428 }
429 }
430
431
432 ///////////////////////////////////////////////////////////
433 // //
434 ///////////////////////////////////////////////////////////
435
436 //---------------------------------------------------------
Save(const CSG_String & File_Name,int Format)437 bool CSG_Shapes::Save(const CSG_String &File_Name, int Format)
438 {
439 SG_UI_Msg_Add(CSG_String::Format("%s %s: %s...", _TL("Saving"), _TL("shapes"), File_Name.c_str()), true);
440
441 //-----------------------------------------------------
442 if( Format == SHAPE_FILE_FORMAT_Undefined )
443 {
444 Format = gSG_Shape_File_Format_Default;
445
446 if( SG_File_Cmp_Extension(File_Name, "shp" ) ) Format = SHAPE_FILE_FORMAT_ESRI ;
447 if( SG_File_Cmp_Extension(File_Name, "gpkg" ) ) Format = SHAPE_FILE_FORMAT_GeoPackage;
448 if( SG_File_Cmp_Extension(File_Name, "geojson") ) Format = SHAPE_FILE_FORMAT_GeoJSON ;
449 }
450
451 //-----------------------------------------------------
452 bool bResult = false;
453
454 switch( Format )
455 {
456 case SHAPE_FILE_FORMAT_ESRI : bResult = _Save_ESRI(File_Name ); break;
457 case SHAPE_FILE_FORMAT_GeoPackage: bResult = _Save_GDAL(File_Name, "GPKG" ); break;
458 case SHAPE_FILE_FORMAT_GeoJSON : bResult = _Save_GDAL(File_Name, "GeoJSON"); break;
459 }
460
461 //-----------------------------------------------------
462 if( bResult )
463 {
464 Set_Modified(false);
465
466 Set_File_Name(File_Name, true);
467
468 SG_UI_Process_Set_Ready();
469 SG_UI_Msg_Add(_TL("okay"), false, SG_UI_MSG_STYLE_SUCCESS);
470
471 return( true );
472 }
473
474 SG_UI_Process_Set_Ready();
475 SG_UI_Msg_Add(_TL("failed"), false, SG_UI_MSG_STYLE_FAILURE);
476
477 return( false );
478 }
479
480
481 ///////////////////////////////////////////////////////////
482 // //
483 // //
484 // //
485 ///////////////////////////////////////////////////////////
486
487 //---------------------------------------------------------
_Get_New_Record(int Index)488 CSG_Table_Record * CSG_Shapes::_Get_New_Record(int Index)
489 {
490 switch( m_Type )
491 {
492 case SHAPE_TYPE_Point:
493 switch( m_Vertex_Type )
494 {
495 case SG_VERTEX_TYPE_XY: default:
496 return( new CSG_Shape_Point (this, Index) );
497
498 case SG_VERTEX_TYPE_XYZ:
499 return( new CSG_Shape_Point_Z (this, Index) );
500
501 case SG_VERTEX_TYPE_XYZM:
502 return( new CSG_Shape_Point_ZM (this, Index) );
503 }
504
505 case SHAPE_TYPE_Points:
506 return( new CSG_Shape_Points (this, Index) );
507
508 case SHAPE_TYPE_Line:
509 return( new CSG_Shape_Line (this, Index) );
510
511 case SHAPE_TYPE_Polygon:
512 return( new CSG_Shape_Polygon (this, Index) );
513
514 default:
515 return( NULL );
516 }
517 }
518
519 //---------------------------------------------------------
Add_Shape(CSG_Table_Record * pCopy,TSG_ADD_Shape_Copy_Mode mCopy)520 CSG_Shape * CSG_Shapes::Add_Shape(CSG_Table_Record *pCopy, TSG_ADD_Shape_Copy_Mode mCopy)
521 {
522 CSG_Shape *pShape = (CSG_Shape *)Add_Record();
523
524 if( pShape && pCopy )
525 {
526 if( (mCopy == SHAPE_COPY || mCopy == SHAPE_COPY_ATTR) )
527 {
528 ((CSG_Table_Record *)pShape)->Assign(pCopy);
529 }
530
531 if( (mCopy == SHAPE_COPY || mCopy == SHAPE_COPY_GEOM) && pCopy->Get_Table()->Get_ObjectType() == SG_DATAOBJECT_TYPE_Shapes )
532 {
533 pShape->Assign((CSG_Shape *)pCopy, false);
534 }
535 }
536
537 return( pShape );
538 }
539
540 //---------------------------------------------------------
Del_Shape(CSG_Shape * pShape)541 bool CSG_Shapes::Del_Shape(CSG_Shape *pShape)
542 {
543 return( Del_Record(pShape->Get_Index()) );
544 }
545
Del_Shape(int iShape)546 bool CSG_Shapes::Del_Shape(int iShape)
547 {
548 return( Del_Record(iShape) );
549 }
550
551
552 ///////////////////////////////////////////////////////////
553 // //
554 // //
555 // //
556 ///////////////////////////////////////////////////////////
557
558 //---------------------------------------------------------
On_Update(void)559 bool CSG_Shapes::On_Update(void)
560 {
561 if( Get_Count() > 0 )
562 {
563 CSG_Shape *pShape = Get_Shape(0);
564
565 m_Extent = pShape->Get_Extent();
566 m_ZMin = pShape->Get_ZMin();
567 m_ZMax = pShape->Get_ZMax();
568 m_MMin = pShape->Get_MMin();
569 m_MMax = pShape->Get_MMax();
570
571 for(int i=1; i<Get_Count(); i++)
572 {
573 pShape = Get_Shape(i);
574
575 m_Extent.Union(pShape->Get_Extent());
576
577 switch( m_Vertex_Type )
578 {
579 case SG_VERTEX_TYPE_XYZM:
580 if( m_MMin > pShape->Get_MMin() ) m_MMin = pShape->Get_MMin();
581 if( m_MMax < pShape->Get_MMax() ) m_MMax = pShape->Get_MMax();
582
583 case SG_VERTEX_TYPE_XYZ:
584 if( m_ZMin > pShape->Get_ZMin() ) m_ZMin = pShape->Get_ZMin();
585 if( m_ZMax < pShape->Get_ZMax() ) m_ZMax = pShape->Get_ZMax();
586 break;
587 default:
588 break;
589 }
590 }
591 }
592 else
593 {
594 m_Extent.Assign(0.0, 0.0, 0.0, 0.0);
595 }
596
597 return( CSG_Table::On_Update() );
598 }
599
600
601 ///////////////////////////////////////////////////////////
602 // //
603 // //
604 // //
605 ///////////////////////////////////////////////////////////
606
607 //---------------------------------------------------------
Get_Shape(const TSG_Point & Point,double Epsilon)608 CSG_Shape * CSG_Shapes::Get_Shape(const TSG_Point &Point, double Epsilon)
609 {
610 CSG_Rect r(Point.x - Epsilon, Point.y - Epsilon, Point.x + Epsilon, Point.y + Epsilon);
611
612 CSG_Shape *pNearest = NULL;
613
614 if( r.Intersects(Get_Extent()) != INTERSECTION_None )
615 {
616 double dNearest = -1.;
617
618 for(int iShape=0; iShape<Get_Count(); iShape++)
619 {
620 CSG_Shape *pShape = Get_Shape(iShape);
621
622 if( pShape->Intersects(r) )
623 {
624 for(int iPart=0; iPart<pShape->Get_Part_Count(); iPart++)
625 {
626 if( r.Intersects(pShape->Get_Extent(iPart)) )
627 {
628 double d = pShape->Get_Distance(Point, iPart);
629
630 if( d == 0. )
631 {
632 return( pShape );
633 }
634 else if( d > 0. && d <= Epsilon && (pNearest == NULL || d < dNearest) )
635 {
636 dNearest = d;
637 pNearest = pShape;
638 }
639 }
640 }
641 }
642 }
643 }
644
645 return( pNearest );
646 }
647
648
649 ///////////////////////////////////////////////////////////
650 // //
651 // //
652 // //
653 ///////////////////////////////////////////////////////////
654
655 //---------------------------------------------------------
Make_Clean(void)656 bool CSG_Shapes::Make_Clean(void)
657 {
658 if( m_Type != SHAPE_TYPE_Polygon )
659 {
660 return( true );
661 }
662
663 for(int iShape=0; iShape<Get_Count() && SG_UI_Process_Set_Progress(iShape, Get_Count()); iShape++)
664 {
665 CSG_Shape_Polygon *pPolygon = (CSG_Shape_Polygon *)Get_Shape(iShape);
666
667 for(int iPart=0; iPart<pPolygon->Get_Part_Count(); iPart++)
668 {
669 if( m_Vertex_Type == SG_VERTEX_TYPE_XY ) // currently we have to disable this check for 3D shapefiles since the
670 // _Update_Area() method can not handle polygons with no horizontal extent
671 {
672 //--------------------------------------------
673 // ring direction: outer rings > clockwise, inner rings (lakes) > counterclockwise !
674
675 if( (pPolygon->is_Lake(iPart) == pPolygon->is_Clockwise(iPart)) )
676 {
677 pPolygon->Revert_Points(iPart);
678 }
679 }
680
681 //--------------------------------------------
682 // last point == first point !
683
684 if( !CSG_Point(pPolygon->Get_Point(0, iPart)).is_Equal(pPolygon->Get_Point(pPolygon->Get_Point_Count(iPart) - 1, iPart)) )
685 {
686 ((CSG_Shape *)pPolygon)->Add_Point(pPolygon->Get_Point(0, iPart), iPart);
687
688 if( m_Vertex_Type != SG_VERTEX_TYPE_XY )
689 {
690 pPolygon->Set_Z(pPolygon->Get_Z(0, iPart), pPolygon->Get_Point_Count(iPart) - 1, iPart);
691
692 if( m_Vertex_Type == SG_VERTEX_TYPE_XYZM )
693 {
694 pPolygon->Set_M(pPolygon->Get_M(0, iPart), pPolygon->Get_Point_Count(iPart) - 1, iPart);
695 }
696 }
697 }
698
699 //--------------------------------------------
700 // no self intersection !
701
702 }
703 }
704
705 return( true );
706 }
707
708
709 ///////////////////////////////////////////////////////////
710 // //
711 // //
712 // //
713 ///////////////////////////////////////////////////////////
714
715 //---------------------------------------------------------
716