OsmAnd
|
00001 00002 #include <android/log.h> 00003 #include <stdio.h> 00004 #include <map> 00005 #include <set> 00006 #include <hash_map> 00007 00008 00009 #include "renderRules.h" 00010 #include "common.h" 00011 #include "mapObjects.h" 00012 00013 #define INT_MAX 0x7fffffff /* max value for an int */ 00014 #define INT_MIN (-0x7fffffff-1) /* min value for an int */ 00015 00016 char textMsg[1024] ; 00017 struct tagValueType { 00018 int type; 00019 std::string tag; 00020 std::string value; 00021 00022 tagValueType(tag_value t, int type) : type(type) { 00023 tag = t.first; 00024 value = t.second; 00025 } 00026 00027 }; 00028 bool operator==(const tagValueType& __x, const tagValueType& __y) { 00029 return __x.type == __y.type; 00030 } 00031 bool operator<(const tagValueType& __x, const tagValueType& __y) { 00032 return __x.type < __y.type; 00033 } 00035 bool isClockwiseWay(std::vector<int_pair>& c) ; 00036 bool calculateLineCoordinates(bool inside, int x, int y, bool pinside, int px, int py, int leftX, int rightX, 00037 int bottomY, int topY, std::vector<int_pair>& coordinates); 00038 00039 void processMultipolygonLine(std::vector<std::vector<int_pair> >& completedRings, std::vector<std::vector<int_pair> >& incompletedRings, 00040 std::vector<std::string> &completedRingsNames, std::vector<std::string> &incompletedRingsNames, std::vector<int_pair> & coordinates, std::string name); 00041 00042 void unifyIncompletedRings(std::vector<std::vector<int_pair> >& incompletedRings, std::vector<std::vector<int_pair> >& completedRings, std::vector<std::string> &completedRingNames, 00043 std::vector<std::string> &incompletedRingNames, int leftX, int rightX, int bottomY, int topY, long dbId, int zoom); 00044 00045 MultiPolygonObject* processMultiPolygon(int leftX, int rightX, int bottomY, int topY, 00046 std::vector<std::vector<int_pair > >& completedRings, std::vector<std::vector<int_pair> >& incompletedRings, 00047 std::vector<std::string>& completedRingNames, std::vector<std::string>& incompletedRingNames, 00048 const tagValueType& type, std::vector<MapDataObject* > & directList, std::vector<MapDataObject*>& inverselist, 00049 int zoom) { 00050 MultiPolygonObject* pl = new MultiPolygonObject(); 00051 // delete direction last bit (to not show point) 00052 pl->tag = type.tag; 00053 pl->value = type.value; 00054 pl->layer = getNegativeWayLayer(type.type); 00055 long long dbId = 0; 00056 for (int km = 0; km < 2; km++) { 00057 std::vector<MapDataObject* >::iterator o = (km == 0 ? directList.begin() : inverselist.begin()); 00058 std::vector<MapDataObject* >::iterator oEnd = (km == 0 ? directList.end() : inverselist.end()); 00059 for (; o != oEnd; o++) { 00060 int len = (*o)->points.size(); 00061 if (len < 2) { 00062 continue; 00063 } 00064 dbId = (*o)->id >> 1; 00065 std::vector<int_pair> coordinates; 00066 int_pair p = (*o)->points.at(km == 0 ? 0 : len - 1); 00067 int px = p.first; 00068 int py = p.second; 00069 int x = p.first; 00070 int y = p.second; 00071 bool pinside = leftX <= x && x <= rightX && y >= topY && y <= bottomY; 00072 if (pinside) { 00073 coordinates.push_back(int_pair(x, y)); 00074 } 00075 for (int i = 1; i < len; i++) { 00076 int_pair cp = (*o)->points.at(km == 0 ? i : len - i - 1); 00077 x = cp.first; 00078 y = cp.second; 00079 bool inside = leftX <= x && x <= rightX && y >= topY && y <= bottomY; 00080 bool lineEnded = calculateLineCoordinates(inside, x, y, pinside, px, py, leftX, rightX, bottomY, topY, 00081 coordinates); 00082 if (lineEnded) { 00083 processMultipolygonLine(completedRings, incompletedRings, completedRingNames, incompletedRingNames, 00084 coordinates, (*o)->name); 00085 // create new line if it goes outside 00086 coordinates.clear(); 00087 } 00088 px = x; 00089 py = y; 00090 pinside = inside; 00091 } 00092 processMultipolygonLine(completedRings, incompletedRings, completedRingNames, incompletedRingNames, 00093 coordinates, (*o)->name); 00094 } 00095 } 00096 if (completedRings.size() == 0 && incompletedRings.size() == 0) { 00097 return NULL; 00098 } 00099 if (incompletedRings.size() > 0) { 00100 unifyIncompletedRings(incompletedRings, completedRings, completedRingNames, incompletedRingNames, leftX, rightX, 00101 bottomY, topY, dbId, zoom); 00102 } else { 00103 // due to self intersection small objects (for low zooms check only coastline) 00104 if (zoom >= 13 || ("natural" == type.tag && "coastline" == type.value)) { 00105 bool clockwiseFound = false; 00106 std::vector<std::vector<int_pair> > ::iterator c = completedRings.begin(); 00107 for (; c != completedRings.end(); c++) { 00108 if (isClockwiseWay(*c)) { 00109 clockwiseFound = true; 00110 break; 00111 } 00112 } 00113 if (!clockwiseFound) { 00114 // add whole bound 00115 std::vector<int_pair> whole; 00116 whole.push_back(int_pair(leftX, topY)); 00117 whole.push_back(int_pair(rightX, topY)); 00118 whole.push_back(int_pair(leftX, bottomY)); 00119 whole.push_back(int_pair(rightX, bottomY)); 00120 completedRings.push_back(whole); 00121 __android_log_print(ANDROID_LOG_INFO, "net.osmand", "!!! Isolated island !!!"); 00122 } 00123 00124 } 00125 } 00126 00127 pl->names = completedRingNames; 00128 pl->points = completedRings; 00129 return pl; 00130 } 00131 00132 static std::vector<MapDataObject*> EMPTY_LIST; 00133 void proccessMultiPolygons(std::map<tagValueType, std::vector<MapDataObject*> >& multyPolygons, int leftX, 00134 int rightX, int bottomY, int topY, int zoom, std::vector<BaseMapDataObject*>& listPolygons) { 00135 std::vector<std::vector<int_pair> > completedRings; 00136 std::vector<std::vector<int_pair> > incompletedRings; 00137 std::vector<std::string> completedRingNames; 00138 std::vector<std::string> incompletedRingNames; 00139 std::map<tagValueType, std::vector<MapDataObject*> >::iterator val = multyPolygons.begin(); 00140 for (; val != multyPolygons.end(); val++) { 00141 std::vector<MapDataObject*>* directList; 00142 std::vector<MapDataObject*>* inverselist; 00143 if (((val->first.type >> 15) & 1) == 1) { 00144 tagValueType directType = val->first; 00145 directType.type = val->first.type & ((1 << 15) - 1); 00146 if (multyPolygons.find(directType) == multyPolygons.end()) { 00147 inverselist = &val->second; 00148 directList = &EMPTY_LIST; 00149 } else { 00150 // continue on inner boundaries 00151 continue; 00152 } 00153 } else { 00154 tagValueType inverseType = val->first; 00155 inverseType.type = val->first.type | (1 << 15); 00156 directList = &val->second; 00157 inverselist = &multyPolygons[inverseType]; 00158 } 00159 completedRings.clear(); 00160 incompletedRings.clear(); 00161 completedRingNames.clear(); 00162 incompletedRingNames.clear(); 00163 00164 sprintf(textMsg, "Process multipolygon %s %s direct list %d rev %d", val->first.tag.c_str(), val->first.value.c_str(), directList->size(), inverselist->size()); 00165 __android_log_print(ANDROID_LOG_INFO, "net.osmand", textMsg); 00166 MultiPolygonObject* pl = processMultiPolygon(leftX, rightX, bottomY, topY, completedRings, incompletedRings, 00167 completedRingNames, incompletedRingNames, val->first, *directList, *inverselist, zoom); 00168 if (pl != NULL) { 00169 listPolygons.push_back(pl); 00170 } else { 00171 __android_log_print(ANDROID_LOG_INFO, "net.osmand", "Multipolygon skipped"); 00172 } 00173 } 00174 } 00175 00176 // Copied from MapAlgorithms 00177 int ray_intersect_x(int prevX, int prevY, int x, int y, int middleY) { 00178 // prev node above line 00179 // x,y node below line 00180 if (prevY > y) { 00181 int tx = prevX; 00182 int ty = prevY; 00183 x = prevX; 00184 y = prevY; 00185 prevX = tx; 00186 prevY = ty; 00187 } 00188 if (y == middleY || prevY == middleY) { 00189 middleY -= 1; 00190 } 00191 if (prevY > middleY || y < middleY) { 00192 return INT_MIN; 00193 } else { 00194 if (y == prevY) { 00195 // the node on the boundary !!! 00196 return x; 00197 } 00198 // that tested on all cases (left/right) 00199 double rx = x + ((double) middleY - y) * ((double) x - prevX) / (((double) y - prevY)); 00200 return (int) rx; 00201 } 00202 } 00203 00204 // Copied from MapAlgorithms 00205 bool isClockwiseWay(std::vector<int_pair>& c) { 00206 if (c.size() == 0) { 00207 return true; 00208 } 00209 00210 // calculate middle Y 00211 long middleY = 0; 00212 for (size_t i = 0; i < c.size(); i++) { 00213 middleY += c.at(i).second; 00214 } 00215 middleY /= (long) c.size(); 00216 00217 double clockwiseSum = 0; 00218 00219 bool firstDirectionUp = false; 00220 int previousX = INT_MIN; 00221 int firstX = INT_MIN; 00222 00223 int prevX = c.at(0).first; 00224 int prevY = c.at(0).second; 00225 00226 for (size_t i = 1; i < c.size(); i++) { 00227 int x = c.at(i).first; 00228 int y = c.at(i).second; 00229 int rX = ray_intersect_x(prevX, prevY, x, y, (int) middleY); 00230 if (rX != INT_MIN) { 00231 bool skipSameSide = (y <= middleY) == (prevY <= middleY); 00232 if (skipSameSide) { 00233 continue; 00234 } 00235 bool directionUp = prevY >= middleY; 00236 if (firstX == INT_MIN) { 00237 firstDirectionUp = directionUp; 00238 firstX = rX; 00239 } else { 00240 bool clockwise = (!directionUp) == (previousX < rX); 00241 if (clockwise) { 00242 clockwiseSum += abs(previousX - rX); 00243 } else { 00244 clockwiseSum -= abs(previousX - rX); 00245 } 00246 } 00247 previousX = rX; 00248 prevX = x; 00249 prevY = y; 00250 } 00251 } 00252 00253 if (firstX != INT_MIN) { 00254 bool clockwise = (!firstDirectionUp) == (previousX < firstX); 00255 if (clockwise) { 00256 clockwiseSum += abs(previousX - firstX); 00257 } else { 00258 clockwiseSum -= abs(previousX - firstX); 00259 } 00260 } 00261 00262 return clockwiseSum >= 0; 00263 } 00264 00265 00266 00267 void processMultipolygonLine(std::vector<std::vector<int_pair> >& completedRings, std::vector<std::vector<int_pair> >& incompletedRings, 00268 std::vector<std::string> &completedRingsNames, 00269 std::vector<std::string> &incompletedRingsNames, std::vector<int_pair> & coordinates, std::string name) { 00270 if (coordinates.size() > 0) { 00271 if (coordinates.at(0) == coordinates.at(coordinates.size() - 1)) { 00272 completedRings.push_back(coordinates); 00273 completedRingsNames.push_back(name); 00274 } else { 00275 bool add = true; 00276 for (size_t k = 0; k < incompletedRings.size();) { 00277 bool remove = false; 00278 std::vector<int_pair> i = incompletedRings.at(k); 00279 std::string oldName = incompletedRingsNames.at(k); 00280 if (coordinates.at(0) == i.at(i.size() - 1)) { 00281 std::vector<int_pair>::iterator tit = coordinates.begin(); 00282 i.insert(i.end(), ++tit, coordinates.end()); 00283 remove = true; 00284 coordinates = i; 00285 } else if (coordinates.at(coordinates.size() - 1) == i.at(0)) { 00286 std::vector<int_pair>::iterator tit = i.begin(); 00287 coordinates.insert(coordinates.end(), ++tit, i.end()); 00288 remove = true; 00289 } 00290 if (remove) { 00291 std::vector<std::vector<int_pair> >::iterator ti = incompletedRings.begin(); 00292 ti += k; 00293 incompletedRings.erase(ti); 00294 std::vector<std::string> :: iterator tis = incompletedRingsNames.begin(); 00295 tis += k; 00296 incompletedRingsNames.erase(tis); 00297 } else { 00298 k++; 00299 } 00300 if (coordinates.at(0) == coordinates.at(coordinates.size() - 1)) { 00301 completedRings.push_back(coordinates); 00302 if (oldName.length() > 0) { 00303 completedRingsNames.push_back(oldName); 00304 } else { 00305 completedRingsNames.push_back(name); 00306 } 00307 add = false; 00308 break; 00309 } 00310 } 00311 if (add) { 00312 incompletedRings.push_back(coordinates); 00313 incompletedRingsNames.push_back(name); 00314 } 00315 } 00316 } 00317 } 00318 00319 int safelyAddDelta(int number, int delta) { 00320 int res = number + delta; 00321 if (delta > 0 && res < number) { 00322 return INT_MAX; 00323 } else if (delta < 0 && res > number) { 00324 return INT_MIN; 00325 } 00326 return res; 00327 } 00328 00329 void unifyIncompletedRings(std::vector<std::vector<int_pair> >& incompletedRings, std::vector<std::vector<int_pair> >& completedRings, 00330 std::vector<std::string> &completedRingNames, std::vector<std::string> & incompletedRingsNames, 00331 int leftX, int rightX, int bottomY, int topY, long dbId, int zoom) { 00332 std::set<int> nonvisitedRings; 00333 std::vector<std::vector<int_pair> >::iterator ir = incompletedRings.begin(); 00334 std::vector<std::string>::iterator irs = incompletedRingsNames.begin(); 00335 int j = 0; 00336 for (j = 0; ir != incompletedRings.end(); ir++, irs++, j++) { 00337 int x = ir->at(0).first; 00338 int y = ir->at(0).second; 00339 int sx = ir->at(ir->size() - 1).first; 00340 int sy = ir->at(ir->size() - 1).second; 00341 bool st = y == topY || x == rightX || y == bottomY || x == leftX; 00342 bool end = sy == topY || sx == rightX || sy == bottomY || sx == leftX; 00343 // something goes wrong 00344 // These exceptions are used to check logic about processing multipolygons 00345 // However this situation could happen because of broken multipolygons (so it should data causes app error) 00346 // that's why these exceptions could be replaced with return; statement. 00347 if (!end || !st) { 00348 // TODO message 00349 // float dx = (float) MapUtils.get31LongitudeX(x); 00350 // float dsx = (float) MapUtils.get31LongitudeX(sx); 00351 // float dy = (float) MapUtils.get31LatitudeY(y); 00352 // float dsy = (float) MapUtils.get31LatitudeY(sy); 00353 // String str; 00354 // if (!end) { 00355 // str = " Start point (to close) not found : end_x = {0}, end_y = {1}, start_x = {2}, start_y = {3} : bounds {4} {5} - {6} {7}"; //$NON-NLS-1$ 00356 // System.err 00357 // .println(MessageFormat.format(dbId + str, dx, dy, dsx, dsy, leftX + "", topY + "", rightX + "", bottomY + "")); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ 00358 // } 00359 // if (!st) { 00360 // str = " End not found : end_x = {0}, end_y = {1}, start_x = {2}, start_y = {3} : bounds {4} {5} - {6} {7}"; //$NON-NLS-1$ 00361 // System.err 00362 // .println(MessageFormat.format(dbId + str, dx, dy, dsx, dsy, leftX + "", topY + "", rightX + "", bottomY + "")); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ 00363 // } 00364 __android_log_print(ANDROID_LOG_INFO, "net.osmand", "Error processing multipolygon"); 00365 } else { 00366 nonvisitedRings.insert(j); 00367 } 00368 } 00369 ir = incompletedRings.begin(); 00370 irs = incompletedRingsNames.begin(); 00371 for (j = 0; ir != incompletedRings.end(); ir++, irs++, j++) { 00372 if (nonvisitedRings.find(j) == nonvisitedRings.end()) { 00373 continue; 00374 } 00375 int x = ir->at(ir->size() - 1).first; 00376 int y = ir->at(ir->size() - 1).second; 00377 // 31 - (zoom + 8) 00378 const int EVAL_DELTA = 6 << (23 - zoom); 00379 const int UNDEFINED_MIN_DIFF = -1 - EVAL_DELTA; 00380 while (true) { 00381 int st = 0; // st already checked to be one of the four 00382 if (y == topY) { 00383 st = 0; 00384 } else if (x == rightX) { 00385 st = 1; 00386 } else if (y == bottomY) { 00387 st = 2; 00388 } else if (x == leftX) { 00389 st = 3; 00390 } 00391 int nextRingIndex = -1; 00392 // BEGIN go clockwise around rectangle 00393 for (int h = st; h < st + 4; h++) { 00394 00395 // BEGIN find closest nonvisited start (including current) 00396 int mindiff = UNDEFINED_MIN_DIFF; 00397 std::vector<std::vector<int_pair> >::iterator cni = incompletedRings.begin(); 00398 int cnik = 0; 00399 for (;cni != incompletedRings.end(); cni++, cnik ++) { 00400 if (nonvisitedRings.find(cnik) == nonvisitedRings.end()) { 00401 continue; 00402 } 00403 int csx = cni->at(0).first; 00404 int csy = cni->at(0).second; 00405 if (h % 4 == 0) { 00406 // top 00407 if (csy == topY && csx >= safelyAddDelta(x, -EVAL_DELTA)) { 00408 if (mindiff == UNDEFINED_MIN_DIFF || (csx - x) <= mindiff) { 00409 mindiff = (csx - x); 00410 nextRingIndex = cnik; 00411 } 00412 } 00413 } else if (h % 4 == 1) { 00414 // right 00415 if (csx == rightX && csy >= safelyAddDelta(y, -EVAL_DELTA)) { 00416 if (mindiff == UNDEFINED_MIN_DIFF || (csy - y) <= mindiff) { 00417 mindiff = (csy - y); 00418 nextRingIndex = cnik; 00419 } 00420 } 00421 } else if (h % 4 == 2) { 00422 // bottom 00423 if (csy == bottomY && csx <= safelyAddDelta(x, EVAL_DELTA)) { 00424 if (mindiff == UNDEFINED_MIN_DIFF || (x - csx) <= mindiff) { 00425 mindiff = (x - csx); 00426 nextRingIndex = cnik; 00427 } 00428 } 00429 } else if (h % 4 == 3) { 00430 // left 00431 if (csx == leftX && csy <= safelyAddDelta(y, EVAL_DELTA)) { 00432 if (mindiff == UNDEFINED_MIN_DIFF || (y - csy) <= mindiff) { 00433 mindiff = (y - csy); 00434 nextRingIndex = cnik; 00435 } 00436 } 00437 } 00438 } // END find closest start (including current) 00439 00440 // we found start point 00441 if (mindiff != UNDEFINED_MIN_DIFF) { 00442 break; 00443 } else { 00444 if (h % 4 == 0) { 00445 // top 00446 y = topY; 00447 x = rightX; 00448 } else if (h % 4 == 1) { 00449 // right 00450 y = bottomY; 00451 x = rightX; 00452 } else if (h % 4 == 2) { 00453 // bottom 00454 y = bottomY; 00455 x = leftX; 00456 } else if (h % 4 == 3) { 00457 y = topY; 00458 x = leftX; 00459 } 00460 ir->push_back(int_pair(x, y)); 00461 } 00462 00463 } // END go clockwise around rectangle 00464 if (nextRingIndex == -1) { 00465 // it is impossible (current start should always be found) 00466 } else if (nextRingIndex == j) { 00467 ir->push_back(ir->at(0)); 00468 nonvisitedRings.erase(j); 00469 break; 00470 } else { 00471 std::vector<int_pair> p = incompletedRings.at(nextRingIndex); 00472 ir->insert(ir->end(), p.begin(), p.end()); 00473 nonvisitedRings.erase(nextRingIndex); 00474 // get last point and start again going clockwise 00475 x = ir->at(ir->size() - 1).first; 00476 y = ir->at(ir->size() - 1).second; 00477 } 00478 } 00479 00480 completedRings.push_back(*ir); 00481 completedRingNames.push_back(*irs); 00482 } 00483 00484 } 00485 00486 00487 00491 bool calculateIntersection(int x, int y, int px, int py, int leftX, int rightX, int bottomY, int topY, int_pair& b) { 00492 // firstly try to search if the line goes in 00493 if (py < topY && y >= topY) { 00494 int tx = (int) (px + ((double) (x - px) * (topY - py)) / (y - py)); 00495 if (leftX <= tx && tx <= rightX) { 00496 b.first = tx; 00497 b.second = topY; 00498 return true; 00499 } 00500 } 00501 if (py > bottomY && y <= bottomY) { 00502 int tx = (int) (px + ((double) (x - px) * (py - bottomY)) / (py - y)); 00503 if (leftX <= tx && tx <= rightX) { 00504 b.first = tx; 00505 b.second = bottomY; 00506 return true; 00507 } 00508 } 00509 if (px < leftX && x >= leftX) { 00510 int ty = (int) (py + ((double) (y - py) * (leftX - px)) / (x - px)); 00511 if (ty >= topY && ty <= bottomY) { 00512 b.first = leftX; 00513 b.second = ty; 00514 return true; 00515 } 00516 00517 } 00518 if (px > rightX && x <= rightX) { 00519 int ty = (int) (py + ((double) (y - py) * (px - rightX)) / (px - x)); 00520 if (ty >= topY && ty <= bottomY) { 00521 b.first = rightX; 00522 b.second = ty; 00523 return true; 00524 } 00525 00526 } 00527 00528 // try to search if point goes out 00529 if (py > topY && y <= topY) { 00530 int tx = (int) (px + ((double) (x - px) * (topY - py)) / (y - py)); 00531 if (leftX <= tx && tx <= rightX) { 00532 b.first = tx; 00533 b.second = topY; 00534 return true; 00535 } 00536 } 00537 if (py < bottomY && y >= bottomY) { 00538 int tx = (int) (px + ((double) (x - px) * (py - bottomY)) / (py - y)); 00539 if (leftX <= tx && tx <= rightX) { 00540 b.first = tx; 00541 b.second = bottomY; 00542 return true; 00543 } 00544 } 00545 if (px > leftX && x <= leftX) { 00546 int ty = (int) (py + ((double) (y - py) * (leftX - px)) / (x - px)); 00547 if (ty >= topY && ty <= bottomY) { 00548 b.first = leftX; 00549 b.second = ty; 00550 return true; 00551 } 00552 00553 } 00554 if (px < rightX && x >= rightX) { 00555 int ty = (int) (py + ((double) (y - py) * (px - rightX)) / (px - x)); 00556 if (ty >= topY && ty <= bottomY) { 00557 b.first = rightX; 00558 b.second = ty; 00559 return true; 00560 } 00561 00562 } 00563 00564 if (px == rightX || px == leftX || py == topY || py == bottomY) { 00565 b.first = px; 00566 b.second = py; 00567 // return true; 00568 // Is it right? to not return anything? 00569 } 00570 return false; 00571 } 00572 00573 bool calculateLineCoordinates(bool inside, int x, int y, bool pinside, int px, int py, int leftX, int rightX, 00574 int bottomY, int topY, std::vector<int_pair>& coordinates) { 00575 bool lineEnded = false; 00576 int_pair b(x, y); 00577 if (pinside) { 00578 if (!inside) { 00579 bool is = calculateIntersection(x, y, px, py, leftX, rightX, bottomY, topY, b); 00580 if (!is) { 00581 b.first = px; 00582 b.second = py; 00583 } 00584 coordinates.push_back(b); 00585 lineEnded = true; 00586 } else { 00587 coordinates.push_back(b); 00588 } 00589 } else { 00590 bool is = calculateIntersection(x, y, px, py, leftX, rightX, bottomY, topY, b); 00591 if (inside) { 00592 // assert is != -1; 00593 coordinates.push_back(b); 00594 int_pair n(x, y); 00595 coordinates.push_back(n); 00596 } else if (is) { 00597 coordinates.push_back(b); 00598 calculateIntersection(x, y, b.first, b.second, leftX, rightX, bottomY, topY, b); 00599 coordinates.push_back(b); 00600 lineEnded = true; 00601 } 00602 } 00603 00604 return lineEnded; 00605 }