1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3  * Copyright 2019 Pelican Mapping
4  * http://osgearth.org
5  *
6  * osgEarth is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 
20 #include <osgEarth/SpatialReference>
21 #include <osgEarth/Registry>
22 #include <osgEarth/Cube>
23 #include <osgEarth/LocalTangentPlane>
24 #include <ogr_spatialref.h>
25 #include <cpl_conv.h>
26 
27 #define LC "[SpatialReference] "
28 
29 using namespace osgEarth;
30 
31 //------------------------------------------------------------------------
32 
33 namespace
34 {
35     std::string
getOGRAttrValue(void * _handle,const std::string & name,int child_num,bool lowercase=false)36     getOGRAttrValue( void* _handle, const std::string& name, int child_num, bool lowercase =false)
37     {
38         GDAL_SCOPED_LOCK;
39         const char* val = OSRGetAttrValue( _handle, name.c_str(), child_num );
40         if ( val )
41         {
42             return lowercase ? toLower(val) : val;
43         }
44         return "";
45     }
46 
geodeticToGeocentric(std::vector<osg::Vec3d> & points,const osg::EllipsoidModel * em)47     void geodeticToGeocentric(std::vector<osg::Vec3d>& points, const osg::EllipsoidModel* em)
48     {
49         for( unsigned i=0; i<points.size(); ++i )
50         {
51             double x, y, z;
52             em->convertLatLongHeightToXYZ(
53                 osg::DegreesToRadians( points[i].y() ), osg::DegreesToRadians( points[i].x() ), points[i].z(),
54                 x, y, z );
55             points[i].set( x, y, z );
56         }
57     }
58 
geocentricToGeodetic(std::vector<osg::Vec3d> & points,const osg::EllipsoidModel * em)59     void geocentricToGeodetic(std::vector<osg::Vec3d>& points, const osg::EllipsoidModel* em)
60     {
61         for( unsigned i=0; i<points.size(); ++i )
62         {
63             double lat, lon, alt;
64             em->convertXYZToLatLongHeight(
65                 points[i].x(), points[i].y(), points[i].z(),
66                 lat, lon, alt );
67 
68             // deal with bug in OSG 3.4.x in which convertXYZToLatLongHeight can return
69             // NANs when converting from (0,0,0) with a spherical ellipsoid -gw 2/5/2019
70             if (osg::isNaN(lon)) lon = 0.0;
71             if (osg::isNaN(lat)) lat = 0.0;
72             if (osg::isNaN(alt)) alt = 0.0;
73 
74             points[i].set( osg::RadiansToDegrees(lon), osg::RadiansToDegrees(lat), alt );
75         }
76     }
77 }
78 
79 //------------------------------------------------------------------------
80 
81 SpatialReference*
createFromPROJ4(const std::string & proj4,const std::string & name)82 SpatialReference::createFromPROJ4( const std::string& proj4, const std::string& name )
83 {
84     SpatialReference* result = NULL;
85     GDAL_SCOPED_LOCK;
86 	void* handle = OSRNewSpatialReference( NULL );
87     if ( OSRImportFromProj4( handle, proj4.c_str() ) == OGRERR_NONE )
88 	{
89         result = new SpatialReference( handle, std::string("PROJ4") );
90 	}
91 	else
92 	{
93         OE_WARN << LC << "Unable to create spatial reference from PROJ4: " << proj4 << std::endl;
94 		OSRDestroySpatialReference( handle );
95 	}
96     return result;
97 }
98 
99 SpatialReference*
createCube()100 SpatialReference::createCube()
101 {
102     // root the cube srs with a WGS84 intermediate ellipsoid.
103     std::string init = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs";
104 
105     SpatialReference* result = NULL;
106     GDAL_SCOPED_LOCK;
107 	void* handle = OSRNewSpatialReference( NULL );
108     if ( OSRImportFromProj4( handle, init.c_str() ) == OGRERR_NONE )
109 	{
110         result = new CubeSpatialReference( handle );
111 	}
112 	else
113 	{
114         OE_WARN << LC << "Unable to create SRS: " << init << std::endl;
115 		OSRDestroySpatialReference( handle );
116 	}
117     return result;
118 }
119 
120 SpatialReference*
createFromWKT(const std::string & wkt,const std::string & name)121 SpatialReference::createFromWKT( const std::string& wkt, const std::string& name )
122 {
123     osg::ref_ptr<SpatialReference> result;
124     GDAL_SCOPED_LOCK;
125     void* handle = OSRNewSpatialReference( NULL );
126     char buf[8192];
127     char* buf_ptr = &buf[0];
128     if (wkt.length() < 8192)
129     {
130         strcpy( buf, wkt.c_str() );
131     }
132     else
133     {
134         OE_WARN << LC << "BUFFER OVERFLOW - INTERNAL ERROR\n";
135         return 0L;
136     }
137 
138     if ( OSRImportFromWkt( handle, &buf_ptr ) == OGRERR_NONE )
139     {
140         result = new SpatialReference( handle, std::string("WKT") );
141         result = result->fixWKT();
142     }
143     else
144     {
145         OE_WARN << LC << "Unable to create spatial reference from WKT: " << wkt << std::endl;
146         OSRDestroySpatialReference( handle );
147     }
148     return result.release();
149 }
150 
151 SpatialReference*
createFromUserInput(const std::string & input,const std::string & name)152 SpatialReference::createFromUserInput( const std::string& input, const std::string& name )
153 {
154     osg::ref_ptr<SpatialReference> result;
155     GDAL_SCOPED_LOCK;
156     void* handle = OSRNewSpatialReference( NULL );
157     if ( OSRSetFromUserInput( handle, input.c_str() ) == OGRERR_NONE )
158     {
159         result = new SpatialReference( handle, std::string("UserInput") );
160     }
161     else
162     {
163         //OE_WARN << LC << "Unable to create spatial reference from user input: " << input << std::endl;
164         OSRDestroySpatialReference( handle );
165     }
166     return result.release();
167 }
168 
169 SpatialReference*
create(const std::string & horiz,const std::string & vert)170 SpatialReference::create( const std::string& horiz, const std::string& vert )
171 {
172     return Registry::instance()->getOrCreateSRS( Key(horiz, vert) );
173 }
174 
175 SpatialReference*
create(const Key & key)176 SpatialReference::create(const Key& key)
177 {
178     // now try to resolve the horizontal SRS:
179     osg::ref_ptr<SpatialReference> srs;
180 
181     // shortcut for spherical-mercator:
182     if (key.horizLower == "spherical-mercator" ||
183         key.horizLower == "epsg:900913"        ||
184         key.horizLower == "epsg:3785"          ||
185         key.horizLower == "epsg:102113")
186     {
187         // note the use of nadgrids=@null (see http://proj.maptools.org/faq.html)
188         srs = createFromPROJ4(
189             "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +towgs84=0,0,0,0,0,0,0 +wktext +no_defs",
190             "Spherical Mercator" );
191     }
192 
193     // ellipsoidal ("world") mercator:
194     else
195         if (key.horizLower == "world-mercator" ||
196             key.horizLower == "epsg:54004"     ||
197             key.horizLower == "epsg:9804"      ||
198             key.horizLower == "epsg:3832"      ||
199             key.horizLower == "epsg:102100"    ||
200             key.horizLower == "esri:102100"    ||
201             key.horizLower == "osgeo:41001" )
202 
203     {
204         srs = createFromPROJ4(
205             "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
206             "World Mercator" );
207     }
208 
209     // common WGS84:
210     else
211         if (key.horizLower == "epsg:4326" ||
212             key.horizLower == "wgs84")
213     {
214         srs = createFromPROJ4(
215             "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
216             "WGS84" );
217     }
218 
219     // WGS84 Plate Carre:
220     else if (key.horizLower == "plate-carre" || key.horizLower == "plate-carree")
221     {
222         // https://proj4.org/operations/projections/eqc.html
223         srs = createFromPROJ4(
224            "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +units=m +ellps=WGS84 +datum=WGS84 +no_defs",
225            "WGS84" );
226 
227         srs->_is_geographic  = false;
228     }
229 
230     // custom srs for the unified cube
231     else if (key.horizLower == "unified-cube" )
232     {
233         srs = createCube();
234     }
235 
236     else if (key.horizLower.find( "+" ) == 0 )
237     {
238         srs = createFromPROJ4( key.horiz, key.horiz );
239     }
240     else if (key.horizLower.find( "epsg:" )  == 0 ||
241              key.horizLower.find( "osgeo:" ) == 0 )
242     {
243         srs = createFromPROJ4( std::string("+init=") + key.horizLower, key.horiz );
244     }
245     else if (key.horizLower.find( "projcs" ) == 0 ||
246              key.horizLower.find( "geogcs" ) == 0 )
247     {
248         srs = createFromWKT( key.horiz, key.horiz );
249     }
250     else
251     {
252         // Try to set it from the user input.  This will handle things like CRS:84
253         // createFromUserInput will actually handle all valid inputs from GDAL, so we might be able to
254         // simplify this function and just call createFromUserInput.
255         srs = createFromUserInput( key.horiz, key.horiz );
256     }
257 
258     // bail out if no SRS exists by this point
259     if ( srs == 0L )
260     {
261         return 0L;
262     }
263 
264     // next, resolve the vertical SRS:
265     if ( !key.vert.empty() && !ciEquals(key.vert, "geodetic") )
266     {
267         srs->_vdatum = VerticalDatum::get( key.vert );
268         if ( !srs->_vdatum.valid() )
269         {
270             OE_WARN << LC << "Failed to locate vertical datum \"" << key.vert << "\"" << std::endl;
271         }
272     }
273 
274     srs->_key = key;
275 
276     return srs.release();
277 }
278 
279 SpatialReference*
create(osg::CoordinateSystemNode * csn)280 SpatialReference::create( osg::CoordinateSystemNode* csn )
281 {
282     SpatialReference* result = NULL;
283     if ( csn && !csn->getCoordinateSystem().empty() )
284     {
285         result = create( csn->getCoordinateSystem() );
286     }
287     return result;
288 }
289 
290 SpatialReference*
createFromHandle(void * ogrHandle)291 SpatialReference::createFromHandle(void* ogrHandle)
292 {
293     if (!ogrHandle)
294     {
295         OE_WARN << LC << "Illegal call to createFromHandle(NULL)" << std::endl;
296         return 0L;
297     }
298 
299     void* clonedHandle = OSRClone(ogrHandle);
300     if (!clonedHandle)
301     {
302         OE_WARN << LC << "Internal error: createFromHandle() failed to clone" << std::endl;
303         return 0L;
304     }
305 
306     return new SpatialReference(clonedHandle);
307 }
308 
309 SpatialReference*
fixWKT()310 SpatialReference::fixWKT()
311 {
312     std::string proj = getOGRAttrValue( _handle, "PROJECTION", 0 );
313 
314     // fix invalid ESRI LCC projections:
315     if ( ciEquals( proj, "Lambert_Conformal_Conic" ) )
316     {
317         bool has_2_sps =
318             !getOGRAttrValue( _handle, "Standard_Parallel_2", 0 ).empty() ||
319             !getOGRAttrValue( _handle, "standard_parallel_2", 0 ).empty();
320 
321         std::string new_wkt = getWKT();
322         if ( has_2_sps )
323         {
324             ciReplaceIn( new_wkt, "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_2SP" );
325         }
326         else
327         {
328             ciReplaceIn( new_wkt, "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_1SP" );
329         }
330 
331         OE_INFO << LC << "Morphing Lambert_Conformal_Conic to 1SP/2SP" << std::endl;
332 
333         return createFromWKT( new_wkt, _name );
334     }
335 
336     // fixes for ESRI Plate_Carree and Equidistant_Cylindrical projections:
337     else if ( proj == "Plate_Carree" )
338     {
339         std::string new_wkt = getWKT();
340         ciReplaceIn( new_wkt, "Plate_Carree", "Equirectangular" );
341         OE_INFO << LC << "Morphing Plate_Carree to Equirectangular" << std::endl;
342         return createFromWKT( new_wkt, _name ); //, input->getReferenceFrame() );
343     }
344     else if ( proj == "Equidistant_Cylindrical" )
345     {
346         std::string new_wkt = getWKT();
347         OE_INFO << LC << "Morphing Equidistant_Cylindrical to Equirectangular" << std::endl;
348         ciReplaceIn( new_wkt, "Equidistant_Cylindrical", "Equirectangular" );
349         return createFromWKT( new_wkt, _name );
350     }
351 
352     // no changes.
353     return this;
354 }
355 
356 
357 /****************************************************************************/
358 
359 
SpatialReference(void * handle,const std::string & init_type)360 SpatialReference::SpatialReference(void* handle,
361                                    const std::string& init_type) :
362 osg::Referenced ( true ),
363 _initialized    ( false ),
364 _handle         ( handle ),
365 _owns_handle    ( true ),
366 _init_type      ( init_type ),
367 _is_geographic  ( false ),
368 _is_geocentric  ( false ),
369 _is_mercator    ( false ),
370 _is_north_polar ( false ),
371 _is_south_polar ( false ),
372 _is_cube        ( false ),
373 _is_contiguous  ( false ),
374 _is_user_defined( false ),
375 _is_ltp         ( false ),
376 _is_spherical_mercator( false ),
377 _ellipsoidId(0u)
378 {
379     // nop
380 }
381 
SpatialReference(void * handle,bool ownsHandle)382 SpatialReference::SpatialReference(void* handle, bool ownsHandle) :
383 osg::Referenced( true ),
384 _initialized   ( false ),
385 _handle        ( handle ),
386 _owns_handle   ( ownsHandle ),
387 _is_ltp        ( false ),
388 _is_geocentric ( false )
389 {
390     //nop
391 }
392 
~SpatialReference()393 SpatialReference::~SpatialReference()
394 {
395     if ( _handle )
396     {
397         if (_initialized)
398         {
399             OE_DEBUG << LC << "Destroying " << getName() << std::endl;
400         }
401         else
402         {
403             OE_DEBUG << LC << "Destroying [unitialized SRS]" << std::endl;
404         }
405 
406         for (TransformHandleCache::iterator itr = _transformHandleCache.begin(); itr != _transformHandleCache.end(); ++itr)
407         {
408             OCTDestroyCoordinateTransformation(itr->second);
409         }
410 
411         if ( _owns_handle )
412         {
413             OSRDestroySpatialReference( _handle );
414         }
415 
416         _handle = NULL;
417     }
418 }
419 
420 bool
isGeographic() const421 SpatialReference::isGeographic() const
422 {
423     if ( !_initialized )
424         const_cast<SpatialReference*>(this)->init();
425     return _is_geographic;
426 }
427 
428 bool
isGeodetic() const429 SpatialReference::isGeodetic() const
430 {
431     if ( !_initialized )
432         const_cast<SpatialReference*>(this)->init();
433     return _is_geographic && !_vdatum.valid();
434 }
435 
436 bool
isProjected() const437 SpatialReference::isProjected() const
438 {
439     if ( !_initialized )
440         const_cast<SpatialReference*>(this)->init();
441     return !_is_geographic && !_is_geocentric;
442 }
443 
444 bool
isGeocentric() const445 SpatialReference::isGeocentric() const
446 {
447     if ( !_initialized )
448         const_cast<SpatialReference*>(this)->init();
449     return _is_geocentric;
450 }
451 
452 const std::string&
getName() const453 SpatialReference::getName() const
454 {
455     if ( !_initialized )
456         const_cast<SpatialReference*>(this)->init();
457     return _name;
458 }
459 
460 const osg::EllipsoidModel*
getEllipsoid() const461 SpatialReference::getEllipsoid() const
462 {
463     if ( !_initialized )
464         const_cast<SpatialReference*>(this)->init();
465     return _ellipsoid.get();
466 }
467 
468 const std::string&
getDatumName() const469 SpatialReference::getDatumName() const
470 {
471     if ( !_initialized )
472         const_cast<SpatialReference*>(this)->init();
473     return _datum;
474 }
475 
476 const Units&
getUnits() const477 SpatialReference::getUnits() const
478 {
479     if ( !_initialized )
480         const_cast<SpatialReference*>(this)->init();
481     return _units;
482 }
483 
484 
485 const std::string&
getWKT() const486 SpatialReference::getWKT() const
487 {
488     if ( !_initialized )
489         const_cast<SpatialReference*>(this)->init();
490     return _wkt;
491 }
492 
493 const std::string&
getInitType() const494 SpatialReference::getInitType() const
495 {
496     if ( !_initialized )
497         const_cast<SpatialReference*>(this)->init();
498     return _init_type;
499 }
500 
501 const VerticalDatum*
getVerticalDatum() const502 SpatialReference::getVerticalDatum() const
503 {
504     if ( !_initialized )
505         const_cast<SpatialReference*>(this)->init();
506     return _vdatum.get();
507 }
508 
509 const SpatialReference::Key&
getKey() const510 SpatialReference::getKey() const
511 {
512     if ( !_initialized )
513         const_cast<SpatialReference*>(this)->init();
514     return _key;
515 }
516 
517 const std::string&
getHorizInitString() const518 SpatialReference::getHorizInitString() const
519 {
520     if ( !_initialized )
521         const_cast<SpatialReference*>(this)->init();
522     return _key.horiz;
523 }
524 
525 const std::string&
getVertInitString() const526 SpatialReference::getVertInitString() const
527 {
528     if ( !_initialized )
529         const_cast<SpatialReference*>(this)->init();
530     return _key.vert;
531 }
532 
533 bool
isEquivalentTo(const SpatialReference * rhs) const534 SpatialReference::isEquivalentTo( const SpatialReference* rhs ) const
535 {
536     if ( !_initialized )
537         const_cast<SpatialReference*>(this)->init();
538     return _isEquivalentTo( rhs, true );
539 }
540 
541 bool
isHorizEquivalentTo(const SpatialReference * rhs) const542 SpatialReference::isHorizEquivalentTo( const SpatialReference* rhs ) const
543 {
544     if ( !_initialized )
545         const_cast<SpatialReference*>(this)->init();
546     return _isEquivalentTo( rhs, false );
547 }
548 
549 bool
isVertEquivalentTo(const SpatialReference * rhs) const550 SpatialReference::isVertEquivalentTo( const SpatialReference* rhs ) const
551 {
552     if ( !_initialized )
553         const_cast<SpatialReference*>(this)->init();
554 
555     // vertical equivalence means the same vertical datum and the same
556     // reference ellipsoid.
557     return
558         _vdatum.get() == rhs->_vdatum.get() &&
559         _ellipsoidId  == rhs->_ellipsoidId;
560 }
561 
562 bool
_isEquivalentTo(const SpatialReference * rhs,bool considerVDatum) const563 SpatialReference::_isEquivalentTo( const SpatialReference* rhs, bool considerVDatum ) const
564 {
565     if ( !rhs )
566         return false;
567 
568     if ( this == rhs )
569         return true;
570 
571     if (isGeographic()  != rhs->isGeographic()  ||
572         isMercator()    != rhs->isMercator()    ||
573         isSphericalMercator() != rhs->isSphericalMercator() ||
574         isNorthPolar()  != rhs->isNorthPolar()  ||
575         isSouthPolar()  != rhs->isSouthPolar()  ||
576         isContiguous()  != rhs->isContiguous()  ||
577         isUserDefined() != rhs->isUserDefined() ||
578         isCube()        != rhs->isCube()        ||
579         isLTP()         != rhs->isLTP() )
580     {
581         return false;
582     }
583 
584     if (isGeocentric() && rhs->isGeocentric())
585         return true;
586 
587     if ( considerVDatum && (_vdatum.get() != rhs->_vdatum.get()) )
588         return false;
589 
590     if (_key.horizLower == rhs->_key.horizLower &&
591         (!considerVDatum || (_key.vertLower == rhs->_key.vertLower) ) )
592     {
593         return true;
594     }
595 
596     if ( _proj4 == rhs->_proj4 )
597         return true;
598 
599     if ( _wkt == rhs->_wkt )
600         return true;
601 
602     if (this->isGeographic() && rhs->isGeographic())
603     {
604         return
605             osg::equivalent( getEllipsoid()->getRadiusEquator(), rhs->getEllipsoid()->getRadiusEquator() ) &&
606             osg::equivalent( getEllipsoid()->getRadiusPolar(), rhs->getEllipsoid()->getRadiusPolar() );
607     }
608 
609     // last resort, since it requires the lock
610     {
611         GDAL_SCOPED_LOCK;
612         return TRUE == ::OSRIsSame( _handle, rhs->_handle );
613     }
614 }
615 
616 const SpatialReference*
getGeographicSRS() const617 SpatialReference::getGeographicSRS() const
618 {
619     if ( !_initialized )
620         const_cast<SpatialReference*>(this)->init();
621 
622     if ( _is_geographic )
623         return this;
624 
625     if ( _is_spherical_mercator )
626         return get("wgs84");
627 
628     if ( !_geo_srs.valid() )
629     {
630         GDAL_SCOPED_LOCK;
631 
632         if ( !_geo_srs.valid() ) // double-check pattern
633         {
634             void* new_handle = OSRNewSpatialReference( NULL );
635             int err = OSRCopyGeogCSFrom( new_handle, _handle );
636             if ( err == OGRERR_NONE )
637             {
638                 // make a new geographic srs, and copy over the vertical datum
639                 SpatialReference* ncthis = const_cast<SpatialReference*>(this);
640                 ncthis->_geo_srs = new SpatialReference( new_handle );
641                 ncthis->_geo_srs->_vdatum = _vdatum.get();
642             }
643             else
644             {
645                 OSRDestroySpatialReference( new_handle );
646                 OE_WARN << LC << "Failed to initialize a geographic SRS for " << getName() << std::endl;
647             }
648         }
649     }
650 
651     return _geo_srs.get();
652 }
653 
654 const SpatialReference*
getGeodeticSRS() const655 SpatialReference::getGeodeticSRS() const
656 {
657     if ( !_initialized )
658         const_cast<SpatialReference*>(this)->init();
659 
660     if ( _is_geographic && !_vdatum.valid() )
661         return this;
662 
663     if ( !_geodetic_srs.valid() )
664     {
665         const SpatialReference* geo = getGeographicSRS();
666 
667         GDAL_SCOPED_LOCK;
668 
669         if ( !_geodetic_srs.valid() ) // double check pattern
670         {
671             void* new_handle = OSRNewSpatialReference( NULL );
672             int err = OSRCopyGeogCSFrom( new_handle, geo->_handle );
673             if ( err == OGRERR_NONE )
674             {
675                 SpatialReference* ncthis = const_cast<SpatialReference*>(this);
676                 ncthis->_geodetic_srs = new SpatialReference( new_handle );
677                 ncthis->_geodetic_srs->_vdatum = 0L; // explicity :)
678             }
679             else
680             {
681                 OSRDestroySpatialReference( new_handle );
682                 OE_WARN << LC << "Failed to initialize a geodetic SRS for " << getName() << std::endl;
683             }
684         }
685     }
686 
687     return _geodetic_srs.get();
688 }
689 
690 const SpatialReference*
getECEF() const691 SpatialReference::getECEF() const
692 {
693     OE_DEPRECATED(SpatialReference::getECEF, getGeocentricSRS);
694     return getGeocentricSRS();
695 }
696 
697 const SpatialReference*
getGeocentricSRS() const698 SpatialReference::getGeocentricSRS() const
699 {
700     if ( !_initialized )
701         const_cast<SpatialReference*>(this)->init();
702 
703     if ( _is_geocentric )
704         return this;
705 
706     if ( !_geocentric_srs.valid() )
707     {
708         const SpatialReference* geo = getGeographicSRS(); // before the lock please.
709 
710         GDAL_SCOPED_LOCK;
711 
712         if ( !_geocentric_srs.valid() ) // double-check pattern
713         {
714             void* new_handle = OSRNewSpatialReference( NULL );
715             int err = OSRCopyGeogCSFrom( new_handle, geo->_handle );
716             if ( err == OGRERR_NONE )
717             {
718                 // make a new geographic srs, and copy over the vertical datum
719                 SpatialReference* ncthis = const_cast<SpatialReference*>(this);
720                 ncthis->_geocentric_srs = new SpatialReference( new_handle );
721                 ncthis->_geocentric_srs->_vdatum = 0L; // no vertical datum in ECEF
722                 ncthis->_geocentric_srs->_is_geocentric = true;
723             }
724             else
725             {
726                 OSRDestroySpatialReference( new_handle );
727                 OE_WARN << LC << "Failed to initialize a ECEF SRS for " << getName() << std::endl;
728             }
729         }
730     }
731 
732     return _geocentric_srs.get();
733 }
734 
735 const SpatialReference*
createTangentPlaneSRS(const osg::Vec3d & pos) const736 SpatialReference::createTangentPlaneSRS( const osg::Vec3d& pos ) const
737 {
738     SpatialReference* result = 0L;
739     osg::Vec3d lla;
740     if ( this->transform(pos, this->getGeographicSRS(), lla) )
741     {
742         result = new TangentPlaneSpatialReference( this->getGeographicSRS()->_handle, lla );
743     }
744     else
745     {
746         OE_WARN << LC << "Unable to create LTP SRS" << std::endl;
747     }
748     return result;
749 }
750 
751 const SpatialReference*
createTransMercFromLongitude(const Angular & lon) const752 SpatialReference::createTransMercFromLongitude( const Angular& lon ) const
753 {
754     // note. using tmerc with +lat_0 <> 0 is sloooooow.
755     std::string datum = getDatumName();
756     std::string horiz = Stringify()
757         << "+proj=tmerc +lat_0=0"
758         << " +lon_0=" << lon.as(Units::DEGREES)
759         << " +datum=" << (!datum.empty() ? "wgs84" : datum);
760     return create( horiz, getVertInitString() );
761 }
762 
763 const SpatialReference*
createUTMFromLonLat(const Angular & lon,const Angular & lat) const764 SpatialReference::createUTMFromLonLat( const Angular& lon, const Angular& lat ) const
765 {
766     // note. UTM is up to 10% faster than TMERC for the same meridian.
767     unsigned zone = 1 + (unsigned)floor((lon.as(Units::DEGREES)+180.0)/6.0);
768     std::string datum = getDatumName();
769     std::string horiz = Stringify()
770         << "+proj=utm +zone=" << zone
771         << (lat.as(Units::DEGREES) < 0 ? " +south" : "")
772         << " +datum=" << (!datum.empty() ? "wgs84" : datum);
773     return create( horiz, getVertInitString() );
774 }
775 
776 const SpatialReference*
createEquirectangularSRS() const777 SpatialReference::createEquirectangularSRS() const
778 {
779     return SpatialReference::create("+proj=eqc +units=m +no_defs", getVertInitString());
780 }
781 
782 bool
isMercator() const783 SpatialReference::isMercator() const
784 {
785     if ( !_initialized )
786         const_cast<SpatialReference*>(this)->init();
787     return _is_mercator;
788 }
789 
790 bool
isSphericalMercator() const791 SpatialReference::isSphericalMercator() const
792 {
793     if ( !_initialized )
794         const_cast<SpatialReference*>(this)->init();
795     return _is_spherical_mercator;
796 }
797 
798 bool
isNorthPolar() const799 SpatialReference::isNorthPolar() const
800 {
801     if ( !_initialized )
802         const_cast<SpatialReference*>(this)->init();
803     return _is_north_polar;
804 }
805 
806 bool
isSouthPolar() const807 SpatialReference::isSouthPolar() const
808 {
809     if ( !_initialized )
810         const_cast<SpatialReference*>(this)->init();
811     return _is_south_polar;
812 }
813 
814 bool
isContiguous() const815 SpatialReference::isContiguous() const
816 {
817     if ( !_initialized )
818         const_cast<SpatialReference*>(this)->init();
819     return _is_contiguous;
820 }
821 
822 bool
isUserDefined() const823 SpatialReference::isUserDefined() const
824 {
825     if ( !_initialized )
826         const_cast<SpatialReference*>(this)->init();
827     return _is_user_defined;
828 }
829 
830 bool
isCube() const831 SpatialReference::isCube() const
832 {
833     if ( !_initialized )
834         const_cast<SpatialReference*>(this)->init();
835     return _is_cube;
836 }
837 
838 osg::CoordinateSystemNode*
createCoordinateSystemNode() const839 SpatialReference::createCoordinateSystemNode() const
840 {
841     if ( !_initialized )
842         const_cast<SpatialReference*>(this)->init();
843 
844     osg::CoordinateSystemNode* csn = new osg::CoordinateSystemNode();
845     populateCoordinateSystemNode( csn );
846     return csn;
847 }
848 
849 bool
populateCoordinateSystemNode(osg::CoordinateSystemNode * csn) const850 SpatialReference::populateCoordinateSystemNode( osg::CoordinateSystemNode* csn ) const
851 {
852     if ( !csn )
853         return false;
854 
855     if ( !_initialized )
856         const_cast<SpatialReference*>(this)->init();
857 
858     if ( !_wkt.empty() )
859     {
860         csn->setFormat( "WKT" );
861         csn->setCoordinateSystem( _wkt );
862     }
863     else if ( !_proj4.empty() )
864     {
865         csn->setFormat( "PROJ4" );
866         csn->setCoordinateSystem( _proj4 );
867     }
868     else
869     {
870         csn->setFormat( _init_type );
871         csn->setCoordinateSystem( getKey().horiz );
872     }
873 
874     csn->setEllipsoidModel( _ellipsoid.get() );
875 
876     return true;
877 }
878 
879 // Make a MatrixTransform suitable for use with a Locator object based on the given extents.
880 // Calling Locator::setTransformAsExtents doesn't work with OSG 2.6 due to the fact that the
881 // _inverse member isn't updated properly.  Calling Locator::setTransform works correctly.
882 static osg::Matrixd
getTransformFromExtents(double minX,double minY,double maxX,double maxY)883 getTransformFromExtents(double minX, double minY, double maxX, double maxY)
884 {
885     osg::Matrixd transform;
886     transform.set(
887         maxX-minX, 0.0,       0.0, 0.0,
888         0.0,       maxY-minY, 0.0, 0.0,
889         0.0,       0.0,       1.0, 0.0,
890         minX,      minY,      0.0, 1.0);
891     return transform;
892 }
893 
894 GeoLocator*
createLocator(double xmin,double ymin,double xmax,double ymax) const895 SpatialReference::createLocator(double xmin, double ymin, double xmax, double ymax ) const
896 {
897     if ( !_initialized )
898         const_cast<SpatialReference*>(this)->init();
899 
900     GeoLocator* locator = new GeoLocator( GeoExtent(this, xmin, ymin, xmax, ymax) );
901     locator->setEllipsoidModel( (osg::EllipsoidModel*)getEllipsoid() );
902     locator->setCoordinateSystemType( isGeographic()? osgTerrain::Locator::GEOGRAPHIC : osgTerrain::Locator::PROJECTED );
903     // note: not setting the format/cs on purpose.
904 
905     if ( isGeographic() )
906     {
907         locator->setTransform( getTransformFromExtents(
908             osg::DegreesToRadians( xmin ),
909             osg::DegreesToRadians( ymin ),
910             osg::DegreesToRadians( xmax ),
911             osg::DegreesToRadians( ymax ) ) );
912     }
913     else
914     {
915         locator->setTransform( getTransformFromExtents( xmin, ymin, xmax, ymax ) );
916     }
917     return locator;
918 }
919 
920 bool
createLocalToWorld(const osg::Vec3d & xyz,osg::Matrixd & out_local2world) const921 SpatialReference::createLocalToWorld(const osg::Vec3d& xyz, osg::Matrixd& out_local2world ) const
922 {
923     if ( isProjected() && !isCube() )
924     {
925         osg::Vec3d world;
926         if ( !transformToWorld( xyz, world ) )
927             return false;
928         out_local2world = osg::Matrix::translate(world);
929     }
930     else if ( isGeocentric() )
931     {
932         _ellipsoid->computeLocalToWorldTransformFromXYZ(xyz.x(), xyz.y(), xyz.z(), out_local2world);
933     }
934     else
935     {
936         // convert to ECEF:
937         osg::Vec3d ecef;
938         if ( !transform(xyz, getGeocentricSRS(), ecef) )
939             return false;
940 
941         // and create the matrix.
942         _ellipsoid->computeLocalToWorldTransformFromXYZ(ecef.x(), ecef.y(), ecef.z(), out_local2world);
943     }
944     return true;
945 }
946 
947 bool
createWorldToLocal(const osg::Vec3d & xyz,osg::Matrixd & out_world2local) const948 SpatialReference::createWorldToLocal(const osg::Vec3d& xyz, osg::Matrixd& out_world2local ) const
949 {
950     osg::Matrixd local2world;
951     if ( !createLocalToWorld(xyz, local2world) )
952         return false;
953     out_world2local.invert(local2world);
954     return true;
955 }
956 
957 
958 bool
transform(const osg::Vec3d & input,const SpatialReference * outputSRS,osg::Vec3d & output) const959 SpatialReference::transform(const osg::Vec3d&       input,
960                             const SpatialReference* outputSRS,
961                             osg::Vec3d&             output) const
962 {
963     if ( !outputSRS )
964         return false;
965 
966     std::vector<osg::Vec3d> v(1, input);
967 
968     if ( transform(v, outputSRS) )
969     {
970         output = v[0];
971         return true;
972     }
973     return false;
974 }
975 
976 
977 bool
transform(std::vector<osg::Vec3d> & points,const SpatialReference * outputSRS) const978 SpatialReference::transform(std::vector<osg::Vec3d>& points,
979                             const SpatialReference*  outputSRS) const
980 {
981     if ( !outputSRS )
982         return false;
983 
984     if ( !_initialized )
985         const_cast<SpatialReference*>(this)->init();
986 
987     // trivial equivalency:
988     if ( isEquivalentTo(outputSRS) )
989         return true;
990 
991     bool success = false;
992 
993     // do the pre-transformation pass:
994     const SpatialReference* inputSRS = preTransform( points );
995     if ( !inputSRS )
996         return false;
997 
998     if ( inputSRS->isGeocentric() && !outputSRS->isGeocentric() )
999     {
1000         const SpatialReference* outputGeoSRS = outputSRS->getGeodeticSRS();
1001         geocentricToGeodetic(points, outputGeoSRS->getEllipsoid());
1002         return outputGeoSRS->transform(points, outputSRS);
1003     }
1004 
1005     else if ( !inputSRS->isGeocentric() && outputSRS->isGeocentric() )
1006     {
1007         const SpatialReference* outputGeoSRS = outputSRS->getGeodeticSRS();
1008         success = inputSRS->transform(points, outputGeoSRS);
1009         geodeticToGeocentric(points, outputGeoSRS->getEllipsoid());
1010         return success;
1011     }
1012 
1013     // if the points are starting as geographic, do the Z's first to avoid an unneccesary
1014     // transformation in the case of differing vdatums.
1015     bool z_done = false;
1016     if ( inputSRS->isGeographic() )
1017     {
1018         z_done = inputSRS->transformZ( points, outputSRS, true );
1019     }
1020 
1021     // move the xy data into straight arrays that OGR can use
1022     unsigned count = points.size();
1023     double* x = new double[count];
1024     double* y = new double[count];
1025 
1026     for( unsigned i=0; i<count; i++ )
1027     {
1028         x[i] = points[i].x();
1029         y[i] = points[i].y();
1030     }
1031 
1032     success = inputSRS->transformXYPointArrays( x, y, count, outputSRS );
1033 
1034     if ( success )
1035     {
1036         if ( inputSRS->isProjected() && outputSRS->isGeographic() )
1037         {
1038             // special case: when going from projected to geographic, clamp the
1039             // points to the maximum geographic extent. Sometimes the conversion from
1040             // a global/projected SRS (like mercator) will result in *slightly* invalid
1041             // geographic points (like long=180.000003), so this addresses that issue.
1042             for( unsigned i=0; i<count; i++ )
1043             {
1044                 points[i].x() = osg::clampBetween( x[i], -180.0, 180.0 );
1045                 points[i].y() = osg::clampBetween( y[i],  -90.0,  90.0 );
1046             }
1047         }
1048         else
1049         {
1050             for( unsigned i=0; i<count; i++ )
1051             {
1052                 points[i].x() = x[i];
1053                 points[i].y() = y[i];
1054             }
1055         }
1056     }
1057 
1058     delete[] x;
1059     delete[] y;
1060 
1061     // calculate the Zs if we haven't already done so
1062     if ( !z_done )
1063     {
1064         z_done = inputSRS->transformZ( points, outputSRS, outputSRS->isGeographic() );
1065     }
1066 
1067     // run the user post-transform code
1068     outputSRS->postTransform( points );
1069 
1070     return success;
1071 }
1072 
1073 
1074 bool
transform2D(double x,double y,const SpatialReference * outputSRS,double & out_x,double & out_y) const1075 SpatialReference::transform2D(double x, double y,
1076                               const SpatialReference* outputSRS,
1077                               double& out_x, double& out_y ) const
1078 {
1079     osg::Vec3d temp(x,y,0);
1080     bool ok = transform(temp, outputSRS, temp);
1081     if ( ok ) {
1082         out_x = temp.x();
1083         out_y = temp.y();
1084     }
1085     return ok;
1086 }
1087 
1088 
1089 bool
transformXYPointArrays(double * x,double * y,unsigned count,const SpatialReference * out_srs) const1090 SpatialReference::transformXYPointArrays(double*  x,
1091                                          double*  y,
1092                                          unsigned count,
1093                                          const SpatialReference* out_srs) const
1094 {
1095     // Transform the X and Y values inside an exclusive GDAL/OGR lock
1096     GDAL_SCOPED_LOCK;
1097 
1098     //OE_INFO << LC << "Attempt transfrom from \n"
1099     //    << "    " << getHorizInitString() << "\n"
1100     //    << " -> " << out_srs->getHorizInitString() << std::endl;
1101 
1102     void* xform_handle = NULL;
1103     TransformHandleCache::const_iterator itr = _transformHandleCache.find(out_srs->getWKT());
1104     if (itr != _transformHandleCache.end())
1105     {
1106         //OE_DEBUG << LC << "using cached transform handle" << std::endl;
1107         xform_handle = itr->second;
1108     }
1109     else
1110     {
1111         OE_DEBUG << LC << "allocating new OCT Transform" << std::endl;
1112         xform_handle = OCTNewCoordinateTransformation( _handle, out_srs->_handle);
1113         const_cast<SpatialReference*>(this)->_transformHandleCache[out_srs->getWKT()] = xform_handle;
1114     }
1115 
1116     if ( !xform_handle )
1117     {
1118         OE_WARN << LC
1119             << "SRS xform not possible" << std::endl
1120             << "    From => " << getName() << std::endl
1121             << "    To   => " << out_srs->getName() << std::endl;
1122 
1123         OE_WARN << LC << "INPUT: " << getWKT() << std::endl
1124             << "OUTPUT: " << out_srs->getWKT() << std::endl;
1125 
1126         OE_WARN << LC << "ERROR:  " << CPLGetLastErrorMsg() << std::endl;
1127 
1128         return false;
1129     }
1130 
1131     return OCTTransform( xform_handle, count, x, y, 0L ) > 0;
1132 }
1133 
1134 
1135 bool
transformZ(std::vector<osg::Vec3d> & points,const SpatialReference * outputSRS,bool pointsAreLatLong) const1136 SpatialReference::transformZ(std::vector<osg::Vec3d>& points,
1137                              const SpatialReference*  outputSRS,
1138                              bool                     pointsAreLatLong) const
1139 {
1140     const VerticalDatum* outVDatum = outputSRS->getVerticalDatum();
1141 
1142     // same vdatum, no xformation necessary.
1143     if ( _vdatum.get() == outVDatum )
1144         return true;
1145 
1146     Units inUnits = _vdatum.valid() ? _vdatum->getUnits() : Units::METERS;
1147     Units outUnits = outVDatum ? outVDatum->getUnits() : inUnits;
1148 
1149     if ( isGeographic() || pointsAreLatLong )
1150     {
1151         for( unsigned i=0; i<points.size(); ++i )
1152         {
1153             if ( _vdatum.valid() )
1154             {
1155                 // to HAE:
1156                 points[i].z() = _vdatum->msl2hae( points[i].y(), points[i].x(), points[i].z() );
1157             }
1158 
1159             // do the units conversion:
1160             points[i].z() = inUnits.convertTo(outUnits, points[i].z());
1161 
1162             if ( outVDatum )
1163             {
1164                 // to MSL:
1165                 points[i].z() = outVDatum->hae2msl( points[i].y(), points[i].x(), points[i].z() );
1166             }
1167         }
1168     }
1169 
1170     else // need to xform input points
1171     {
1172         // copy the points and convert them to geographic coordinates (lat/long with the same Z):
1173         std::vector<osg::Vec3d> geopoints(points);
1174         transform( geopoints, getGeographicSRS() );
1175 
1176         for( unsigned i=0; i<geopoints.size(); ++i )
1177         {
1178             if ( _vdatum.valid() )
1179             {
1180                 // to HAE:
1181                 points[i].z() = _vdatum->msl2hae( geopoints[i].y(), geopoints[i].x(), points[i].z() );
1182             }
1183 
1184             // do the units conversion:
1185             points[i].z() = inUnits.convertTo(outUnits, points[i].z());
1186 
1187             if ( outVDatum )
1188             {
1189                 // to MSL:
1190                 points[i].z() = outVDatum->hae2msl( geopoints[i].y(), geopoints[i].x(), points[i].z() );
1191             }
1192         }
1193     }
1194 
1195     return true;
1196 }
1197 
1198 bool
transformToWorld(const osg::Vec3d & input,osg::Vec3d & output) const1199 SpatialReference::transformToWorld(const osg::Vec3d& input,
1200                                    osg::Vec3d&       output ) const
1201 {
1202     if ( isGeographic() || isCube() )
1203     {
1204         return transform(input, getGeocentricSRS(), output);
1205     }
1206     else // isProjected
1207     {
1208         output = input;
1209         if ( _vdatum.valid() )
1210         {
1211             osg::Vec3d geo(input);
1212             if ( !transform(input, getGeographicSRS(), geo) )
1213                 return false;
1214 
1215             output.z() = _vdatum->msl2hae( geo.y(), geo.x(), input.z() );
1216         }
1217         return true;
1218     }
1219 }
1220 
1221 bool
transformFromWorld(const osg::Vec3d & world,osg::Vec3d & output,double * out_haeZ) const1222 SpatialReference::transformFromWorld(const osg::Vec3d& world,
1223                                      osg::Vec3d&       output,
1224                                      double*           out_haeZ ) const
1225 {
1226     if ( isGeographic() || isCube() )
1227     {
1228         bool ok = getGeocentricSRS()->transform(world, this, output);
1229         if ( ok && out_haeZ )
1230         {
1231             if ( _vdatum.valid() )
1232                 *out_haeZ = _vdatum->msl2hae(output.y(), output.x(), output.z());
1233             else
1234                 *out_haeZ = output.z();
1235         }
1236         return ok;
1237     }
1238     else // isProjected
1239     {
1240         output = world;
1241 
1242         if (out_haeZ)
1243             *out_haeZ = world.z();
1244 
1245         if ( _vdatum.valid() )
1246         {
1247             // get the geographic coords by converting x/y/hae -> lat/long/msl:
1248             osg::Vec3d lla;
1249             if (!transform(world, getGeographicSRS(), lla) )
1250                 return false;
1251 
1252             output.z() = lla.z();
1253         }
1254 
1255         return true;
1256     }
1257 }
1258 
1259 double
transformUnits(double input,const SpatialReference * outSRS,double latitude) const1260 SpatialReference::transformUnits(double                  input,
1261                                  const SpatialReference* outSRS,
1262                                  double                  latitude) const
1263 {
1264     if ( this->isProjected() && outSRS->isGeographic() )
1265     {
1266         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1267         double inputDegrees = getUnits().convertTo(Units::METERS, input) / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1268         return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
1269     }
1270     else if ( this->isGeocentric() && outSRS->isGeographic() )
1271     {
1272         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1273         double inputDegrees = input / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1274         return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
1275     }
1276     else if ( this->isGeographic() && outSRS->isProjected() )
1277     {
1278         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1279         double inputMeters = getUnits().convertTo(Units::DEGREES, input) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1280         return Units::METERS.convertTo( outSRS->getUnits(), inputMeters );
1281     }
1282     else if ( this->isGeographic() && outSRS->isGeocentric() )
1283     {
1284         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1285         return getUnits().convertTo(Units::DEGREES, input) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1286     }
1287     else // both projected or both geographic.
1288     {
1289         return getUnits().convertTo( outSRS->getUnits(), input );
1290     }
1291 }
1292 
1293 double
transformUnits(const Distance & distance,const SpatialReference * outSRS,double latitude)1294 SpatialReference::transformUnits(const Distance&         distance,
1295                                  const SpatialReference* outSRS,
1296                                  double                  latitude)
1297 {
1298     if ( distance.getUnits().isLinear() && outSRS->isGeographic() )
1299     {
1300         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1301         double inputDegrees = distance.as(Units::METERS) / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1302         return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
1303     }
1304     else if ( distance.getUnits().isAngular() && outSRS->isProjected() )
1305     {
1306         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
1307         double inputMeters = distance.as(Units::DEGREES) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
1308         return Units::METERS.convertTo( outSRS->getUnits(), inputMeters );
1309     }
1310     else // both projected or both geographic.
1311     {
1312         return distance.as( outSRS->getUnits() );
1313     }
1314 }
1315 
1316 bool
transformExtentToMBR(const SpatialReference * to_srs,double & in_out_xmin,double & in_out_ymin,double & in_out_xmax,double & in_out_ymax) const1317 SpatialReference::transformExtentToMBR(const SpatialReference* to_srs,
1318                                        double&                 in_out_xmin,
1319                                        double&                 in_out_ymin,
1320                                        double&                 in_out_xmax,
1321                                        double&                 in_out_ymax  ) const
1322 {
1323     if ( !_initialized )
1324         const_cast<SpatialReference*>(this)->init();
1325 
1326     // Transform all points and take the maximum bounding rectangle the resulting points
1327     std::vector<osg::Vec3d> v;
1328 
1329     double height = in_out_ymax - in_out_ymin;
1330     double width = in_out_xmax - in_out_xmin;
1331     v.push_back( osg::Vec3d(in_out_xmin, in_out_ymin, 0) ); // ll
1332     v.push_back( osg::Vec3d(in_out_xmin, in_out_ymax, 0) ); // ul
1333     v.push_back( osg::Vec3d(in_out_xmax, in_out_ymax, 0) ); // ur
1334     v.push_back( osg::Vec3d(in_out_xmax, in_out_ymin, 0) ); // lr
1335 
1336     //We also sample along the edges of the bounding box and include them in the
1337     //MBR computation in case you are dealing with a projection that will cause the edges
1338     //of the bounding box to be expanded.  This was first noticed when dealing with converting
1339     //Hotline Oblique Mercator to WGS84
1340 
1341     //Sample the edges
1342     unsigned int numSamples = 5;
1343     double dWidth  = width / (numSamples - 1 );
1344     double dHeight = height / (numSamples - 1 );
1345 
1346     //Left edge
1347     for (unsigned int i = 0; i < numSamples; i++)
1348     {
1349         v.push_back( osg::Vec3d(in_out_xmin, in_out_ymin + dHeight * (double)i, 0) );
1350     }
1351 
1352     //Right edge
1353     for (unsigned int i = 0; i < numSamples; i++)
1354     {
1355         v.push_back( osg::Vec3d(in_out_xmax, in_out_ymin + dHeight * (double)i, 0) );
1356     }
1357 
1358     //Top edge
1359     for (unsigned int i = 0; i < numSamples; i++)
1360     {
1361         v.push_back( osg::Vec3d(in_out_xmin + dWidth * (double)i, in_out_ymax, 0) );
1362     }
1363 
1364     //Bottom edge
1365     for (unsigned int i = 0; i < numSamples; i++)
1366     {
1367         v.push_back( osg::Vec3d(in_out_xmin + dWidth * (double)i, in_out_ymin, 0) );
1368     }
1369 
1370     if ( transform(v, to_srs) )
1371     {
1372         bool swapXValues = ( isGeographic() && in_out_xmin > in_out_xmax );
1373         in_out_xmin = DBL_MAX;
1374         in_out_ymin = DBL_MAX;
1375         in_out_xmax = -DBL_MAX;
1376         in_out_ymax = -DBL_MAX;
1377 
1378         for (unsigned int i = 0; i < v.size(); i++)
1379         {
1380             in_out_xmin = osg::minimum( v[i].x(), in_out_xmin );
1381             in_out_ymin = osg::minimum( v[i].y(), in_out_ymin );
1382             in_out_xmax = osg::maximum( v[i].x(), in_out_xmax );
1383             in_out_ymax = osg::maximum( v[i].y(), in_out_ymax );
1384         }
1385 
1386         if ( swapXValues )
1387             std::swap( in_out_xmin, in_out_xmax );
1388 
1389         return true;
1390     }
1391 
1392     return false;
1393 }
1394 
transformExtentPoints(const SpatialReference * to_srs,double in_xmin,double in_ymin,double in_xmax,double in_ymax,double * x,double * y,unsigned int numx,unsigned int numy) const1395 bool SpatialReference::transformExtentPoints(const SpatialReference* to_srs,
1396                                              double in_xmin, double in_ymin,
1397                                              double in_xmax, double in_ymax,
1398                                              double* x, double* y,
1399                                              unsigned int numx, unsigned int numy ) const
1400 {
1401     std::vector<osg::Vec3d> points;
1402 
1403     const double dx = (in_xmax - in_xmin) / (numx - 1);
1404     const double dy = (in_ymax - in_ymin) / (numy - 1);
1405 
1406     unsigned int pixel = 0;
1407     double fc = 0.0;
1408     for (unsigned int c = 0; c < numx; ++c, ++fc)
1409     {
1410         const double dest_x = in_xmin + fc * dx;
1411         double fr = 0.0;
1412         for (unsigned int r = 0; r < numy; ++r, ++fr)
1413         {
1414             const double dest_y = in_ymin + fr * dy;
1415 
1416             points.push_back(osg::Vec3d(dest_x, dest_y, 0));
1417             pixel++;
1418         }
1419     }
1420 
1421     if ( transform( points, to_srs ) )
1422     {
1423         for( unsigned i=0; i<points.size(); ++i )
1424         {
1425             x[i] = points[i].x();
1426             y[i] = points[i].y();
1427         }
1428         return true;
1429     }
1430     return false;
1431 }
1432 
1433 void
init()1434 SpatialReference::init()
1435 {
1436     GDAL_SCOPED_LOCK;
1437 
1438     // always double-check the _initialized flag after obtaining the lock.
1439     if ( !_initialized )
1440     {
1441         // calls the internal version, which can be overriden by the developer.
1442         // therefore do not call init() from the constructor!
1443         _init();
1444     }
1445 }
1446 
1447 void
_init()1448 SpatialReference::_init()
1449 {
1450     // set defaults:
1451     _is_user_defined = false;
1452     _is_contiguous = true;
1453     _is_cube = false;
1454     if ( _is_geocentric )
1455         _is_geographic = false;
1456     else
1457         _is_geographic = OSRIsGeographic( _handle ) != 0;
1458 
1459     // extract the ellipsoid parameters:
1460     int err;
1461     double semi_major_axis = OSRGetSemiMajor( _handle, &err );
1462     double semi_minor_axis = OSRGetSemiMinor( _handle, &err );
1463     _ellipsoid = new osg::EllipsoidModel( semi_major_axis, semi_minor_axis );
1464 
1465     // unique ID for comparing ellipsoids quickly:
1466     _ellipsoidId = hashString( Stringify()
1467         << std::fixed << std::setprecision(10)
1468         << _ellipsoid->getRadiusEquator() << ";" << _ellipsoid->getRadiusPolar() );
1469 
1470     // try to get an ellipsoid name:
1471     _ellipsoid->setName( getOGRAttrValue(_handle, "SPHEROID", 0, true) );
1472 
1473     // extract the projection:
1474     if ( _name.empty() || _name == "unnamed" )
1475     {
1476         _name = _is_geographic?
1477             getOGRAttrValue( _handle, "GEOGCS", 0 ) :
1478             getOGRAttrValue( _handle, "PROJCS", 0 );
1479     }
1480     std::string proj = getOGRAttrValue( _handle, "PROJECTION", 0, true );
1481 
1482     // check for the Mercator projection:
1483     _is_mercator = !proj.empty() && proj.find("mercator")==0;
1484 
1485     // check for spherical mercator (a special case)
1486     _is_spherical_mercator = _is_mercator && osg::equivalent(semi_major_axis, semi_minor_axis);
1487 
1488     // check for the Polar projection:
1489     if ( !proj.empty() && proj.find("polar_stereographic") != std::string::npos )
1490     {
1491         double lat = as<double>( getOGRAttrValue( _handle, "latitude_of_origin", 0, true ), -90.0 );
1492         _is_north_polar = lat > 0.0;
1493         _is_south_polar = lat < 0.0;
1494     }
1495     else
1496     {
1497       _is_north_polar = false;
1498       _is_south_polar = false;
1499     }
1500 
1501     // Try to extract the horizontal datum
1502     _datum = getOGRAttrValue( _handle, "DATUM", 0, true );
1503 
1504     // Extract the base units:
1505     std::string units = getOGRAttrValue( _handle, "UNIT", 0, true );
1506     double unitMultiplier = osgEarth::as<double>( getOGRAttrValue( _handle, "UNIT", 1, true ), 1.0 );
1507     if ( _is_geographic )
1508         _units = Units(units, units, Units::TYPE_ANGULAR, unitMultiplier);
1509     else
1510         _units = Units(units, units, Units::TYPE_LINEAR, unitMultiplier);
1511 
1512     // Give the SRS a name if it doesn't have one:
1513     if ( _name == "unnamed" || _name.empty() )
1514     {
1515         _name =
1516             _is_geographic? "Geographic CS" :
1517             _is_mercator? "Mercator CS" :
1518             ( !proj.empty()? proj : "Projected CS" );
1519     }
1520 
1521     // Try to extract the PROJ4 initialization string:
1522     char* proj4buf;
1523     if ( OSRExportToProj4( _handle, &proj4buf ) == OGRERR_NONE )
1524     {
1525         _proj4 = proj4buf;
1526         CPLFree( proj4buf );
1527     }
1528 
1529     // Try to extract the OGC well-known-text (WKT) string:
1530     char* wktbuf;
1531     if ( OSRExportToWkt( _handle, &wktbuf ) == OGRERR_NONE )
1532     {
1533         _wkt = wktbuf;
1534         CPLFree( wktbuf );
1535     }
1536 
1537     // Build a 'normalized' initialization key.
1538     if ( !_proj4.empty() )
1539     {
1540         _key.horiz = _proj4;
1541         _key.horizLower = toLower(_key.horiz);
1542         _init_type = "PROJ4";
1543     }
1544     else if ( !_wkt.empty() )
1545     {
1546         _key.horiz = _wkt;
1547         _key.horizLower = toLower(_key.horiz);
1548         _init_type = "WKT";
1549     }
1550     if ( _vdatum.valid() )
1551     {
1552         _key.vert = _vdatum->getInitString();
1553         _key.vertLower = toLower(_key.vert);
1554     }
1555 
1556     _initialized = true;
1557 }
1558 
1559 bool
guessBounds(Bounds & bounds) const1560 SpatialReference::guessBounds(Bounds& bounds) const
1561 {
1562     if (isGeographic())
1563     {
1564         bounds.set(-180.0, -90.0, 180.0, 90.0);
1565         return true;
1566     }
1567 
1568     if (isMercator() || isSphericalMercator())
1569     {
1570         bounds.set(MERC_MINX, MERC_MINY, MERC_MAXX, MERC_MAXY);
1571         return true;
1572     }
1573 
1574     GDAL_SCOPED_LOCK;
1575 
1576     int isNorth;
1577     if (OSRGetUTMZone(_handle, &isNorth))
1578     {
1579         if (isNorth)
1580             bounds.set(166000, 0, 834000, 9330000);
1581         else
1582             bounds.set(166000, 1116915, 834000, 10000000);
1583         return true;
1584     }
1585 
1586     return false;
1587 }
1588