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