00001
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #ifdef _MSC_VER
00025 #include <windows_config.h>
00026 #else
00027 #include <config.h>
00028 #endif
00029
00030 #include <string>
00031 #include <iostream>
00032 #include <map>
00033 #include <xercesc/sax/HandlerBase.hpp>
00034 #include <xercesc/sax/AttributeList.hpp>
00035 #include <xercesc/sax/SAXParseException.hpp>
00036 #include <xercesc/sax/SAXException.hpp>
00037 #include "NIXMLEdgesHandler.h"
00038 #include <cmath>
00039 #include <utils/xml/SUMOSAXHandler.h>
00040 #include <netbuild/NBNodeCont.h>
00041 #include <netbuild/NBTypeCont.h>
00042 #include <utils/xml/SUMOXMLDefinitions.h>
00043 #include <utils/common/MsgHandler.h>
00044 #include <utils/common/TplConvert.h>
00045 #include <utils/common/StringTokenizer.h>
00046 #include <utils/geom/GeomConvHelper.h>
00047 #include <utils/common/ToString.h>
00048 #include <utils/options/OptionsCont.h>
00049 #include <utils/geom/GeoConvHelper.h>
00050
00051 #ifdef CHECK_MEMORY_LEAKS
00052 #include <foreign/nvwa/debug_new.h>
00053 #endif // CHECK_MEMORY_LEAKS
00054
00055
00056
00057
00058
00059 using namespace std;
00060
00061 const SUMOReal SUMOXML_INVALID_POSITION = -999999.;
00062
00063
00064
00065
00066 NIXMLEdgesHandler::NIXMLEdgesHandler(NBNodeCont &nc,
00067 NBEdgeCont &ec,
00068 NBTypeCont &tc,
00069 NBDistrictCont &dc,
00070 OptionsCont &options) throw()
00071 : SUMOSAXHandler("xml-edges - file"),
00072 myOptions(options),
00073 myNodeCont(nc), myEdgeCont(ec), myTypeCont(tc), myDistrictCont(dc),
00074 myCurrentEdge(0),
00075 myHaveReportedAboutFunctionDeprecation(false) {}
00076
00077
00078 NIXMLEdgesHandler::~NIXMLEdgesHandler() throw() {}
00079
00080
00081 void
00082 NIXMLEdgesHandler::myStartElement(SumoXMLTag element,
00083 const SUMOSAXAttributes &attrs) throw(ProcessError) {
00084 if (element==SUMO_TAG_EDGE) {
00085 myIsUpdate = false;
00086 bool ok = true;
00087
00088 myCurrentEdge = 0;
00089 mySplits.clear();
00090
00091 if (!attrs.setIDFromAttributes("edge", myCurrentID)) {
00092 return;
00093 }
00094 myCurrentEdge = myEdgeCont.retrieve(myCurrentID);
00095
00096 if (!myHaveReportedAboutFunctionDeprecation&&attrs.hasAttribute(SUMO_ATTR_FUNCTION)) {
00097 MsgHandler::getWarningInstance()->inform("While parsing edge '" + myCurrentID + "': 'function' is deprecated.\n All occurences are ignored.");
00098 myHaveReportedAboutFunctionDeprecation = true;
00099 }
00100
00101 myCurrentSpeed = myTypeCont.getDefaultSpeed();
00102 myCurrentPriority = myTypeCont.getDefaultPriority();
00103 myCurrentLaneNo = myTypeCont.getDefaultNoLanes();
00104
00105 if (myCurrentEdge!=0) {
00106 myIsUpdate = true;
00107 if (!myHaveReportedAboutOverwriting) {
00108 MsgHandler::getMessageInstance()->inform("Duplicate edge id occured ('" + myCurrentID + "'); assuming overwriting is wished.");
00109 myHaveReportedAboutOverwriting = true;
00110 }
00111 myCurrentSpeed = myCurrentEdge->getSpeed();
00112 myCurrentPriority = myCurrentEdge->getPriority();
00113 myCurrentLaneNo = myCurrentEdge->getNoLanes();
00114 myCurrentType = myCurrentEdge->getTypeID();
00115 }
00116
00117 myCurrentType = "";
00118 if (attrs.hasAttribute(SUMO_ATTR_TYPE)) {
00119 myCurrentType = attrs.getStringReporting(SUMO_ATTR_TYPE, "edge", myCurrentID.c_str(), ok);
00120 if (!ok) {
00121 return;
00122 }
00123 if (!myTypeCont.knows(myCurrentType)) {
00124 MsgHandler::getErrorInstance()->inform("Type '" + myCurrentType + "' used by edge '" + myCurrentID + "' was not defined.");
00125 return;
00126 }
00127 myCurrentSpeed = myTypeCont.getSpeed(myCurrentType);
00128 myCurrentPriority = myTypeCont.getPriority(myCurrentType);
00129 myCurrentLaneNo = myTypeCont.getNoLanes(myCurrentType);
00130 }
00131
00132
00133 if (attrs.hasAttribute(SUMO_ATTR_SPEED)) {
00134 myCurrentSpeed = attrs.getSUMORealReporting(SUMO_ATTR_SPEED, "edge", myCurrentID.c_str(), ok);
00135 }
00136 if (myOptions.getBool("speed-in-kmh")) {
00137 myCurrentSpeed = myCurrentSpeed / (SUMOReal) 3.6;
00138 }
00139
00140 if (attrs.hasAttribute(SUMO_ATTR_NOLANES)) {
00141 myCurrentLaneNo = attrs.getIntReporting(SUMO_ATTR_NOLANES, "edge", myCurrentID.c_str(), ok);
00142 }
00143
00144 if (attrs.hasAttribute(SUMO_ATTR_PRIORITY)) {
00145 myCurrentPriority = attrs.getIntReporting(SUMO_ATTR_PRIORITY, "edge", myCurrentID.c_str(), ok);
00146 }
00147
00148
00149 myShape = tryGetShape(attrs);
00150
00151 if (attrs.getOptStringReporting(SUMO_ATTR_SPREADFUNC, "edge", myCurrentID.c_str(), ok, "")=="center") {
00152 myLanesSpread = NBEdge::LANESPREAD_CENTER;
00153 } else {
00154 myLanesSpread = NBEdge::LANESPREAD_RIGHT;
00155 }
00156
00157 if (!setNodes(attrs)) {
00158
00159 return;
00160 }
00161
00162 if (attrs.hasAttribute(SUMO_ATTR_LENGTH)) {
00163 myLength = attrs.getSUMORealReporting(SUMO_ATTR_LENGTH, "edge", myCurrentID.c_str(), ok);
00164 } else {
00165 myLength = 0;
00166 }
00168 if (!ok) {
00169 return;
00170 }
00171
00172 if (myCurrentEdge!=0) {
00173 myCurrentEdge->reinit(myFromNode, myToNode, myCurrentType, myCurrentSpeed,
00174 myCurrentLaneNo, myCurrentPriority, myShape,
00175 myLanesSpread);
00176 } else {
00177
00178 if (myShape.size()==0) {
00179 myCurrentEdge = new NBEdge(myCurrentID, myFromNode, myToNode, myCurrentType, myCurrentSpeed,
00180 myCurrentLaneNo, myCurrentPriority, myLanesSpread);
00181 } else {
00182 myCurrentEdge = new NBEdge(myCurrentID, myFromNode, myToNode, myCurrentType, myCurrentSpeed,
00183 myCurrentLaneNo, myCurrentPriority, myShape,
00184 myLanesSpread, OptionsCont::getOptions().getBool("xml.keep-shape"));
00185 }
00186 myCurrentEdge->setLoadedLength(myLength);
00187 }
00188 }
00189 if (element==SUMO_TAG_LANE) {
00190 if (myCurrentEdge==0) {
00191 if (!OptionsCont::getOptions().isInStringVector("remove-edges", myCurrentID)) {
00192 MsgHandler::getErrorInstance()->inform("Additional lane information could not been set - the edge with id '" + myCurrentID + "' is not known.");
00193 }
00194 return;
00195 }
00196 bool ok = true;
00197 int lane = attrs.getIntReporting(SUMO_ATTR_ID, "lane", 0, ok);
00198 std::vector<std::string> disallowed, allowed, preferred;
00199 SUMOSAXAttributes::parseStringVector(attrs.getOptStringReporting(SUMO_ATTR_DISALLOW, "lane", 0, ok, ""), disallowed);
00200 SUMOSAXAttributes::parseStringVector(attrs.getOptStringReporting(SUMO_ATTR_ALLOW, "lane", 0, ok, ""), allowed);
00201 SUMOSAXAttributes::parseStringVector(attrs.getOptStringReporting(SUMO_ATTR_PREFER, "lane", 0, ok, ""), preferred);
00202 if (!ok) {
00203 return;
00204 }
00205 if (lane<0) {
00206 MsgHandler::getErrorInstance()->inform("Missing lane-id in lane definition (edge '" + myCurrentID + "').");
00207 return;
00208 }
00209
00210 if (lane>=(int) myCurrentEdge->getNoLanes()) {
00211 MsgHandler::getErrorInstance()->inform("Lane-id is larger than number of lanes (edge '" + myCurrentID + "').");
00212 return;
00213 }
00214
00215 for (std::vector<std::string>::iterator i=disallowed.begin(); i!=disallowed.end(); ++i) {
00216 myCurrentEdge->disallowVehicleClass(lane, getVehicleClassID(*i));
00217 }
00218 for (std::vector<std::string>::iterator i=allowed.begin(); i!=allowed.end(); ++i) {
00219 myCurrentEdge->allowVehicleClass(lane, getVehicleClassID(*i));
00220 }
00221 for (std::vector<std::string>::iterator i=preferred.begin(); i!=preferred.end(); ++i) {
00222 myCurrentEdge->preferVehicleClass(lane, getVehicleClassID(*i));
00223 }
00224
00225
00226 if (attrs.hasAttribute(SUMO_ATTR_FORCE_LENGTH)) {
00227 bool ok = true;
00228 int forcedLength = attrs.getIntReporting(SUMO_ATTR_FORCE_LENGTH, "lane", myCurrentID.c_str(), ok);
00229 if (ok) {
00230 int nameid = forcedLength;
00231 forcedLength = (int)(myCurrentEdge->getGeometry().length() - forcedLength);
00232 std::vector<Split>::iterator i;
00233 i = find_if(mySplits.begin(), mySplits.end(), split_by_pos_finder((SUMOReal) forcedLength));
00234 if (i==mySplits.end()) {
00235 Split e;
00236 e.pos = (SUMOReal) forcedLength;
00237 e.nameid = nameid;
00238 for (unsigned int j=0; j<myCurrentEdge->getNoLanes(); j++) {
00239 e.lanes.push_back(j);
00240 }
00241 mySplits.push_back(e);
00242 }
00243 i = find_if(mySplits.begin(), mySplits.end(), split_by_pos_finder((SUMOReal) forcedLength));
00244 std::vector<int>::iterator k = find((*i).lanes.begin(), (*i).lanes.end(), lane);
00245 if (k!=(*i).lanes.end()) {
00246 (*i).lanes.erase(k);
00247 }
00248 }
00249 }
00250 }
00251 if (element==SUMO_TAG_SPLIT) {
00252 bool ok = true;
00253 Split e;
00254 e.pos = attrs.getSUMORealReporting(SUMO_ATTR_POSITION, "split", 0, ok);
00255 std::vector<Split>::iterator i = find_if(mySplits.begin(), mySplits.end(), split_by_pos_finder(e.pos));
00256 if (i!=mySplits.end()) {
00257 MsgHandler::getErrorInstance()->inform("Edge '" + myCurrentID + "' has already a split at position " + toString(e.pos) + ".");
00258 return;
00259 }
00260 if (e.pos<0) {
00261 e.pos = myCurrentEdge->getGeometry().length() - e.pos;
00262 }
00263 e.nameid = e.pos;
00264 if (ok) {
00265 if (myCurrentEdge==0) {
00266 if (!OptionsCont::getOptions().isInStringVector("remove-edges", myCurrentID)) {
00267 MsgHandler::getErrorInstance()->inform("Additional lane information could not been set - the edge with id '" + myCurrentID + "' is not known.");
00268 }
00269 return;
00270 }
00271 if (e.pos<0) {
00272 e.pos = myCurrentEdge->getGeometry().length() + e.pos;
00273 }
00274 std::vector<std::string> lanes;
00275 SUMOSAXAttributes::parseStringVector(attrs.getOptStringReporting(SUMO_ATTR_LANES, "split", 0, ok, ""), lanes);
00276 for (std::vector<std::string>::iterator i=lanes.begin(); i!=lanes.end(); ++i) {
00277 try {
00278 int lane = TplConvert<char>::_2int((*i).c_str());
00279 e.lanes.push_back(lane);
00280 } catch (NumberFormatException &) {
00281 MsgHandler::getErrorInstance()->inform("Error on parsing a split (edge '" + myCurrentID + "').");
00282 } catch (EmptyData &) {
00283 MsgHandler::getErrorInstance()->inform("Error on parsing a split (edge '" + myCurrentID + "').");
00284 }
00285 }
00286 if (e.lanes.size()==0) {
00287 MsgHandler::getErrorInstance()->inform("Missing lane information in split of edge '" + myCurrentID + "'.");
00288 } else {
00289 mySplits.push_back(e);
00290 }
00291 }
00292 }
00293 }
00294
00295
00296 bool
00297 NIXMLEdgesHandler::setNodes(const SUMOSAXAttributes &attrs) throw() {
00298
00299
00300 bool ok = true;
00301 std::string begNodeID = myIsUpdate ? myCurrentEdge->getFromNode()->getID() : "";
00302 std::string endNodeID = myIsUpdate ? myCurrentEdge->getToNode()->getID() : "";
00303 begNodeID = attrs.hasAttribute(SUMO_ATTR_FROMNODE) ? attrs.getStringReporting(SUMO_ATTR_FROMNODE, "edge", 0, ok) : begNodeID;
00304 endNodeID = attrs.hasAttribute(SUMO_ATTR_TONODE) ? attrs.getStringReporting(SUMO_ATTR_TONODE, "edge", 0, ok) : endNodeID;
00305 if (!ok) {
00306 return false;
00307 }
00308
00309 SUMOReal begNodeXPos = tryGetPosition(attrs, SUMO_ATTR_XFROM, "XFrom");
00310 SUMOReal begNodeYPos = tryGetPosition(attrs, SUMO_ATTR_YFROM, "YFrom");
00311 SUMOReal endNodeXPos = tryGetPosition(attrs, SUMO_ATTR_XTO, "XTo");
00312 SUMOReal endNodeYPos = tryGetPosition(attrs, SUMO_ATTR_YTO, "YTo");
00313 if (begNodeXPos!=SUMOXML_INVALID_POSITION&&begNodeYPos!=SUMOXML_INVALID_POSITION) {
00314 Position2D pos(begNodeXPos, begNodeYPos);
00315 GeoConvHelper::x2cartesian(pos);
00316 begNodeXPos = pos.x();
00317 begNodeYPos = pos.y();
00318 }
00319 if (endNodeXPos!=SUMOXML_INVALID_POSITION&&endNodeYPos!=SUMOXML_INVALID_POSITION) {
00320 Position2D pos(endNodeXPos, endNodeYPos);
00321 GeoConvHelper::x2cartesian(pos);
00322 endNodeXPos = pos.x();
00323 endNodeYPos = pos.y();
00324 }
00325
00326 myFromNode = insertNodeChecking(Position2D(begNodeXPos, begNodeYPos), begNodeID, "from");
00327 myToNode = insertNodeChecking(Position2D(endNodeXPos, endNodeYPos), endNodeID, "to");
00328 return myFromNode!=0&&myToNode!=0;
00329 }
00330
00331
00332 SUMOReal
00333 NIXMLEdgesHandler::tryGetPosition(const SUMOSAXAttributes &attrs, SumoXMLAttr attrID,
00334 const std::string &attrName) {
00335 bool ok = true;
00336 return attrs.getOptSUMORealReporting(attrID, "edge", myCurrentID.c_str(), ok, SUMOXML_INVALID_POSITION);
00337 }
00338
00339
00340 NBNode *
00341 NIXMLEdgesHandler::insertNodeChecking(const Position2D &pos,
00342 const std::string &name, const std::string &dir) {
00343 NBNode *ret = 0;
00344 if (name=="" && (pos.x()==SUMOXML_INVALID_POSITION || pos.y()==SUMOXML_INVALID_POSITION)) {
00345 MsgHandler::getErrorInstance()->inform("Neither the name nor the position of the " + dir + "-node is given for edge '" + myCurrentID + "'.");
00346 return ret;
00347 }
00348 if (name!="") {
00349 if (pos.x()!=SUMOXML_INVALID_POSITION && pos.y()!=SUMOXML_INVALID_POSITION) {
00350
00351 if (!myNodeCont.insert(name, pos)) {
00352 MsgHandler::getErrorInstance()->inform("Position of " + dir + "-node '" + name + "' mismatches previous positions.");
00353 return 0;
00354 }
00355 }
00356
00357 ret = myNodeCont.retrieve(name);
00358 if (ret==0) {
00359 MsgHandler::getErrorInstance()->inform("Edge's '" + myCurrentID + "' " + dir + "-node '" + name + "' is not known.");
00360 }
00361 } else {
00362 ret = myNodeCont.retrieve(pos);
00363 if (ret==0) {
00364 ret = new NBNode(myNodeCont.getFreeID(), pos);
00365 if (!myNodeCont.insert(ret)) {
00366 MsgHandler::getErrorInstance()->inform("Could not insert " + dir + "-node at position " + toString(pos) + ".");
00367 delete ret;
00368 return 0;
00369 }
00370 }
00371 }
00372 return ret;
00373 }
00374
00375
00376 Position2DVector
00377 NIXMLEdgesHandler::tryGetShape(const SUMOSAXAttributes &attrs) throw() {
00378 if (!attrs.hasAttribute(SUMO_ATTR_SHAPE)) {
00379 return Position2DVector();
00380 }
00381
00382 bool ok = true;
00383 std::string shpdef = attrs.getOptStringReporting(SUMO_ATTR_SHAPE, "edge", 0, ok, "");
00384 if (shpdef=="") {
00385 return Position2DVector();
00386 }
00387 Position2DVector shape1 = GeomConvHelper::parseShapeReporting(shpdef, "edge", 0, ok, true);
00388 Position2DVector shape;
00389 for (int i=0; i<(int) shape1.size(); ++i) {
00390 Position2D pos(shape1[i]);
00391 if (!GeoConvHelper::x2cartesian(pos)) {
00392 MsgHandler::getErrorInstance()->inform("Unable to project coordinates for edge '" + myCurrentID + "'.");
00393 }
00394 shape.push_back(pos);
00395 }
00396 return shape;
00397 }
00398
00399
00400 void
00401 NIXMLEdgesHandler::parseSplitLanes(const std::string &val) throw(ProcessError) {
00402 if (mySplits.size()!=0) {
00403 Split &e = mySplits.back();
00404 std::vector<std::string> lanes;
00405 SUMOSAXAttributes::parseStringVector(val, lanes);
00406 for (std::vector<std::string>::iterator i=lanes.begin(); i!=lanes.end(); ++i) {
00407 try {
00408 int lane = TplConvert<char>::_2int((*i).c_str());
00409 e.lanes.push_back(lane);
00410 } catch (NumberFormatException &) {
00411 MsgHandler::getErrorInstance()->inform("Error on parsing a split (edge '" + myCurrentID + "').");
00412 } catch (EmptyData &) {
00413 MsgHandler::getErrorInstance()->inform("Error on parsing a split (edge '" + myCurrentID + "').");
00414 }
00415 }
00416 }
00417 }
00418
00419
00420 void
00421 NIXMLEdgesHandler::myEndElement(SumoXMLTag element) throw(ProcessError) {
00422 if (element==SUMO_TAG_EDGE && myCurrentEdge!=0) {
00423 if (!myIsUpdate) {
00424 try {
00425 if (!myEdgeCont.insert(myCurrentEdge)) {
00426 MsgHandler::getErrorInstance()->inform("Duplicate edge occured. ID='" + myCurrentID + "'");
00427 delete myCurrentEdge;
00428 }
00429 } catch (InvalidArgument &e) {
00430 MsgHandler::getErrorInstance()->inform(e.what());
00431 throw;
00432 } catch (...) {
00433 MsgHandler::getErrorInstance()->inform("An important information is missing in edge '" + myCurrentID + "'.");
00434 }
00435 }
00436 if (mySplits.size()!=0) {
00437 std::vector<Split>::iterator i, i2;
00438 sort(mySplits.begin(), mySplits.end(), split_sorter());
00439 NBEdge *e = myCurrentEdge;
00440 unsigned int noLanesMax = e->getNoLanes();
00441
00442 for (i=mySplits.begin(); i!=mySplits.end(); ++i) {
00443 (*i).gpos = e->getGeometry().positionAtLengthPosition((*i).pos);
00444 sort((*i).lanes.begin(), (*i).lanes.end());
00445 noLanesMax = MAX2(noLanesMax, (unsigned int)(*i).lanes.size());
00446 }
00447
00448 std::vector<int> currLanes;
00449 for (unsigned int l=0; l<noLanesMax; ++l) {
00450 currLanes.push_back(l);
00451 }
00452 std::string edgeid = e->getID();
00453 SUMOReal seen = 0;
00454 for (i=mySplits.begin(); i!=mySplits.end(); ++i) {
00455 const Split &exp = *i;
00456 assert(exp.lanes.size()!=0);
00457 if (exp.pos>0 && e->getGeometry().length()+seen>exp.pos) {
00458 std::string nid = edgeid + "." + toString(exp.nameid);
00459 NBNode *rn = new NBNode(nid, exp.gpos);
00460 if (myNodeCont.insert(rn)) {
00461
00462 std::string nid = myCurrentID + "." + toString(exp.nameid);
00463 std::string pid = e->getID();
00464 myEdgeCont.splitAt(myDistrictCont, e, exp.pos-seen, rn,
00465 pid, nid, e->getNoLanes(), (unsigned int) exp.lanes.size());
00466 seen = exp.pos;
00467 std::vector<int> newLanes = exp.lanes;
00468 NBEdge *pe = myEdgeCont.retrieve(pid);
00469 NBEdge *ne = myEdgeCont.retrieve(nid);
00470
00471 pe->invalidateConnections(true);
00472
00473 unsigned int rightMostP = currLanes[0];
00474 unsigned int rightMostN = newLanes[0];
00475 for (int l=0; l<(int) rightMostP-(int) rightMostN; ++l) {
00476 pe->addLane2LaneConnection(0, ne, l, NBEdge::L2L_VALIDATED, true);
00477 }
00478
00479 unsigned int leftMostP = currLanes.back();
00480 unsigned int leftMostN = newLanes.back();
00481 for (int l=0; l<(int) leftMostN-(int) leftMostP; ++l) {
00482 pe->addLane2LaneConnection(pe->getNoLanes()-1, ne, leftMostN-l, NBEdge::L2L_VALIDATED, true);
00483 }
00484
00485 for (unsigned int l=0; l<noLanesMax; ++l) {
00486 if (find(currLanes.begin(), currLanes.end(), l)==currLanes.end()) {
00487 continue;
00488 }
00489 if (find(newLanes.begin(), newLanes.end(), l)==newLanes.end()) {
00490 continue;
00491 }
00492 pe->addLane2LaneConnection(l-rightMostP, ne, l-rightMostN, NBEdge::L2L_VALIDATED, true);
00493 }
00494
00495 e = ne;
00496 currLanes = newLanes;
00497 } else {
00498 MsgHandler::getWarningInstance()->inform("Error on parsing a split (edge '" + myCurrentID + "').");
00499 }
00500 } else if (exp.pos==0) {
00501 e->decLaneNo(e->getNoLanes()-exp.lanes.size());
00502 currLanes = exp.lanes;
00503 } else {
00504 MsgHandler::getWarningInstance()->inform("Split at '" + toString(exp.pos) + "' lies beyond the edge's length (edge '" + myCurrentID + "').");
00505 }
00506 }
00507
00508 e = myEdgeCont.retrieve(edgeid);
00509 i = mySplits.begin();
00510 if ((*i).pos!=0) {
00511 e = e->getToNode()->getOutgoingEdges()[0];
00512 }
00513 for (; i!=mySplits.end(); ++i) {
00514 unsigned int maxLeft = (*i).lanes.back();
00515 if (maxLeft<noLanesMax) {
00516 Position2DVector g = e->getGeometry();
00517 g.move2side(SUMO_const_laneWidthAndOffset*(noLanesMax-1-maxLeft));
00518 e->setGeometry(g);
00519 }
00520 if (e->getToNode()->getOutgoingEdges().size()!=0) {
00521 e = e->getToNode()->getOutgoingEdges()[0];
00522 }
00523 }
00524 }
00525 }
00526 }
00527
00528
00529
00530
00531