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