├── .gitignore ├── GeographicLib ├── Config.h ├── Constants.hpp ├── Geoid.hpp ├── Math.hpp └── Utility.hpp ├── Geoid.cpp ├── Makefile ├── README.md ├── common.py ├── egm96-15.pgm ├── geoid_height_wrapper.cpp ├── rpc_crop.py ├── rpc_crop_gui.py ├── rpc_model.py ├── rpc_utils.py ├── srtm.py ├── srtm4.c └── vlight.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | srtm4 4 | kk 5 | -------------------------------------------------------------------------------- /GeographicLib/Config.h: -------------------------------------------------------------------------------- 1 | // This will be overwritten by ./configure 2 | 3 | #define GEOGRAPHICLIB_VERSION_STRING "1.32" 4 | #define GEOGRAPHICLIB_VERSION_MAJOR 1 5 | #define GEOGRAPHICLIB_VERSION_MINOR 32 6 | #define GEOGRAPHICLIB_VERSION_PATCH 0 7 | 8 | // Undefine HAVE_LONG_DOUBLE if this type is unknown to the compiler 9 | #define HAVE_LONG_DOUBLE 1 10 | 11 | // Define WORDS_BIGENDIAN to be 1 if your machine is big endian 12 | /* #undef WORDS_BIGENDIAN */ 13 | -------------------------------------------------------------------------------- /GeographicLib/Constants.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Constants.hpp 3 | * \brief Header for GeographicLib::Constants class 4 | * 5 | * Copyright (c) Charles Karney (2008-2011) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * http://geographiclib.sourceforge.net/ 8 | **********************************************************************/ 9 | 10 | #if !defined(GEOGRAPHICLIB_CONSTANTS_HPP) 11 | #define GEOGRAPHICLIB_CONSTANTS_HPP 1 12 | 13 | #include 14 | 15 | /** 16 | * A compile-time assert. Use C++11 static_assert, if available. 17 | **********************************************************************/ 18 | #if !defined(STATIC_ASSERT) 19 | # if defined(__GXX_EXPERIMENTAL_CXX0X__) 20 | # define STATIC_ASSERT static_assert 21 | # elif defined(_MSC_VER) && _MSC_VER >= 1600 22 | # define STATIC_ASSERT static_assert 23 | # else 24 | # define STATIC_ASSERT(cond,reason) \ 25 | { enum{ STATIC_ASSERT_ENUM = 1/int(cond) }; } 26 | # endif 27 | #endif 28 | 29 | #if defined(_WIN32) && defined(GEOGRAPHIC_SHARED_LIB) && GEOGRAPHIC_SHARED_LIB 30 | # if defined(Geographic_EXPORTS) 31 | # define GEOGRAPHIC_EXPORT __declspec(dllexport) 32 | # else 33 | # define GEOGRAPHIC_EXPORT __declspec(dllimport) 34 | # endif 35 | #else 36 | # define GEOGRAPHIC_EXPORT 37 | #endif 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | /** 44 | * \brief Namespace for %GeographicLib 45 | * 46 | * All of %GeographicLib is defined within the GeographicLib namespace. In 47 | * addition all the header files are included via %GeographicLib/Class.hpp. 48 | * This minimizes the likelihood of conflicts with other packages. 49 | **********************************************************************/ 50 | namespace GeographicLib { 51 | 52 | /** 53 | * \brief %Constants needed by %GeographicLib 54 | * 55 | * Define constants specifying the WGS84 ellipsoid, the UTM and UPS 56 | * projections, and various unit conversions. 57 | * 58 | * Example of use: 59 | * \include example-Constants.cpp 60 | **********************************************************************/ 61 | class GEOGRAPHIC_EXPORT Constants { 62 | private: 63 | typedef Math::real real; 64 | Constants(); // Disable constructor 65 | 66 | public: 67 | /** 68 | * A synonym for Math::degree(). 69 | **********************************************************************/ 70 | static inline Math::real degree() throw() { return Math::degree(); } 71 | /** 72 | * @return the number of radians in an arcminute. 73 | **********************************************************************/ 74 | static inline Math::real arcminute() throw() 75 | { return Math::degree() / 60; } 76 | /** 77 | * @return the number of radians in an arcsecond. 78 | **********************************************************************/ 79 | static inline Math::real arcsecond() throw() 80 | { return Math::degree() / 3600; } 81 | 82 | /** \name Ellipsoid parameters 83 | **********************************************************************/ 84 | ///@{ 85 | /** 86 | * @tparam T the type of the returned value. 87 | * @return the equatorial radius of WGS84 ellipsoid (6378137 m). 88 | **********************************************************************/ 89 | template static inline T WGS84_a() throw() 90 | { return T(6378137) * meter(); } 91 | /** 92 | * A synonym for WGS84_a(). 93 | **********************************************************************/ 94 | static inline Math::real WGS84_a() throw() { return WGS84_a(); } 95 | /** 96 | * @tparam T the type of the returned value. 97 | * @return the flattening of WGS84 ellipsoid (1/298.257223563). 98 | **********************************************************************/ 99 | template static inline T WGS84_f() throw() 100 | { return T(1) / ( T(298) + T(257223563) / T(1000000000) ); } 101 | /** 102 | * A synonym for WGS84_f(). 103 | **********************************************************************/ 104 | static inline Math::real WGS84_f() throw() { return WGS84_f(); } 105 | /** 106 | * @tparam T the type of the returned value. 107 | * @return the gravitational constant of the WGS84 ellipsoid, \e GM, in 108 | * m3 s−2. 109 | **********************************************************************/ 110 | template static inline T WGS84_GM() throw() 111 | { return T(3986004) * T(100000000) + T(41800000); } 112 | /** 113 | * @tparam T the type of the returned value. 114 | * @return the angular velocity of the WGS84 ellipsoid, ω, in rad 115 | * s−1. 116 | **********************************************************************/ 117 | template static inline T WGS84_omega() throw() 118 | { return T(7292115) / (T(1000000) * T(100000)); } 119 | /// \cond SKIP 120 | /** 121 | * DEPRECATED 122 | * @return the reciprocal flattening of WGS84 ellipsoid. 123 | **********************************************************************/ 124 | template static inline T WGS84_r() throw() 125 | { return 1/WGS84_f(); } 126 | /** 127 | * DEPRECATED 128 | * A synonym for WGS84_r(). 129 | **********************************************************************/ 130 | static inline Math::real WGS84_r() throw() { return WGS84_r(); } 131 | /// \endcond 132 | /** 133 | * @tparam T the type of the returned value. 134 | * @return the equatorial radius of GRS80 ellipsoid, \e a, in m. 135 | **********************************************************************/ 136 | template static inline T GRS80_a() throw() 137 | { return T(6378137); } 138 | /** 139 | * @tparam T the type of the returned value. 140 | * @return the gravitational constant of the GRS80 ellipsoid, \e GM, in 141 | * m3 s−2. 142 | **********************************************************************/ 143 | template static inline T GRS80_GM() throw() 144 | { return T(3986005) * T(100000000); } 145 | /** 146 | * @tparam T the type of the returned value. 147 | * @return the angular velocity of the GRS80 ellipsoid, ω, in rad 148 | * s−1. 149 | * 150 | * This is about 2 π 366.25 / (365.25 × 24 × 3600) rad 151 | * s−1. 365.25 is the number of days in a Julian year and 152 | * 365.35/366.25 converts from solar days to sidereal days. Using the 153 | * number of days in a Gregorian year (365.2425) results in a worse 154 | * approximation (because the Gregorian year includes the precession of the 155 | * earth's axis). 156 | **********************************************************************/ 157 | template static inline T GRS80_omega() throw() 158 | { return T(7292115) / (T(1000000) * T(100000)); } 159 | /** 160 | * @tparam T the type of the returned value. 161 | * @return the dynamical form factor of the GRS80 ellipsoid, 162 | * J2. 163 | **********************************************************************/ 164 | template static inline T GRS80_J2() throw() 165 | { return T(108263) / T(100000000); } 166 | /** 167 | * @tparam T the type of the returned value. 168 | * @return the central scale factor for UTM (0.9996). 169 | **********************************************************************/ 170 | template static inline T UTM_k0() throw() 171 | {return T(9996) / T(10000); } 172 | /** 173 | * A synonym for UTM_k0(). 174 | **********************************************************************/ 175 | static inline Math::real UTM_k0() throw() { return UTM_k0(); } 176 | /** 177 | * @tparam T the type of the returned value. 178 | * @return the central scale factor for UPS (0.994). 179 | **********************************************************************/ 180 | template static inline T UPS_k0() throw() 181 | { return T(994) / T(1000); } 182 | /** 183 | * A synonym for UPS_k0(). 184 | **********************************************************************/ 185 | static inline Math::real UPS_k0() throw() { return UPS_k0(); } 186 | ///@} 187 | 188 | /** \name SI units 189 | **********************************************************************/ 190 | ///@{ 191 | /** 192 | * @tparam T the type of the returned value. 193 | * @return the number of meters in a meter. 194 | * 195 | * This is unity, but this lets the internal system of units be changed if 196 | * necessary. 197 | **********************************************************************/ 198 | template static inline T meter() throw() { return T(1); } 199 | /** 200 | * A synonym for meter(). 201 | **********************************************************************/ 202 | static inline Math::real meter() throw() { return meter(); } 203 | /** 204 | * @return the number of meters in a kilometer. 205 | **********************************************************************/ 206 | static inline Math::real kilometer() throw() 207 | { return 1000 * meter(); } 208 | /** 209 | * @return the number of meters in a nautical mile (approximately 1 arc 210 | * minute) 211 | **********************************************************************/ 212 | static inline Math::real nauticalmile() throw() 213 | { return 1852 * meter(); } 214 | 215 | /** 216 | * @tparam T the type of the returned value. 217 | * @return the number of square meters in a square meter. 218 | * 219 | * This is unity, but this lets the internal system of units be changed if 220 | * necessary. 221 | **********************************************************************/ 222 | template static inline T square_meter() throw() 223 | { return meter() * meter(); } 224 | /** 225 | * A synonym for square_meter(). 226 | **********************************************************************/ 227 | static inline Math::real square_meter() throw() 228 | { return square_meter(); } 229 | /** 230 | * @return the number of square meters in a hectare. 231 | **********************************************************************/ 232 | static inline Math::real hectare() throw() 233 | { return 10000 * square_meter(); } 234 | /** 235 | * @return the number of square meters in a square kilometer. 236 | **********************************************************************/ 237 | static inline Math::real square_kilometer() throw() 238 | { return kilometer() * kilometer(); } 239 | /** 240 | * @return the number of square meters in a square nautical mile. 241 | **********************************************************************/ 242 | static inline Math::real square_nauticalmile() throw() 243 | { return nauticalmile() * nauticalmile(); } 244 | ///@} 245 | 246 | /** \name Anachronistic British units 247 | **********************************************************************/ 248 | ///@{ 249 | /** 250 | * @return the number of meters in an international foot. 251 | **********************************************************************/ 252 | static inline Math::real foot() throw() 253 | { return real(0.0254L) * 12 * meter(); } 254 | /** 255 | * @return the number of meters in a yard. 256 | **********************************************************************/ 257 | static inline Math::real yard() throw() { return 3 * foot(); } 258 | /** 259 | * @return the number of meters in a fathom. 260 | **********************************************************************/ 261 | static inline Math::real fathom() throw() { return 2 * yard(); } 262 | /** 263 | * @return the number of meters in a chain. 264 | **********************************************************************/ 265 | static inline Math::real chain() throw() { return 22 * yard(); } 266 | /** 267 | * @return the number of meters in a furlong. 268 | **********************************************************************/ 269 | static inline Math::real furlong() throw() { return 10 * chain(); } 270 | /** 271 | * @return the number of meters in a statute mile. 272 | **********************************************************************/ 273 | static inline Math::real mile() throw() { return 8 * furlong(); } 274 | /** 275 | * @return the number of square meters in an acre. 276 | **********************************************************************/ 277 | static inline Math::real acre() throw() { return chain() * furlong(); } 278 | /** 279 | * @return the number of square meters in a square statute mile. 280 | **********************************************************************/ 281 | static inline Math::real square_mile() throw() { return mile() * mile(); } 282 | ///@} 283 | 284 | /** \name Anachronistic US units 285 | **********************************************************************/ 286 | ///@{ 287 | /** 288 | * @return the number of meters in a US survey foot. 289 | **********************************************************************/ 290 | static inline Math::real surveyfoot() throw() 291 | { return real(1200) / real(3937) * meter(); } 292 | ///@} 293 | }; 294 | 295 | /** 296 | * \brief Exception handling for %GeographicLib 297 | * 298 | * A class to handle exceptions. It's derived from std::runtime_error so it 299 | * can be caught by the usual catch clauses. 300 | * 301 | * Example of use: 302 | * \include example-GeographicErr.cpp 303 | **********************************************************************/ 304 | class GeographicErr : public std::runtime_error { 305 | public: 306 | 307 | /** 308 | * Constructor 309 | * 310 | * @param[in] msg a string message, which is accessible in the catch 311 | * clause via what(). 312 | **********************************************************************/ 313 | GeographicErr(const std::string& msg) : std::runtime_error(msg) {} 314 | }; 315 | 316 | } // namespace GeographicLib 317 | 318 | #endif // GEOGRAPHICLIB_CONSTANTS_HPP 319 | -------------------------------------------------------------------------------- /GeographicLib/Geoid.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Geoid.hpp 3 | * \brief Header for GeographicLib::Geoid class 4 | * 5 | * Copyright (c) Charles Karney (2009-2012) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * http://geographiclib.sourceforge.net/ 8 | **********************************************************************/ 9 | 10 | #if !defined(GEOGRAPHICLIB_GEOID_HPP) 11 | #define GEOGRAPHICLIB_GEOID_HPP 1 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #if defined(_MSC_VER) 18 | // Squelch warnings about dll vs vector and constant conditional expressions 19 | # pragma warning (push) 20 | # pragma warning (disable: 4251 4127) 21 | #endif 22 | 23 | #if !defined(PGM_PIXEL_WIDTH) 24 | /** 25 | * The size of the pixel data in the pgm data files for the geoids. 2 is the 26 | * standard size corresponding to a maxval 216−1. Setting it 27 | * to 4 uses a maxval of 232−1 and changes the extension for 28 | * the data files from .pgm to .pgm4. Note that the format of these pgm4 files 29 | * is a non-standard extension of the pgm format. 30 | **********************************************************************/ 31 | # define PGM_PIXEL_WIDTH 2 32 | #endif 33 | 34 | namespace GeographicLib { 35 | 36 | /** 37 | * \brief Looking up the height of the geoid 38 | * 39 | * This class evaluated the height of one of the standard geoids, EGM84, 40 | * EGM96, or EGM2008 by bilinear or cubic interpolation into a rectangular 41 | * grid of data. These geoid models are documented in 42 | * - EGM84: 43 | * http://earth-info.nga.mil/GandG/wgs84/gravitymod/wgs84_180/wgs84_180.html 44 | * - EGM96: 45 | * http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html 46 | * - EGM2008: 47 | * http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008 48 | * 49 | * The geoids are defined in terms of spherical harmonics. However in order 50 | * to provide a quick and flexible method of evaluating the geoid heights, 51 | * this class evaluates the height by interpolation into a grid of 52 | * precomputed values. 53 | * 54 | * See \ref geoid for details of how to install the data sets, the data 55 | * format, estimates of the interpolation errors, and how to use caching. 56 | * 57 | * In addition to returning the geoid height, the gradient of the geoid can 58 | * be calculated. The gradient is defined as the rate of change of the geoid 59 | * as a function of position on the ellipsoid. This uses the parameters for 60 | * the WGS84 ellipsoid. The gradient defined in terms of the interpolated 61 | * heights. As a result of the way that the geoid data is stored, the 62 | * calculation of gradients can result in large quantization errors. This is 63 | * particularly acute for fine grids, at high latitudes, and for the easterly 64 | * gradient. 65 | * 66 | * This class is typically \e not thread safe in that a single instantiation 67 | * cannot be safely used by multiple threads because of the way the object 68 | * reads the data set and because it maintains a single-cell cache. If 69 | * multiple threads need to calculate geoid heights they should all construct 70 | * thread-local instantiations. Alternatively, set the optional \e 71 | * threadsafe parameter to true in the constructor. This causes the 72 | * constructor to read all the data into memory and to turn off the 73 | * single-cell caching which results in a Geoid object which \e is thread 74 | * safe. 75 | * 76 | * Example of use: 77 | * \include example-Geoid.cpp 78 | * 79 | * GeoidEval is a command-line utility 80 | * providing access to the functionality of Geoid. 81 | **********************************************************************/ 82 | 83 | class GEOGRAPHIC_EXPORT Geoid { 84 | private: 85 | typedef Math::real real; 86 | #if PGM_PIXEL_WIDTH != 4 87 | typedef unsigned short pixel_t; 88 | static const unsigned pixel_size_ = 2; 89 | static const unsigned pixel_max_ = 0xffffu; 90 | #else 91 | typedef unsigned pixel_t; 92 | static const unsigned pixel_size_ = 4; 93 | static const unsigned pixel_max_ = 0xffffffffu; 94 | #endif 95 | static const unsigned stencilsize_ = 12; 96 | static const unsigned nterms_ = ((3 + 1) * (3 + 2))/2; // for a cubic fit 97 | static const real c0_; 98 | static const real c0n_; 99 | static const real c0s_; 100 | static const real c3_[stencilsize_ * nterms_]; 101 | static const real c3n_[stencilsize_ * nterms_]; 102 | static const real c3s_[stencilsize_ * nterms_]; 103 | 104 | std::string _name, _dir, _filename; 105 | const bool _cubic; 106 | const real _a, _e2, _degree, _eps; 107 | mutable std::ifstream _file; 108 | real _rlonres, _rlatres; 109 | std::string _description, _datetime; 110 | real _offset, _scale, _maxerror, _rmserror; 111 | int _width, _height; 112 | unsigned long long _datastart, _swidth; 113 | bool _threadsafe; 114 | // Area cache 115 | mutable std::vector< std::vector > _data; 116 | mutable bool _cache; 117 | // NE corner and extent of cache 118 | mutable int _xoffset, _yoffset, _xsize, _ysize; 119 | // Cell cache 120 | mutable int _ix, _iy; 121 | mutable real _v00, _v01, _v10, _v11; 122 | mutable real _t[nterms_]; 123 | void filepos(int ix, int iy) const { 124 | _file.seekg( 125 | #if !(defined(__GNUC__) && __GNUC__ < 4) 126 | // g++ 3.x doesn't know about the cast to streamoff. 127 | std::ios::streamoff 128 | #endif 129 | (_datastart + 130 | pixel_size_ * (unsigned(iy)*_swidth + unsigned(ix)))); 131 | } 132 | real rawval(int ix, int iy) const { 133 | if (ix < 0) 134 | ix += _width; 135 | else if (ix >= _width) 136 | ix -= _width; 137 | if (_cache && iy >= _yoffset && iy < _yoffset + _ysize && 138 | ((ix >= _xoffset && ix < _xoffset + _xsize) || 139 | (ix + _width >= _xoffset && ix + _width < _xoffset + _xsize))) { 140 | return real(_data[iy - _yoffset] 141 | [ix >= _xoffset ? ix - _xoffset : ix + _width - _xoffset]); 142 | } else { 143 | if (iy < 0 || iy >= _height) { 144 | iy = iy < 0 ? -iy : 2 * (_height - 1) - iy; 145 | ix += (ix < _width/2 ? 1 : -1) * _width/2; 146 | } 147 | try { 148 | filepos(ix, iy); 149 | char a, b; 150 | _file.get(a); 151 | _file.get(b); 152 | unsigned r = ((unsigned char)(a) << 8) | (unsigned char)(b); 153 | if (pixel_size_ == 4) { 154 | _file.get(a); 155 | _file.get(b); 156 | r = (r << 16) | ((unsigned char)(a) << 8) | (unsigned char)(b); 157 | } 158 | return real(r); 159 | } 160 | catch (const std::exception& e) { 161 | // throw GeographicErr("Error reading " + _filename + ": " 162 | // + e.what()); 163 | // triggers complaints about the "binary '+'" under Visual Studio. 164 | // So use '+=' instead. 165 | std::string err("Error reading "); 166 | err += _filename; 167 | err += ": "; 168 | err += e.what(); 169 | throw GeographicErr(err); 170 | } 171 | } 172 | } 173 | real height(real lat, real lon, bool gradp, 174 | real& grade, real& gradn) const; 175 | Geoid(const Geoid&); // copy constructor not allowed 176 | Geoid& operator=(const Geoid&); // copy assignment not allowed 177 | public: 178 | 179 | /** 180 | * Flags indicating conversions between heights above the geoid and heights 181 | * above the ellipsoid. 182 | **********************************************************************/ 183 | enum convertflag { 184 | /** 185 | * The multiplier for converting from heights above the geoid to heights 186 | * above the ellipsoid. 187 | **********************************************************************/ 188 | ELLIPSOIDTOGEOID = -1, 189 | /** 190 | * No conversion. 191 | **********************************************************************/ 192 | NONE = 0, 193 | /** 194 | * The multiplier for converting from heights above the ellipsoid to 195 | * heights above the geoid. 196 | **********************************************************************/ 197 | GEOIDTOELLIPSOID = 1, 198 | }; 199 | 200 | /** \name Setting up the geoid 201 | **********************************************************************/ 202 | ///@{ 203 | /** 204 | * Construct a geoid. 205 | * 206 | * @param[in] name the name of the geoid. 207 | * @param[in] path (optional) directory for data file. 208 | * @param[in] cubic (optional) interpolation method; false means bilinear, 209 | * true (the default) means cubic. 210 | * @param[in] threadsafe (optional), if true, construct a thread safe 211 | * object. The default is false 212 | * @exception GeographicErr if the data file cannot be found, is 213 | * unreadable, or is corrupt. 214 | * @exception GeographicErr if \e threadsafe is true but the memory 215 | * necessary for caching the data can't be allocated. 216 | * 217 | * The data file is formed by appending ".pgm" to the name. If \e path is 218 | * specified (and is non-empty), then the file is loaded from directory, \e 219 | * path. Otherwise the path is given by DefaultGeoidPath(). If the \e 220 | * threadsafe parameter is true, the data set is read into memory, the data 221 | * file is closed, and single-cell caching is turned off; this results in a 222 | * Geoid object which \e is thread safe. 223 | **********************************************************************/ 224 | explicit Geoid(const std::string& name, const std::string& path = "", 225 | bool cubic = true, bool threadsafe = false); 226 | 227 | /** 228 | * Set up a cache. 229 | * 230 | * @param[in] south latitude (degrees) of the south edge of the cached area. 231 | * @param[in] west longitude (degrees) of the west edge of the cached area. 232 | * @param[in] north latitude (degrees) of the north edge of the cached area. 233 | * @param[in] east longitude (degrees) of the east edge of the cached area. 234 | * @exception GeographicErr if the memory necessary for caching the data 235 | * can't be allocated (in this case, you will have no cache and can try 236 | * again with a smaller area). 237 | * @exception GeographicErr if there's a problem reading the data. 238 | * @exception GeographicErr if this is called on a threadsafe Geoid. 239 | * 240 | * Cache the data for the specified "rectangular" area bounded by the 241 | * parallels \e south and \e north and the meridians \e west and \e east. 242 | * \e east is always interpreted as being east of \e west, if necessary by 243 | * adding 360° to its value. \e south and \e north should be in 244 | * the range [−90°, 90°]; \e west and \e east should 245 | * be in the range [−540°, 540°). 246 | **********************************************************************/ 247 | void CacheArea(real south, real west, real north, real east) const; 248 | 249 | /** 250 | * Cache all the data. 251 | * 252 | * @exception GeographicErr if the memory necessary for caching the data 253 | * can't be allocated (in this case, you will have no cache and can try 254 | * again with a smaller area). 255 | * @exception GeographicErr if there's a problem reading the data. 256 | * @exception GeographicErr if this is called on a threadsafe Geoid. 257 | * 258 | * On most computers, this is fast for data sets with grid resolution of 5' 259 | * or coarser. For a 1' grid, the required RAM is 450MB; a 2.5' grid needs 260 | * 72MB; and a 5' grid needs 18MB. 261 | **********************************************************************/ 262 | void CacheAll() const { CacheArea(real(-90), real(0), 263 | real(90), real(360)); } 264 | 265 | /** 266 | * Clear the cache. This never throws an error. (This does nothing with a 267 | * thread safe Geoid.) 268 | **********************************************************************/ 269 | void CacheClear() const throw(); 270 | 271 | ///@} 272 | 273 | /** \name Compute geoid heights 274 | **********************************************************************/ 275 | ///@{ 276 | /** 277 | * Compute the geoid height at a point 278 | * 279 | * @param[in] lat latitude of the point (degrees). 280 | * @param[in] lon longitude of the point (degrees). 281 | * @exception GeographicErr if there's a problem reading the data; this 282 | * never happens if (\e lat, \e lon) is within a successfully cached area. 283 | * @return geoid height (meters). 284 | * 285 | * The latitude should be in [−90°, 90°] and 286 | * longitude should be in [−540°, 540°). 287 | **********************************************************************/ 288 | Math::real operator()(real lat, real lon) const { 289 | real gradn, grade; 290 | return height(lat, lon, false, gradn, grade); 291 | } 292 | 293 | /** 294 | * Compute the geoid height and gradient at a point 295 | * 296 | * @param[in] lat latitude of the point (degrees). 297 | * @param[in] lon longitude of the point (degrees). 298 | * @param[out] gradn northerly gradient (dimensionless). 299 | * @param[out] grade easterly gradient (dimensionless). 300 | * @exception GeographicErr if there's a problem reading the data; this 301 | * never happens if (\e lat, \e lon) is within a successfully cached area. 302 | * @return geoid height (meters). 303 | * 304 | * The latitude should be in [−90°, 90°] and 305 | * longitude should be in [−540°, 540°). As a result 306 | * of the way that the geoid data is stored, the calculation of gradients 307 | * can result in large quantization errors. This is particularly acute for 308 | * fine grids, at high latitudes, and for the easterly gradient. If you 309 | * need to compute the direction of the acceleration due to gravity 310 | * accurately, you should use GravityModel::Gravity. 311 | **********************************************************************/ 312 | Math::real operator()(real lat, real lon, real& gradn, real& grade) const { 313 | return height(lat, lon, true, gradn, grade); 314 | } 315 | 316 | /** 317 | * Convert a height above the geoid to a height above the ellipsoid and 318 | * vice versa. 319 | * 320 | * @param[in] lat latitude of the point (degrees). 321 | * @param[in] lon longitude of the point (degrees). 322 | * @param[in] h height of the point (degrees). 323 | * @param[in] d a Geoid::convertflag specifying the direction of the 324 | * conversion; Geoid::GEOIDTOELLIPSOID means convert a height above the 325 | * geoid to a height above the ellipsoid; Geoid::ELLIPSOIDTOGEOID means 326 | * convert a height above the ellipsoid to a height above the geoid. 327 | * @exception GeographicErr if there's a problem reading the data; this 328 | * never happens if (\e lat, \e lon) is within a successfully cached area. 329 | * @return converted height (meters). 330 | **********************************************************************/ 331 | Math::real ConvertHeight(real lat, real lon, real h, 332 | convertflag d) const { 333 | real gradn, grade; 334 | return h + real(d) * height(lat, lon, true, gradn, grade); 335 | } 336 | 337 | ///@} 338 | 339 | /** \name Inspector functions 340 | **********************************************************************/ 341 | ///@{ 342 | /** 343 | * @return geoid description, if available, in the data file; if 344 | * absent, return "NONE". 345 | **********************************************************************/ 346 | const std::string& Description() const throw() { return _description; } 347 | 348 | /** 349 | * @return date of the data file; if absent, return "UNKNOWN". 350 | **********************************************************************/ 351 | const std::string& DateTime() const throw() { return _datetime; } 352 | 353 | /** 354 | * @return full file name used to load the geoid data. 355 | **********************************************************************/ 356 | const std::string& GeoidFile() const throw() { return _filename; } 357 | 358 | /** 359 | * @return "name" used to load the geoid data (from the first argument of 360 | * the constructor). 361 | **********************************************************************/ 362 | const std::string& GeoidName() const throw() { return _name; } 363 | 364 | /** 365 | * @return directory used to load the geoid data. 366 | **********************************************************************/ 367 | const std::string& GeoidDirectory() const throw() { return _dir; } 368 | 369 | /** 370 | * @return interpolation method ("cubic" or "bilinear"). 371 | **********************************************************************/ 372 | const std::string Interpolation() const 373 | { return std::string(_cubic ? "cubic" : "bilinear"); } 374 | 375 | /** 376 | * @return estimate of the maximum interpolation and quantization error 377 | * (meters). 378 | * 379 | * This relies on the value being stored in the data file. If the value is 380 | * absent, return −1. 381 | **********************************************************************/ 382 | Math::real MaxError() const throw() { return _maxerror; } 383 | 384 | /** 385 | * @return estimate of the RMS interpolation and quantization error 386 | * (meters). 387 | * 388 | * This relies on the value being stored in the data file. If the value is 389 | * absent, return −1. 390 | **********************************************************************/ 391 | Math::real RMSError() const throw() { return _rmserror; } 392 | 393 | /** 394 | * @return offset (meters). 395 | * 396 | * This in used in converting from the pixel values in the data file to 397 | * geoid heights. 398 | **********************************************************************/ 399 | Math::real Offset() const throw() { return _offset; } 400 | 401 | /** 402 | * @return scale (meters). 403 | * 404 | * This in used in converting from the pixel values in the data file to 405 | * geoid heights. 406 | **********************************************************************/ 407 | Math::real Scale() const throw() { return _scale; } 408 | 409 | /** 410 | * @return true if the object is constructed to be thread safe. 411 | **********************************************************************/ 412 | bool ThreadSafe() const throw() { return _threadsafe; } 413 | 414 | /** 415 | * @return true if a data cache is active. 416 | **********************************************************************/ 417 | bool Cache() const throw() { return _cache; } 418 | 419 | /** 420 | * @return west edge of the cached area; the cache includes this edge. 421 | **********************************************************************/ 422 | Math::real CacheWest() const throw() { 423 | return _cache ? ((_xoffset + (_xsize == _width ? 0 : _cubic) 424 | + _width/2) % _width - _width/2) / _rlonres : 425 | 0; 426 | } 427 | 428 | /** 429 | * @return east edge of the cached area; the cache excludes this edge. 430 | **********************************************************************/ 431 | Math::real CacheEast() const throw() { 432 | return _cache ? 433 | CacheWest() + 434 | (_xsize - (_xsize == _width ? 0 : 1 + 2 * _cubic)) / _rlonres : 435 | 0; 436 | } 437 | 438 | /** 439 | * @return north edge of the cached area; the cache includes this edge. 440 | **********************************************************************/ 441 | Math::real CacheNorth() const throw() { 442 | return _cache ? 90 - (_yoffset + _cubic) / _rlatres : 0; 443 | } 444 | 445 | /** 446 | * @return south edge of the cached area; the cache excludes this edge 447 | * unless it's the south pole. 448 | **********************************************************************/ 449 | Math::real CacheSouth() const throw() { 450 | return _cache ? 90 - ( _yoffset + _ysize - 1 - _cubic) / _rlatres : 0; 451 | } 452 | 453 | /** 454 | * @return \e a the equatorial radius of the WGS84 ellipsoid (meters). 455 | * 456 | * (The WGS84 value is returned because the supported geoid models are all 457 | * based on this ellipsoid.) 458 | **********************************************************************/ 459 | Math::real MajorRadius() const throw() 460 | { return Constants::WGS84_a(); } 461 | 462 | /** 463 | * @return \e f the flattening of the WGS84 ellipsoid. 464 | * 465 | * (The WGS84 value is returned because the supported geoid models are all 466 | * based on this ellipsoid.) 467 | **********************************************************************/ 468 | Math::real Flattening() const throw() { return Constants::WGS84_f(); } 469 | ///@} 470 | 471 | /// \cond SKIP 472 | /** 473 | * DEPRECATED 474 | * @return \e r the inverse flattening of the WGS84 ellipsoid. 475 | **********************************************************************/ 476 | Math::real InverseFlattening() const throw() 477 | { return 1/Constants::WGS84_f(); } 478 | /// \endcond 479 | 480 | /** 481 | * @return the default path for geoid data files. 482 | * 483 | * This is the value of the environment variable GEOID_PATH, if set; 484 | * otherwise, it is $GEOGRAPHICLIB_DATA/geoids if the environment variable 485 | * GEOGRAPHICLIB_DATA is set; otherwise, it is a compile-time default 486 | * (/usr/local/share/GeographicLib/geoids on non-Windows systems and 487 | * C:/Documents and Settings/All Users/Application 488 | * Data/GeographicLib/geoids on Windows systems). 489 | **********************************************************************/ 490 | static std::string DefaultGeoidPath(); 491 | 492 | /** 493 | * @return the default name for the geoid. 494 | * 495 | * This is the value of the environment variable GEOID_NAME, if set, 496 | * otherwise, it is "egm96-5". The Geoid class does not use this function; 497 | * it is just provided as a convenience for a calling program when 498 | * constructing a Geoid object. 499 | **********************************************************************/ 500 | static std::string DefaultGeoidName(); 501 | 502 | }; 503 | 504 | } // namespace GeographicLib 505 | 506 | #if defined(_MSC_VER) 507 | # pragma warning (pop) 508 | #endif 509 | 510 | #endif // GEOGRAPHICLIB_GEOID_HPP 511 | -------------------------------------------------------------------------------- /GeographicLib/Math.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Math.hpp 3 | * \brief Header for GeographicLib::Math class 4 | * 5 | * Copyright (c) Charles Karney (2008-2011) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * http://geographiclib.sourceforge.net/ 8 | **********************************************************************/ 9 | 10 | // Constants.hpp includes Math.hpp. Place this include outside Math.hpp's 11 | // include guard to enforce this ordering. 12 | #include 13 | 14 | #if !defined(GEOGRAPHICLIB_MATH_HPP) 15 | #define GEOGRAPHICLIB_MATH_HPP 1 16 | 17 | /** 18 | * Are C++11 math functions available? 19 | **********************************************************************/ 20 | #if !defined(GEOGRAPHICLIB_CPLUSPLUS11_MATH) 21 | # if defined(__GXX_EXPERIMENTAL_CXX0X__) 22 | # define GEOGRAPHICLIB_CPLUSPLUS11_MATH 1 23 | # else 24 | # define GEOGRAPHICLIB_CPLUSPLUS11_MATH 0 25 | # endif 26 | #endif 27 | 28 | #if !defined(WORDS_BIGENDIAN) 29 | # define WORDS_BIGENDIAN 0 30 | #endif 31 | 32 | #if !defined(HAVE_LONG_DOUBLE) 33 | # define HAVE_LONG_DOUBLE 0 34 | #endif 35 | 36 | #if !defined(GEOGRAPHICLIB_PRECISION) 37 | /** 38 | * The precision of floating point numbers used in %GeographicLib. 1 means 39 | * float (single precision); 2 (the default) means double; 3 means long double; 40 | * 4 is reserved for quadruple precision. Nearly all the testing has been 41 | * carried out with doubles and that's the recommended configuration. In order 42 | * for long double to be used, HAVE_LONG_DOUBLE needs to be defined. Note that 43 | * with Microsoft Visual Studio, long double is the same as double. 44 | **********************************************************************/ 45 | # define GEOGRAPHICLIB_PRECISION 2 46 | #endif 47 | 48 | #include 49 | #include 50 | #include 51 | #if defined(_LIBCPP_VERSION) 52 | #include 53 | #endif 54 | 55 | namespace GeographicLib { 56 | 57 | /** 58 | * \brief Mathematical functions needed by %GeographicLib 59 | * 60 | * Define mathematical functions in order to localize system dependencies and 61 | * to provide generic versions of the functions. In addition define a real 62 | * type to be used by %GeographicLib. 63 | * 64 | * Example of use: 65 | * \include example-Math.cpp 66 | **********************************************************************/ 67 | class GEOGRAPHIC_EXPORT Math { 68 | private: 69 | void dummy() { 70 | STATIC_ASSERT(GEOGRAPHICLIB_PRECISION >= 1 && 71 | GEOGRAPHICLIB_PRECISION <= 3, 72 | "Bad value of precision"); 73 | } 74 | Math(); // Disable constructor 75 | public: 76 | 77 | #if HAVE_LONG_DOUBLE 78 | /** 79 | * The extended precision type for real numbers, used for some testing. 80 | * This is long double on computers with this type; otherwise it is double. 81 | **********************************************************************/ 82 | typedef long double extended; 83 | #else 84 | typedef double extended; 85 | #endif 86 | 87 | #if GEOGRAPHICLIB_PRECISION == 2 88 | /** 89 | * The real type for %GeographicLib. Nearly all the testing has been done 90 | * with \e real = double. However, the algorithms should also work with 91 | * float and long double (where available). (CAUTION: reasonable 92 | * accuracy typically cannot be obtained using floats.) 93 | **********************************************************************/ 94 | typedef double real; 95 | #elif GEOGRAPHICLIB_PRECISION == 1 96 | typedef float real; 97 | #elif GEOGRAPHICLIB_PRECISION == 3 98 | typedef extended real; 99 | #else 100 | typedef double real; 101 | #endif 102 | 103 | /** 104 | * Number of additional decimal digits of precision of real relative to 105 | * double (0 for float). 106 | **********************************************************************/ 107 | static const int extradigits = 108 | std::numeric_limits::digits10 > 109 | std::numeric_limits::digits10 ? 110 | std::numeric_limits::digits10 - 111 | std::numeric_limits::digits10 : 0; 112 | 113 | /** 114 | * true if the machine is big-endian. 115 | **********************************************************************/ 116 | static const bool bigendian = WORDS_BIGENDIAN; 117 | 118 | /** 119 | * @tparam T the type of the returned value. 120 | * @return π. 121 | **********************************************************************/ 122 | template static inline T pi() throw() 123 | { return std::atan2(T(0), -T(1)); } 124 | /** 125 | * A synonym for pi(). 126 | **********************************************************************/ 127 | static inline real pi() throw() { return pi(); } 128 | 129 | /** 130 | * @tparam T the type of the returned value. 131 | * @return the number of radians in a degree. 132 | **********************************************************************/ 133 | template static inline T degree() throw() 134 | { return pi() / T(180); } 135 | /** 136 | * A synonym for degree(). 137 | **********************************************************************/ 138 | static inline real degree() throw() { return degree(); } 139 | 140 | /** 141 | * Square a number. 142 | * 143 | * @tparam T the type of the argument and the returned value. 144 | * @param[in] x 145 | * @return x2. 146 | **********************************************************************/ 147 | template static inline T sq(T x) throw() 148 | { return x * x; } 149 | 150 | #if defined(DOXYGEN) 151 | /** 152 | * The hypotenuse function avoiding underflow and overflow. 153 | * 154 | * @tparam T the type of the arguments and the returned value. 155 | * @param[in] x 156 | * @param[in] y 157 | * @return sqrt(x2 + y2). 158 | **********************************************************************/ 159 | template static inline T hypot(T x, T y) throw() { 160 | x = std::abs(x); y = std::abs(y); 161 | T a = (std::max)(x, y), b = (std::min)(x, y) / (a ? a : 1); 162 | return a * std::sqrt(1 + b * b); 163 | // For an alternative method see 164 | // C. Moler and D. Morrision (1983) http://dx.doi.org/10.1147/rd.276.0577 165 | // and A. A. Dubrulle (1983) http://dx.doi.org/10.1147/rd.276.0582 166 | } 167 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 168 | template static inline T hypot(T x, T y) throw() 169 | { return std::hypot(x, y); } 170 | #elif defined(_MSC_VER) 171 | static inline double hypot(double x, double y) throw() 172 | { return _hypot(x, y); } 173 | # if _MSC_VER < 1400 174 | // Visual C++ 7.1/VS .NET 2003 does not have _hypotf() 175 | static inline float hypot(float x, float y) throw() 176 | { return float(_hypot(x, y)); } 177 | # else 178 | static inline float hypot(float x, float y) throw() 179 | { return _hypotf(x, y); } 180 | # endif 181 | # if HAVE_LONG_DOUBLE 182 | static inline long double hypot(long double x, long double y) throw() 183 | { return _hypot(double(x), double(y)); } // Suppress loss of data warning 184 | # endif 185 | #else 186 | // Use overloading to define generic versions 187 | static inline double hypot(double x, double y) throw() 188 | { return ::hypot(x, y); } 189 | static inline float hypot(float x, float y) throw() 190 | { return ::hypotf(x, y); } 191 | # if HAVE_LONG_DOUBLE 192 | static inline long double hypot(long double x, long double y) throw() 193 | { return ::hypotl(x, y); } 194 | # endif 195 | #endif 196 | 197 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 198 | /** 199 | * exp(\e x) − 1 accurate near \e x = 0. This is taken from 200 | * N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd 201 | * Edition (SIAM, 2002), Sec 1.14.1, p 19. 202 | * 203 | * @tparam T the type of the argument and the returned value. 204 | * @param[in] x 205 | * @return exp(\e x) − 1. 206 | **********************************************************************/ 207 | template static inline T expm1(T x) throw() { 208 | volatile T 209 | y = std::exp(x), 210 | z = y - 1; 211 | // The reasoning here is similar to that for log1p. The expression 212 | // mathematically reduces to exp(x) - 1, and the factor z/log(y) = (y - 213 | // 1)/log(y) is a slowly varying quantity near y = 1 and is accurately 214 | // computed. 215 | return std::abs(x) > 1 ? z : (z == 0 ? x : x * z / std::log(y)); 216 | } 217 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 218 | template static inline T expm1(T x) throw() 219 | { return std::expm1(x); } 220 | #else 221 | static inline double expm1(double x) throw() { return ::expm1(x); } 222 | static inline float expm1(float x) throw() { return ::expm1f(x); } 223 | # if HAVE_LONG_DOUBLE 224 | static inline long double expm1(long double x) throw() 225 | { return ::expm1l(x); } 226 | # endif 227 | #endif 228 | 229 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 230 | /** 231 | * log(1 + \e x) accurate near \e x = 0. 232 | * 233 | * This is taken from D. Goldberg, 234 | * What every computer 235 | * scientist should know about floating-point arithmetic (1991), 236 | * Theorem 4. See also, Higham (op. cit.), Answer to Problem 1.5, p 528. 237 | * 238 | * @tparam T the type of the argument and the returned value. 239 | * @param[in] x 240 | * @return log(1 + \e x). 241 | **********************************************************************/ 242 | template static inline T log1p(T x) throw() { 243 | volatile T 244 | y = 1 + x, 245 | z = y - 1; 246 | // Here's the explanation for this magic: y = 1 + z, exactly, and z 247 | // approx x, thus log(y)/z (which is nearly constant near z = 0) returns 248 | // a good approximation to the true log(1 + x)/x. The multiplication x * 249 | // (log(y)/z) introduces little additional error. 250 | return z == 0 ? x : x * std::log(y) / z; 251 | } 252 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 253 | template static inline T log1p(T x) throw() 254 | { return std::log1p(x); } 255 | #else 256 | static inline double log1p(double x) throw() { return ::log1p(x); } 257 | static inline float log1p(float x) throw() { return ::log1pf(x); } 258 | # if HAVE_LONG_DOUBLE 259 | static inline long double log1p(long double x) throw() 260 | { return ::log1pl(x); } 261 | # endif 262 | #endif 263 | 264 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 265 | /** 266 | * The inverse hyperbolic sine function. This is defined in terms of 267 | * Math::log1p(\e x) in order to maintain accuracy near \e x = 0. In 268 | * addition, the odd parity of the function is enforced. 269 | * 270 | * @tparam T the type of the argument and the returned value. 271 | * @param[in] x 272 | * @return asinh(\e x). 273 | **********************************************************************/ 274 | template static inline T asinh(T x) throw() { 275 | T y = std::abs(x); // Enforce odd parity 276 | y = log1p(y * (1 + y/(hypot(T(1), y) + 1))); 277 | return x < 0 ? -y : y; 278 | } 279 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 280 | template static inline T asinh(T x) throw() 281 | { return std::asinh(x); } 282 | #else 283 | static inline double asinh(double x) throw() { return ::asinh(x); } 284 | static inline float asinh(float x) throw() { return ::asinhf(x); } 285 | # if HAVE_LONG_DOUBLE 286 | static inline long double asinh(long double x) throw() 287 | { return ::asinhl(x); } 288 | # endif 289 | #endif 290 | 291 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 292 | /** 293 | * The inverse hyperbolic tangent function. This is defined in terms of 294 | * Math::log1p(\e x) in order to maintain accuracy near \e x = 0. In 295 | * addition, the odd parity of the function is enforced. 296 | * 297 | * @tparam T the type of the argument and the returned value. 298 | * @param[in] x 299 | * @return atanh(\e x). 300 | **********************************************************************/ 301 | template static inline T atanh(T x) throw() { 302 | T y = std::abs(x); // Enforce odd parity 303 | y = log1p(2 * y/(1 - y))/2; 304 | return x < 0 ? -y : y; 305 | } 306 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 307 | template static inline T atanh(T x) throw() 308 | { return std::atanh(x); } 309 | #else 310 | static inline double atanh(double x) throw() { return ::atanh(x); } 311 | static inline float atanh(float x) throw() { return ::atanhf(x); } 312 | # if HAVE_LONG_DOUBLE 313 | static inline long double atanh(long double x) throw() 314 | { return ::atanhl(x); } 315 | # endif 316 | #endif 317 | 318 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 319 | /** 320 | * The cube root function. 321 | * 322 | * @tparam T the type of the argument and the returned value. 323 | * @param[in] x 324 | * @return the real cube root of \e x. 325 | **********************************************************************/ 326 | template static inline T cbrt(T x) throw() { 327 | T y = std::pow(std::abs(x), 1/T(3)); // Return the real cube root 328 | return x < 0 ? -y : y; 329 | } 330 | #elif GEOGRAPHICLIB_CPLUSPLUS11_MATH 331 | template static inline T cbrt(T x) throw() 332 | { return std::cbrt(x); } 333 | #else 334 | static inline double cbrt(double x) throw() { return ::cbrt(x); } 335 | static inline float cbrt(float x) throw() { return ::cbrtf(x); } 336 | # if HAVE_LONG_DOUBLE 337 | static inline long double cbrt(long double x) throw() { return ::cbrtl(x); } 338 | # endif 339 | #endif 340 | 341 | /** 342 | * The error-free sum of two numbers. 343 | * 344 | * @tparam T the type of the argument and the returned value. 345 | * @param[in] u 346 | * @param[in] v 347 | * @param[out] t the exact error given by (\e u + \e v) - \e s. 348 | * @return \e s = round(\e u + \e v). 349 | * 350 | * See D. E. Knuth, TAOCP, Vol 2, 4.2.2, Theorem B. (Note that \e t can be 351 | * the same as one of the first two arguments.) 352 | **********************************************************************/ 353 | template static inline T sum(T u, T v, T& t) throw() { 354 | volatile T s = u + v; 355 | volatile T up = s - v; 356 | volatile T vpp = s - up; 357 | up -= u; 358 | vpp -= v; 359 | t = -(up + vpp); 360 | // u + v = s + t 361 | // = round(u + v) + t 362 | return s; 363 | } 364 | 365 | /** 366 | * Normalize an angle (restricted input range). 367 | * 368 | * @tparam T the type of the argument and returned value. 369 | * @param[in] x the angle in degrees. 370 | * @return the angle reduced to the range [−180°, 180°). 371 | * 372 | * \e x must lie in [−540°, 540°). 373 | **********************************************************************/ 374 | template static inline T AngNormalize(T x) throw() 375 | { return x >= 180 ? x - 360 : (x < -180 ? x + 360 : x); } 376 | 377 | /** 378 | * Normalize an arbitrary angle. 379 | * 380 | * @tparam T the type of the argument and returned value. 381 | * @param[in] x the angle in degrees. 382 | * @return the angle reduced to the range [−180°, 180°). 383 | * 384 | * The range of \e x is unrestricted. 385 | **********************************************************************/ 386 | template static inline T AngNormalize2(T x) throw() 387 | { return AngNormalize(std::fmod(x, T(360))); } 388 | 389 | /** 390 | * Difference of two angles reduced to [−180°, 180°] 391 | * 392 | * @tparam T the type of the arguments and returned value. 393 | * @param[in] x the first angle in degrees. 394 | * @param[in] y the second angle in degrees. 395 | * @return \e y − \e x, reduced to the range [−180°, 396 | * 180°]. 397 | * 398 | * \e x and \e y must both lie in [−180°, 180°]. The result 399 | * is equivalent to computing the difference exactly, reducing it to 400 | * (−180°, 180°] and rounding the result. Note that this 401 | * prescription allows −180° to be returned (e.g., if \e x is 402 | * tiny and negative and \e y = 180°). 403 | **********************************************************************/ 404 | template static inline T AngDiff(T x, T y) throw() { 405 | T t, d = sum(-x, y, t); 406 | if ((d - T(180)) + t > T(0)) // y - x > 180 407 | d -= T(360); // exact 408 | else if ((d + T(180)) + t <= T(0)) // y - x <= -180 409 | d += T(360); // exact 410 | return d + t; 411 | } 412 | 413 | #if defined(DOXYGEN) 414 | /** 415 | * Test for finiteness. 416 | * 417 | * @tparam T the type of the argument. 418 | * @param[in] x 419 | * @return true if number is finite, false if NaN or infinite. 420 | **********************************************************************/ 421 | template static inline bool isfinite(T x) throw() { 422 | return std::abs(x) <= (std::numeric_limits::max)(); 423 | } 424 | #elif (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 425 | template static inline bool isfinite(T x) throw() { 426 | return _finite(double(x)) != 0; 427 | } 428 | #elif defined(_LIBCPP_VERSION) 429 | // libc++ implements std::isfinite() as a template that only allows 430 | // floating-point types. isfinite is invoked by Utility::str to format 431 | // numbers conveniently and this allows integer arguments, so we need to 432 | // allow Math::isfinite to work on integers. 433 | template static inline 434 | typename std::enable_if::value, bool>::type 435 | isfinite(T x) throw() { 436 | return std::isfinite(x); 437 | } 438 | template static inline 439 | typename std::enable_if::value, bool>::type 440 | isfinite(T /*x*/) throw() { 441 | return true; 442 | } 443 | #else 444 | template static inline bool isfinite(T x) throw() { 445 | return std::isfinite(x); 446 | } 447 | #endif 448 | 449 | /** 450 | * The NaN (not a number) 451 | * 452 | * @tparam T the type of the returned value. 453 | * @return NaN if available, otherwise return the max real of type T. 454 | **********************************************************************/ 455 | template static inline T NaN() throw() { 456 | return std::numeric_limits::has_quiet_NaN ? 457 | std::numeric_limits::quiet_NaN() : 458 | (std::numeric_limits::max)(); 459 | } 460 | /** 461 | * A synonym for NaN(). 462 | **********************************************************************/ 463 | static inline real NaN() throw() { return NaN(); } 464 | 465 | /** 466 | * Test for NaN. 467 | * 468 | * @tparam T the type of the argument. 469 | * @param[in] x 470 | * @return true if argument is a NaN. 471 | **********************************************************************/ 472 | template static inline bool isnan(T x) throw() { 473 | #if defined(DOXYGEN) || (defined(_MSC_VER) && !GEOGRAPHICLIB_CPLUSPLUS11_MATH) 474 | return x != x; 475 | #else 476 | return std::isnan(x); 477 | #endif 478 | } 479 | 480 | /** 481 | * Infinity 482 | * 483 | * @tparam T the type of the returned value. 484 | * @return infinity if available, otherwise return the max real. 485 | **********************************************************************/ 486 | template static inline T infinity() throw() { 487 | return std::numeric_limits::has_infinity ? 488 | std::numeric_limits::infinity() : 489 | (std::numeric_limits::max)(); 490 | } 491 | /** 492 | * A synonym for infinity(). 493 | **********************************************************************/ 494 | static inline real infinity() throw() { return infinity(); } 495 | 496 | /** 497 | * Swap the bytes of a quantity 498 | * 499 | * @tparam T the type of the argument and the returned value. 500 | * @param[in] x 501 | * @return x with its bytes swapped. 502 | **********************************************************************/ 503 | template static inline T swab(T x) { 504 | union { 505 | T r; 506 | unsigned char c[sizeof(T)]; 507 | } b; 508 | b.r = x; 509 | for (int i = sizeof(T)/2; i--; ) 510 | std::swap(b.c[i], b.c[sizeof(T) - 1 - i]); 511 | return b.r; 512 | } 513 | }; 514 | 515 | } // namespace GeographicLib 516 | 517 | #endif // GEOGRAPHICLIB_MATH_HPP 518 | -------------------------------------------------------------------------------- /GeographicLib/Utility.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Utility.hpp 3 | * \brief Header for GeographicLib::Utility class 4 | * 5 | * Copyright (c) Charles Karney (2011-2012) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * http://geographiclib.sourceforge.net/ 8 | **********************************************************************/ 9 | 10 | #if !defined(GEOGRAPHICLIB_UTILITY_HPP) 11 | #define GEOGRAPHICLIB_UTILITY_HPP 1 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #if defined(_MSC_VER) 20 | // Squelch warnings about constant conditional expressions 21 | # pragma warning (push) 22 | # pragma warning (disable: 4127) 23 | #endif 24 | 25 | namespace GeographicLib { 26 | 27 | /** 28 | * \brief Some utility routines for %GeographicLib 29 | * 30 | * Example of use: 31 | * \include example-Utility.cpp 32 | **********************************************************************/ 33 | class GEOGRAPHIC_EXPORT Utility { 34 | private: 35 | static bool gregorian(int y, int m, int d) { 36 | // The original cut over to the Gregorian calendar in Pope Gregory XIII's 37 | // time had 1582-10-04 followed by 1582-10-15. Here we implement the 38 | // switch over used by the English-speaking world where 1752-09-02 was 39 | // followed by 1752-09-14. We also assume that the year always begins 40 | // with January 1, whereas in reality it often was reckoned to begin in 41 | // March. 42 | return 100 * (100 * y + m) + d >= 17520914; // or 15821004 43 | } 44 | static bool gregorian(int s) { 45 | return s >= 639799; // 1752-09-14 46 | } 47 | public: 48 | 49 | /** 50 | * Convert a date to the day numbering sequentially starting with 51 | * 0001-01-01 as day 1. 52 | * 53 | * @param[in] y the year (must be positive). 54 | * @param[in] m the month, Jan = 1, etc. (must be positive). Default = 1. 55 | * @param[in] d the day of the month (must be positive). Default = 1. 56 | * @return the sequential day number. 57 | **********************************************************************/ 58 | static int day(int y, int m = 1, int d = 1) throw() { 59 | // Convert from date to sequential day and vice versa 60 | // 61 | // Here is some code to convert a date to sequential day and vice 62 | // versa. The sequential day is numbered so that January 1, 1 AD is day 1 63 | // (a Saturday). So this is offset from the "Julian" day which starts the 64 | // numbering with 4713 BC. 65 | // 66 | // This is inspired by a talk by John Conway at the John von Neumann 67 | // National Supercomputer Center when he described his Doomsday algorithm 68 | // for figuring the day of the week. The code avoids explicitly doing ifs 69 | // (except for the decision of whether to use the Julian or Gregorian 70 | // calendar). Instead the equivalent result is achieved using integer 71 | // arithmetic. I got this idea from the routine for the day of the week 72 | // in MACLisp (I believe that that routine was written by Guy Steele). 73 | // 74 | // There are three issues to take care of 75 | // 76 | // 1. the rules for leap years, 77 | // 2. the inconvenient placement of leap days at the end of February, 78 | // 3. the irregular pattern of month lengths. 79 | // 80 | // We deal with these as follows: 81 | // 82 | // 1. Leap years are given by simple rules which are straightforward to 83 | // accommodate. 84 | // 85 | // 2. We simplify the calculations by moving January and February to the 86 | // previous year. Here we internally number the months March–December, 87 | // January, February as 0–9, 10, 11. 88 | // 89 | // 3. The pattern of month lengths from March through January is regular 90 | // with a 5-month period—31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31. The 91 | // 5-month period is 153 days long. Since February is now at the end of 92 | // the year, we don't need to include its length in this part of the 93 | // calculation. 94 | bool greg = gregorian(y, m, d); 95 | y += (m + 9) / 12 - 1; // Move Jan and Feb to previous year, 96 | m = (m + 9) % 12; // making March month 0. 97 | return 98 | (1461 * y) / 4 // Julian years converted to days. Julian year is 365 + 99 | // 1/4 = 1461/4 days. 100 | // Gregorian leap year corrections. The 2 offset with respect to the 101 | // Julian calendar synchronizes the vernal equinox with that at the time 102 | // of the Council of Nicea (325 AD). 103 | + (greg ? (y / 100) / 4 - (y / 100) + 2 : 0) 104 | + (153 * m + 2) / 5 // The zero-based start of the m'th month 105 | + d - 1 // The zero-based day 106 | - 305; // The number of days between March 1 and December 31. 107 | // This makes 0001-01-01 day 1 108 | } 109 | 110 | /** 111 | * Convert a date to the day numbering sequentially starting with 112 | * 0001-01-01 as day 1. 113 | * 114 | * @param[in] y the year (must be positive). 115 | * @param[in] m the month, Jan = 1, etc. (must be positive). Default = 1. 116 | * @param[in] d the day of the month (must be positive). Default = 1. 117 | * @param[in] check whether to check the date. 118 | * @exception GeographicErr if the date is invalid and \e check is true. 119 | * @return the sequential day number. 120 | **********************************************************************/ 121 | static int day(int y, int m, int d, bool check) { 122 | int s = day(y, m, d); 123 | if (!check) 124 | return s; 125 | int y1, m1, d1; 126 | date(s, y1, m1, d1); 127 | if (!(s > 0 && y == y1 && m == m1 && d == d1)) 128 | throw GeographicErr("Invalid date " + 129 | str(y) + "-" + str(m) + "-" + str(d) 130 | + (s > 0 ? "; use " + 131 | str(y1) + "-" + str(m1) + "-" + str(d1) : 132 | " before 0001-01-01")); 133 | return s; 134 | } 135 | 136 | /** 137 | * Given a day (counting from 0001-01-01 as day 1), return the date. 138 | * 139 | * @param[in] s the sequential day number (must be positive) 140 | * @param[out] y the year. 141 | * @param[out] m the month, Jan = 1, etc. 142 | * @param[out] d the day of the month. 143 | **********************************************************************/ 144 | static void date(int s, int& y, int& m, int& d) throw() { 145 | int c = 0; 146 | bool greg = gregorian(s); 147 | s += 305; // s = 0 on March 1, 1BC 148 | if (greg) { 149 | s -= 2; // The 2 day Gregorian offset 150 | // Determine century with the Gregorian rules for leap years. The 151 | // Gregorian year is 365 + 1/4 - 1/100 + 1/400 = 146097/400 days. 152 | c = (4 * s + 3) / 146097; 153 | s -= (c * 146097) / 4; // s = 0 at beginning of century 154 | } 155 | y = (4 * s + 3) / 1461; // Determine the year using Julian rules. 156 | s -= (1461 * y) / 4; // s = 0 at start of year, i.e., March 1 157 | y += c * 100; // Assemble full year 158 | m = (5 * s + 2) / 153; // Determine the month 159 | s -= (153 * m + 2) / 5; // s = 0 at beginning of month 160 | d = s + 1; // Determine day of month 161 | y += (m + 2) / 12; // Move Jan and Feb back to original year 162 | m = (m + 2) % 12 + 1; // Renumber the months so January = 1 163 | } 164 | 165 | /** 166 | * Given a date as a string in the format yyyy, yyyy-mm, or yyyy-mm-dd, 167 | * return the numeric values for the year, month, and day. No checking is 168 | * done on these values. 169 | * 170 | * @param[in] s the date in string format. 171 | * @param[out] y the year. 172 | * @param[out] m the month, Jan = 1, etc. 173 | * @param[out] d the day of the month. 174 | * @exception GeographicErr is \e s is malformed. 175 | **********************************************************************/ 176 | static void date(const std::string& s, int& y, int& m, int& d) { 177 | int y1, m1 = 1, d1 = 1; 178 | const char* digits = "0123456789"; 179 | std::string::size_type p1 = s.find_first_not_of(digits); 180 | if (p1 == std::string::npos) 181 | y1 = num(s); 182 | else if (s[p1] != '-') 183 | throw GeographicErr("Delimiter not hyphen in date " + s); 184 | else if (p1 == 0) 185 | throw GeographicErr("Empty year field in date " + s); 186 | else { 187 | y1 = num(s.substr(0, p1)); 188 | if (++p1 == s.size()) 189 | throw GeographicErr("Empty month field in date " + s); 190 | std::string::size_type p2 = s.find_first_not_of(digits, p1); 191 | if (p2 == std::string::npos) 192 | m1 = num(s.substr(p1)); 193 | else if (s[p2] != '-') 194 | throw GeographicErr("Delimiter not hyphen in date " + s); 195 | else if (p2 == p1) 196 | throw GeographicErr("Empty month field in date " + s); 197 | else { 198 | m1 = num(s.substr(p1, p2 - p1)); 199 | if (++p2 == s.size()) 200 | throw GeographicErr("Empty day field in date " + s); 201 | d1 = num(s.substr(p2)); 202 | } 203 | } 204 | y = y1; m = m1; d = d1; 205 | } 206 | 207 | /** 208 | * Given the date, return the day of the week. 209 | * 210 | * @param[in] y the year (must be positive). 211 | * @param[in] m the month, Jan = 1, etc. (must be positive). 212 | * @param[in] d the day of the month (must be positive). 213 | * @return the day of the week with Sunday, Monday--Saturday = 0, 214 | * 1--6. 215 | **********************************************************************/ 216 | static int dow(int y, int m, int d) throw() { return dow(day(y, m, d)); } 217 | 218 | /** 219 | * Given the sequential day, return the day of the week. 220 | * 221 | * @param[in] s the sequential day (must be positive). 222 | * @return the day of the week with Sunday, Monday--Saturday = 0, 223 | * 1--6. 224 | **********************************************************************/ 225 | static int dow(int s) throw() { 226 | return (s + 5) % 7; // The 5 offset makes day 1 (0001-01-01) a Saturday. 227 | } 228 | 229 | /** 230 | * Convert a string representing a date to a fractional year. 231 | * 232 | * @tparam T the type of the argument. 233 | * @param[in] s the string to be converted. 234 | * @exception GeographicErr if \e s can't be interpreted as a date. 235 | * @return the fractional year. 236 | * 237 | * The string is first read as an ordinary number (e.g., 2010 or 2012.5); 238 | * if this is successful, the value is returned. Otherwise the string 239 | * should be of the form yyyy-mm or yyyy-mm-dd and this is converted to a 240 | * number with 2010-01-01 giving 2010.0 and 2012-07-03 giving 2012.5. 241 | **********************************************************************/ 242 | template static T fractionalyear(const std::string& s) { 243 | try { 244 | return num(s); 245 | } 246 | catch (const std::exception&) { 247 | } 248 | int y, m, d; 249 | date(s, y, m, d); 250 | int t = day(y, m, d, true); 251 | return T(y) + T(t - day(y)) / T(day(y + 1) - day(y)); 252 | } 253 | 254 | /** 255 | * Convert a object of type T to a string. 256 | * 257 | * @tparam T the type of the argument. 258 | * @param[in] x the value to be converted. 259 | * @param[in] p the precision used (default −1). 260 | * @exception std::bad_alloc if memory for the string can't be allocated. 261 | * @return the string representation. 262 | * 263 | * If \e p ≥ 0, then the number fixed format is used with p bits of 264 | * precision. With p < 0, there is no manipulation of the format. 265 | **********************************************************************/ 266 | template static std::string str(T x, int p = -1) { 267 | if (!std::numeric_limits::is_integer && !Math::isfinite(x)) 268 | return x < 0 ? std::string("-inf") : 269 | (x > 0 ? std::string("inf") : std::string("nan")); 270 | std::ostringstream s; 271 | if (p >= 0) s << std::fixed << std::setprecision(p); 272 | s << x; return s.str(); 273 | } 274 | 275 | /** 276 | * Convert a string to an object of type T. 277 | * 278 | * @tparam T the type of the return value. 279 | * @param[in] s the string to be converted. 280 | * @exception GeographicErr is \e s is not readable as a T. 281 | * @return object of type T 282 | **********************************************************************/ 283 | template static T num(const std::string& s) { 284 | T x; 285 | std::string errmsg; 286 | do { // Executed once (provides the ability to break) 287 | std::istringstream is(s); 288 | if (!(is >> x)) { 289 | errmsg = "Cannot decode " + s; 290 | break; 291 | } 292 | int pos = int(is.tellg()); // Returns -1 at end of string? 293 | if (!(pos < 0 || pos == int(s.size()))) { 294 | errmsg = "Extra text " + s.substr(pos) + " at end of " + s; 295 | break; 296 | } 297 | return x; 298 | } while (false); 299 | x = std::numeric_limits::is_integer ? 0 : nummatch(s); 300 | if (x == 0) 301 | throw GeographicErr(errmsg); 302 | return x; 303 | } 304 | 305 | /** 306 | * Match "nan" and "inf" (and variants thereof) in a string. 307 | * 308 | * @tparam T the type of the return value. 309 | * @param[in] s the string to be matched. 310 | * @return appropriate special value (±∞, nan) or 0 if none is 311 | * found. 312 | **********************************************************************/ 313 | template static T nummatch(const std::string& s) { 314 | if (s.length() < 3) 315 | return 0; 316 | std::string t; 317 | t.resize(s.length()); 318 | std::transform(s.begin(), s.end(), t.begin(), (int(*)(int))std::toupper); 319 | for (size_t i = s.length(); i--;) 320 | t[i] = char(std::toupper(s[i])); 321 | int sign = t[0] == '-' ? -1 : 1; 322 | std::string::size_type p0 = t[0] == '-' || t[0] == '+' ? 1 : 0; 323 | std::string::size_type p1 = t.find_last_not_of('0'); 324 | if (p1 == std::string::npos || p1 + 1 < p0 + 3) 325 | return 0; 326 | // Strip off sign and trailing 0s 327 | t = t.substr(p0, p1 + 1 - p0); // Length at least 3 328 | if (t == "NAN" || t == "1.#QNAN" || t == "1.#SNAN" || t == "1.#IND" || 329 | t == "1.#R") 330 | return Math::NaN(); 331 | else if (t == "INF" || t == "1.#INF") 332 | return sign * Math::infinity(); 333 | return 0; 334 | } 335 | 336 | /** 337 | * Read a simple fraction, e.g., 3/4, from a string to an object of type T. 338 | * 339 | * @tparam T the type of the return value. 340 | * @param[in] s the string to be converted. 341 | * @exception GeographicErr is \e s is not readable as a fraction of type T. 342 | * @return object of type T 343 | **********************************************************************/ 344 | template static T fract(const std::string& s) { 345 | std::string::size_type delim = s.find('/'); 346 | return 347 | !(delim != std::string::npos && delim >= 1 && delim + 2 <= s.size()) ? 348 | num(s) : 349 | // delim in [1, size() - 2] 350 | num(s.substr(0, delim)) / num(s.substr(delim + 1)); 351 | } 352 | 353 | /** 354 | * Lookup up a character in a string. 355 | * 356 | * @param[in] s the string to be searched. 357 | * @param[in] c the character to look for. 358 | * @return the index of the first occurrence character in the string or 359 | * −1 is the character is not present. 360 | * 361 | * \e c is converted to upper case before search \e s. Therefore, it is 362 | * intended that \e s should not contain any lower case letters. 363 | **********************************************************************/ 364 | static int lookup(const std::string& s, char c) throw() { 365 | std::string::size_type r = s.find(char(toupper(c))); 366 | return r == std::string::npos ? -1 : int(r); 367 | } 368 | 369 | /** 370 | * Read data of type ExtT from a binary stream to an array of type IntT. 371 | * The data in the file is in (bigendp ? big : little)-endian format. 372 | * 373 | * @tparam ExtT the type of the objects in the binary stream (external). 374 | * @tparam IntT the type of the objects in the array (internal). 375 | * @tparam bigendp true if the external storage format is big-endian. 376 | * @param[in] str the input stream containing the data of type ExtT 377 | * (external). 378 | * @param[out] array the output array of type IntT (internal). 379 | * @param[in] num the size of the array. 380 | * @exception GeographicErr if the data cannot be read. 381 | **********************************************************************/ 382 | template 383 | static inline void readarray(std::istream& str, 384 | IntT array[], size_t num) { 385 | if (sizeof(IntT) == sizeof(ExtT) && 386 | std::numeric_limits::is_integer == 387 | std::numeric_limits::is_integer) { 388 | // Data is compatible (aside from the issue of endian-ness). 389 | str.read(reinterpret_cast(array), num * sizeof(ExtT)); 390 | if (!str.good()) 391 | throw GeographicErr("Failure reading data"); 392 | if (bigendp != Math::bigendian) { // endian mismatch -> swap bytes 393 | for (size_t i = num; i--;) 394 | array[i] = Math::swab(array[i]); 395 | } 396 | } else { 397 | const int bufsize = 1024; // read this many values at a time 398 | ExtT buffer[bufsize]; // temporary buffer 399 | int k = int(num); // data values left to read 400 | int i = 0; // index into output array 401 | while (k) { 402 | int n = (std::min)(k, bufsize); 403 | str.read(reinterpret_cast(buffer), n * sizeof(ExtT)); 404 | if (!str.good()) 405 | throw GeographicErr("Failure reading data"); 406 | for (int j = 0; j < n; ++j) 407 | // fix endian-ness and cast to IntT 408 | array[i++] = IntT(bigendp == Math::bigendian ? buffer[j] : 409 | Math::swab(buffer[j])); 410 | k -= n; 411 | } 412 | } 413 | return; 414 | } 415 | 416 | /** 417 | * Read data of type ExtT from a binary stream to a vector array of type 418 | * IntT. The data in the file is in (bigendp ? big : little)-endian 419 | * format. 420 | * 421 | * @tparam ExtT the type of the objects in the binary stream (external). 422 | * @tparam IntT the type of the objects in the array (internal). 423 | * @tparam bigendp true if the external storage format is big-endian. 424 | * @param[in] str the input stream containing the data of type ExtT 425 | * (external). 426 | * @param[out] array the output vector of type IntT (internal). 427 | * @exception GeographicErr if the data cannot be read. 428 | **********************************************************************/ 429 | template 430 | static inline void readarray(std::istream& str, 431 | std::vector& array) { 432 | readarray(str, &array[0], array.size()); 433 | } 434 | 435 | /** 436 | * Write data in an array of type IntT as type ExtT to a binary stream. 437 | * The data in the file is in (bigendp ? big : little)-endian format. 438 | * 439 | * @tparam ExtT the type of the objects in the binary stream (external). 440 | * @tparam IntT the type of the objects in the array (internal). 441 | * @tparam bigendp true if the external storage format is big-endian. 442 | * @param[out] str the output stream for the data of type ExtT (external). 443 | * @param[in] array the input array of type IntT (internal). 444 | * @param[in] num the size of the array. 445 | * @exception GeographicErr if the data cannot be written. 446 | **********************************************************************/ 447 | template 448 | static inline void writearray(std::ostream& str, 449 | const IntT array[], size_t num) { 450 | if (sizeof(IntT) == sizeof(ExtT) && 451 | std::numeric_limits::is_integer == 452 | std::numeric_limits::is_integer && 453 | bigendp == Math::bigendian) { 454 | // Data is compatible (including endian-ness). 455 | str.write(reinterpret_cast(array), num * sizeof(ExtT)); 456 | if (!str.good()) 457 | throw GeographicErr("Failure writing data"); 458 | } else { 459 | const int bufsize = 1024; // write this many values at a time 460 | ExtT buffer[bufsize]; // temporary buffer 461 | int k = int(num); // data values left to write 462 | int i = 0; // index into output array 463 | while (k) { 464 | int n = (std::min)(k, bufsize); 465 | for (int j = 0; j < n; ++j) 466 | // cast to ExtT and fix endian-ness 467 | buffer[j] = bigendp == Math::bigendian ? ExtT(array[i++]) : 468 | Math::swab(ExtT(array[i++])); 469 | str.write(reinterpret_cast(buffer), n * sizeof(ExtT)); 470 | if (!str.good()) 471 | throw GeographicErr("Failure writing data"); 472 | k -= n; 473 | } 474 | } 475 | return; 476 | } 477 | 478 | /** 479 | * Write data in an array of type IntT as type ExtT to a binary stream. 480 | * The data in the file is in (bigendp ? big : little)-endian format. 481 | * 482 | * @tparam ExtT the type of the objects in the binary stream (external). 483 | * @tparam IntT the type of the objects in the array (internal). 484 | * @tparam bigendp true if the external storage format is big-endian. 485 | * @param[out] str the output stream for the data of type ExtT (external). 486 | * @param[in] array the input vector of type IntT (internal). 487 | * @exception GeographicErr if the data cannot be written. 488 | **********************************************************************/ 489 | template 490 | static inline void writearray(std::ostream& str, 491 | std::vector& array) { 492 | writearray(str, &array[0], array.size()); 493 | } 494 | 495 | /** 496 | * Parse a KEY VALUE line. 497 | * 498 | * @param[in] line the input line. 499 | * @param[out] key the key. 500 | * @param[out] val the value. 501 | * @exception std::bad_alloc if memory for the internal strings can't be 502 | * allocated. 503 | * @return whether a key was found. 504 | * 505 | * A # character and everything after it are discarded. If the results is 506 | * just white space, the routine returns false (and \e key and \e val are 507 | * not set). Otherwise the first token is taken to be the key and the rest 508 | * of the line (trimmed of leading and trailing white space) is the value. 509 | **********************************************************************/ 510 | static bool ParseLine(const std::string& line, 511 | std::string& key, std::string& val); 512 | 513 | }; 514 | 515 | } // namespace GeographicLib 516 | 517 | #if defined(_MSC_VER) 518 | # pragma warning (pop) 519 | #endif 520 | 521 | #endif // GEOGRAPHICLIB_UTILITY_HPP 522 | -------------------------------------------------------------------------------- /Geoid.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file Geoid.cpp 3 | * \brief Implementation for GeographicLib::Geoid class 4 | * 5 | * Copyright (c) Charles Karney (2009-2012) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * http://geographiclib.sourceforge.net/ 8 | **********************************************************************/ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #if !defined(GEOGRAPHICLIB_DATA) 15 | # if defined(_MSC_VER) 16 | # define GEOGRAPHICLIB_DATA \ 17 | "C:/Documents and Settings/All Users/Application Data/GeographicLib" 18 | # else 19 | # define GEOGRAPHICLIB_DATA "/usr/local/share/GeographicLib" 20 | # endif 21 | #endif 22 | 23 | #if !defined(GEOID_DEFAULT_NAME) 24 | # define GEOID_DEFAULT_NAME "egm96-5" 25 | #endif 26 | 27 | #if defined(_MSC_VER) 28 | // Squelch warnings about unsafe use of getenv 29 | # pragma warning (disable: 4996) 30 | #endif 31 | 32 | namespace GeographicLib { 33 | 34 | using namespace std; 35 | 36 | // This is the transfer matrix for a 3rd order fit with a 12-point stencil 37 | // with weights 38 | // 39 | // \x -1 0 1 2 40 | // y 41 | // -1 . 1 1 . 42 | // 0 1 2 2 1 43 | // 1 1 2 2 1 44 | // 2 . 1 1 . 45 | // 46 | // A algorithm for n-dimensional polynomial fits is described in 47 | // F. H. Lesh, 48 | // Multi-dimensional least-squares polynomial curve fitting, 49 | // CACM 2, 29-30 (1959). 50 | // 51 | // Here's the Maxima code to generate this matrix: 52 | // 53 | // /* The stencil and the weights */ 54 | // xarr:[ 55 | // 0, 1, 56 | // -1, 0, 1, 2, 57 | // -1, 0, 1, 2, 58 | // 0, 1]$ 59 | // yarr:[ 60 | // -1,-1, 61 | // 0, 0, 0, 0, 62 | // 1, 1, 1, 1, 63 | // 2, 2]$ 64 | // warr:[ 65 | // 1, 1, 66 | // 1, 2, 2, 1, 67 | // 1, 2, 2, 1, 68 | // 1, 1]$ 69 | // 70 | // /* [x exponent, y exponent] for cubic fit */ 71 | // pows:[ 72 | // [0,0], 73 | // [1,0],[0,1], 74 | // [2,0],[1,1],[0,2], 75 | // [3,0],[2,1],[1,2],[0,3]]$ 76 | // 77 | // basisvec(x,y,pows):=map(lambda([ex],(if ex[1]=0 then 1 else x^ex[1])* 78 | // (if ex[2]=0 then 1 else y^ex[2])),pows)$ 79 | // addterm(x,y,f,w,pows):=block([a,b,bb:basisvec(x,y,pows)], 80 | // a:w*(transpose(bb).bb), 81 | // b:(w*f) * bb, 82 | // [a,b])$ 83 | // 84 | // c3row(k):=block([a,b,c,pows:pows,n], 85 | // n:length(pows), 86 | // a:zeromatrix(n,n), 87 | // b:copylist(part(a,1)), 88 | // c:[a,b], 89 | // for i:1 thru length(xarr) do 90 | // c:c+addterm(xarr[i],yarr[i],if i=k then 1 else 0,warr[i],pows), 91 | // a:c[1],b:c[2], 92 | // part(transpose( a^^-1 . transpose(b)),1))$ 93 | // c3:[]$ 94 | // for k:1 thru length(warr) do c3:endcons(c3row(k),c3)$ 95 | // c3:apply(matrix,c3)$ 96 | // c0:part(ratsimp( 97 | // genmatrix(yc,1,length(warr)).abs(c3).genmatrix(yd,length(pows),1)),2)$ 98 | // c3:c0*c3$ 99 | 100 | const Math::real Geoid::c0_ = 240; // Common denominator 101 | const Math::real Geoid::c3_[stencilsize_ * nterms_] = { 102 | 9, -18, -88, 0, 96, 90, 0, 0, -60, -20, 103 | -9, 18, 8, 0, -96, 30, 0, 0, 60, -20, 104 | 9, -88, -18, 90, 96, 0, -20, -60, 0, 0, 105 | 186, -42, -42, -150, -96, -150, 60, 60, 60, 60, 106 | 54, 162, -78, 30, -24, -90, -60, 60, -60, 60, 107 | -9, -32, 18, 30, 24, 0, 20, -60, 0, 0, 108 | -9, 8, 18, 30, -96, 0, -20, 60, 0, 0, 109 | 54, -78, 162, -90, -24, 30, 60, -60, 60, -60, 110 | -54, 78, 78, 90, 144, 90, -60, -60, -60, -60, 111 | 9, -8, -18, -30, -24, 0, 20, 60, 0, 0, 112 | -9, 18, -32, 0, 24, 30, 0, 0, -60, 20, 113 | 9, -18, -8, 0, -24, -30, 0, 0, 60, 20, 114 | }; 115 | 116 | // Like c3, but with the coeffs of x, x^2, and x^3 constrained to be zero. 117 | // Use this at the N pole so that the height in independent of the longitude 118 | // there. 119 | // 120 | // Here's the Maxima code to generate this matrix (continued from above). 121 | // 122 | // /* figure which terms to exclude so that fit is indep of x at y=0 */ 123 | // mask:part(zeromatrix(1,length(pows)),1)+1$ 124 | // for i:1 thru length(pows) do 125 | // if pows[i][1]>0 and pows[i][2]=0 then mask[i]:0$ 126 | // 127 | // /* Same as c3row but with masked pows. */ 128 | // c3nrow(k):=block([a,b,c,powsa:[],n,d,e], 129 | // for i:1 thru length(mask) do if mask[i]>0 then 130 | // powsa:endcons(pows[i],powsa), 131 | // n:length(powsa), 132 | // a:zeromatrix(n,n), 133 | // b:copylist(part(a,1)), 134 | // c:[a,b], 135 | // for i:1 thru length(xarr) do 136 | // c:c+addterm(xarr[i],yarr[i],if i=k then 1 else 0,warr[i],powsa), 137 | // a:c[1],b:c[2], 138 | // d:part(transpose( a^^-1 . transpose(b)),1), 139 | // e:[], 140 | // for i:1 thru length(mask) do 141 | // if mask[i]>0 then (e:endcons(first(d),e),d:rest(d)) else e:endcons(0,e), 142 | // e)$ 143 | // c3n:[]$ 144 | // for k:1 thru length(warr) do c3n:endcons(c3nrow(k),c3n)$ 145 | // c3n:apply(matrix,c3n)$ 146 | // c0n:part(ratsimp( 147 | // genmatrix(yc,1,length(warr)).abs(c3n).genmatrix(yd,length(pows),1)),2)$ 148 | // c3n:c0n*c3n$ 149 | 150 | const Math::real Geoid::c0n_ = 372; // Common denominator 151 | const Math::real Geoid::c3n_[stencilsize_ * nterms_] = { 152 | 0, 0, -131, 0, 138, 144, 0, 0, -102, -31, 153 | 0, 0, 7, 0, -138, 42, 0, 0, 102, -31, 154 | 62, 0, -31, 0, 0, -62, 0, 0, 0, 31, 155 | 124, 0, -62, 0, 0, -124, 0, 0, 0, 62, 156 | 124, 0, -62, 0, 0, -124, 0, 0, 0, 62, 157 | 62, 0, -31, 0, 0, -62, 0, 0, 0, 31, 158 | 0, 0, 45, 0, -183, -9, 0, 93, 18, 0, 159 | 0, 0, 216, 0, 33, 87, 0, -93, 12, -93, 160 | 0, 0, 156, 0, 153, 99, 0, -93, -12, -93, 161 | 0, 0, -45, 0, -3, 9, 0, 93, -18, 0, 162 | 0, 0, -55, 0, 48, 42, 0, 0, -84, 31, 163 | 0, 0, -7, 0, -48, -42, 0, 0, 84, 31, 164 | }; 165 | 166 | // Like c3n, but y -> 1-y so that h is independent of x at y = 1. Use this 167 | // at the S pole so that the height in independent of the longitude there. 168 | // 169 | // Here's the Maxima code to generate this matrix (continued from above). 170 | // 171 | // /* Transform c3n to c3s by transforming y -> 1-y */ 172 | // vv:[ 173 | // v[11],v[12], 174 | // v[7],v[8],v[9],v[10], 175 | // v[3],v[4],v[5],v[6], 176 | // v[1],v[2]]$ 177 | // poly:expand(vv.(c3n/c0n).transpose(basisvec(x,1-y,pows)))$ 178 | // c3sf[i,j]:=coeff(coeff(coeff(poly,v[i]),x,pows[j][1]),y,pows[j][2])$ 179 | // c3s:genmatrix(c3sf,length(vv),length(pows))$ 180 | // c0s:part(ratsimp( 181 | // genmatrix(yc,1,length(warr)).abs(c3s).genmatrix(yd,length(pows),1)),2)$ 182 | // c3s:c0s*c3s$ 183 | 184 | const Math::real Geoid::c0s_ = 372; // Common denominator 185 | const Math::real Geoid::c3s_[stencilsize_ * nterms_] = { 186 | 18, -36, -122, 0, 120, 135, 0, 0, -84, -31, 187 | -18, 36, -2, 0, -120, 51, 0, 0, 84, -31, 188 | 36, -165, -27, 93, 147, -9, 0, -93, 18, 0, 189 | 210, 45, -111, -93, -57, -192, 0, 93, 12, 93, 190 | 162, 141, -75, -93, -129, -180, 0, 93, -12, 93, 191 | -36, -21, 27, 93, 39, 9, 0, -93, -18, 0, 192 | 0, 0, 62, 0, 0, 31, 0, 0, 0, -31, 193 | 0, 0, 124, 0, 0, 62, 0, 0, 0, -62, 194 | 0, 0, 124, 0, 0, 62, 0, 0, 0, -62, 195 | 0, 0, 62, 0, 0, 31, 0, 0, 0, -31, 196 | -18, 36, -64, 0, 66, 51, 0, 0, -102, 31, 197 | 18, -36, 2, 0, -66, -51, 0, 0, 102, 31, 198 | }; 199 | 200 | Geoid::Geoid(const std::string& name, const std::string& path, bool cubic, 201 | bool threadsafe) 202 | : _name(name) 203 | , _dir(path) 204 | , _cubic(cubic) 205 | , _a( Constants::WGS84_a() ) 206 | , _e2( (2 - Constants::WGS84_f()) * Constants::WGS84_f() ) 207 | , _degree( Math::degree() ) 208 | , _eps( sqrt(numeric_limits::epsilon()) ) 209 | , _threadsafe(false) // Set after cache is read 210 | { 211 | STATIC_ASSERT(sizeof(pixel_t) == pixel_size_, "pixel_t has the wrong size"); 212 | if (_dir.empty()) 213 | _dir = DefaultGeoidPath(); 214 | _filename = _dir + "/" + _name + (pixel_size_ != 4 ? ".pgm" : ".pgm4"); 215 | _file.open(_filename.c_str(), ios::binary); 216 | if (!(_file.good())) 217 | throw GeographicErr("File not readable " + _filename); 218 | string s; 219 | if (!(getline(_file, s) && s == "P5")) 220 | throw GeographicErr("File not in PGM format " + _filename); 221 | _offset = numeric_limits::max(); 222 | _scale = 0; 223 | _maxerror = _rmserror = -1; 224 | _description = "NONE"; 225 | _datetime = "UNKNOWN"; 226 | while (getline(_file, s)) { 227 | if (s.empty()) 228 | continue; 229 | if (s[0] == '#') { 230 | istringstream is(s); 231 | string commentid, key; 232 | if (!(is >> commentid >> key) || commentid != "#") 233 | continue; 234 | if (key == "Description" || key =="DateTime") { 235 | string::size_type p = 236 | s.find_first_not_of(" \t", unsigned(is.tellg())); 237 | if (p != string::npos) 238 | (key == "Description" ? _description : _datetime) = s.substr(p); 239 | } else if (key == "Offset") { 240 | if (!(is >> _offset)) 241 | throw GeographicErr("Error reading offset " + _filename); 242 | } else if (key == "Scale") { 243 | if (!(is >> _scale)) 244 | throw GeographicErr("Error reading scale " + _filename); 245 | } else if (key == (_cubic ? "MaxCubicError" : "MaxBilinearError")) { 246 | // It's not an error if the error can't be read 247 | is >> _maxerror; 248 | } else if (key == (_cubic ? "RMSCubicError" : "RMSBilinearError")) { 249 | // It's not an error if the error can't be read 250 | is >> _rmserror; 251 | } 252 | } else { 253 | istringstream is(s); 254 | if (!(is >> _width >> _height)) 255 | throw GeographicErr("Error reading raster size " + _filename); 256 | break; 257 | } 258 | } 259 | { 260 | unsigned maxval; 261 | if (!(_file >> maxval)) 262 | throw GeographicErr("Error reading maxval " + _filename); 263 | if (maxval != pixel_max_) 264 | throw GeographicErr("Incorrect value of maxval " + _filename); 265 | // Add 1 for whitespace after maxval 266 | _datastart = (unsigned long long)(_file.tellg()) + 1ULL; 267 | _swidth = (unsigned long long)(_width); 268 | } 269 | if (_offset == numeric_limits::max()) 270 | throw GeographicErr("Offset not set " + _filename); 271 | if (_scale == 0) 272 | throw GeographicErr("Scale not set " + _filename); 273 | if (_scale < 0) 274 | throw GeographicErr("Scale must be positive " + _filename); 275 | if (_height < 2 || _width < 2) 276 | // Coarsest grid spacing is 180deg. 277 | throw GeographicErr("Raster size too small " + _filename); 278 | if (_width & 1) 279 | // This is so that longitude grids can be extended thru the poles. 280 | throw GeographicErr("Raster width is odd " + _filename); 281 | if (!(_height & 1)) 282 | // This is so that latitude grid includes the equator. 283 | throw GeographicErr("Raster height is even " + _filename); 284 | _file.seekg(0, ios::end); 285 | if (!_file.good() || 286 | _datastart + pixel_size_ * _swidth * (unsigned long long)(_height) != 287 | (unsigned long long)(_file.tellg())) 288 | // Possibly this test should be "<" because the file contains, e.g., a 289 | // second image. However, for now we are more strict. 290 | throw GeographicErr("File has the wrong length " + _filename); 291 | _rlonres = _width / real(360); 292 | _rlatres = (_height - 1) / real(180); 293 | _cache = false; 294 | _ix = _width; 295 | _iy = _height; 296 | // Ensure that file errors throw exceptions 297 | _file.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit); 298 | if (threadsafe) { 299 | CacheAll(); 300 | _file.close(); 301 | _threadsafe = true; 302 | } 303 | } 304 | 305 | Math::real Geoid::height(real lat, real lon, bool gradp, 306 | real& gradn, real& grade) const { 307 | if (Math::isnan(lat) || Math::isnan(lon)) { 308 | if (gradp) gradn = grade = Math::NaN(); 309 | return Math::NaN(); 310 | } 311 | lon = Math::AngNormalize(lon); 312 | real 313 | fx = lon * _rlonres, 314 | fy = -lat * _rlatres; 315 | int 316 | ix = int(floor(fx)), 317 | iy = min((_height - 1)/2 - 1, int(floor(fy))); 318 | fx -= ix; 319 | fy -= iy; 320 | iy += (_height - 1)/2; 321 | ix += ix < 0 ? _width : (ix >= _width ? -_width : 0); 322 | real v00 = 0, v01 = 0, v10 = 0, v11 = 0; 323 | real t[nterms_]; 324 | 325 | if (_threadsafe || !(ix == _ix && iy == _iy)) { 326 | if (!_cubic) { 327 | v00 = rawval(ix , iy ); 328 | v01 = rawval(ix + 1, iy ); 329 | v10 = rawval(ix , iy + 1); 330 | v11 = rawval(ix + 1, iy + 1); 331 | } else { 332 | real v[stencilsize_]; 333 | int k = 0; 334 | v[k++] = rawval(ix , iy - 1); 335 | v[k++] = rawval(ix + 1, iy - 1); 336 | v[k++] = rawval(ix - 1, iy ); 337 | v[k++] = rawval(ix , iy ); 338 | v[k++] = rawval(ix + 1, iy ); 339 | v[k++] = rawval(ix + 2, iy ); 340 | v[k++] = rawval(ix - 1, iy + 1); 341 | v[k++] = rawval(ix , iy + 1); 342 | v[k++] = rawval(ix + 1, iy + 1); 343 | v[k++] = rawval(ix + 2, iy + 1); 344 | v[k++] = rawval(ix , iy + 2); 345 | v[k++] = rawval(ix + 1, iy + 2); 346 | 347 | const real* c3x = iy == 0 ? c3n_ : (iy == _height - 2 ? c3s_ : c3_); 348 | real c0x = iy == 0 ? c0n_ : (iy == _height - 2 ? c0s_ : c0_); 349 | for (unsigned i = 0; i < nterms_; ++i) { 350 | t[i] = 0; 351 | for (unsigned j = 0; j < stencilsize_; ++j) 352 | t[i] += v[j] * c3x[nterms_ * j + i]; 353 | t[i] /= c0x; 354 | } 355 | } 356 | } else { // same cell; used cached coefficients 357 | if (!_cubic) { 358 | v00 = _v00; 359 | v01 = _v01; 360 | v10 = _v10; 361 | v11 = _v11; 362 | } else 363 | copy(_t, _t + nterms_, t); 364 | } 365 | if (!_cubic) { 366 | real 367 | a = (1 - fx) * v00 + fx * v01, 368 | b = (1 - fx) * v10 + fx * v11, 369 | c = (1 - fy) * a + fy * b, 370 | h = _offset + _scale * c; 371 | if (gradp) { 372 | real 373 | phi = lat * _degree, 374 | cosphi = cos(phi), 375 | sinphi = sin(phi), 376 | n = 1/sqrt(1 - _e2 * sinphi * sinphi); 377 | gradn = ((1 - fx) * (v00 - v10) + fx * (v01 - v11)) * 378 | _rlatres / (_degree * _a * (1 - _e2) * n * n * n); 379 | grade = (cosphi > _eps ? 380 | ((1 - fy) * (v01 - v00) + fy * (v11 - v10)) / cosphi : 381 | (sinphi > 0 ? v11 - v10 : v01 - v00) * 382 | _rlatres / _degree ) * 383 | _rlonres / (_degree * _a * n); 384 | gradn *= _scale; 385 | grade *= _scale; 386 | } 387 | if (!_threadsafe) { 388 | _ix = ix; 389 | _iy = iy; 390 | _v00 = v00; 391 | _v01 = v01; 392 | _v10 = v10; 393 | _v11 = v11; 394 | } 395 | return h; 396 | } else { 397 | real h = t[0] + fx * (t[1] + fx * (t[3] + fx * t[6])) + 398 | fy * (t[2] + fx * (t[4] + fx * t[7]) + 399 | fy * (t[5] + fx * t[8] + fy * t[9])); 400 | h = _offset + _scale * h; 401 | if (gradp) { 402 | // Avoid 0/0 at the poles by backing off 1/100 of a cell size 403 | lat = min(lat, 90 - 1/(100 * _rlatres)); 404 | lat = max(lat, -90 + 1/(100 * _rlatres)); 405 | fy = (90 - lat) * _rlatres; 406 | fy -= int(fy); 407 | real 408 | phi = lat * _degree, 409 | cosphi = cos(phi), 410 | sinphi = sin(phi), 411 | n = 1/sqrt(1 - _e2 * sinphi * sinphi); 412 | gradn = t[2] + fx * (t[4] + fx * t[7]) + 413 | fy * (2 * t[5] + fx * 2 * t[8] + 3 * fy * t[9]); 414 | grade = t[1] + fx * (2 * t[3] + fx * 3 * t[6]) + 415 | fy * (t[4] + fx * 2 * t[7] + fy * t[8]); 416 | gradn *= - _rlatres / (_degree * _a * (1 - _e2) * n * n * n) * _scale; 417 | grade *= _rlonres / (_degree * _a * n * cosphi) * _scale; 418 | } 419 | if (!_threadsafe) { 420 | _ix = ix; 421 | _iy = iy; 422 | copy(t, t + nterms_, _t); 423 | } 424 | return h; 425 | } 426 | } 427 | 428 | void Geoid::CacheClear() const throw() { 429 | if (!_threadsafe) { 430 | _cache = false; 431 | try { 432 | _data.clear(); 433 | // Use swap to release memory back to system 434 | vector< vector >().swap(_data); 435 | } 436 | catch (const exception&) { 437 | } 438 | } 439 | } 440 | 441 | void Geoid::CacheArea(real south, real west, real north, real east) const { 442 | if (_threadsafe) 443 | throw GeographicErr("Attempt to change cache of threadsafe Geoid"); 444 | if (south > north) { 445 | CacheClear(); 446 | return; 447 | } 448 | west = Math::AngNormalize(west); // west in [-180, 180) 449 | east = Math::AngNormalize(east); 450 | if (east <= west) 451 | east += 360; // east - west in (0, 360] 452 | int 453 | iw = int(floor(west * _rlonres)), 454 | ie = int(floor(east * _rlonres)), 455 | in = int(floor(-north * _rlatres)) + (_height - 1)/2, 456 | is = int(floor(-south * _rlatres)) + (_height - 1)/2; 457 | in = max(0, min(_height - 2, in)); 458 | is = max(0, min(_height - 2, is)); 459 | is += 1; 460 | ie += 1; 461 | if (_cubic) { 462 | in -= 1; 463 | is += 1; 464 | iw -= 1; 465 | ie += 1; 466 | } 467 | if (ie - iw >= _width - 1) { 468 | // Include entire longitude range 469 | iw = 0; 470 | ie = _width - 1; 471 | } else { 472 | ie += iw < 0 ? _width : (iw >= _width ? -_width : 0); 473 | iw += iw < 0 ? _width : (iw >= _width ? -_width : 0); 474 | } 475 | int oysize = int(_data.size()); 476 | _xsize = ie - iw + 1; 477 | _ysize = is - in + 1; 478 | _xoffset = iw; 479 | _yoffset = in; 480 | 481 | try { 482 | _data.resize(_ysize, vector(_xsize)); 483 | for (int iy = min(oysize, _ysize); iy--;) 484 | _data[iy].resize(_xsize); 485 | } 486 | catch (const bad_alloc&) { 487 | CacheClear(); 488 | throw GeographicErr("Insufficient memory for caching " + _filename); 489 | } 490 | 491 | try { 492 | for (int iy = in; iy <= is; ++iy) { 493 | int iy1 = iy, iw1 = iw; 494 | if (iy < 0 || iy >= _height) { 495 | // Allow points "beyond" the poles to support interpolation 496 | iy1 = iy1 < 0 ? -iy1 : 2 * (_height - 1) - iy1; 497 | iw1 += _width/2; 498 | if (iw1 >= _width) 499 | iw1 -= _width; 500 | } 501 | int xs1 = min(_width - iw1, _xsize); 502 | filepos(iw1, iy1); 503 | Utility::readarray 504 | (_file, &(_data[iy - in][0]), xs1); 505 | if (xs1 < _xsize) { 506 | // Wrap around longitude = 0 507 | filepos(0, iy1); 508 | Utility::readarray 509 | (_file, &(_data[iy - in][xs1]), _xsize - xs1); 510 | } 511 | } 512 | _cache = true; 513 | } 514 | catch (const exception& e) { 515 | CacheClear(); 516 | throw GeographicErr(string("Error filling cache ") + e.what()); 517 | } 518 | } 519 | 520 | std::string Geoid::DefaultGeoidPath() { 521 | string path; 522 | char* geoidpath = getenv("GEOID_PATH"); 523 | if (geoidpath) 524 | path = string(geoidpath); 525 | if (path.length()) 526 | return path; 527 | char* datapath = getenv("GEOGRAPHICLIB_DATA"); 528 | if (datapath) 529 | path = string(datapath); 530 | return (path.length() ? path : string(GEOGRAPHICLIB_DATA)) + "/geoids"; 531 | } 532 | 533 | std::string Geoid::DefaultGeoidName() { 534 | string name; 535 | char* geoidname = getenv("GEOID_NAME"); 536 | if (geoidname) 537 | name = string(geoidname); 538 | return name.length() ? name : string(GEOID_DEFAULT_NAME); 539 | } 540 | 541 | } // namespace GeographicLib 542 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: srtm4 srtm4_which_tile 2 | 3 | srtm4: Geoid.o geoid_height_wrapper.o srtm4.o 4 | gcc $^ -ltiff -lm -lstdc++ -o $@ 5 | 6 | srtm4_which_tile: Geoid.o geoid_height_wrapper.o srtm4_which_tile.o 7 | gcc $^ -ltiff -lm -lstdc++ -o $@ 8 | 9 | Geoid.o: Geoid.cpp 10 | g++ -c -g -O3 $^ -I. -o $@ 11 | 12 | geoid_height_wrapper.o: geoid_height_wrapper.cpp 13 | g++ -c -g -O3 $^ -I. -o $@ 14 | 15 | srtm4.o: srtm4.c 16 | gcc -c -g -O3 -std=c99 -DNDEBUG -DMAIN_SRTM4 $^ -o $@ 17 | 18 | srtm4_which_tile.o: srtm4.c 19 | gcc -c -g -O3 -std=c99 -DNDEBUG -DMAIN_SRTM4_WHICH_TILE $^ -o $@ 20 | 21 | clean: 22 | -rm *.o 23 | -rm srtm4 24 | -rm srtm4_which_tile 25 | -rm *.pyc 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RPC Cropper 2 | 3 | A small standalone tool to crop satellite images and their RPC. 4 | 5 | ## Usage 6 | Suppose you have two images `im1.tif` and `im2.tif`, and the associated RPC 7 | coefficients in the files `rpc1.xml` and `rpc2.xml`. The call 8 | 9 | $ ./rpc_crop.py out im1.tif rpc1.xml im2.tif rpc2.xml 12300 4200 800 600 10 | 11 | will write in the folder `out` the cropped files. The cropped region is defined 12 | in the first image by the last four arguments. In the example above, `12300 13 | 4200` are the column and the row of the top-left corner of the region, and `800 14 | 600` are the width and the height of the region. The second image is cropped in 15 | such a way that the region visible in the first crop is still visible in the 16 | second crop. This is done using SRTM data. 17 | 18 | To interactively select the region to crop, use the `rpc_crop_gui.py` 19 | script. A preview of the reference image is needed: 20 | 21 | $ ./rpc_crop_gui.py outdir preview.jpg im1.tif rpc1.xml im2.tif rpc2.xml 22 | 23 | 24 | ## Installation 25 | Nothing to do, the only needed binary will be compiled on the fly during the first 26 | call to one of the python scripts. 27 | 28 | ## Known dependencies 29 | * python 2.7 or 2.6 30 | * numpy 31 | * pyopengl 32 | * gdal 33 | * g++ 34 | * libtiff 35 | 36 | On Ubuntu 13.10, all the needed dependencies can be installed through the 37 | package manager: 38 | 39 | $ sudo apt-get install python-opengl python-numpy gdal-bin libtiff4-dev g++ 40 | 41 | Note that the use of SRTM data requires an internet connection, at least the 42 | first time you run this tool on a dataset. 43 | 44 | 45 | ## Authors 46 | Gabriele Facciolo, Enric Meinhardt-Llopis, Carlo de Franchis 47 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, Carlo de Franchis 2 | # Copyright (C) 2013, Gabriele Facciolo 3 | 4 | # This Python file uses the following encoding: utf-8 5 | from __future__ import print_function 6 | import numpy as np 7 | import os 8 | import sys 9 | import subprocess 10 | import requests 11 | import tempfile 12 | import urlparse 13 | import urllib2 14 | import re 15 | 16 | 17 | # add the current folder to system path 18 | current_dir = os.path.dirname(os.path.abspath(__file__)) 19 | os.environ['PATH'] = current_dir + os.pathsep + os.environ['PATH'] 20 | 21 | # global variable 22 | # list of intermediary files generated by the script 23 | garbage = list() 24 | 25 | def tmpfile(ext=''): 26 | """ 27 | Creates a temporary file in the /tmp directory. 28 | 29 | Args: 30 | ext: desired file extension 31 | 32 | Returns: 33 | absolute path to the created file 34 | 35 | The path of the created file is added to the garbage list to allow cleaning 36 | at the end of the pipeline. 37 | """ 38 | fd, out = tempfile.mkstemp(suffix = ext, prefix = 's2p_', dir = '.') 39 | garbage.append(out) 40 | os.close(fd) # http://www.logilab.org/blogentry/17873 41 | return out 42 | 43 | 44 | def run(cmd): 45 | """ 46 | Runs a shell command, and print it before running. 47 | 48 | Arguments: 49 | cmd: string to be passed to a shell 50 | 51 | Both stdout and stderr of the shell in which the command is run are those 52 | of the parent process. 53 | """ 54 | print(cmd) 55 | subprocess.call(cmd, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, 56 | env=os.environ) 57 | return 58 | 59 | 60 | def shellquote(s): 61 | return "'" + s.replace("'", "'\\''") + "'" 62 | 63 | 64 | 65 | def image_size_gdal(im): 66 | """ 67 | Reads the width and height of an image, using gdal. 68 | 69 | Args: 70 | im: path to the input image file 71 | Returns: 72 | a tuple of size 2, giving width and height 73 | """ 74 | try: 75 | with open(im): 76 | p1 = subprocess.Popen(['gdalinfo', im], stdout=subprocess.PIPE) 77 | p2 = subprocess.Popen(['grep', 'Size'], stdin=p1.stdout, stdout=subprocess.PIPE) 78 | line = p2.stdout.readline() 79 | out = re.findall(r"[\w']+", line) 80 | nc = int(out[2]) 81 | nr = int(out[3]) 82 | return (nc, nr) 83 | except IOError: 84 | print("image_size_gdal: the input file %s doesn't exist" % str(im)) 85 | sys.exit() 86 | 87 | 88 | def image_size_tiffinfo(im): 89 | """ 90 | Reads the width and height of an image, using tiffinfo. 91 | 92 | Args: 93 | im: path to the input tif image file 94 | Returns: 95 | a tuple of size 2, giving width and height 96 | """ 97 | if not im.lower().endswith('.tif'): 98 | print("image_size_tiffinfo function works only with TIF files") 99 | print("use image_size_gdal or image_size instead") 100 | sys.exit() 101 | try: 102 | with open(im): 103 | # redirect stderr to /dev/null on tiffinfo call to discard noisy 104 | # msg about unknown field with tag 42112 105 | fnull = open(os.devnull, "w") 106 | p1 = subprocess.Popen(['tiffinfo', im], stdout=subprocess.PIPE, 107 | stderr=fnull) 108 | p2 = subprocess.Popen(['grep', 'Image Width'], stdin=p1.stdout, 109 | stdout=subprocess.PIPE) 110 | line = p2.stdout.readline() 111 | out = re.findall(r"[\w']+", line) 112 | nc = int(out[2]) 113 | nr = int(out[5]) 114 | return (nc, nr) 115 | except IOError: 116 | print("image_size_tiffinfo: the input file %s doesn't exist" % str(im)) 117 | sys.exit() 118 | 119 | 120 | 121 | def bounding_box2D(pts): 122 | """ 123 | bounding box for the points pts 124 | """ 125 | dim = len(pts[0]) #should be 2 126 | bb_min = [ min([ t[i] for t in pts ]) for i in range(0, dim) ] 127 | bb_max = [ max([ t[i] for t in pts ]) for i in range(0, dim) ] 128 | x, y, w, h = bb_min[0], bb_min[1], bb_max[0]-bb_min[0], bb_max[1]-bb_min[1] 129 | return x, y, w, h 130 | 131 | 132 | def image_crop_TIFF(im, x, y, w, h, out=None): 133 | """ 134 | Crops tif images. 135 | 136 | Args: 137 | im: path to a tif image, or to a tile map file (*.til) 138 | x, y, w, h: four integers definig the rectangular crop in the image. 139 | (x, y) is the top-left corner, and (w, h) are the dimensions of the 140 | rectangle. 141 | out (optional): path to the output crop 142 | 143 | Returns: 144 | path to cropped tif image 145 | 146 | The crop is made with the gdal_translate binary, from gdal library. We 147 | tried to use tiffcrop but it fails. 148 | """ 149 | if (int(x) != x or int(y) != y): 150 | print('Warning: image_crop_TIFF will round the coordinates of your crop') 151 | 152 | if out is None: 153 | out = tmpfile('.tif') 154 | 155 | try: 156 | with open(im, 'r'): 157 | # do the crop with gdal_translate, with option to remove any GDAL or GeoTIFF tag 158 | run('gdal_translate -co profile=baseline -srcwin %d %d %d %d %s %s' % (x, 159 | y, w, h, shellquote(im), shellquote(out))) 160 | 161 | except IOError: 162 | print("""image_crop_TIFF: input image not found! Verify your paths to 163 | Pleiades full images""") 164 | sys.exit() 165 | 166 | return out 167 | 168 | 169 | def run_binary_on_list_of_points(points, binary, option=None, binary_workdir=None): 170 | """ 171 | Runs a binary that reads its input on stdin. 172 | 173 | Args: 174 | points: numpy array containing all the input points, one per line 175 | binary: path to the binary. It is supposed to write one output value on 176 | stdout for each input point 177 | option: optional option to pass to the binary 178 | binary_workdir: optional workdir for the binary to be launched 179 | 180 | Returns: 181 | a numpy array containing all the output points, one per line. 182 | """ 183 | # run the binary 184 | pts_file = tmpfile('.txt') 185 | np.savetxt(pts_file, points, '%.18f') 186 | p1 = subprocess.Popen(['cat', pts_file], stdout = subprocess.PIPE) 187 | if binary_workdir == None: 188 | binary_workdir = os.getcwd() 189 | if option: 190 | p2 = subprocess.Popen([binary, option], stdin = p1.stdout, stdout = 191 | subprocess.PIPE, cwd = binary_workdir) 192 | else: 193 | p2 = subprocess.Popen([binary], stdin = p1.stdout, stdout = 194 | subprocess.PIPE, cwd = binary_workdir) 195 | 196 | # recover output values: first point first, then loop over all the others 197 | line = p2.stdout.readline() 198 | out = np.array([[float(val) for val in line.split()]]) 199 | for i in range(1, len(points)): 200 | line = p2.stdout.readline() 201 | l = [float(val) for val in line.split()] 202 | out = np.vstack((out, l)) 203 | 204 | return out 205 | 206 | 207 | def image_zoom_gdal(im, f, out=None, w=None, h=None): 208 | """ 209 | Zooms an image using gdal (average interpolation) 210 | 211 | Args: 212 | im: path to the input image 213 | f: zoom factor. f in [0,1] for zoom in, f in [1 +inf] for zoom out. 214 | out (optional): path to the ouput file 215 | w, h (optional): input image dimensions 216 | 217 | Returns: 218 | path to the output image. In case f=1, the input image is returned 219 | """ 220 | if f == 1: 221 | return im 222 | 223 | if out is None: 224 | out = tmpfile('.tif') 225 | 226 | tmp = tmpfile('.tif') 227 | 228 | if w is None or h is None: 229 | sz = image_size_tiffinfo(im) 230 | w = sz[0] 231 | h = sz[1] 232 | 233 | # First, we need to make sure the dataset has a proper origin/spacing 234 | run('gdal_translate -a_ullr 0 0 %d %d %s %s' % (w/float(f), -h/float(f), im, tmp)) 235 | 236 | # do the zoom with gdalwarp 237 | run('gdalwarp -ts %d %d %s %s' % (w/float(f), h/float(f), tmp, out)) 238 | return out 239 | 240 | 241 | 242 | 243 | def download(to_file, from_url): 244 | """ 245 | Download a file from the internet. 246 | 247 | Args: 248 | to_file: path where to store the downloaded file 249 | from_url: url of the file to download 250 | """ 251 | r = requests.get(from_url, stream=True) 252 | file_size = int(r.headers['content-length']) 253 | print("Downloading: %s Bytes: %s" % (to_file, file_size)) 254 | 255 | downloaded = 0 256 | with open(to_file, 'wb') as f: 257 | for chunk in r.iter_content(chunk_size=8192): 258 | if chunk: # filter out keep-alive new chunks 259 | f.write(chunk) 260 | downloaded += len(chunk) 261 | status = r"%10d [%3.2f%%]" % (downloaded, downloaded * 100. / file_size) 262 | status = status + chr(8)*(len(status)+1) 263 | print(status, end=" ") 264 | -------------------------------------------------------------------------------- /egm96-15.pgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlodef/rpc_cropper/0e6d1f1e27a3a4a45f03146bcf0675b4f81d3db8/egm96-15.pgm -------------------------------------------------------------------------------- /geoid_height_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "GeographicLib/Geoid.hpp" 4 | 5 | extern "C" void geoid_height(double *out, double lat, double lon) 6 | { 7 | GeographicLib::Geoid egm96("egm96-15", "./"); 8 | *out = egm96(lat, lon); 9 | } 10 | -------------------------------------------------------------------------------- /rpc_crop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2013, Carlo de Franchis 4 | # Copyright (C) 2013, Gabriele Facciolo 5 | # Copyright (C) 2013, Enric Meinhardt Llopis 6 | 7 | import os 8 | import sys 9 | 10 | import rpc_model 11 | import rpc_utils 12 | import srtm 13 | import common 14 | 15 | 16 | ### Hack build srtm4 on the fly 17 | here = os.path.dirname(__file__) 18 | srtm4_binary = os.path.join(here, 'srtm4') 19 | try: 20 | os.stat(srtm4_binary) 21 | except OSError: 22 | print 'BUILDING SRTM4...' 23 | os.system('cd %s; make' % here) 24 | 25 | 26 | def procedure1(poly, a11, a22, b1, b2): 27 | """ 28 | given the polynomial of degree three of three variables poly(x,y,z) 29 | computes the polynomial coefficients implementing 30 | the variable change x = a11 x' + b1 31 | y = a22 y' + b2 32 | here the variable z is height of the RPC model 33 | VERIFIED! 34 | """ 35 | newpoly = [None] * 20 36 | newpoly[0] = poly[0] + b2*b2*b2* poly[11] + b1*b1* b2* poly[12] + b1* b2*b2* poly[14] + b1*b1*b1* poly[15] + b2* poly[1] + b1* poly[2] + b1* b2* poly[4] + b2*b2* poly[7] + b1*b1* poly[8] 37 | newpoly[1] = (3* a22* b2*b2* poly[11] + a22* b1*b1 *poly[12] + 2* a22* b1* b2* poly[14] + a22* poly[1] + a22* b1* poly[4] + 2* a22* b2* poly[7]) 38 | newpoly[2] = (2* a11* b1* b2* poly[12] + a11* b2*b2* poly[14] + 3* a11* b1*b1* poly[15] + a11* poly[2] + a11* b2* poly[4] + 2* a11* b1* poly[8]) 39 | newpoly[3] = (b1* b2* poly[10] + b2*b2* poly[17] + b1*b1* poly[18] + poly[3] + b2* poly[5] + b1* poly[6]) 40 | newpoly[4] = (2* a11* a22* b1* poly[12] + 2* a11* a22* b2* poly[14] + a11* a22* poly[4]) 41 | newpoly[5] = (a22* b1* poly[10] + 2* a22* b2* poly[17] + a22* poly[5]) 42 | newpoly[6] = (a11* b2* poly[10] + 2* a11* b1* poly[18] + a11* poly[6]) 43 | newpoly[7] = (3* a22*a22* b2* poly[11] + a22*a22* b1* poly[14] + a22*a22* poly[7]) 44 | newpoly[8] = (a11*a11* b2* poly[12] + 3 *a11*a11* b1* poly[15] + a11*a11* poly[8]) 45 | newpoly[9] = (b2* poly[13] + b1* poly[16] + poly[9]) 46 | newpoly[10] = a11* a22* poly[10] 47 | newpoly[11] = a22*a22*a22* poly[11] 48 | newpoly[12] = a11*a11* a22* poly[12] 49 | newpoly[13] = a22* poly[13] 50 | newpoly[14] = a11* a22*a22* poly[14] 51 | newpoly[15] = a11*a11*a11* poly[15] 52 | newpoly[16] = a11* poly[16] 53 | newpoly[17] = a22*a22* poly[17] 54 | newpoly[18] = a11*a11* poly[18] 55 | newpoly[19] = poly[19] 56 | return newpoly 57 | 58 | 59 | def poly_variable_change_in(polyNum, polyDen, a11, a22, b1, b2): 60 | """ 61 | given the RPC polynomials polyNum(x,y,z)/polyDen(x,y,z) 62 | computes the polynomial coefficients implementing 63 | the variable change x = a11 x' + b1 64 | y = a22 y' + b2 65 | VERIFIED! 66 | """ 67 | #print a11,a22,b1,b2 68 | newNum = procedure1(polyNum, a11, a22, b1, b2) 69 | newDen = procedure1(polyDen, a11, a22, b1, b2) 70 | return newNum, newDen 71 | 72 | 73 | def poly_variable_change_out(polyNum, polyDen, a11, b1): 74 | """ 75 | given the RPC polynomials polyNum(x,y,z)/polyDen(x,y,z) 76 | computes the polynomial coefficients implementing 77 | the operation a11*(polyNum(x,y,z)/polyDen(x,y,z)) + b1 78 | VERIFIED! 79 | """ 80 | import numpy as np 81 | newNum = list(float(a11) * np.array(polyNum) + float(b1) * np.array(polyDen)) 82 | newDen = polyDen 83 | return newNum, newDen 84 | 85 | 86 | 87 | def rpc_apply_crop_to_rpc_model(rpc, x0, y0, w, h): 88 | import copy 89 | rpcout = copy.deepcopy(rpc) 90 | 91 | ## compute the scale and shift parameter for the normalized RPC 92 | a11 = (float(w)/2) / (rpc.colScale) 93 | a22 = (float(h)/2) / (rpc.linScale) 94 | # a11 = 1.0 95 | # a22 = 1.0 96 | b1 = float(x0)/float(rpc.colScale) 97 | b2 = float(y0)/float(rpc.linScale) 98 | ## apply the transformation to the direct polynomials, BEWARE!! The order of the variables is reversed 99 | rpcout.directLonNum, rpcout.directLonDen = poly_variable_change_in(rpc.directLonNum, rpc.directLonDen, a22,a11,b2,b1) 100 | rpcout.directLatNum, rpcout.directLatDen = poly_variable_change_in(rpc.directLatNum, rpc.directLatDen, a22,a11,b2,b1) 101 | 102 | 103 | # scale the RPC domain (I'm not sure its [-1,1]^2) 104 | # # TODO correct RPC so that the validity domain is still the square [-1,1]^2 105 | rpcout.colScale= float(w)/2 106 | rpcout.linScale= float(h)/2 107 | # rpcout.colScale= rpc.colScale ## keep it unchanged (it also works) 108 | # rpcout.linScale= rpc.linScale 109 | 110 | 111 | ## compute the scale and shift parameters for the normalized RPC 112 | b1 = float(x0)/float(rpcout.colScale) 113 | b2 = float(y0)/float(rpcout.linScale) 114 | a11 = float(rpc.colScale)/float(rpcout.colScale) 115 | a22 = float(rpc.linScale)/float(rpcout.linScale) 116 | # a11 = 1.0 117 | # a22 = 1.0 118 | ## apply the transform to the inverse polynomials 119 | rpcout.inverseColNum, rpcout.inverseColDen = poly_variable_change_out(rpcout.inverseColNum, rpcout.inverseColDen, a11, -b1) 120 | rpcout.inverseLinNum, rpcout.inverseLinDen = poly_variable_change_out(rpcout.inverseLinNum, rpcout.inverseLinDen, a22, -b2) 121 | 122 | return rpcout 123 | 124 | 125 | def test_me(rpcfile): 126 | import numpy as np 127 | import rpc_model, rpc_crop 128 | reload(rpc_crop) 129 | r1 = rpc_model.RPCModel('RPC_PHR1A_P_201309231105136_SEN_756965101-001.XML') 130 | r2 = rpc_crop.rpc_apply_crop_to_rpc_model(r1, 10000,20000,2000,2000) 131 | 132 | #print "direct estimate error:" 133 | geo1 = np.array(r1.direct_estimate(11000,20100,10, return_normalized=False)) 134 | geo2 = np.array(r2.direct_estimate(1000,100,10, return_normalized=False)) 135 | print geo1 - geo2 136 | 137 | #print "inverse estimate error:" 138 | pix1 = np.array(r1.inverse_estimate(geo1[0], geo1[1], geo1[2])) 139 | pix2 = np.array(r2.inverse_estimate(geo1[0], geo1[1], geo1[2])) 140 | print pix1 - pix2 141 | 142 | r2.write('cropped.xml') 143 | 144 | 145 | def crop_rpc_and_image(out_dir, img, rpc, rpc_ref, x, y, w, h): 146 | """ 147 | Crops an image and its rpc. The ROI may be defined on another image. 148 | 149 | Args: 150 | out_dir: path to the output directory. The cropped image and rpc files 151 | will be written there. 152 | img: path to the input image 153 | rpc: path to the input rpc 154 | rpc_ref: path to the rpc file of the reference image 155 | x, y, w, h: 4 integers defining a rectangular ROI in the reference 156 | image 157 | """ 158 | r = rpc_model.RPCModel(rpc) 159 | 160 | # recompute the roi if the input image is not the reference image 161 | if rpc_ref is not rpc: 162 | r_ref = rpc_model.RPCModel(rpc_ref) 163 | x, y, w, h = rpc_utils.corresponding_roi(r_ref, r, x, y, w, h) 164 | 165 | # output filenames 166 | crop_rpc_and_image.counter += 1 167 | s = "_%02d" % crop_rpc_and_image.counter 168 | out_img_file = os.path.join(out_dir, "img%s.tif" % s) 169 | out_rpc_file = os.path.join(out_dir, "rpc%s.xml" % s) 170 | out_prv_file = os.path.join(out_dir, "prv%s.png" % s) 171 | 172 | # do the crop 173 | out_r = rpc_apply_crop_to_rpc_model(r, x, y, w, h) 174 | out_r.write(out_rpc_file) 175 | common.run('gdal_translate -srcwin %d %d %d %d "%s" "%s"' % (x, y, w, h, img, out_img_file)) 176 | 177 | # do the preview: it has to fit a 1366x768 rectangle 178 | w = float(w) 179 | h = float(h) 180 | if w > 1366 or h > 768: 181 | if w/h > float(1366) / 768: 182 | f = w/1366 183 | else: 184 | f = h/768 185 | tmp = common.tmpfile('.tif') 186 | common.image_zoom_gdal(out_img_file, f, tmp, w, h) 187 | common.run('gdal_translate -of png -ot Byte -scale %s %s' % (tmp, out_prv_file)) 188 | else: 189 | common.run('gdal_translate -of png -ot Byte -scale %s %s' % (out_img_file, out_prv_file)) 190 | common.run('rm %s.aux.xml' % out_prv_file) 191 | 192 | 193 | 194 | def main(): 195 | 196 | # verify input 197 | if len(sys.argv) in [8, 10, 12]: 198 | out_dir = sys.argv[1] 199 | img1 = sys.argv[2] 200 | rpc1 = sys.argv[3] 201 | if len(sys.argv) == 8: 202 | x = float(sys.argv[4]) 203 | y = float(sys.argv[5]) 204 | w = float(sys.argv[6]) 205 | h = float(sys.argv[7]) 206 | elif len(sys.argv) == 10: 207 | img2 = sys.argv[4] 208 | rpc2 = sys.argv[5] 209 | x = float(sys.argv[6]) 210 | y = float(sys.argv[7]) 211 | w = float(sys.argv[8]) 212 | h = float(sys.argv[9]) 213 | else: 214 | img2 = sys.argv[4] 215 | rpc2 = sys.argv[5] 216 | img3 = sys.argv[6] 217 | rpc3 = sys.argv[7] 218 | x = float(sys.argv[8]) 219 | y = float(sys.argv[9]) 220 | w = float(sys.argv[10]) 221 | h = float(sys.argv[11]) 222 | else: 223 | print "Tool to crop an image and its RPC." 224 | print "Incorrect syntax, use:" 225 | print " > %s out_dir img1 rpc1 [img2 rpc2 [img3 rpc3]] x y w h" % sys.argv[0] 226 | exit(1) 227 | 228 | try: 229 | os.stat(img1) 230 | os.stat(rpc1) 231 | # os.stat(img2) 232 | # os.stat(rpc2) 233 | except OSError: 234 | exit(1) 235 | 236 | # create output dir 237 | if not os.path.exists(out_dir): 238 | os.makedirs(out_dir) 239 | 240 | # download needed srtm files 241 | for s in srtm.list_srtm_tiles(rpc1, x, y, w, h): 242 | srtm.get_srtm_tile(s, srtm.cfg['srtm_dir']) 243 | 244 | # do the crops 245 | crop_rpc_and_image.counter = 0 # used to name the output files 246 | crop_rpc_and_image(out_dir, img1, rpc1, rpc1, x, y, w, h) 247 | if 'img2' in locals() and 'rpc2' in locals(): 248 | crop_rpc_and_image(out_dir, img2, rpc2, rpc1, x, y, w, h) 249 | if 'img3' in locals() and 'rpc3' in locals(): 250 | crop_rpc_and_image(out_dir, img3, rpc3, rpc1, x, y, w, h) 251 | 252 | # clean tmp files 253 | while common.garbage: 254 | common.run('rm %s' % common.garbage.pop()) 255 | 256 | 257 | if __name__ == '__main__': main() 258 | -------------------------------------------------------------------------------- /rpc_crop_gui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2013, Gabriele Facciolo 3 | 4 | ## {{{ http://code.activestate.com/recipes/325391/ (r1) 5 | import sys 6 | from OpenGL.GLUT import * 7 | from OpenGL.GLU import * 8 | from OpenGL.GL import * 9 | from PIL import Image 10 | 11 | name = 'select a region' 12 | 13 | # global variables for the mouse 14 | x0=0; y0=0; w0=0; h0=0; b0state=''; 15 | 16 | # global variables for the image size 17 | ix,iy=100,100 18 | # and for the relative displacement 19 | dx,dy=0,0 20 | imageData=0 21 | I1='' # preview 22 | img1='' 23 | rpc1='' 24 | img2='' 25 | rpc2='' 26 | 27 | 28 | def loadImage(imageName): 29 | im = Image.open(imageName) 30 | try: 31 | ix, iy, image = im.size[0], im.size[1], im.convert("RGBA").tostring("raw", "RGBA", 0, -1) 32 | except SystemError: 33 | ix, iy, image = im.size[0], im.size[1], im.convert("RGBA").tostring("raw", "RGBX", 0, -1) 34 | return (image,ix,iy) 35 | 36 | 37 | 38 | def display( ): 39 | """Render scene geometry""" 40 | 41 | glClear(GL_COLOR_BUFFER_BIT); 42 | 43 | # glDisable( GL_LIGHTING) # context lights by default 44 | 45 | # this is the effective size of the current window 46 | # ideally winx,winy should be equal to ix,iy BUT if the 47 | # image is larger than the screen glutReshapeWindow(ix,iy) 48 | # will fail and winx,winy will be truncated to the size of the screen 49 | winx=glutGet(GLUT_WINDOW_WIDTH) 50 | winy=glutGet(GLUT_WINDOW_HEIGHT) 51 | 52 | 53 | # setup camera 54 | glMatrixMode (GL_PROJECTION); 55 | glLoadIdentity (); 56 | glOrtho (0, winx, winy, 0, -1, 1); 57 | 58 | 59 | glEnable (GL_TEXTURE_2D); #/* enable texture mapping */ 60 | glBindTexture (GL_TEXTURE_2D, 13); #/* bind to our texture, has id of 13 */ 61 | 62 | global dx,dy,ix,iy 63 | 64 | glBegin( GL_QUADS ); 65 | glColor3f(1.0, 0.0, 0.0); 66 | glTexCoord2d(0.0,0.0); glVertex3d(0 -dx,iy-dy,0); 67 | glTexCoord2d(1.0,0.0); glVertex3d(ix-dx,iy-dy,0); 68 | glTexCoord2d(1.0,1.0); glVertex3d(ix-dx,0 -dy,0); 69 | glTexCoord2d(0.0,1.0); glVertex3d(0 -dx,0 -dy,0); 70 | glEnd(); 71 | glDisable (GL_TEXTURE_2D); #/* disable texture mapping */ 72 | 73 | 74 | # show region 75 | global x0,y0,w0,h0,b0state 76 | if(b0state=='pressed'): 77 | glEnable (GL_BLEND) 78 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 79 | 80 | glBegin( GL_QUADS ); 81 | glColor4f(1.0, 0.0, 0.0,.6); 82 | glVertex3d(x0 ,y0 ,-0.1); 83 | glColor4f(0.0, 1.0, 1.0,.3); 84 | glVertex3d(x0+w0,y0 ,-0.1); 85 | glColor4f(0.0, 0.0, 1.0,.6); 86 | glVertex3d(x0+w0,y0+h0,-0.1); 87 | glColor4f(0.0, 1.0, 0.0,.3); 88 | glVertex3d(x0 ,y0+h0,-0.1); 89 | glEnd(); 90 | 91 | glDisable (GL_BLEND) 92 | 93 | 94 | glFlush() 95 | glutSwapBuffers() 96 | 97 | 98 | def setupTexture(imageData, ix,iy): 99 | """texture environment setup""" 100 | glBindTexture(GL_TEXTURE_2D, 13) 101 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 102 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 103 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 104 | # glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 105 | # glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 106 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 107 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 108 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL) 109 | 110 | glTexImage2D( 111 | GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, 112 | GL_RGBA, GL_UNSIGNED_BYTE, imageData 113 | ) 114 | 115 | 116 | def mouseMotionPass(x,y): 117 | global dx,dy 118 | title='p:%s,%s' % (x+dx,y+dy) 119 | glutSetWindowTitle(title) 120 | 121 | 122 | def mouseMotion(x,y): 123 | global x0,y0,w0,h0,b0state 124 | global dx,dy 125 | w0,h0 = x-x0,y-y0 126 | title='p:%s,%s [+%s+%s %sx%s]' % (x+dx,y+dy,x0+dx,y0+dy,w0,h0) 127 | glutSetWindowTitle(title) 128 | glutPostRedisplay() 129 | 130 | 131 | def mouseButtons(button, state, x,y): 132 | global x0,y0,w0,h0,b0state 133 | if button==GLUT_LEFT_BUTTON and state==GLUT_DOWN: 134 | x0,y0=x,y 135 | b0state='pressed' 136 | elif button==GLUT_LEFT_BUTTON and state==GLUT_UP: 137 | w0,h0 = x-x0,y-y0 138 | b0state='released' 139 | print x0+dx, y0+dy, x+dx, y+dy 140 | global I1, img1, img2, img3, rpc1, rpc2, rpc3, out_dir 141 | 142 | import common 143 | # read preview/full images dimensions 144 | nc, nr = common.image_size_gdal(img1) 145 | nc_preview, nr_preview = common.image_size_gdal(I1) 146 | 147 | # rescale according to preview/full ratio 148 | x2= int(x0*nc/nc_preview) 149 | y2= int(y0*nr/nr_preview) 150 | w2= int(w0*nc/nc_preview) 151 | h2= int(h0*nr/nr_preview) 152 | if rpc2 is None: 153 | os.system('./rpc_crop.py %s "%s" "%s" %d %d %d %d' % (out_dir, img1, rpc1, x2, y2, w2, h2)) 154 | elif rpc3 is None: 155 | os.system('./rpc_crop.py %s "%s" "%s" "%s" "%s" %d %d %d %d' % (out_dir, img1, rpc1, img2, rpc2, x2, y2, w2, h2)) 156 | else: 157 | os.system('./rpc_crop.py %s "%s" "%s" "%s" "%s" "%s" "%s" %d %d %d %d' % (out_dir, img1, rpc1, img2, rpc2, img3, rpc3, x2, y2, w2, h2)) 158 | 159 | os.system('./vlight.py %s/prv_??.png &' % out_dir) 160 | #exit(0) 161 | 162 | 163 | 164 | # letters and numbers 165 | def keyboard(key, x, y): 166 | if key=='q': 167 | exit(0) 168 | # print ord(key) 169 | 170 | 171 | 172 | # handle arrow keys 173 | def keyboard2(key, x, y): 174 | global dx,dy 175 | winx=glutGet(GLUT_WINDOW_WIDTH) 176 | winy=glutGet(GLUT_WINDOW_HEIGHT) 177 | if key==102: 178 | dx=dx+int(winx/16) 179 | elif key==101: 180 | dy=dy-int(winy/16) 181 | elif key==100: 182 | dx=dx-int(winx/16) 183 | elif key==103: 184 | dy=dy+int(winy/16) 185 | # print key 186 | glutPostRedisplay() 187 | 188 | 189 | 190 | 191 | def main(): 192 | global I1, img1, img2, img3, rpc1, rpc2, rpc3, out_dir 193 | 194 | # verify input 195 | if len(sys.argv) in [5, 7, 9]: 196 | out_dir = sys.argv[1] 197 | I1 = sys.argv[2] 198 | img1 = sys.argv[3] 199 | rpc1 = sys.argv[4] 200 | img2 = None 201 | rpc2 = None 202 | img3 = None 203 | rpc3 = None 204 | if len(sys.argv) > 5: 205 | img2 = sys.argv[5] 206 | rpc2 = sys.argv[6] 207 | if len(sys.argv) == 9: 208 | img3 = sys.argv[7] 209 | rpc3 = sys.argv[8] 210 | else: 211 | print "Incorrect syntax, use:" 212 | print " > %s out_dir preview.jpg img1.tif rpc1.xml [img2.tif rpc2.xml [img3.tif rpc3.xml]]" % sys.argv[0] 213 | sys.exit(1) 214 | 215 | # globals 216 | global ix,iy,imageData 217 | 218 | #init opengl 219 | glutInit(sys.argv) 220 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH) 221 | glutInitWindowSize(ix,iy) 222 | glutCreateWindow(name) 223 | glutDisplayFunc(display) 224 | # glutReshapeFunc (glutResize); 225 | # glutIdleFunc(display) 226 | glutMouseFunc(mouseButtons) 227 | glutKeyboardFunc(keyboard) 228 | glutSpecialFunc(keyboard2) 229 | glutPassiveMotionFunc(mouseMotionPass); 230 | glutMotionFunc(mouseMotion); 231 | 232 | # read the image 233 | (imageData,ix,iy) = loadImage(I1) 234 | 235 | glutReshapeWindow(ix,iy) 236 | 237 | # setup texture 238 | setupTexture(imageData,ix,iy) 239 | 240 | glutMainLoop() 241 | 242 | 243 | if __name__ == '__main__': main() 244 | ## end of http://code.activestate.com/recipes/325391/ }}} 245 | -------------------------------------------------------------------------------- /rpc_model.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, Carlo de Franchis 2 | # Copyright (C) 2015, Gabriele Facciolo 3 | # Copyright (C) 2015, Enric Meinhardt 4 | 5 | 6 | from __future__ import print_function 7 | import copy 8 | import numpy as np 9 | from xml.etree.ElementTree import ElementTree 10 | 11 | 12 | def apply_poly(poly, x, y, z): 13 | """ 14 | Evaluates a 3-variables polynom of degree 3 on a triplet of numbers. 15 | 16 | Args: 17 | poly: list of the 20 coefficients of the 3-variate degree 3 polynom, 18 | ordered following the RPC convention. 19 | x, y, z: triplet of floats. They may be numpy arrays of same length. 20 | 21 | Returns: 22 | the value(s) of the polynom on the input point(s). 23 | """ 24 | out = 0 25 | out += poly[0] 26 | out += poly[1]*y + poly[2]*x + poly[3]*z 27 | out += poly[4]*y*x + poly[5]*y*z +poly[6]*x*z 28 | out += poly[7]*y*y + poly[8]*x*x + poly[9]*z*z 29 | out += poly[10]*x*y*z 30 | out += poly[11]*y*y*y 31 | out += poly[12]*y*x*x + poly[13]*y*z*z + poly[14]*y*y*x 32 | out += poly[15]*x*x*x 33 | out += poly[16]*x*z*z + poly[17]*y*y*z + poly[18]*x*x*z 34 | out += poly[19]*z*z*z 35 | return out 36 | 37 | def apply_rfm(num, den, x, y, z): 38 | """ 39 | Evaluates a Rational Function Model (rfm), on a triplet of numbers. 40 | 41 | Args: 42 | num: list of the 20 coefficients of the numerator 43 | den: list of the 20 coefficients of the denominator 44 | All these coefficients are ordered following the RPC convention. 45 | x, y, z: triplet of floats. They may be numpy arrays of same length. 46 | 47 | Returns: 48 | the value(s) of the rfm on the input point(s). 49 | """ 50 | return apply_poly(num, x, y, z) / apply_poly(den, x, y, z) 51 | 52 | 53 | # this function was written to use numpy.polynomial.polynomial.polyval3d 54 | # function, instead of our apply_poly function. 55 | def reshape_coefficients_vector(c): 56 | """ 57 | Transform a 1D array of coefficients of a 3D polynom into a 3D array. 58 | 59 | Args: 60 | c: 1D array of length 20, containing the coefficients of the 61 | 3-variables polynom of degree 3, ordered with the RPC convention. 62 | Returns: 63 | a 4x4x4 ndarray, with at most 20 non-zero entries, containing the 64 | coefficients of input array. 65 | """ 66 | out = np.zeros((4, 4, 4)) 67 | out[0, 0, 0] = c[0] 68 | out[0, 1, 0] = c[1] 69 | out[1, 0, 0] = c[2] 70 | out[0, 0, 1] = c[3] 71 | out[1, 1, 0] = c[4] 72 | out[0, 1, 1] = c[5] 73 | out[1, 0, 1] = c[6] 74 | out[0, 2, 0] = c[7] 75 | out[2, 0, 0] = c[8] 76 | out[0, 0, 2] = c[9] 77 | out[1, 1, 1] = c[10] 78 | out[0, 3, 0] = c[11] 79 | out[2, 1, 0] = c[12] 80 | out[0, 1, 2] = c[13] 81 | out[1, 2, 0] = c[14] 82 | out[3, 0, 0] = c[15] 83 | out[1, 0, 2] = c[16] 84 | out[0, 2, 1] = c[17] 85 | out[2, 0, 1] = c[18] 86 | out[0, 0, 3] = c[19] 87 | return out 88 | 89 | def apply_rfm_numpy(num, den, x, y, z): 90 | """ 91 | Alternative implementation of apply_rfm, that uses numpy to evaluate 92 | polynoms. 93 | """ 94 | c_num = reshape_coefficients_vector(num) 95 | c_den = reshape_coefficients_vector(den) 96 | a = np.polynomial.polynomial.polyval3d(x, y, z, c_num) 97 | b = np.polynomial.polynomial.polyval3d(x, y, z, c_den) 98 | return a/b 99 | 100 | class RPCModel: 101 | def __init__(self, rpc_file): 102 | self.nan_rpc() 103 | self.read_rpc(rpc_file) 104 | 105 | def nan_rpc(self): 106 | self.linOff = np.nan 107 | self.colOff = np.nan 108 | self.latOff = np.nan 109 | self.lonOff = np.nan 110 | self.altOff = np.nan 111 | self.linScale = np.nan 112 | self.colScale = np.nan 113 | self.latScale = np.nan 114 | self.lonScale = np.nan 115 | self.altScale = np.nan 116 | self.directLonNum = [np.nan] * 20 117 | self.directLonDen = [np.nan] * 20 118 | self.directLatNum = [np.nan] * 20 119 | self.directLatDen = [np.nan] * 20 120 | self.inverseLinNum = [np.nan] * 20 121 | self.inverseLinDen = [np.nan] * 20 122 | self.inverseColNum = [np.nan] * 20 123 | self.inverseColDen = [np.nan] * 20 124 | 125 | def read_rpc(self, rpc_file): 126 | self.filepath = rpc_file 127 | 128 | if rpc_file.lower().endswith('xml'): 129 | tree = ElementTree() 130 | tree.parse(rpc_file) 131 | self.tree = tree # store the xml tree in the object 132 | self.read_rpc_xml(tree) 133 | else: 134 | # we assume that non xml rpc files follow the ikonos convention 135 | self.read_rpc_ikonos(rpc_file) 136 | 137 | def read_rpc_ikonos(self, rpc_file): 138 | lines = open(rpc_file).read().split('\n') 139 | for l in lines: 140 | ll = l.split() 141 | if len(ll) > 1: self.add_tag_rpc(ll[0], ll[1]) 142 | 143 | def add_tag_rpc(self, tag, val): 144 | a = tag.split('_') 145 | if len(a) == 2: 146 | if a[1] == "OFF:": 147 | if a[0] == "LINE": self.linOff = float(val) 148 | elif a[0] == "SAMP": self.colOff = float(val) 149 | elif a[0] == "LAT": self.latOff = float(val) 150 | elif a[0] == "LONG": self.lonOff = float(val) 151 | elif a[0] == "HEIGHT": self.altOff = float(val) 152 | elif a[1] == "SCALE:": 153 | if a[0] == "LINE": self.linScale = float(val) 154 | elif a[0] == "SAMP": self.colScale = float(val) 155 | elif a[0] == "LAT": self.latScale = float(val) 156 | elif a[0] == "LONG": self.lonScale = float(val) 157 | elif a[0] == "HEIGHT": self.altScale = float(val) 158 | 159 | elif len(a) == 4 and a[2] == "COEFF": 160 | # remove ':', convert to int and decrease the coeff index 161 | a[3] = int(a[3][:-1]) - 1 162 | if a[0] == "LINE": 163 | if a[1] == "NUM": self.inverseLinNum[a[3]] = float(val) 164 | elif a[1] == "DEN": self.inverseLinDen[a[3]] = float(val) 165 | elif a[0] == "SAMP": 166 | if a[1] == "NUM": self.inverseColNum[a[3]] = float(val) 167 | elif a[1] == "DEN": self.inverseColDen[a[3]] = float(val) 168 | 169 | def read_rpc_xml(self, tree): 170 | # determine wether it's a pleiades, spot-6 or worldview image 171 | a = tree.find('Metadata_Identification/METADATA_PROFILE') # PHR_SENSOR 172 | b = tree.find('IMD/IMAGE/SATID') # WorldView 173 | if a is not None: 174 | if a.text in ['PHR_SENSOR', 'S6_SENSOR', 'S7_SENSOR']: 175 | self.read_rpc_pleiades(tree) 176 | else: 177 | print('unknown sensor type') 178 | elif b is not None: 179 | if b.text == 'WV02' or b.text == 'WV01': 180 | self.read_rpc_worldview(tree) 181 | else: 182 | print('unknown sensor type') 183 | 184 | def parse_coeff(self, element, prefix, indices): 185 | tab = [] 186 | for x in indices: 187 | tab.append(float(element.find("%s_%s" % (prefix, str(x))).text)) 188 | return tab 189 | 190 | def read_rpc_pleiades(self, tree): 191 | # direct model 192 | d = tree.find('Rational_Function_Model/Global_RFM/Direct_Model') 193 | self.directLonNum = self.parse_coeff(d, "SAMP_NUM_COEFF", xrange(1, 21)) 194 | self.directLonDen = self.parse_coeff(d, "SAMP_DEN_COEFF", xrange(1, 21)) 195 | self.directLatNum = self.parse_coeff(d, "LINE_NUM_COEFF", xrange(1, 21)) 196 | self.directLatDen = self.parse_coeff(d, "LINE_DEN_COEFF", xrange(1, 21)) 197 | self.directBias = self.parse_coeff(d, "ERR_BIAS", ['X', 'Y']) 198 | 199 | # inverse model 200 | i = tree.find('Rational_Function_Model/Global_RFM/Inverse_Model') 201 | self.inverseColNum = self.parse_coeff(i, "SAMP_NUM_COEFF", xrange(1, 21)) 202 | self.inverseColDen = self.parse_coeff(i, "SAMP_DEN_COEFF", xrange(1, 21)) 203 | self.inverseLinNum = self.parse_coeff(i, "LINE_NUM_COEFF", xrange(1, 21)) 204 | self.inverseLinDen = self.parse_coeff(i, "LINE_DEN_COEFF", xrange(1, 21)) 205 | self.inverseBias = self.parse_coeff(i, "ERR_BIAS", ['ROW', 'COL']) 206 | 207 | # validity domains 208 | v = tree.find('Rational_Function_Model/Global_RFM/RFM_Validity') 209 | vd = v.find('Direct_Model_Validity_Domain') 210 | self.firstRow = float(vd.find('FIRST_ROW').text) 211 | self.firstCol = float(vd.find('FIRST_COL').text) 212 | self.lastRow = float(vd.find('LAST_ROW').text) 213 | self.lastCol = float(vd.find('LAST_COL').text) 214 | 215 | vi = v.find('Inverse_Model_Validity_Domain') 216 | self.firstLon = float(vi.find('FIRST_LON').text) 217 | self.firstLat = float(vi.find('FIRST_LAT').text) 218 | self.lastLon = float(vi.find('LAST_LON').text) 219 | self.lastLat = float(vi.find('LAST_LAT').text) 220 | 221 | # scale and offset 222 | # the -1 in line and column offsets is due to Pleiades RPC convention 223 | # that states that the top-left pixel of an image has coordinates 224 | # (1, 1) 225 | self.linOff = float(v.find('LINE_OFF').text) - 1 226 | self.colOff = float(v.find('SAMP_OFF').text) - 1 227 | self.latOff = float(v.find('LAT_OFF').text) 228 | self.lonOff = float(v.find('LONG_OFF').text) 229 | self.altOff = float(v.find('HEIGHT_OFF').text) 230 | self.linScale = float(v.find('LINE_SCALE').text) 231 | self.colScale = float(v.find('SAMP_SCALE').text) 232 | self.latScale = float(v.find('LAT_SCALE').text) 233 | self.lonScale = float(v.find('LONG_SCALE').text) 234 | self.altScale = float(v.find('HEIGHT_SCALE').text) 235 | 236 | def read_rpc_worldview(self, tree): 237 | # inverse model 238 | im = tree.find('RPB/IMAGE') 239 | l = im.find('LINENUMCOEFList/LINENUMCOEF') 240 | self.inverseLinNum = [float(c) for c in l.text.split()] 241 | l = im.find('LINEDENCOEFList/LINEDENCOEF') 242 | self.inverseLinDen = [float(c) for c in l.text.split()] 243 | l = im.find('SAMPNUMCOEFList/SAMPNUMCOEF') 244 | self.inverseColNum = [float(c) for c in l.text.split()] 245 | l = im.find('SAMPDENCOEFList/SAMPDENCOEF') 246 | self.inverseColDen = [float(c) for c in l.text.split()] 247 | self.inverseBias = float(im.find('ERRBIAS').text) 248 | 249 | # scale and offset 250 | self.linOff = float(im.find('LINEOFFSET').text) 251 | self.colOff = float(im.find('SAMPOFFSET').text) 252 | self.latOff = float(im.find('LATOFFSET').text) 253 | self.lonOff = float(im.find('LONGOFFSET').text) 254 | self.altOff = float(im.find('HEIGHTOFFSET').text) 255 | 256 | self.linScale = float(im.find('LINESCALE').text) 257 | self.colScale = float(im.find('SAMPSCALE').text) 258 | self.latScale = float(im.find('LATSCALE').text) 259 | self.lonScale = float(im.find('LONGSCALE').text) 260 | self.altScale = float(im.find('HEIGHTSCALE').text) 261 | 262 | # image dimensions 263 | self.lastRow = int(tree.find('IMD/NUMROWS').text) 264 | self.lastCol = int(tree.find('IMD/NUMCOLUMNS').text) 265 | 266 | 267 | def inverse_estimate(self, lon, lat, alt): 268 | cLon = (lon - self.lonOff) / self.lonScale 269 | cLat = (lat - self.latOff) / self.latScale 270 | cAlt = (alt - self.altOff) / self.altScale 271 | cCol = apply_rfm(self.inverseColNum, self.inverseColDen, cLat, cLon, cAlt) 272 | cLin = apply_rfm(self.inverseLinNum, self.inverseLinDen, cLat, cLon, cAlt) 273 | col = cCol*self.colScale + self.colOff 274 | lin = cLin*self.linScale + self.linOff 275 | return col, lin, alt 276 | 277 | 278 | def direct_estimate(self, col, lin, alt, return_normalized=False): 279 | 280 | if np.isnan(self.directLatNum[0]): 281 | return self.direct_estimate_iterative(col, lin, alt, return_normalized) 282 | 283 | cCol = (col - self.colOff) / self.colScale 284 | cLin = (lin - self.linOff) / self.linScale 285 | cAlt = (alt - self.altOff) / self.altScale 286 | cLon = apply_rfm(self.directLonNum, self.directLonDen, cLin, cCol, cAlt) 287 | cLat = apply_rfm(self.directLatNum, self.directLatDen, cLin, cCol, cAlt) 288 | lon = cLon*self.lonScale + self.lonOff 289 | lat = cLat*self.latScale + self.latOff 290 | if return_normalized: 291 | return cLon, cLat, cAlt 292 | return lon, lat, alt 293 | 294 | 295 | def direct_estimate_iterative(self, col, row, alt, return_normalized=False): 296 | """ 297 | Iterative estimation of direct projection (image to ground), for a 298 | list (or array) of image points expressed in image coordinates. 299 | 300 | Args: 301 | col, row: image coordinates 302 | alt: altitude (in meters above the ellipsoid) of the corresponding 303 | 3D point 304 | return_normalized: boolean flag. If true, then return normalized 305 | coordinates 306 | 307 | 308 | Returns: 309 | lon, lat, alt 310 | """ 311 | # normalise input image coordinates 312 | cCol = (col - self.colOff) / self.colScale 313 | cRow = (row - self.linOff) / self.linScale 314 | cAlt = (alt - self.altOff) / self.altScale 315 | 316 | # target point: Xf (f for final) 317 | Xf = np.vstack([cCol, cRow]).T 318 | 319 | # use 3 corners of the lon, lat domain and project them into the image 320 | # to get the first estimation of (lon, lat) 321 | # EPS is 2 for the first iteration, then 0.1. 322 | lon = -np.ones(len(Xf)) 323 | lat = -np.ones(len(Xf)) 324 | EPS = 2 325 | x0 = apply_rfm(self.inverseColNum, self.inverseColDen, lat, lon, cAlt) 326 | y0 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat, lon, cAlt) 327 | x1 = apply_rfm(self.inverseColNum, self.inverseColDen, lat, lon + EPS, cAlt) 328 | y1 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat, lon + EPS, cAlt) 329 | x2 = apply_rfm(self.inverseColNum, self.inverseColDen, lat + EPS, lon, cAlt) 330 | y2 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat + EPS, lon, cAlt) 331 | 332 | n = 0 333 | while not np.all((x0 - cCol) ** 2 + (y0 - cRow) ** 2 < 1e-18): 334 | X0 = np.vstack([x0, y0]).T 335 | X1 = np.vstack([x1, y1]).T 336 | X2 = np.vstack([x2, y2]).T 337 | e1 = X1 - X0 338 | e2 = X2 - X0 339 | u = Xf - X0 340 | 341 | # project u on the base (e1, e2): u = a1*e1 + a2*e2 342 | # the exact computation is given by: 343 | # M = np.vstack((e1, e2)).T 344 | # a = np.dot(np.linalg.inv(M), u) 345 | # but I don't know how to vectorize this. 346 | # Assuming that e1 and e2 are orthogonal, a1 is given by 347 | # / 348 | num = np.sum(np.multiply(u, e1), axis=1) 349 | den = np.sum(np.multiply(e1, e1), axis=1) 350 | a1 = np.divide(num, den) 351 | 352 | num = np.sum(np.multiply(u, e2), axis=1) 353 | den = np.sum(np.multiply(e2, e2), axis=1) 354 | a2 = np.divide(num, den) 355 | 356 | # use the coefficients a1, a2 to compute an approximation of the 357 | # point on the gound which in turn will give us the new X0 358 | lon += a1 * EPS 359 | lat += a2 * EPS 360 | 361 | # update X0, X1 and X2 362 | EPS = .1 363 | x0 = apply_rfm(self.inverseColNum, self.inverseColDen, lat, lon, cAlt) 364 | y0 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat, lon, cAlt) 365 | x1 = apply_rfm(self.inverseColNum, self.inverseColDen, lat, lon + EPS, cAlt) 366 | y1 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat, lon + EPS, cAlt) 367 | x2 = apply_rfm(self.inverseColNum, self.inverseColDen, lat + EPS, lon, cAlt) 368 | y2 = apply_rfm(self.inverseLinNum, self.inverseLinDen, lat + EPS, lon, cAlt) 369 | #n += 1 370 | 371 | #print('direct_estimate_iterative: %d iterations' % n) 372 | 373 | if return_normalized: 374 | return lon, lat, cAlt 375 | 376 | # else denormalize and return 377 | lon = lon*self.lonScale + self.lonOff 378 | lat = lat*self.latScale + self.latOff 379 | return lon, lat, alt 380 | 381 | 382 | def __write_pleiades(self, filename): 383 | """ 384 | Writes a new XML file with the rpc parameters 385 | If the read was performed on a pleiades RPC 386 | write can only be done using the pleiades format. 387 | """ 388 | ## First transfer the coefficients back to the internal xml parsing tree 389 | tree = copy.deepcopy(self.tree) 390 | 391 | # list concatenation of direct model parameters 392 | direct = self.directLonNum + self.directLonDen + self.directLatNum \ 393 | + self.directLatDen + self.directBias 394 | d = tree.find('Rational_Function_Model/Global_RFM/Direct_Model') 395 | for child,id in zip(d,range(82)): 396 | child.text = str(direct[id]) 397 | 398 | # list concatenation of inverse model parameters 399 | inverse = self.inverseColNum + self.inverseColDen + self.inverseLinNum \ 400 | + self.inverseLinDen + self.inverseBias 401 | i = tree.find('Rational_Function_Model/Global_RFM/Inverse_Model') 402 | for child,id in zip(i,range(82)): 403 | child.text = str(inverse[id]) 404 | 405 | # validity domains 406 | v = tree.find('Rational_Function_Model/Global_RFM/RFM_Validity') 407 | vd = v.find('Direct_Model_Validity_Domain') 408 | vd.find('FIRST_ROW').text = str(self.firstRow) 409 | vd.find('FIRST_COL').text = str(self.firstCol) 410 | vd.find('LAST_ROW').text = str(self.lastRow ) 411 | vd.find('LAST_COL').text = str(self.lastCol ) 412 | 413 | vi = v.find('Inverse_Model_Validity_Domain') 414 | vi.find('FIRST_LON').text = str(self.firstLon) 415 | vi.find('FIRST_LAT').text = str(self.firstLat) 416 | vi.find('LAST_LON').text = str(self.lastLon ) 417 | vi.find('LAST_LAT').text = str(self.lastLat ) 418 | 419 | # scale and offset 420 | v.find('LINE_OFF').text = str(self.linOff ) 421 | v.find('SAMP_OFF').text = str(self.colOff ) 422 | v.find('LAT_OFF').text = str(self.latOff ) 423 | v.find('LONG_OFF').text = str(self.lonOff ) 424 | v.find('HEIGHT_OFF').text = str(self.altOff ) 425 | v.find('LINE_SCALE').text = str(self.linScale) 426 | v.find('SAMP_SCALE').text = str(self.colScale) 427 | v.find('LAT_SCALE').text = str(self.latScale) 428 | v.find('LONG_SCALE').text = str(self.lonScale) 429 | v.find('HEIGHT_SCALE').text = str(self.altScale) 430 | 431 | ## Write the XML file! 432 | tree.write(filename) 433 | 434 | 435 | def __write_worldview(self, filename): 436 | """ 437 | Writes a new XML file with the rpc parameters 438 | If the read was performed on a worldview RPC 439 | write can only be done using the worldview format. 440 | """ 441 | ## First transfer the coefficients back to the internal xml parsing tree 442 | tree = copy.deepcopy(self.tree) 443 | v = tree.find('RPB/IMAGE') 444 | 445 | # inverse model parameters 446 | a = [str(x) for x in self.inverseLinNum] 447 | b = [str(x) for x in self.inverseLinDen] 448 | c = [str(x) for x in self.inverseColNum] 449 | d = [str(x) for x in self.inverseColDen] 450 | v.find('LINENUMCOEFList/LINENUMCOEF').text = ' '.join(a) 451 | v.find('LINEDENCOEFList/LINEDENCOEF').text = ' '.join(b) 452 | v.find('SAMPNUMCOEFList/SAMPNUMCOEF').text = ' '.join(c) 453 | v.find('SAMPDENCOEFList/SAMPDENCOEF').text = ' '.join(d) 454 | 455 | # scale and offset 456 | v.find('LINEOFFSET').text = str(self.linOff) 457 | v.find('SAMPOFFSET').text = str(self.colOff) 458 | v.find('LATOFFSET').text = str(self.latOff) 459 | v.find('LONGOFFSET').text = str(self.lonOff) 460 | v.find('HEIGHTOFFSET').text = str(self.altOff) 461 | v.find('LINESCALE').text = str(self.linScale) 462 | v.find('SAMPSCALE').text = str(self.colScale) 463 | v.find('LATSCALE').text = str(self.latScale) 464 | v.find('LONGSCALE').text = str(self.lonScale) 465 | v.find('HEIGHTSCALE').text = str(self.altScale) 466 | 467 | # image dimensions 468 | tree.find('IMD/NUMROWS').text = str(self.lastRow) 469 | tree.find('IMD/NUMCOLUMNS').text = str(self.lastCol) 470 | 471 | ## Write the XML file! 472 | tree.write(filename) 473 | 474 | 475 | def __write_ikonos(self, filename): 476 | """ 477 | Writes a text file with the rpc parameters in the Ikonos format. 478 | 479 | If the read was performed on an Ikonos RPC, write can only be done 480 | using the Ikonos format. 481 | """ 482 | f = open(filename, "w") 483 | 484 | # scale and offset 485 | f.write('LINE_OFF: %.12f pixels\n' % self.linOff ) 486 | f.write('SAMP_OFF: %.12f pixels\n' % self.colOff ) 487 | f.write('LAT_OFF: %.12f degrees\n' % self.latOff ) 488 | f.write('LONG_OFF: %.12f degrees\n' % self.lonOff ) 489 | f.write('HEIGHT_OFF: %.12f meters\n' % self.altOff ) 490 | f.write('LINE_SCALE: %.12f pixels\n' % self.linScale) 491 | f.write('SAMP_SCALE: %.12f pixels\n' % self.colScale) 492 | f.write('LAT_SCALE: %.12f degrees\n' % self.latScale) 493 | f.write('LONG_SCALE: %.12f degrees\n' % self.lonScale) 494 | f.write('HEIGHT_SCALE: %.12f meters\n' % self.altScale) 495 | 496 | # inverse model parameters 497 | for i in range(20): 498 | f.write('LINE_NUM_COEFF_%d: %.12e\n' % (i+1, self.inverseLinNum[i])) 499 | for i in range(20): 500 | f.write('LINE_DEN_COEFF_%d: %.12e\n' % (i+1, self.inverseLinDen[i])) 501 | for i in range(20): 502 | f.write('SAMP_NUM_COEFF_%d: %.12e\n' % (i+1, self.inverseColNum[i])) 503 | for i in range(20): 504 | f.write('SAMP_DEN_COEFF_%d: %.12e\n' % (i+1, self.inverseLinDen[i])) 505 | f.close() 506 | 507 | def write(self, filename): 508 | """ 509 | Saves an rpc object to a file, choosing the Pleiades/Worldview/Ikonos 510 | format according to the type of the input rpc object 511 | 512 | Args: 513 | filename: path to the file 514 | """ 515 | # distinguish 3 cases: pleiades, worldview or ikonos formats 516 | if hasattr(self, 'tree') and np.isfinite(self.directLatNum[0]): 517 | self.__write_pleiades(filename) 518 | elif hasattr(self, 'tree') and np.isnan(self.directLatNum[0]): 519 | self.__write_worldview(filename) 520 | else: 521 | self.__write_ikonos(filename) 522 | 523 | 524 | def __repr__(self): 525 | return ''' 526 | ### Direct Model ### 527 | directLatNum = {directLatNum} 528 | directLatDen = {directLatDen} 529 | directLonNum = {directLonNum} 530 | directLonDen = {directLonDen} 531 | 532 | ### Inverse Model ### 533 | inverseColNum = {inverseColNum} 534 | inverseColDen = {inverseColDen} 535 | inverseLinNum = {inverseLinNum} 536 | inverseLinDen = {inverseLinDen} 537 | 538 | ### Scale and Offsets ### 539 | linOff = {linOff} 540 | colOff = {colOff} 541 | latOff = {latOff} 542 | lonOff = {lonOff} 543 | altOff = {altOff} 544 | linScale = {linScale} 545 | colScale = {colScale} 546 | latScale = {latScale} 547 | lonScale = {lonScale} 548 | altScale = {altScale}'''.format( 549 | directLatNum = self.directLatNum, 550 | directLatDen = self.directLatDen, 551 | directLonNum = self.directLonNum, 552 | directLonDen = self.directLonDen, 553 | inverseColNum = self.inverseColNum, 554 | inverseColDen = self.inverseColDen, 555 | inverseLinNum = self.inverseLinNum, 556 | inverseLinDen = self.inverseLinDen, 557 | lonScale = self.lonScale, 558 | lonOff = self.lonOff, 559 | latScale = self.latScale, 560 | latOff = self.latOff, 561 | altScale = self.altScale, 562 | altOff = self.altOff, 563 | colScale = self.colScale, 564 | colOff = self.colOff, 565 | linScale = self.linScale, 566 | linOff = self.linOff) 567 | 568 | 569 | if __name__ == '__main__': 570 | # test on the first haiti image 571 | rpc = RPCModel('../pleiades_data/rpc/haiti/rpc01.xml') 572 | col, lin = 20000, 8000 573 | alt = 90 574 | print('col={col}, lin={lin}, alt={alt}'.format(col=col, lin=lin, alt=alt)) 575 | lon, lat, alt = rpc.direct_estimate(col, lin, alt) 576 | print('lon={lon}, lat={lat}, alt={alt}'.format(lon=lon, lat=lat, alt=alt)) 577 | col, lin, alt = rpc.inverse_estimate(lon, lat, alt) 578 | print('col={col}, lin={lin}, alt={alt}'.format(col=col, lin=lin, alt=alt)) 579 | -------------------------------------------------------------------------------- /rpc_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, Carlo de Franchis 2 | # Copyright (C) 2013, Gabriele Facciolo 3 | # Copyright (C) 2013, Enric Meinhardt-Llopis 4 | 5 | import numpy as np 6 | 7 | import common 8 | 9 | 10 | def find_corresponding_point(model_a, model_b, x, y, z): 11 | """ 12 | Finds corresponding points in the second image, given the heights. 13 | 14 | Arguments: 15 | model_a, model_b: two instances of the rpc_model.RPCModel class, or of 16 | the projective_model.ProjModel class 17 | x, y, z: three 1D numpy arrrays, of the same length. x, y are the 18 | coordinates of pixels in the image, and z contains the altitudes of the 19 | corresponding 3D point. 20 | 21 | Returns: 22 | xp, yp, z: three 1D numpy arrrays, of the same length as the input. xp, 23 | yp contains the coordinates of the projection of the 3D point in image 24 | b. 25 | """ 26 | t1, t2, t3 = model_a.direct_estimate(x, y, z) 27 | xp, yp, zp = model_b.inverse_estimate(t1, t2, z) 28 | return (xp, yp, z) 29 | 30 | 31 | def geodesic_bounding_box(rpc, x, y, w, h): 32 | """ 33 | Computes a bounding box on the WGS84 ellipsoid associated to a Pleiades 34 | image region of interest, through its rpc function. 35 | 36 | Args: 37 | rpc: instance of the rpc_model.RPCModel class 38 | x, y, w, h: four integers definig a rectangular region of interest 39 | (ROI) in the image. (x, y) is the top-left corner, and (w, h) are the 40 | dimensions of the rectangle. 41 | 42 | Returns: 43 | 4 geodesic coordinates: the min and max longitudes, and the min and 44 | max latitudes. 45 | """ 46 | # compute altitude coarse extrema from rpc data 47 | m = rpc.altOff - rpc.altScale 48 | M = rpc.altOff + rpc.altScale 49 | 50 | # build an array with vertices of the 3D ROI, obtained as {2D ROI} x [m, M] 51 | x = np.array([x, x, x, x, x+w, x+w, x+w, x+w]) 52 | y = np.array([y, y, y+h, y+h, y, y, y+h, y+h]) 53 | a = np.array([m, M, m, M, m, M, m, M]) 54 | 55 | # compute geodetic coordinates of corresponding world points 56 | lon, lat, alt = rpc.direct_estimate(x, y, a) 57 | 58 | # extract extrema 59 | # TODO: handle the case where longitudes pass over -180 degrees 60 | # for latitudes it doesn't matter since for latitudes out of [-60, 60] 61 | # there is no SRTM data 62 | return np.min(lon), np.max(lon), np.min(lat), np.max(lat) 63 | 64 | 65 | def sample_bounding_box(lon_m, lon_M, lat_m, lat_M): 66 | """ 67 | Samples a geodetic "rectangular" region with regularly spaced points. 68 | The sampling distance is the srtm resolution, ie 3 arcseconds. 69 | 70 | Args: 71 | lon_m, lon_M: min and max longitudes, between -180 and 180 72 | lat_m, lat_M: min and max latitudes, between -60 and 60 73 | 74 | Returns: 75 | a numpy array, of size N x 2, containing the list of sample locations 76 | in geodetic coordinates. 77 | """ 78 | # check parameters 79 | assert lon_m > -180 80 | assert lon_M < 180 81 | assert lon_m < lon_M 82 | assert lat_m > -60 83 | assert lat_M < 60 84 | assert lat_m < lat_M 85 | 86 | # width of srtm bin: 6000x6000 samples in a tile of 5x5 degrees, ie 3 87 | # arcseconds (in degrees) 88 | srtm_bin = 1.0/1200 89 | 90 | # round down lon_m, lat_m and round up lon_M, lat_M so they are integer 91 | # multiples of 3 arcseconds 92 | lon_m, lon_M = round_updown(lon_m, lon_M, srtm_bin) 93 | lat_m, lat_M = round_updown(lat_m, lat_M, srtm_bin) 94 | 95 | # compute the samples: one in the center of each srtm bin 96 | lons = np.arange(lon_m, lon_M, srtm_bin) + .5 * srtm_bin 97 | lats = np.arange(lat_m, lat_M, srtm_bin) + .5 * srtm_bin 98 | 99 | # put all the samples in an array. There should be a more pythonic way to 100 | # do this 101 | out = np.zeros((len(lons)*len(lats), 2)) 102 | for i in xrange(len(lons)): 103 | for j in xrange(len(lats)): 104 | out[i*len(lats)+j, 0] = lons[i] 105 | out[i*len(lats)+j, 1] = lats[j] 106 | 107 | return out 108 | 109 | 110 | def round_updown(a, b, q): 111 | """ 112 | Rounds down (resp. up) a (resp. b) to the closest multiple of q. 113 | 114 | Args: 115 | a: float value to round down 116 | b: float value to round up 117 | q: float value defining the targeted multiples 118 | 119 | Returns: 120 | the two modified values 121 | """ 122 | a = q*np.floor(a/q) 123 | b = q*np.ceil(b/q) 124 | return a, b 125 | 126 | 127 | def altitude_range_coarse(rpc): 128 | """ 129 | Computes a coarse altitude range using the RPC informations only. 130 | 131 | Args: 132 | rpc: instance of the rpc_model.RPCModel class 133 | 134 | Returns: 135 | the altitude validity range of the RPC. 136 | """ 137 | m = rpc.altOff - rpc.altScale 138 | M = rpc.altOff + rpc.altScale 139 | return m, M 140 | 141 | 142 | def altitude_range(rpc, x, y, w, h, margin_top, margin_bottom): 143 | """ 144 | Computes an altitude range using SRTM data. 145 | 146 | Args: 147 | rpc: instance of the rpc_model.RPCModel class 148 | x, y, w, h: four integers definig a rectangular region of interest 149 | (ROI) in the image. (x, y) is the top-left corner, and (w, h) are the 150 | dimensions of the rectangle. 151 | margin_top: margin (in meters) to add to the upper bound of the range 152 | margin_bottom: margin (usually negative) to add to the lower bound of 153 | the range 154 | 155 | Returns: 156 | lower and upper bounds on the altitude of the world points that are 157 | imaged by the RPC projection function in the provided ROI. To compute 158 | these bounds, we use SRTM data. The altitudes are computed with respect 159 | to the WGS84 reference ellipsoid. 160 | """ 161 | # TODO: iterate the procedure used here to get a finer estimation of the 162 | # TODO: bounding box on the ellipsoid and thus of the altitude range. For flat 163 | # TODO: regions it will not improve much, but for mountainous regions there is a 164 | # TODO: lot to improve. 165 | 166 | # find bounding box on the ellipsoid (in geodesic coordinates) 167 | lon_m, lon_M, lat_m, lat_M = geodesic_bounding_box(rpc, x, y, w, h) 168 | 169 | # if bounding box is out of srtm domain, return coarse altitude estimation 170 | if (lat_m < -60 or lat_M > 60): 171 | print "Out of SRTM domain, returning coarse range from rpc" 172 | return altitude_range_coarse(rpc) 173 | 174 | # sample the bounding box with regular step of 3 arcseconds (srtm 175 | # resolution) 176 | ellipsoid_points = sample_bounding_box(lon_m, lon_M, lat_m, lat_M) 177 | 178 | # compute srtm height on all these points 179 | # these altitudes are computed with respect to the WGS84 ellipsoid 180 | import os 181 | srtm = common.run_binary_on_list_of_points(ellipsoid_points, 'srtm4', 182 | option=None, binary_workdir=os.path.dirname(__file__)) 183 | srtm = np.ravel(srtm) 184 | 185 | # srtm data may contain 'nan' values (meaning no data is available there). 186 | # These points are most likely water (sea) and thus their height with 187 | # respect to geoid is 0. Thus we replace the nans with 0. 188 | srtm[np.isnan(srtm)] = 0 189 | 190 | # extract extrema (and add a +-100m security margin) 191 | h_m = np.round(srtm.min()) + margin_bottom 192 | h_M = np.round(srtm.max()) + margin_top 193 | 194 | return h_m, h_M 195 | 196 | 197 | def corresponding_roi(rpc1, rpc2, x, y, w, h): 198 | """ 199 | Uses RPC functions to determine the region of im2 associated to the 200 | specified ROI of im1. 201 | 202 | Args: 203 | rpc1, rpc2: two instances of the rpc_model.RPCModel class 204 | x, y, w, h: four integers defining a rectangular region of interest 205 | (ROI) in the first view. (x, y) is the top-left corner, and (w, h) 206 | are the dimensions of the rectangle. 207 | 208 | Returns: 209 | four integers defining a ROI in the second view. This ROI is supposed 210 | to contain the projections of the 3D points that are visible in the 211 | input ROI. 212 | """ 213 | m, M = altitude_range(rpc1, x, y, w, h, 0, 0) 214 | 215 | # build an array with vertices of the 3D ROI, obtained as {2D ROI} x [m, M] 216 | a = np.array([x, x, x, x, x+w, x+w, x+w, x+w]) 217 | b = np.array([y, y, y+h, y+h, y, y, y+h, y+h]) 218 | c = np.array([m, M, m, M, m, M, m, M]) 219 | 220 | # corresponding points in im2 221 | xx, yy = find_corresponding_point(rpc1, rpc2, a, b, c)[0:2] 222 | 223 | # return coordinates of the bounding box in im2 224 | return common.bounding_box2D(np.vstack([xx, yy]).T) 225 | -------------------------------------------------------------------------------- /srtm.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015, Carlo de Franchis 2 | # Copyright (C) 2015, Gabriele Facciolo 3 | # Copyright (C) 2015, Enric Meinhardt 4 | 5 | from __future__ import print_function 6 | import subprocess 7 | import zipfile 8 | import urllib 9 | import errno 10 | import os 11 | 12 | import common 13 | import rpc_model 14 | import rpc_utils 15 | 16 | cfg = {} 17 | cfg['srtm_url'] = 'http://data_public:GDdci@data.cgiar-csi.org/srtm/tiles/GeoTIFF' 18 | cfg['srtm_dir'] = os.environ['SRTM4_CACHE'] if os.environ.has_key('SRTM4_CACHE') else os.path.join(os.path.expanduser('~'), '.srtm4') 19 | 20 | 21 | def mkdir_p(path): 22 | """ 23 | Create a directory without complaining if it already exists. 24 | """ 25 | try: 26 | os.makedirs(path) 27 | except OSError as exc: # Python > 2.5 28 | if exc.errno == errno.EEXIST and os.path.isdir(path): 29 | pass 30 | else: raise 31 | 32 | 33 | def list_srtm_tiles(rpcfile, x, y, w, h): 34 | """ 35 | Tells which srtm tiles are needed to cover a given region of interest. 36 | 37 | Args: 38 | rpcfile: path to the xml file describing the rpc model. 39 | x, y, w, h: four integers defining a rectangular region of interest 40 | (ROI) in the image. (x, y) is the top-left corner, and (w, h) are 41 | the dimensions of the rectangle. 42 | 43 | Returns: 44 | the set of srtm tiles corresponding to the input ROI. 45 | """ 46 | rpc = rpc_model.RPCModel(rpcfile) 47 | lon_min, lon_max, lat_min, lat_max = rpc_utils.geodesic_bounding_box(rpc, 48 | x, y, 49 | w, h) 50 | out = [] 51 | for lon in [lon_min, lon_max]: 52 | for lat in [lat_min, lat_max]: 53 | p = subprocess.Popen(['srtm4_which_tile', str(lon), str(lat)], 54 | stdout=subprocess.PIPE) 55 | out.append(p.stdout.readline().split()[0]) 56 | out = set(out) 57 | print("Needed srtm tiles: ", out) 58 | return out 59 | 60 | 61 | def get_srtm_tile(srtm_tile, out_dir): 62 | """ 63 | Downloads and extract an srtm tile from the internet. 64 | 65 | Args: 66 | srtm_tile: string following the pattern 'srtm_%02d_%02d', identifying 67 | the desired strm tile 68 | out_dir: directory where to store and extract the srtm tiles 69 | """ 70 | # check if the tile is already there 71 | mkdir_p(out_dir) 72 | if os.path.exists(os.path.join(out_dir, '%s.tif' % srtm_tile)): 73 | return 74 | 75 | # download the zip file 76 | srtm_tile_url = '%s/%s.zip' % (cfg['srtm_url'], srtm_tile) 77 | zip_path = os.path.join(out_dir, '%s.zip' % srtm_tile) 78 | common.download(zip_path, srtm_tile_url) 79 | 80 | # extract the tif file 81 | if zipfile.is_zipfile(zip_path): 82 | z = zipfile.ZipFile(zip_path, 'r') 83 | z.extract('%s.tif' % srtm_tile, out_dir) 84 | else: 85 | print("%s not available" % srtm_tile) 86 | 87 | # remove the zip file 88 | os.remove(zip_path) 89 | 90 | 91 | def srtm4(lon, lat): 92 | """ 93 | Gives the SRTM height of a point. It is a wrapper to the srtm4 binary. 94 | 95 | Args: 96 | lon, lat: longitude and latitude 97 | 98 | Returns: 99 | the height, in meters above the WGS84 geoid (not ellipsoid) 100 | """ 101 | 102 | new_env = os.environ.copy() 103 | new_env['SRTM4_CACHE'] = cfg['srtm_dir'] 104 | p = subprocess.Popen(['srtm4', str(lon), str(lat)], 105 | stdout=subprocess.PIPE, env=new_env) 106 | return float(p.stdout.readline().split()[0]) 107 | -------------------------------------------------------------------------------- /srtm4.c: -------------------------------------------------------------------------------- 1 | // SRTM4 files are of size 6000x6000 pixels and cover an area of 5x5 degrees 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define NO_DATA NAN 16 | #define SRTM4_ASC "%s/srtm_%02d_%02d.asc" 17 | #define SRTM4_TIF "%s/srtm_%02d_%02d.tif" 18 | 19 | // headers 20 | void geoid_height(double *out, double lat, double lon); 21 | 22 | 23 | // read a TIFF int16 image 24 | static int16_t *readTIFF(TIFF *tif, int *nx, int *ny) 25 | { 26 | uint32 w = 0, h = 0; 27 | int16_t *data, *line; 28 | 29 | TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); 30 | TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); 31 | assert((size_t) TIFFScanlineSize(tif) == w * sizeof(int16_t)); 32 | 33 | data = (int16_t *) malloc(w*h*sizeof(int16_t)); 34 | *nx = (int) w; 35 | *ny = (int) h; 36 | for (int i = 0; i < h; i++) { 37 | line = data + i * w; 38 | if (TIFFReadScanline(tif, line, i, 0) < 0) { 39 | fprintf(stderr, "readTIFF: error reading row %u\n", i); 40 | free(data); 41 | return NULL; 42 | } 43 | } 44 | 45 | return data; 46 | } 47 | 48 | // load TIFF int16 image 49 | int16_t *read_tiff_int16_gray(const char *fname, int *nx, int *ny) 50 | { 51 | int16_t *data; 52 | TIFFSetWarningHandler(NULL); //suppress warnings 53 | TIFF *tif = TIFFOpen(fname, "r"); 54 | if (!tif) { 55 | fprintf(stderr, "Unable to read TIFF file %s\n", fname); 56 | return NULL; 57 | } 58 | data = readTIFF(tif, nx, ny); 59 | TIFFClose(tif); 60 | return data; 61 | } 62 | 63 | // return the name of the cache directory 64 | static char *cachedir(void) 65 | { 66 | // XXX TODO FIXME WRONG WARNING : THIS code is not reentrant 67 | static char output_dirname[FILENAME_MAX]; 68 | char *env_cache = getenv("SRTM4_CACHE"); 69 | if (env_cache) { 70 | snprintf(output_dirname, FILENAME_MAX, "%s", env_cache); 71 | } else { 72 | char *homedir = getenv("HOME"); 73 | if (!homedir) 74 | homedir = "/tmp"; 75 | snprintf(output_dirname, FILENAME_MAX, "%s/.srtm4", homedir); 76 | } 77 | int r = mkdir(output_dirname, 0777); 78 | if (r != 0 && errno != EEXIST) { 79 | fprintf(stderr, "SRTM4: cannot create cachedir \"%s\"\n", 80 | output_dirname); 81 | exit(2); 82 | } 83 | return output_dirname; 84 | } 85 | 86 | 87 | static void get_tile_index_and_position( 88 | int *tlon, int *tlat, 89 | float *xlon, float *xlat, 90 | double lon, double lat) 91 | { 92 | if (lat > 60) lat = 60; 93 | if (lat < -60) lat = -60; 94 | 95 | // tiles longitude indexes go from 1 to 72, covering the range from -180 to 96 | // +180 97 | *tlon = fmod(1+floor((lon+180)/5), 72); 98 | if (*tlon == 0) *tlon = 72; 99 | lon = fmod(lon + 180, 360); 100 | *xlon = 1200*fmod(lon, 5); 101 | 102 | // tiles latitude indexes go from 1 to 24, covering the range from 60 to 103 | // -60 104 | *tlat = 1+floor((60-lat)/5); 105 | if (*tlat == 25) *tlat = 24; 106 | *xlat = 1200*fmod(60-lat, 5); 107 | 108 | // fprintf(stderr, "tlon = %d\n", *tlon); 109 | // fprintf(stderr, "tlat = %d\n", *tlat); 110 | // fprintf(stderr, "xlon = %g\n", *xlon); 111 | // fprintf(stderr, "xlat = %g\n", *xlat); 112 | 113 | assert(*tlon > 0); assert(*tlon <= 72); 114 | assert(*tlat > 0); assert(*tlat <= 24); 115 | assert(*xlon >= 0); assert(*xlon < 6000); 116 | assert(*xlat >= 0); assert(*xlat < 6000); 117 | } 118 | 119 | static bool file_exists(const char *fname) 120 | { 121 | FILE *f = fopen(fname, "r"); 122 | if (f) 123 | { 124 | fclose(f); 125 | return true; 126 | } 127 | return false; 128 | } 129 | 130 | static char *get_tile_filename(int tlon, int tlat, bool tif) 131 | { 132 | // XXX TODO FIXME WRONG WARNING : THIS code is not reentrant 133 | static char fname[FILENAME_MAX]; 134 | // use TIF or ASC filename pattern according to boolean param 'tif' 135 | if (tif) 136 | snprintf(fname, FILENAME_MAX, SRTM4_TIF, cachedir(), tlon, tlat); 137 | else 138 | snprintf(fname, FILENAME_MAX, SRTM4_ASC, cachedir(), tlon, tlat); 139 | return fname; 140 | } 141 | 142 | static char *malloc_file_contents(char *filename, int *size) 143 | { 144 | FILE *f = fopen(filename, "r"); 145 | if (!f) 146 | return NULL; 147 | fseek(f, 0, SEEK_END); 148 | int n = ftell(f); 149 | fseek(f, 0, SEEK_SET); 150 | char *r = malloc(n+1); 151 | if (!r) 152 | return NULL; 153 | int rr = fread(r, 1, n, f); 154 | if (rr != n) 155 | return NULL; 156 | fclose(f); 157 | r[n] = '\0'; 158 | *size = n+1; 159 | return r; 160 | } 161 | 162 | static int my_atoi(char *s) 163 | { 164 | int r = 0, sign = 1; 165 | if (*s == '-') { 166 | sign = -1; 167 | s += 1; 168 | } 169 | while (*s) 170 | { 171 | r *= 10; 172 | r += *s - '0'; 173 | s += 1; 174 | } 175 | return sign * r; 176 | } 177 | 178 | static const int spacing[0x100] = { 179 | [' '] = 1, ['\t'] = 1, ['\f'] = 1, ['\n'] = 1, ['\r'] = 1 180 | }; 181 | 182 | static int my_isspace(unsigned char c) 183 | { 184 | return spacing[c]; 185 | } 186 | 187 | // this function is like regular strtok, but faster 188 | // the delimiter list is, implicitly, spaces 189 | static char *my_strtok(char *str) 190 | { 191 | static char *next; // points to the remaining part of the string 192 | char *begin = str ? str : next, *s = begin; 193 | if (!*s) 194 | return NULL; 195 | while (*s && !my_isspace(*s)) 196 | s++; 197 | if (!*s) 198 | return begin; 199 | assert(my_isspace(*s)); 200 | while(*s && my_isspace(*s)) 201 | s++; 202 | if (!*s) 203 | return begin; 204 | assert(!my_isspace(*s)); 205 | s--; 206 | *s = '\0'; 207 | next = s + 1; 208 | return begin; 209 | } 210 | 211 | // parse a tile file into memory 212 | // (this function is ugly due to the error checking) 213 | static float *malloc_tile_data(char *tile_filename) 214 | { 215 | int fsize; 216 | char *f = malloc_file_contents(tile_filename, &fsize); 217 | if (!f) 218 | return NULL; 219 | int finc, ncols, nrows, xllcorner, yllcorner; 220 | double cellsize, nodatavalue; 221 | cellsize=nodatavalue=finc=ncols=nrows=xllcorner=yllcorner=-42; 222 | 223 | int r = sscanf(f, "ncols %d\nnrows %d\nxllcorner %d\nyllcorner %d\n" 224 | "cellsize %lf\nNODATA_value %lf\n%n", &ncols, &nrows, 225 | &xllcorner, &yllcorner, &cellsize, &nodatavalue, &finc); 226 | 227 | if (r != 6) { 228 | fprintf(stderr, "strange tile file format on \"%s\" (%d)\n", 229 | tile_filename, r); 230 | exit(2); 231 | } 232 | if (ncols != 6000 || nrows != 6000) { 233 | fprintf(stderr,"ncols,nrows = %d,%d != 6000,6000",ncols,nrows); 234 | exit(2); 235 | } 236 | int n = 6000*6000; 237 | float *t = malloc(n*sizeof*t); 238 | if (!t) { 239 | fprintf(stderr, "out of memory!\n"); 240 | exit(2); 241 | } 242 | int cx = 0; 243 | char *fp = f + finc; 244 | char *tok = my_strtok(fp); 245 | while (tok && cx < n) { 246 | int x = my_atoi(tok); 247 | t[cx++] = x > -1000 ? x : NAN; 248 | tok = my_strtok(NULL); 249 | } 250 | free(f); 251 | if (cx != n) { 252 | fprintf(stderr, "could only read %d numbers from file \n", cx); 253 | exit(2); 254 | } 255 | return t; 256 | } 257 | 258 | static void cast_int16_to_float(float *out, int16_t *in, int n) 259 | { 260 | for (int i = 0; i < n; i++) 261 | out[i] = (float) in[i]; 262 | } 263 | 264 | static float *global_table_of_tiles[360][180] = {{0}}; 265 | 266 | static float *produce_tile(int tlon, int tlat, bool tif) 267 | { 268 | float *t = global_table_of_tiles[tlon][tlat]; 269 | if (!t) { 270 | char *fname = get_tile_filename(tlon, tlat, tif); 271 | if (!file_exists(fname)) { 272 | fprintf(stderr, "WARNING: this srtm tile is not available\n"); 273 | return NULL; 274 | } 275 | if (tif) { 276 | int w, h; 277 | int16_t *tmp = read_tiff_int16_gray(fname, &w, &h); 278 | if (NULL == tmp) { 279 | fprintf(stderr, "failed to read the tif file\n"); 280 | abort(); 281 | } 282 | if ((w != 6000) || (h != 6000)) { 283 | fprintf(stderr, "produce_tile: tif srtm file isn't 6000x6000\n"); 284 | abort(); 285 | } 286 | t = malloc(w*h*sizeof*t); 287 | cast_int16_to_float(t, tmp, w*h); 288 | } 289 | else 290 | t = malloc_tile_data(fname); 291 | global_table_of_tiles[tlon][tlat] = t; 292 | } 293 | return t; 294 | } 295 | 296 | static float evaluate_bilinear_cell(float a, float b, float c, float d, 297 | float x, float y) 298 | { 299 | float r = 0; 300 | r += a * (1-x) * (1-y); 301 | r += b * ( x ) * (1-y); 302 | r += c * (1-x) * ( y ); 303 | r += d * ( x ) * ( y ); 304 | return r; 305 | } 306 | 307 | static float getpixel_1(float *x, int w, int h, int i, int j) 308 | { 309 | if (i < 0) i = 0; 310 | if (j < 0) j = 0; 311 | if (i >= w) i = w-1; 312 | if (j >= h) j = h-1; 313 | return x[j*w+i]; 314 | } 315 | 316 | static float bilinear_interpolation_at(float *x, int w, int h, float p, float q) 317 | { 318 | int ip = p; 319 | int iq = q; 320 | float a = getpixel_1(x, w, h, ip , iq ); 321 | float b = getpixel_1(x, w, h, ip+1, iq ); 322 | float c = getpixel_1(x, w, h, ip , iq+1); 323 | float d = getpixel_1(x, w, h, ip+1, iq+1); 324 | float r = evaluate_bilinear_cell(a, b, c, d, p-ip, q-iq); 325 | return r; 326 | } 327 | 328 | static float nearest_neighbor_interpolation_at(float *x, 329 | int w, int h, float p, float q) 330 | { 331 | int ip = rintf(p); 332 | int iq = rintf(q); 333 | float r = getpixel_1(x, w, h, ip, iq); 334 | if (r < -1000) return NAN; 335 | return r; 336 | } 337 | 338 | 339 | double srtm4(double lon, double lat) 340 | { 341 | if (lat > 60 || lat < -60) { 342 | return NO_DATA; 343 | } 344 | int tlon, tlat; 345 | float xlon, xlat; 346 | get_tile_index_and_position(&tlon, &tlat, &xlon, &xlat, lon, lat); 347 | float *t = produce_tile(tlon, tlat, true); 348 | if (t == NULL) 349 | return NO_DATA; 350 | else 351 | return bilinear_interpolation_at(t, 6000, 6000, xlon, xlat); 352 | } 353 | 354 | double srtm4_nn(double lon, double lat) 355 | { 356 | if (lat > 60 || lat < -60) 357 | return NO_DATA; 358 | int tlon, tlat; 359 | float xlon, xlat; 360 | get_tile_index_and_position(&tlon, &tlat, &xlon, &xlat, lon, lat); 361 | float *t = produce_tile(tlon, tlat, true); 362 | if (t == NULL) 363 | return NO_DATA; 364 | else 365 | return nearest_neighbor_interpolation_at(t, 6000, 6000, xlon, xlat); 366 | } 367 | 368 | double srtm4_wrt_ellipsoid(double lon, double lat) 369 | { 370 | if (lat > 60 || lat < -60) 371 | return NO_DATA; 372 | int tlon, tlat; 373 | float xlon, xlat; 374 | get_tile_index_and_position(&tlon, &tlat, &xlon, &xlat, lon, lat); 375 | float *t = produce_tile(tlon, tlat, true); 376 | if (t == NULL) 377 | return NO_DATA; 378 | double srtm = bilinear_interpolation_at(t, 6000, 6000, xlon, xlat); 379 | double geoid = 0; 380 | geoid_height(&geoid, lat, lon); 381 | return srtm + geoid; 382 | } 383 | 384 | double srtm4_nn_wrt_ellipsoid(double lon, double lat) 385 | { 386 | if (lat > 60 || lat < -60) 387 | return NO_DATA; 388 | int tlon, tlat; 389 | float xlon, xlat; 390 | get_tile_index_and_position(&tlon, &tlat, &xlon, &xlat, lon, lat); 391 | float *t = produce_tile(tlon, tlat, true); 392 | if (t == NULL) 393 | return NO_DATA; 394 | double srtm = nearest_neighbor_interpolation_at(t, 6000, 6000, xlon, xlat); 395 | double geoid = 0; 396 | geoid_height(&geoid, lat, lon); 397 | return srtm + geoid; 398 | } 399 | 400 | void srtm4_free_tiles(void) 401 | { 402 | for (int j = 0; j < 360; j++) 403 | for (int i = 0; i < 180; i++) 404 | if (global_table_of_tiles[j][i]) 405 | free(global_table_of_tiles[j][i]); 406 | } 407 | 408 | #ifdef MAIN_SRTM4 409 | int main(int c, char *v[]) 410 | { 411 | if (c != 1 && c != 3) { 412 | fprintf(stderr, "usage:\n\t%s longitude latitude\n", *v); 413 | return 1; 414 | } 415 | if (c == 3) { 416 | double lon = atof(v[1]); 417 | double lat = atof(v[2]); 418 | double r = srtm4_wrt_ellipsoid(lon, lat); 419 | printf("%g\n", r); 420 | return 0; 421 | } 422 | else { 423 | double lon, lat, r; 424 | while(2 == scanf("%lf %lf\n", &lon, &lat)) { 425 | r = srtm4_nn_wrt_ellipsoid(lon, lat); 426 | printf("%g\n", r); 427 | } 428 | } 429 | } 430 | #endif//MAIN_SRTM4 431 | 432 | 433 | #ifndef MAIN_SRTM4 434 | #ifdef MAIN_SRTM4_WHICH_TILE 435 | static void print_tile_filename(double lon, double lat) 436 | { 437 | int tlon, tlat; 438 | float xlon, xlat; 439 | get_tile_index_and_position(&tlon, &tlat, &xlon, &xlat, lon, lat); 440 | printf("srtm_%02d_%02d\n", tlon, tlat); 441 | return; 442 | } 443 | 444 | 445 | int main(int c, char *v[]) 446 | { 447 | if (c != 1 && c != 3) { 448 | fprintf(stderr, "usage:\n\t%s longitude latitude\n", *v); 449 | return 1; 450 | } 451 | if (c == 3) { 452 | double lon = atof(v[1]); 453 | double lat = atof(v[2]); 454 | print_tile_filename(lon, lat); 455 | return 0; 456 | } 457 | else { 458 | double lon, lat; 459 | while(2 == scanf("%lf %lf\n", &lon, &lat)) { 460 | print_tile_filename(lon, lat); 461 | } 462 | } 463 | } 464 | #endif//MAIN_SRTM4_WHICH_TILE 465 | #endif 466 | -------------------------------------------------------------------------------- /vlight.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2013, Gabriele Facciolo 3 | 4 | ## {{{ http://code.activestate.com/recipes/325391/ (r1) 5 | import sys 6 | from OpenGL.GLUT import * 7 | from OpenGL.GLU import * 8 | from OpenGL.GL import * 9 | from PIL import Image 10 | 11 | name = 'viewer (press spacebar/backspace to navigate)' 12 | 13 | # global variables for the mouse 14 | x0=0; y0=0; w0=0; h0=0; b0state=''; 15 | 16 | # global variables for the image size 17 | ix,iy=100,100 18 | # and for the relative displacement 19 | dx,dy=0,0 20 | imageData=0 21 | I1='' # preview 22 | 23 | 24 | def loadImage(imageName): 25 | im = Image.open(imageName) 26 | try: 27 | ix, iy, image = im.size[0], im.size[1], im.convert("RGBA").tostring("raw", "RGBA", 0, -1) 28 | except SystemError: 29 | ix, iy, image = im.size[0], im.size[1], im.convert("RGBA").tostring("raw", "RGBX", 0, -1) 30 | return (image,ix,iy) 31 | 32 | 33 | 34 | def display( ): 35 | """Render scene geometry""" 36 | 37 | glClear(GL_COLOR_BUFFER_BIT); 38 | 39 | # glDisable( GL_LIGHTING) # context lights by default 40 | 41 | # this is the effective size of the current window 42 | # ideally winx,winy should be equal to ix,iy BUT if the 43 | # image is larger than the screen glutReshapeWindow(ix,iy) 44 | # will fail and winx,winy will be truncated to the size of the screen 45 | winx=glutGet(GLUT_WINDOW_WIDTH) 46 | winy=glutGet(GLUT_WINDOW_HEIGHT) 47 | 48 | 49 | # setup camera 50 | glMatrixMode (GL_PROJECTION); 51 | glLoadIdentity (); 52 | glOrtho (0, winx, winy, 0, -1, 1); 53 | 54 | 55 | glEnable (GL_TEXTURE_2D); #/* enable texture mapping */ 56 | glBindTexture (GL_TEXTURE_2D, 13); #/* bind to our texture, has id of 13 */ 57 | 58 | global dx,dy,ix,iy 59 | 60 | glBegin( GL_QUADS ); 61 | glColor3f(1.0, 0.0, 0.0); 62 | glTexCoord2d(0.0,0.0); glVertex3d(0 -dx,iy-dy,0); 63 | glTexCoord2d(1.0,0.0); glVertex3d(ix-dx,iy-dy,0); 64 | glTexCoord2d(1.0,1.0); glVertex3d(ix-dx,0 -dy,0); 65 | glTexCoord2d(0.0,1.0); glVertex3d(0 -dx,0 -dy,0); 66 | glEnd(); 67 | glDisable (GL_TEXTURE_2D); #/* disable texture mapping */ 68 | 69 | 70 | # show region 71 | global x0,y0,w0,h0,b0state 72 | if(b0state=='pressed'): 73 | glEnable (GL_BLEND) 74 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 75 | 76 | glBegin( GL_QUADS ); 77 | glColor4f(1.0, 0.0, 0.0,.6); 78 | glVertex3d(x0 ,y0 ,-0.1); 79 | glColor4f(0.0, 1.0, 1.0,.3); 80 | glVertex3d(x0+w0,y0 ,-0.1); 81 | glColor4f(0.0, 0.0, 1.0,.6); 82 | glVertex3d(x0+w0,y0+h0,-0.1); 83 | glColor4f(0.0, 1.0, 0.0,.3); 84 | glVertex3d(x0 ,y0+h0,-0.1); 85 | glEnd(); 86 | 87 | glDisable (GL_BLEND) 88 | 89 | 90 | glFlush() 91 | glutSwapBuffers() 92 | 93 | 94 | def setupTexture(imageData, ix,iy): 95 | """texture environment setup""" 96 | glBindTexture(GL_TEXTURE_2D, 13) 97 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 98 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 99 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 100 | # glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 101 | # glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 102 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 103 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 104 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL) 105 | 106 | glTexImage2D( 107 | GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, 108 | GL_RGBA, GL_UNSIGNED_BYTE, imageData 109 | ) 110 | 111 | 112 | def mouseMotionPass(x,y): 113 | global dx,dy 114 | title='p:%s,%s' % (x+dx,y+dy) 115 | glutSetWindowTitle(title) 116 | 117 | 118 | def mouseMotion(x,y): 119 | global x0,y0,w0,h0,b0state 120 | global dx,dy 121 | w0,h0 = x-x0,y-y0 122 | title='p:%s,%s [+%s+%s %sx%s]' % (x+dx,y+dy,x0+dx,y0+dy,w0,h0) 123 | glutSetWindowTitle(title) 124 | glutPostRedisplay() 125 | 126 | 127 | def mouseButtons(button, state, x,y): 128 | global x0,y0,w0,h0,b0state 129 | if button==GLUT_LEFT_BUTTON and state==GLUT_DOWN: 130 | x0,y0=x,y 131 | b0state='pressed' 132 | elif button==GLUT_LEFT_BUTTON and state==GLUT_UP: 133 | w0,h0 = x-x0,y-y0 134 | b0state='released' 135 | print x0+dx, y0+dy, x+dx, y+dy 136 | 137 | 138 | 139 | # letters and numbers 140 | def keyboard(key, x, y): 141 | if key=='q': 142 | exit(0) 143 | # print ord(key) 144 | 145 | 146 | # handle spece/backspace 147 | global I1, cidx 148 | if key==' ': # space 149 | cidx = (cidx+1) % len(I1) 150 | if ord(key)==127: # backspace 151 | cidx = (cidx-1) % len(I1) 152 | 153 | # read the image 154 | global ix,iy,imageData 155 | (imageData,ix,iy) = loadImage(I1[cidx]) 156 | glutReshapeWindow(ix,iy) 157 | # setup texture 158 | setupTexture(imageData,ix,iy) 159 | glutPostRedisplay() 160 | 161 | 162 | # handle arrow keys 163 | def keyboard2(key, x, y): 164 | global dx,dy 165 | winx=glutGet(GLUT_WINDOW_WIDTH) 166 | winy=glutGet(GLUT_WINDOW_HEIGHT) 167 | if key==102: 168 | dx=dx+int(winx/16) 169 | elif key==101: 170 | dy=dy-int(winy/16) 171 | elif key==100: 172 | dx=dx-int(winx/16) 173 | elif key==103: 174 | dy=dy+int(winy/16) 175 | # print key 176 | glutPostRedisplay() 177 | 178 | 179 | 180 | 181 | def main(): 182 | global I1, cidx 183 | 184 | # verify input 185 | if len(sys.argv) > 1: 186 | I1 = sys.argv[1:] 187 | cidx=0 188 | else: 189 | print "Incorrect syntax, use:" 190 | print " > %s img1 img2 ..." % sys.argv[0] 191 | sys.exit(1) 192 | 193 | # globals 194 | global ix,iy,imageData 195 | 196 | #init opengl 197 | glutInit(sys.argv) 198 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH) 199 | glutInitWindowSize(ix,iy) 200 | glutCreateWindow(name) 201 | glutDisplayFunc(display) 202 | # glutReshapeFunc (glutResize); 203 | # glutIdleFunc(display) 204 | glutMouseFunc(mouseButtons) 205 | glutKeyboardFunc(keyboard) 206 | glutSpecialFunc(keyboard2) 207 | glutPassiveMotionFunc(mouseMotionPass); 208 | glutMotionFunc(mouseMotion); 209 | 210 | # read the image 211 | (imageData,ix,iy) = loadImage(I1[cidx]) 212 | 213 | glutReshapeWindow(ix,iy) 214 | 215 | # setup texture 216 | setupTexture(imageData,ix,iy) 217 | 218 | glutMainLoop() 219 | 220 | 221 | if __name__ == '__main__': main() 222 | ## end of http://code.activestate.com/recipes/325391/ }}} 223 | --------------------------------------------------------------------------------