Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netedit/elements/additional/GNECalibrator.cpp
169684 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
4
// This program and the accompanying materials are made available under the
5
// terms of the Eclipse Public License 2.0 which is available at
6
// https://www.eclipse.org/legal/epl-2.0/
7
// This Source Code may also be made available under the following Secondary
8
// Licenses when the conditions for such availability set forth in the Eclipse
9
// Public License 2.0 are satisfied: GNU General Public License, version 2
10
// or later which is available at
11
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
/****************************************************************************/
14
/// @file GNECalibrator.cpp
15
/// @author Pablo Alvarez Lopez
16
/// @date Nov 2015
17
///
18
// Calibrator over edge or lane
19
/****************************************************************************/
20
#include <config.h>
21
22
#include <netedit/changes/GNEChange_Attribute.h>
23
#include <netedit/dialogs/elements/GNECalibratorDialog.h>
24
#include <netedit/GNENet.h>
25
#include <netedit/GNETagProperties.h>
26
#include <netedit/GNEUndoList.h>
27
#include <netedit/GNEViewNet.h>
28
#include <utils/gui/div/GLHelper.h>
29
#include <utils/gui/div/GUIGlobalViewObjectsHandler.h>
30
#include <utils/gui/globjects/GLIncludes.h>
31
#include <utils/xml/NamespaceIDs.h>
32
33
#include "GNECalibrator.h"
34
35
// ===========================================================================
36
// member method definitions
37
// ===========================================================================
38
39
GNECalibrator::GNECalibrator(SumoXMLTag tag, GNENet* net) :
40
GNEAdditional("", net, "", tag, ""),
41
myCalibratorContours(new std::vector<GNEContour*>()) {
42
}
43
44
45
GNECalibrator::GNECalibrator(const std::string& id, GNENet* net, const std::string& filename, GNEEdge* edge, double pos, SUMOTime frequency, const std::string& name,
46
const std::string& output, const double jamThreshold, const std::vector<std::string>& vTypes, const Parameterised::Map& parameters) :
47
GNEAdditional(id, net, filename, SUMO_TAG_CALIBRATOR, name),
48
Parameterised(parameters),
49
myPositionOverLane(pos),
50
myFrequency(frequency),
51
myOutput(output),
52
myJamThreshold(jamThreshold),
53
myVTypes(vTypes),
54
myCalibratorContours(new std::vector<GNEContour*>()) {
55
// set parents
56
setParent<GNEEdge*>(edge);
57
// update centering boundary without updating grid
58
updateCenteringBoundary(false);
59
}
60
61
62
GNECalibrator::GNECalibrator(const std::string& id, GNENet* net, const std::string& filename, GNEEdge* edge, double pos, SUMOTime frequency, const std::string& name,
63
const std::string& output, GNEAdditional* routeProbe, const double jamThreshold, const std::vector<std::string>& vTypes,
64
const Parameterised::Map& parameters) :
65
GNEAdditional(id, net, filename, SUMO_TAG_CALIBRATOR, name),
66
Parameterised(parameters),
67
myPositionOverLane(pos),
68
myFrequency(frequency),
69
myOutput(output),
70
myJamThreshold(jamThreshold),
71
myVTypes(vTypes),
72
myCalibratorContours(new std::vector<GNEContour*>()) {
73
// set parents
74
setParent<GNEEdge*>(edge);
75
setParent<GNEAdditional*>(routeProbe);
76
// update centering boundary without updating grid
77
updateCenteringBoundary(false);
78
}
79
80
81
GNECalibrator::GNECalibrator(const std::string& id, GNENet* net, const std::string& filename, GNELane* lane, double pos, SUMOTime frequency, const std::string& name,
82
const std::string& output, const double jamThreshold, const std::vector<std::string>& vTypes, const Parameterised::Map& parameters) :
83
GNEAdditional(id, net, filename, GNE_TAG_CALIBRATOR_LANE, name),
84
Parameterised(parameters),
85
myPositionOverLane(pos),
86
myFrequency(frequency),
87
myOutput(output),
88
myJamThreshold(jamThreshold),
89
myVTypes(vTypes),
90
myCalibratorContours(new std::vector<GNEContour*>()) {
91
// set parents
92
setParent<GNELane*>(lane);
93
// update centering boundary without updating grid
94
updateCenteringBoundary(false);
95
}
96
97
98
GNECalibrator::GNECalibrator(const std::string& id, GNENet* net, const std::string& filename, GNELane* lane, double pos, SUMOTime frequency, const std::string& name,
99
const std::string& output, GNEAdditional* routeProbe, const double jamThreshold, const std::vector<std::string>& vTypes,
100
const Parameterised::Map& parameters) :
101
GNEAdditional(id, net, filename, GNE_TAG_CALIBRATOR_LANE, name),
102
Parameterised(parameters),
103
myPositionOverLane(pos),
104
myFrequency(frequency),
105
myOutput(output),
106
myJamThreshold(jamThreshold),
107
myVTypes(vTypes),
108
myCalibratorContours(new std::vector<GNEContour*>()) {
109
// set parents
110
setParent<GNELane*>(lane);
111
setParent<GNEAdditional*>(routeProbe);
112
// update centering boundary without updating grid
113
updateCenteringBoundary(false);
114
}
115
116
117
GNECalibrator::~GNECalibrator() {
118
for (auto it = myCalibratorContours->begin(); it != myCalibratorContours->end(); it++) {
119
delete *it;
120
}
121
delete myCalibratorContours;
122
}
123
124
125
void
126
GNECalibrator::writeAdditional(OutputDevice& device) const {
127
// open tag
128
device.openTag(SUMO_TAG_CALIBRATOR);
129
// write parameters
130
device.writeAttr(SUMO_ATTR_ID, getID());
131
if (getParentEdges().size() > 0) {
132
device.writeAttr(SUMO_ATTR_EDGE, getParentEdges().front()->getID());
133
}
134
if (getParentLanes().size() > 0) {
135
device.writeAttr(SUMO_ATTR_LANE, getParentLanes().front()->getID());
136
}
137
device.writeAttr(SUMO_ATTR_POSITION, myPositionOverLane);
138
if (time2string(myFrequency) != "1.00") {
139
device.writeAttr(SUMO_ATTR_PERIOD, time2string(myFrequency));
140
}
141
if (!myAdditionalName.empty()) {
142
device.writeAttr(SUMO_ATTR_NAME, myAdditionalName);
143
}
144
if (!myOutput.empty()) {
145
device.writeAttr(SUMO_ATTR_OUTPUT, myOutput);
146
}
147
if (getParentAdditionals().size() > 0) {
148
device.writeAttr(SUMO_ATTR_ROUTEPROBE, getParentAdditionals().front()->getID());
149
}
150
if (myJamThreshold != 0.5) {
151
device.writeAttr(SUMO_ATTR_JAM_DIST_THRESHOLD, myJamThreshold);
152
}
153
if (myVTypes.size() > 0) {
154
device.writeAttr(SUMO_ATTR_VTYPES, myVTypes);
155
}
156
// write calibrator flows
157
for (const auto& calibratorFlow : getChildAdditionals()) {
158
if (calibratorFlow->getTagProperty()->getTag() == GNE_TAG_CALIBRATOR_FLOW) {
159
calibratorFlow->writeAdditional(device);
160
}
161
}
162
// write parameters (Always after children to avoid problems with additionals.xsd)
163
writeParams(device);
164
device.closeTag();
165
}
166
167
168
bool
169
GNECalibrator::isAdditionalValid() const {
170
return true;
171
}
172
173
174
std::string
175
GNECalibrator::getAdditionalProblem() const {
176
return "";
177
}
178
179
180
void
181
GNECalibrator::fixAdditionalProblem() {
182
// nothing to fix
183
}
184
185
186
GNEMoveOperation*
187
GNECalibrator::getMoveOperation() {
188
// calibrators cannot be moved
189
return nullptr;
190
}
191
192
193
void
194
GNECalibrator::updateGeometry() {
195
// get shape depending of we have a edge or a lane
196
if (getParentLanes().size() > 0) {
197
// update geometry
198
myAdditionalGeometry.updateGeometry(getParentLanes().front()->getLaneShape(), myPositionOverLane, 0);
199
} else if (getParentEdges().size() > 0) {
200
// clear extra geometries and contours
201
if (getParentEdges().size() != myCalibratorContours->size()) {
202
for (auto it = myCalibratorContours->begin(); it != myCalibratorContours->end(); it++) {
203
delete *it;
204
}
205
myCalibratorContours->clear();
206
for (int i = 1; i < (int)getParentEdges().front()->getChildLanes().size(); i++) {
207
myCalibratorContours->push_back(new GNEContour());
208
}
209
}
210
myEdgeCalibratorGeometries.clear();
211
// iterate over every lane and upadte geometries
212
for (const auto& lane : getParentEdges().front()->getChildLanes()) {
213
if (lane == getParentEdges().front()->getChildLanes().front()) {
214
myAdditionalGeometry.updateGeometry(lane->getLaneShape(), myPositionOverLane, 0);
215
} else {
216
// add new calibrator geometry
217
GUIGeometry calibratorGeometry;
218
calibratorGeometry.updateGeometry(lane->getLaneShape(), myPositionOverLane, 0);
219
myEdgeCalibratorGeometries.push_back(calibratorGeometry);
220
}
221
}
222
} else {
223
throw ProcessError(TL("Both edges and lanes aren't defined"));
224
}
225
}
226
227
228
Position
229
GNECalibrator::getPositionInView() const {
230
return myAdditionalGeometry.getShape().getPolygonCenter();
231
}
232
233
234
void
235
GNECalibrator::updateCenteringBoundary(const bool /*updateGrid*/) {
236
// nothing to update
237
}
238
239
240
void
241
GNECalibrator::splitEdgeGeometry(const double splitPosition, const GNENetworkElement* /*originalElement*/, const GNENetworkElement* newElement, GNEUndoList* undoList) {
242
if (splitPosition < myPositionOverLane) {
243
// change lane or edge
244
if (newElement->getTagProperty()->getTag() == SUMO_TAG_LANE) {
245
setAttribute(SUMO_ATTR_LANE, newElement->getID(), undoList);
246
} else {
247
setAttribute(SUMO_ATTR_EDGE, newElement->getID(), undoList);
248
}
249
// now adjust start position
250
setAttribute(SUMO_ATTR_POSITION, toString(myPositionOverLane - splitPosition), undoList);
251
}
252
}
253
254
255
std::string
256
GNECalibrator::getParentName() const {
257
// get parent name depending of we have a edge or a lane
258
if (getParentLanes().size() > 0) {
259
return getParentLanes().front()->getID();
260
} else if (getParentEdges().size() > 0) {
261
return getParentEdges().front()->getChildLanes().at(0)->getID();
262
} else {
263
throw ProcessError(TL("Both myEdge and myLane aren't defined"));
264
}
265
}
266
267
268
void
269
GNECalibrator::drawGL(const GUIVisualizationSettings& s) const {
270
const auto& inspectedElements = myNet->getViewNet()->getInspectedElements();
271
// first check if additional has to be drawn
272
if (myNet->getViewNet()->getDataViewOptions().showAdditionals()) {
273
// get values
274
const double exaggeration = getExaggeration(s);
275
// get detail level
276
const auto d = s.getDetailLevel(exaggeration);
277
// draw first calibrator symbols
278
drawCalibratorSymbol(s, d, exaggeration, myAdditionalGeometry.getShape().front(),
279
myAdditionalGeometry.getShapeRotations().front() + 90, -1);
280
// draw rest of calibrator symbols
281
for (int i = 0; i < (int)myEdgeCalibratorGeometries.size(); i++) {
282
drawCalibratorSymbol(s, d, exaggeration, myEdgeCalibratorGeometries.at(i).getShape().front(),
283
myEdgeCalibratorGeometries.at(i).getShapeRotations().front() + 90, i);
284
}
285
// draw additional ID
286
drawAdditionalID(s);
287
// iterate over additionals and check if drawn
288
for (const auto& calibratorFlow : getChildAdditionals()) {
289
// if calibrator is being inspected or selected, then draw
290
if (myNet->getViewNet()->getNetworkViewOptions().showSubAdditionals() ||
291
isAttributeCarrierSelected() || inspectedElements.isACInspected(this) ||
292
calibratorFlow->isAttributeCarrierSelected() || inspectedElements.isACInspected(calibratorFlow) ||
293
calibratorFlow->isMarkedForDrawingFront()) {
294
calibratorFlow->drawGL(s);
295
}
296
}
297
}
298
}
299
300
301
bool
302
GNECalibrator::checkDrawMoveContour() const {
303
// get edit modes
304
const auto& editModes = myNet->getViewNet()->getEditModes();
305
// check if we're in move mode
306
if (!myNet->getViewNet()->isCurrentlyMovingElements() && editModes.isCurrentSupermodeNetwork() &&
307
!myNet->getViewNet()->getEditNetworkElementShapes().getEditedNetworkElement() &&
308
(editModes.networkEditMode == NetworkEditMode::NETWORK_MOVE) && myNet->getViewNet()->checkOverLockedElement(this, mySelected)) {
309
// only move the first element
310
return myNet->getViewNet()->getViewObjectsSelector().getGUIGlObjectFront() == this;
311
} else {
312
return false;
313
}
314
}
315
316
317
void
318
GNECalibrator::openAdditionalDialog() {
319
// Open calibrator dialog
320
GNECalibratorDialog calibratorDialog(this);
321
}
322
323
324
std::string
325
GNECalibrator::getAttribute(SumoXMLAttr key) const {
326
switch (key) {
327
case SUMO_ATTR_ID:
328
return getMicrosimID();
329
case SUMO_ATTR_EDGE:
330
return getParentEdges().front()->getID();
331
case SUMO_ATTR_LANE:
332
return getParentLanes().front()->getID();
333
case SUMO_ATTR_POSITION:
334
return toString(myPositionOverLane);
335
case SUMO_ATTR_PERIOD:
336
case SUMO_ATTR_FREQUENCY:
337
return time2string(myFrequency);
338
case SUMO_ATTR_NAME:
339
return myAdditionalName;
340
case SUMO_ATTR_OUTPUT:
341
return myOutput;
342
case SUMO_ATTR_ROUTEPROBE:
343
if (getParentAdditionals().size() > 0) {
344
return getParentAdditionals().front()->getID();
345
} else {
346
return "";
347
}
348
case SUMO_ATTR_JAM_DIST_THRESHOLD:
349
return toString(myJamThreshold);
350
case SUMO_ATTR_VTYPES:
351
return toString(myVTypes);
352
case GNE_ATTR_SHIFTLANEINDEX:
353
return "";
354
default:
355
return getCommonAttribute(this, key);
356
}
357
}
358
359
360
double
361
GNECalibrator::getAttributeDouble(SumoXMLAttr key) const {
362
throw InvalidArgument(getTagStr() + " doesn't have a double attribute of type '" + toString(key) + "'");
363
}
364
365
366
const Parameterised::Map&
367
GNECalibrator::getACParametersMap() const {
368
return getParametersMap();
369
}
370
371
372
void
373
GNECalibrator::setAttribute(SumoXMLAttr key, const std::string& value, GNEUndoList* undoList) {
374
switch (key) {
375
case SUMO_ATTR_ID:
376
case SUMO_ATTR_EDGE:
377
case SUMO_ATTR_LANE:
378
case SUMO_ATTR_POSITION:
379
case SUMO_ATTR_PERIOD:
380
case SUMO_ATTR_FREQUENCY:
381
case SUMO_ATTR_NAME:
382
case SUMO_ATTR_OUTPUT:
383
case SUMO_ATTR_ROUTEPROBE:
384
case SUMO_ATTR_JAM_DIST_THRESHOLD:
385
case SUMO_ATTR_VTYPES:
386
case GNE_ATTR_SHIFTLANEINDEX:
387
GNEChange_Attribute::changeAttribute(this, key, value, undoList);
388
break;
389
default:
390
setCommonAttribute(key, value, undoList);
391
break;
392
}
393
394
}
395
396
397
bool
398
GNECalibrator::isValid(SumoXMLAttr key, const std::string& value) {
399
switch (key) {
400
case SUMO_ATTR_ID:
401
return isValidAdditionalID(NamespaceIDs::calibrators, value);
402
case SUMO_ATTR_EDGE:
403
if (myNet->getAttributeCarriers()->retrieveEdge(value, false) != nullptr) {
404
return true;
405
} else {
406
return false;
407
}
408
case SUMO_ATTR_LANE:
409
if (myNet->getAttributeCarriers()->retrieveLane(value, false) != nullptr) {
410
return true;
411
} else {
412
return false;
413
}
414
case SUMO_ATTR_POSITION:
415
if (canParse<double>(value)) {
416
// obtain position and check if is valid
417
const double newPosition = parse<double>(value);
418
if (isTemplate()) {
419
return (newPosition >= 0);
420
}
421
// get shape
422
PositionVector shape = (getParentLanes().size() > 0) ? getParentLanes().front()->getLaneShape() : getParentEdges().front()->getChildLanes().at(0)->getLaneShape();
423
if ((newPosition < 0) || (newPosition > shape.length())) {
424
return false;
425
} else {
426
return true;
427
}
428
} else {
429
return false;
430
}
431
case SUMO_ATTR_PERIOD:
432
case SUMO_ATTR_FREQUENCY:
433
return canParse<SUMOTime>(value) ? (parse<SUMOTime>(value) >= 0) : false;
434
case SUMO_ATTR_NAME:
435
return SUMOXMLDefinitions::isValidAttribute(value);
436
case SUMO_ATTR_OUTPUT:
437
return SUMOXMLDefinitions::isValidFilename(value);
438
case SUMO_ATTR_ROUTEPROBE:
439
if (value.empty()) {
440
return true;
441
} else {
442
return (myNet->getAttributeCarriers()->retrieveAdditional(SUMO_TAG_ROUTEPROBE, value, false) != nullptr);
443
}
444
case SUMO_ATTR_JAM_DIST_THRESHOLD:
445
return canParse<double>(value) ? (parse<double>(value) >= 0) : false;
446
case SUMO_ATTR_VTYPES:
447
if (value.empty()) {
448
return true;
449
} else {
450
return SUMOXMLDefinitions::isValidListOfTypeID(value);
451
}
452
default:
453
return isCommonValid(key, value);
454
}
455
}
456
457
458
std::string
459
GNECalibrator::getPopUpID() const {
460
return getTagStr() + ": " + getID();
461
}
462
463
464
std::string
465
GNECalibrator::getHierarchyName() const {
466
return getTagStr();
467
}
468
469
// ===========================================================================
470
// private
471
// ===========================================================================
472
473
void
474
GNECalibrator::drawCalibratorSymbol(const GUIVisualizationSettings& s, const GUIVisualizationSettings::Detail d, const double exaggeration,
475
const Position& pos, const double rot, const int symbolIndex) const {
476
// draw geometry only if we'rent in drawForObjectUnderCursor mode
477
if (s.checkDrawAdditional(d, isAttributeCarrierSelected())) {
478
// push layer matrix
479
GLHelper::pushMatrix();
480
// translate to front
481
drawInLayer(GLO_CALIBRATOR);
482
// translate to position
483
glTranslated(pos.x(), pos.y(), 0);
484
// rotate over lane
485
GUIGeometry::rotateOverLane(rot);
486
// scale
487
glScaled(exaggeration, exaggeration, 1);
488
// set drawing mode
489
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
490
// set color
491
GLHelper::setColor(drawUsingSelectColor() ? s.colorSettings.selectedAdditionalColor : s.additionalSettings.calibratorColor);
492
// base
493
glBegin(GL_TRIANGLES);
494
glVertex2d(0 - s.additionalSettings.calibratorWidth, 0);
495
glVertex2d(0 - s.additionalSettings.calibratorWidth, s.additionalSettings.calibratorHeight);
496
glVertex2d(0 + s.additionalSettings.calibratorWidth, s.additionalSettings.calibratorHeight);
497
glVertex2d(0 + s.additionalSettings.calibratorWidth, 0);
498
glVertex2d(0 - s.additionalSettings.calibratorWidth, 0);
499
glVertex2d(0 + s.additionalSettings.calibratorWidth, s.additionalSettings.calibratorHeight);
500
glEnd();
501
// draw text if isn't being drawn for selecting
502
if (d <= GUIVisualizationSettings::Detail::Text) {
503
// set color depending of selection status
504
RGBColor textColor = drawUsingSelectColor() ? s.colorSettings.selectionColor : RGBColor::BLACK;
505
// draw "C"
506
GLHelper::drawText("C", Position(0, 1.5), 0.1, 3, textColor, 180);
507
// draw "edge" or "lane "
508
if (getParentLanes().size() > 0) {
509
GLHelper::drawText("lane", Position(0, 3), .1, 1, textColor, 180);
510
} else if (getParentEdges().size() > 0) {
511
GLHelper::drawText("edge", Position(0, 3), .1, 1, textColor, 180);
512
} else {
513
throw ProcessError(TL("Both myEdge and myLane aren't defined"));
514
}
515
}
516
// pop layer matrix
517
GLHelper::popMatrix();
518
// draw dotted contours
519
if (symbolIndex == -1) {
520
myAdditionalContour.drawDottedContours(s, d, this, s.dottedContourSettings.segmentWidth, true);
521
} else if (symbolIndex < (int)myCalibratorContours->size()) {
522
myCalibratorContours->at(symbolIndex)->drawDottedContours(s, d, this, s.dottedContourSettings.segmentWidth, true);
523
}
524
}
525
GUIGlObject* parentBoundary = nullptr;
526
if (getParentEdges().size() > 0) {
527
parentBoundary = getParentEdges().front();
528
} else if (getParentLanes().size() > 0) {
529
parentBoundary = getParentLanes().front();
530
}
531
// calculate dotted contour
532
if (symbolIndex == -1) {
533
myAdditionalContour.calculateContourRectangleShape(s, d, this, pos, s.additionalSettings.calibratorWidth,
534
s.additionalSettings.calibratorHeight * 0.5, getType(), 0, s.additionalSettings.calibratorHeight * 0.5, rot,
535
exaggeration, parentBoundary);
536
} else if (symbolIndex < (int)myCalibratorContours->size()) {
537
myCalibratorContours->at(symbolIndex)->calculateContourRectangleShape(s, d, this, pos, s.additionalSettings.calibratorWidth,
538
s.additionalSettings.calibratorHeight * 0.5, getType(), 0, s.additionalSettings.calibratorHeight * 0.5, rot,
539
exaggeration, parentBoundary);
540
}
541
}
542
543
void
544
GNECalibrator::setAttribute(SumoXMLAttr key, const std::string& value) {
545
switch (key) {
546
case SUMO_ATTR_ID:
547
// update microsimID
548
setAdditionalID(value);
549
break;
550
case SUMO_ATTR_EDGE:
551
replaceAdditionalParentEdges(value);
552
break;
553
case SUMO_ATTR_LANE:
554
replaceAdditionalParentLanes(value);
555
break;
556
case SUMO_ATTR_POSITION:
557
myPositionOverLane = parse<double>(value);
558
break;
559
case SUMO_ATTR_PERIOD:
560
case SUMO_ATTR_FREQUENCY:
561
myFrequency = parse<SUMOTime>(value);
562
break;
563
case SUMO_ATTR_NAME:
564
myAdditionalName = value;
565
break;
566
case SUMO_ATTR_OUTPUT:
567
myOutput = value;
568
break;
569
case SUMO_ATTR_ROUTEPROBE:
570
replaceAdditionalParent(SUMO_TAG_ROUTEPROBE, value, 0);
571
break;
572
case SUMO_ATTR_JAM_DIST_THRESHOLD:
573
myJamThreshold = parse<double>(value);
574
break;
575
case SUMO_ATTR_VTYPES:
576
myVTypes = parse<std::vector<std::string> >(value);
577
break;
578
case GNE_ATTR_SHIFTLANEINDEX:
579
shiftLaneIndex();
580
break;
581
default:
582
setCommonAttribute(this, key, value);
583
break;
584
}
585
}
586
587
588
void
589
GNECalibrator::setMoveShape(const GNEMoveResult& /*moveResult*/) {
590
// nothing to do
591
}
592
593
594
void
595
GNECalibrator::commitMoveShape(const GNEMoveResult& /*moveResult*/, GNEUndoList* /*undoList*/) {
596
// nothing to do
597
}
598
599
600
/****************************************************************************/
601
602