1 // BucketBox.cxx -- Helper for on demand database paging.
2 //
3 // Copyright (C) 2010 - 2013 Mathias Froehlich
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 //
19
20 #ifndef _BUCKETBOX_HXX
21 #define _BUCKETBOX_HXX
22
23 #include <cassert>
24 #include <istream>
25 #include <iomanip>
26 #include <ostream>
27 #include <sstream>
28
29 #include <osg/Geode>
30 #include <osg/CullFace>
31
32 #include <simgear/bucket/newbucket.hxx>
33 #include <simgear/math/SGGeometry.hxx>
34 #include <simgear/scene/util/OsgMath.hxx>
35
36 #ifdef ENABLE_GDAL
37 #include <simgear/scene/dem/SGMesh.hxx>
38 #endif
39
40 namespace simgear {
41
42 #define Elements(x) (sizeof(x)/sizeof((x)[0]))
43
44 // 2*5*3*3 * 4 = 360
45 static const unsigned _lonFactors[] = { 2, 5, 3, 3, 2, 2, /* sub degree */ 2, 2, 2 };
46 // 3*3*5 * 4 = 180
47 static const unsigned _latFactors[] = { 3, 5, 1, 3, 2, 2, /* sub degree */ 2, 2, 2 };
48
product(const unsigned * factors,unsigned count)49 static unsigned product(const unsigned* factors, unsigned count)
50 {
51 unsigned index = 1;
52 while (count--)
53 index *= *factors++;
54 return index;
55 }
56
57 /// /Rectangular/ sub part of the earths surface.
58 /// Stored with offset point in lon/lat and width and height.
59 /// The values are stored in a fixed point format having 3 fractional
60 /// bits which matches the SGBuckets maximum tile resolution.
61 ///
62 /// Notable /design/ decision:
63 /// * The longitude maps to the interval [-180,180[.
64 /// The latitude maps to the interval [-90,90].
65 /// This works now that the tiles do no longer cut
66 /// neither the 180deg nor the 0deg boundary.
67 /// * This is not meant to be an API class for simgear. This is
68 /// just an internal tool that I would like to keep in the SPT loader.
69 /// But I want to have coverage somehow tested with the usual unit
70 /// tests, which is the reason to have this file split out.
71 ///
72 class BucketBox {
73 public:
BucketBox()74 BucketBox()
75 { _offset[0] = 0; _offset[1] = 0; _size[0] = 0; _size[1] = 0; }
BucketBox(double lon,double lat,double width,double height)76 BucketBox(double lon, double lat, double width, double height)
77 {
78 _offset[0] = _longitudeDegToOffset(lon);
79 _offset[1] = _latitudeDegToOffset(lat);
80 _size[0] = _degToSize(width);
81 _size[1] = _degToSize(height);
82 }
BucketBox(const BucketBox & bucketBox)83 BucketBox(const BucketBox& bucketBox)
84 {
85 _offset[0] = bucketBox._offset[0];
86 _offset[1] = bucketBox._offset[1];
87 _size[0] = bucketBox._size[0];
88 _size[1] = bucketBox._size[1];
89 }
90
operator =(const BucketBox & bucketBox)91 BucketBox& operator=(const BucketBox& bucketBox)
92 {
93 _offset[0] = bucketBox._offset[0];
94 _offset[1] = bucketBox._offset[1];
95 _size[0] = bucketBox._size[0];
96 _size[1] = bucketBox._size[1];
97 return *this;
98 }
99
empty() const100 bool empty() const
101 { return _size[0] == 0 || _size[1] == 0; }
102
getOffset(unsigned i) const103 unsigned getOffset(unsigned i) const
104 { return _offset[i]; }
setOffset(unsigned i,unsigned offset)105 void setOffset(unsigned i, unsigned offset)
106 { _offset[i] = offset; }
107
getSize(unsigned i) const108 unsigned getSize(unsigned i) const
109 { return _size[i]; }
setSize(unsigned i,unsigned size)110 void setSize(unsigned i, unsigned size)
111 { _size[i] = size; }
112
getLongitudeDeg() const113 double getLongitudeDeg() const
114 { return _offsetToLongitudeDeg(_offset[0]); }
setLongitudeDeg(double lon)115 void setLongitudeDeg(double lon)
116 { _offset[0] = _longitudeDegToOffset(lon); }
117
getLatitudeDeg() const118 double getLatitudeDeg() const
119 { return _offsetToLatitudeDeg(_offset[1]); }
setLatitudeDeg(double lat)120 void setLatitudeDeg(double lat)
121 { _offset[1] = _latitudeDegToOffset(lat); }
122
getWidthDeg() const123 double getWidthDeg() const
124 { return _sizeToDeg(_size[0]); }
setWidthDeg(double width)125 void setWidthDeg(double width)
126 { _size[0] = _degToSize(width); }
127
getHeightDeg() const128 double getHeightDeg() const
129 { return _sizeToDeg(_size[1]); }
setHeightDeg(double height)130 void setHeightDeg(double height)
131 { _size[1] = _degToSize(height); }
132
getWidthIsBucketSize() const133 bool getWidthIsBucketSize() const
134 {
135 if (_size[0] <= _bucketSpanAtOffset(_offset[1]))
136 return true;
137 return _size[0] <= _bucketSpanAtOffset(_offset[1] + _size[1] - 1);
138 }
139
getHeightIsBucketSize() const140 bool getHeightIsBucketSize() const
141 { return _size[1] == 1; }
getIsBucketSize() const142 bool getIsBucketSize() const
143 { return getHeightIsBucketSize() && getWidthIsBucketSize(); }
144
getBucket() const145 SGBucket getBucket() const
146 {
147 // left align longitude offsets
148 unsigned offset = _offset[0] - _offset[0] % _bucketSpanAtOffset(_offset[1]);
149 return SGBucket( SGGeod::fromDeg(_offsetToLongitudeDeg(offset), _offsetToLatitudeDeg(_offset[1])) );
150 }
151
getParentBox(unsigned level) const152 BucketBox getParentBox(unsigned level) const
153 {
154 BucketBox box;
155 unsigned plon = product(_lonFactors + level, Elements(_lonFactors) - level);
156 unsigned plat = product(_latFactors + level, Elements(_latFactors) - level);
157 box._offset[0] = _offset[0] - _offset[0] % plon;
158 box._offset[0] = _normalizeLongitude(box._offset[0]);
159 box._offset[1] = _offset[1] - _offset[1] % plat;
160 box._size[0] = plon;
161 box._size[1] = plat;
162
163 return box;
164 }
165
getSubBoxHeight(unsigned j,unsigned level) const166 BucketBox getSubBoxHeight(unsigned j, unsigned level) const
167 {
168 assert(0 < level);
169 BucketBox box;
170 unsigned plat = product(_latFactors + level, Elements(_latFactors) - level);
171 unsigned plat1 = plat*_latFactors[level - 1];
172 box._offset[0] = _offset[0];
173 box._offset[1] = _offset[1] - _offset[1] % plat1 + j*plat;
174 box._size[0] = _size[0];
175 box._size[1] = plat;
176
177 return box;
178 }
179
getSubBoxWidth(unsigned i,unsigned level) const180 BucketBox getSubBoxWidth(unsigned i, unsigned level) const
181 {
182 assert(0 < level);
183 BucketBox box;
184 unsigned plon = product(_lonFactors + level, Elements(_lonFactors) - level);
185 unsigned plon1 = plon*_lonFactors[level - 1];
186 box._offset[0] = _offset[0] - _offset[0] % plon1 + i*plon;
187 box._offset[1] = _offset[1];
188 box._size[0] = plon;
189 box._size[1] = _size[1];
190
191 box._offset[0] = _normalizeLongitude(box._offset[0]);
192
193 return box;
194 }
195
getWidthLevel() const196 unsigned getWidthLevel() const
197 { return _getLevel(_lonFactors, Elements(_lonFactors), _offset[0], _offset[0] + _size[0]); }
getHeightLevel() const198 unsigned getHeightLevel() const
199 { return _getLevel(_latFactors, Elements(_latFactors), _offset[1], _offset[1] + _size[1]); }
200
getWidthIncrement(unsigned level) const201 unsigned getWidthIncrement(unsigned level) const
202 {
203 level = SGMisc<unsigned>::clip(level, 5, Elements(_lonFactors));
204 return product(_lonFactors + level, Elements(_lonFactors) - level);
205 }
getHeightIncrement(unsigned level) const206 unsigned getHeightIncrement(unsigned level) const
207 {
208 level = SGMisc<unsigned>::clip(level, 5, Elements(_latFactors));
209 return product(_latFactors + level, Elements(_latFactors) - level);
210 }
211
getStartLevel() const212 unsigned getStartLevel() const
213 {
214 if (getWidthIsBucketSize())
215 return getHeightLevel();
216 return std::min(getWidthLevel(), getHeightLevel());
217 }
218
getBoundingSphere() const219 SGSpheref getBoundingSphere() const
220 {
221 SGBoxf box;
222 for (unsigned i = 0, incx = 10*8; incx != 0; i += incx) {
223 for (unsigned j = 0, incy = 10*8; incy != 0; j += incy) {
224 box.expandBy(SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, -1000)));
225 box.expandBy(SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, 10000)));
226 incy = std::min(incy, _size[1] - j);
227 }
228 incx = std::min(incx, _size[0] - i);
229 }
230 SGSpheref sphere(box.getCenter(), 0);
231 for (unsigned i = 0, incx = 10*8; incx != 0; i += incx) {
232 for (unsigned j = 0, incy = 10*8; incy != 0; j += incy) {
233 float r2;
234 r2 = distSqr(sphere.getCenter(), SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, -1000)));
235 if (sphere.getRadius2() < r2)
236 sphere.setRadius(sqrt(r2));
237 r2 = distSqr(sphere.getCenter(), SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, 10000)));
238 if (sphere.getRadius2() < r2)
239 sphere.setRadius(sqrt(r2));
240 incy = std::min(incy, _size[1] - j);
241 }
242 incx = std::min(incx, _size[0] - i);
243 }
244 return sphere;
245 }
246
247 // Split the current box into up to two boxes that do not cross the 360 deg border.
periodicSplit(BucketBox bucketBoxList[2]) const248 unsigned periodicSplit(BucketBox bucketBoxList[2]) const
249 {
250 if (empty())
251 return 0;
252
253 bucketBoxList[0] = *this;
254 bucketBoxList[0]._offset[0] = _normalizeLongitude(bucketBoxList[0]._offset[0]);
255 if (bucketBoxList[0]._offset[0] + bucketBoxList[0]._size[0] <= 360*8)
256 return 1;
257
258 bucketBoxList[1] = bucketBoxList[0];
259 bucketBoxList[0]._size[0] = 360*8 - bucketBoxList[0]._offset[0];
260
261 bucketBoxList[1]._offset[0] = 0;
262 bucketBoxList[1]._size[0] = _size[0] - bucketBoxList[0]._size[0];
263
264 return 2;
265 }
266
getSubDivision(BucketBox bucketBoxList[],unsigned bucketBoxListSize) const267 unsigned getSubDivision(BucketBox bucketBoxList[], unsigned bucketBoxListSize) const
268 {
269 unsigned numTiles = 0;
270
271 // Quad tree like structure in x and y direction
272 unsigned widthLevel = getWidthLevel();
273 unsigned heightLevel = getHeightLevel();
274
275 unsigned level;
276 if (getWidthIsBucketSize()) {
277 level = heightLevel;
278 } else {
279 level = std::min(widthLevel, heightLevel);
280 }
281 for (unsigned j = 0; j < _latFactors[level]; ++j) {
282 BucketBox heightSplitBox = getSubBoxHeight(j, level + 1);
283
284 heightSplitBox = _intersection(*this, heightSplitBox);
285 if (heightSplitBox.empty())
286 continue;
287
288 if (heightSplitBox.getWidthIsBucketSize()) {
289 bucketBoxList[numTiles++] = heightSplitBox;
290 assert(numTiles <= bucketBoxListSize);
291 } else {
292 for (unsigned i = 0; i < _lonFactors[widthLevel]; ++i) {
293 BucketBox childBox = _intersection(heightSplitBox, heightSplitBox.getSubBoxWidth(i, widthLevel + 1));
294 if (childBox.empty())
295 continue;
296
297 bucketBoxList[numTiles++] = childBox;
298 assert(numTiles <= bucketBoxListSize);
299 }
300 }
301 }
302 return numTiles;
303 }
304
getTileTriangles(unsigned i,unsigned j,unsigned width,unsigned height,SGVec3f points[6],SGVec3f normals[6],SGVec2f texCoords[6]) const305 unsigned getTileTriangles(unsigned i, unsigned j, unsigned width, unsigned height,
306 SGVec3f points[6], SGVec3f normals[6], SGVec2f texCoords[6]) const
307 {
308 unsigned numPoints = 0;
309
310 unsigned x0 = _offset[0] + i;
311 unsigned x1 = x0 + width;
312
313 unsigned y0 = _offset[1] + j;
314 unsigned y1 = y0 + height;
315
316 SGGeod p00 = _offsetToGeod(x0, y0, 0);
317 SGVec3f v00 = SGVec3f::fromGeod(p00);
318 SGVec3f n00 = SGQuatf::fromLonLat(p00).backTransform(SGVec3f(0, 0, -1));
319 SGVec2f t00(x0*1.0/(360*8), y0*1.0/(180*8));
320
321 SGGeod p10 = _offsetToGeod(x1, y0, 0);
322 SGVec3f v10 = SGVec3f::fromGeod(p10);
323 SGVec3f n10 = SGQuatf::fromLonLat(p10).backTransform(SGVec3f(0, 0, -1));
324 SGVec2f t10(x1*1.0/(360*8), y0*1.0/(180*8));
325
326 SGGeod p11 = _offsetToGeod(x1, y1, 0);
327 SGVec3f v11 = SGVec3f::fromGeod(p11);
328 SGVec3f n11 = SGQuatf::fromLonLat(p11).backTransform(SGVec3f(0, 0, -1));
329 SGVec2f t11(x1*1.0/(360*8), y1*1.0/(180*8));
330
331 SGGeod p01 = _offsetToGeod(x0, y1, 0);
332 SGVec3f v01 = SGVec3f::fromGeod(p01);
333 SGVec3f n01 = SGQuatf::fromLonLat(p01).backTransform(SGVec3f(0, 0, -1));
334 SGVec2f t01(x0*1.0/(360*8), y1*1.0/(180*8));
335
336 if (y0 != 0) {
337 points[numPoints] = v00;
338 normals[numPoints] = n00;
339 texCoords[numPoints] = t00;
340 ++numPoints;
341
342 points[numPoints] = v10;
343 normals[numPoints] = n10;
344 texCoords[numPoints] = t10;
345 ++numPoints;
346
347 points[numPoints] = v01;
348 normals[numPoints] = n01;
349 texCoords[numPoints] = t01;
350 ++numPoints;
351 }
352 if (y1 != 180*8) {
353 points[numPoints] = v11;
354 normals[numPoints] = n11;
355 texCoords[numPoints] = t11;
356 ++numPoints;
357
358 points[numPoints] = v01;
359 normals[numPoints] = n01;
360 texCoords[numPoints] = t01;
361 ++numPoints;
362
363 points[numPoints] = v10;
364 normals[numPoints] = n10;
365 texCoords[numPoints] = t10;
366 ++numPoints;
367 }
368 return numPoints;
369 }
370
371 #ifdef ENABLE_GDAL
getTileTriangleMesh(const SGDemPtr dem,unsigned res,SGMesh::TextureMethod tm,const osgDB::Options * options) const372 osg::Geode* getTileTriangleMesh( const SGDemPtr dem, unsigned res, SGMesh::TextureMethod tm, const osgDB::Options* options ) const
373 {
374 unsigned widthLevel = getWidthLevel();
375 unsigned heightLevel = getHeightLevel();
376
377 unsigned x0 = _offset[0];
378 unsigned x1 = x0 + _size[0];
379
380 unsigned y0 = _offset[1];
381 unsigned y1 = y0 + _size[1];
382
383 SGSpheref sphere = getBoundingSphere();
384 osg::Matrixd transform;
385 transform.makeTranslate(toOsg(-sphere.getCenter()));
386
387 // create a mesh of this dimension
388 if ( (y0 != 0) && (y1 != 180*8) ) {
389 SGMesh mesh( dem, x0, y0, x1, y1, heightLevel, widthLevel, transform, tm, options );
390 return mesh.getGeode();
391 } else {
392 // todo - handle poles
393 return 0;
394 }
395 }
396 #endif
397
398 private:
_normalizeLongitude(unsigned offset)399 static unsigned _normalizeLongitude(unsigned offset)
400 { return offset - (360*8)*(offset/(360*8)); }
401
_longitudeDegToOffset(double lon)402 static unsigned _longitudeDegToOffset(double lon)
403 {
404 unsigned offset = (unsigned)(8*(lon + 180) + 0.5);
405 return _normalizeLongitude(offset);
406 }
_offsetToLongitudeDeg(unsigned offset)407 static double _offsetToLongitudeDeg(unsigned offset)
408 { return offset*0.125 - 180; }
409
_latitudeDegToOffset(double lat)410 static unsigned _latitudeDegToOffset(double lat)
411 {
412 if (lat < -90)
413 return 0;
414 unsigned offset = (unsigned)(8*(lat + 90) + 0.5);
415 if (8*180 < offset)
416 return 8*180;
417 return offset;
418 }
_offsetToLatitudeDeg(unsigned offset)419 static double _offsetToLatitudeDeg(unsigned offset)
420 { return offset*0.125 - 90; }
421
_degToSize(double deg)422 static unsigned _degToSize(double deg)
423 {
424 if (deg <= 0)
425 return 0;
426 return (unsigned)(8*deg + 0.5);
427 }
_sizeToDeg(unsigned size)428 static double _sizeToDeg(unsigned size)
429 { return size*0.125; }
430
_bucketSpanAtOffset(unsigned offset)431 static unsigned _bucketSpanAtOffset(unsigned offset)
432 { return (unsigned)(8*sg_bucket_span(_offsetToLatitudeDeg(offset) + 0.0625) + 0.5); }
433
_offsetToGeod(unsigned offset0,unsigned offset1,double elev)434 static SGGeod _offsetToGeod(unsigned offset0, unsigned offset1, double elev)
435 { return SGGeod::fromDegM(_offsetToLongitudeDeg(offset0), _offsetToLatitudeDeg(offset1), elev); }
436
_getLevel(const unsigned factors[],unsigned nFactors,unsigned begin,unsigned end)437 static unsigned _getLevel(const unsigned factors[], unsigned nFactors, unsigned begin, unsigned end)
438 {
439 unsigned rbegin = end - 1;
440 for (; 0 < nFactors;) {
441 if (begin == rbegin)
442 break;
443 --nFactors;
444 begin /= factors[nFactors];
445 rbegin /= factors[nFactors];
446 }
447
448 return nFactors;
449 }
450
_intersection(const BucketBox & box0,const BucketBox & box1)451 static BucketBox _intersection(const BucketBox& box0, const BucketBox& box1)
452 {
453 BucketBox box;
454 for (unsigned i = 0; i < 2; ++i) {
455 box._offset[i] = std::max(box0._offset[i], box1._offset[i]);
456 unsigned m = std::min(box0._offset[i] + box0._size[i], box1._offset[i] + box1._size[i]);
457 if (m <= box._offset[i])
458 box._size[i] = 0;
459 else
460 box._size[i] = m - box._offset[i];
461 }
462
463 box._offset[0] = _normalizeLongitude(box._offset[0]);
464
465 return box;
466 }
467
468 unsigned _offset[2];
469 unsigned _size[2];
470 };
471
472 inline bool
operator ==(const BucketBox & bucketBox0,const BucketBox & bucketBox1)473 operator==(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
474 {
475 if (bucketBox0.getOffset(0) != bucketBox1.getOffset(0))
476 return false;
477 if (bucketBox0.getOffset(1) != bucketBox1.getOffset(1))
478 return false;
479 if (bucketBox0.getSize(0) != bucketBox1.getSize(0))
480 return false;
481 if (bucketBox0.getSize(1) != bucketBox1.getSize(1))
482 return false;
483 return true;
484 }
485
486 inline bool
operator !=(const BucketBox & bucketBox0,const BucketBox & bucketBox1)487 operator!=(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
488 { return !operator==(bucketBox0, bucketBox1); }
489
490 inline bool
operator <(const BucketBox & bucketBox0,const BucketBox & bucketBox1)491 operator<(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
492 {
493 if (bucketBox0.getOffset(0) < bucketBox1.getOffset(0)) return true;
494 else if (bucketBox1.getOffset(0) < bucketBox0.getOffset(0)) return false;
495 else if (bucketBox0.getOffset(1) < bucketBox1.getOffset(1)) return true;
496 else if (bucketBox1.getOffset(1) < bucketBox0.getOffset(1)) return false;
497 else if (bucketBox0.getSize(0) < bucketBox1.getSize(0)) return true;
498 else if (bucketBox1.getSize(0) < bucketBox0.getSize(0)) return false;
499 else return bucketBox0.getSize(1) < bucketBox1.getSize(1);
500 }
501
502 inline bool
operator >(const BucketBox & bucketBox0,const BucketBox & bucketBox1)503 operator>(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
504 { return operator<(bucketBox1, bucketBox0); }
505
506 inline bool
operator <=(const BucketBox & bucketBox0,const BucketBox & bucketBox1)507 operator<=(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
508 { return !operator>(bucketBox0, bucketBox1); }
509
510 inline bool
operator >=(const BucketBox & bucketBox0,const BucketBox & bucketBox1)511 operator>=(const BucketBox& bucketBox0, const BucketBox& bucketBox1)
512 { return !operator<(bucketBox0, bucketBox1); }
513
514 /// Stream output operator.
515 /// Note that this is not only used for pretty printing but also for
516 /// generating the meta file names for on demand paging.
517 /// So, don't modify unless you know where else this is used.
518 template<typename char_type, typename traits_type>
519 std::basic_ostream<char_type, traits_type>&
operator <<(std::basic_ostream<char_type,traits_type> & os,const BucketBox & bucketBox)520 operator<<(std::basic_ostream<char_type, traits_type>& os, const BucketBox& bucketBox)
521 {
522 std::basic_stringstream<char_type, traits_type> ss;
523
524 double minSize = std::min(bucketBox.getWidthDeg(), bucketBox.getHeightDeg());
525
526 unsigned fieldPrecision = 0;
527 if (minSize <= 0.125) {
528 fieldPrecision = 3;
529 } else if (minSize <= 0.25) {
530 fieldPrecision = 2;
531 } else if (minSize <= 0.5) {
532 fieldPrecision = 1;
533 }
534
535 unsigned lonFieldWidth = 3;
536 if (fieldPrecision)
537 lonFieldWidth += 1 + fieldPrecision;
538
539 ss << std::fixed << std::setfill('0') << std::setprecision(fieldPrecision);
540
541 double lon = bucketBox.getLongitudeDeg();
542 if (lon < 0)
543 ss << "w" << std::setw(lonFieldWidth) << -lon << std::setw(0);
544 else
545 ss << "e" << std::setw(lonFieldWidth) << lon << std::setw(0);
546
547 unsigned latFieldWidth = 2;
548 if (fieldPrecision)
549 latFieldWidth += 1 + fieldPrecision;
550
551 double lat = bucketBox.getLatitudeDeg();
552 if (lat < -90)
553 lat = -90;
554 if (90 < lat)
555 lat = 90;
556 if (lat < 0)
557 ss << "s" << std::setw(latFieldWidth) << -lat << std::setw(0);
558 else
559 ss << "n" << std::setw(latFieldWidth) << lat << std::setw(0);
560
561 ss << "-" << bucketBox.getWidthDeg() << "x" << bucketBox.getHeightDeg();
562
563 return os << ss.str();
564 }
565
566 /// Stream inout operator.
567 /// Note that this is used for reading the meta file names for on demand paging.
568 /// So, don't modify unless you know where else this is used.
569 template<typename char_type, typename traits_type>
570 std::basic_istream<char_type, traits_type>&
operator >>(std::basic_istream<char_type,traits_type> & is,BucketBox & bucketBox)571 operator>>(std::basic_istream<char_type, traits_type>& is, BucketBox& bucketBox)
572 {
573 char c;
574 is >> c;
575 if (is.fail())
576 return is;
577
578 int sign = 0;
579 if (c == 'w')
580 sign = -1;
581 else if (c == 'e')
582 sign = 1;
583 else {
584 is.setstate(std::ios::failbit | is.rdstate());
585 return is;
586 }
587
588 double num;
589 is >> num;
590 if (is.fail()) {
591 is.setstate(std::ios::failbit | is.rdstate());
592 return is;
593 }
594 bucketBox.setLongitudeDeg(sign*num);
595
596 is >> c;
597 if (is.fail())
598 return is;
599
600 sign = 0;
601 if (c == 's')
602 sign = -1;
603 else if (c == 'n')
604 sign = 1;
605 else {
606 is.setstate(std::ios::failbit | is.rdstate());
607 return is;
608 }
609
610 is >> num;
611 if (is.fail())
612 return is;
613 bucketBox.setLatitudeDeg(sign*num);
614
615 is >> c;
616 if (is.fail())
617 return is;
618 if (c != '-'){
619 is.setstate(std::ios::failbit | is.rdstate());
620 return is;
621 }
622
623 is >> num;
624 if (is.fail())
625 return is;
626 bucketBox.setWidthDeg(SGMiscd::min(num, 360));
627
628 is >> c;
629 if (is.fail())
630 return is;
631 if (c != 'x'){
632 is.setstate(std::ios::failbit | is.rdstate());
633 return is;
634 }
635
636 is >> num;
637 if (is.fail())
638 return is;
639 bucketBox.setHeightDeg(SGMiscd::min(num, 90 - bucketBox.getLatitudeDeg()));
640
641 return is;
642 }
643
644 } // namespace simgear
645
646 #endif
647