1 /* 2 * gdal_image.cpp -- Load GIS data into OpenCV Containers using the Geospatial Data Abstraction Library 3 */ 4 5 // OpenCV Headers 6 #include "opencv2/core/core.hpp" 7 #include "opencv2/imgproc/imgproc.hpp" 8 #include "opencv2/highgui/highgui.hpp" 9 10 // C++ Standard Libraries 11 #include <cmath> 12 #include <iostream> 13 #include <stdexcept> 14 #include <vector> 15 16 using namespace std; 17 18 // define the corner points 19 // Note that GDAL library can natively determine this 20 cv::Point2d tl( -122.441017, 37.815664 ); 21 cv::Point2d tr( -122.370919, 37.815311 ); 22 cv::Point2d bl( -122.441533, 37.747167 ); 23 cv::Point2d br( -122.3715, 37.746814 ); 24 25 // determine dem corners 26 cv::Point2d dem_bl( -122.0, 38); 27 cv::Point2d dem_tr( -123.0, 37); 28 29 // range of the heat map colors 30 std::vector<std::pair<cv::Vec3b,double> > color_range; 31 32 33 // List of all function prototypes 34 cv::Point2d lerp( const cv::Point2d&, const cv::Point2d&, const double& ); 35 36 cv::Vec3b get_dem_color( const double& ); 37 38 cv::Point2d world2dem( const cv::Point2d&, const cv::Size&); 39 40 cv::Point2d pixel2world( const int&, const int&, const cv::Size& ); 41 42 void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r ); 43 44 45 46 /* 47 * Linear Interpolation 48 * p1 - Point 1 49 * p2 - Point 2 50 * t - Ratio from Point 1 to Point 2 51 */ 52 cv::Point2d lerp( cv::Point2d const& p1, cv::Point2d const& p2, const double& t ){ 53 return cv::Point2d( ((1-t)*p1.x) + (t*p2.x), 54 ((1-t)*p1.y) + (t*p2.y)); 55 } 56 57 /* 58 * Interpolate Colors 59 */ 60 template <typename DATATYPE, int N> 61 cv::Vec<DATATYPE,N> lerp( cv::Vec<DATATYPE,N> const& minColor, 62 cv::Vec<DATATYPE,N> const& maxColor, 63 double const& t ){ 64 65 cv::Vec<DATATYPE,N> output; 66 for( int i=0; i<N; i++ ){ 67 output[i] = (uchar)(((1-t)*minColor[i]) + (t * maxColor[i])); 68 } 69 return output; 70 } 71 72 /* 73 * Compute the dem color 74 */ 75 cv::Vec3b get_dem_color( const double& elevation ){ 76 77 // if the elevation is below the minimum, return the minimum 78 if( elevation < color_range[0].second ){ 79 return color_range[0].first; 80 } 81 // if the elevation is above the maximum, return the maximum 82 if( elevation > color_range.back().second ){ 83 return color_range.back().first; 84 } 85 86 // otherwise, find the proper starting index 87 int idx=0; 88 double t = 0; 89 for( int x=0; x<(int)(color_range.size()-1); x++ ){ 90 91 // if the current elevation is below the next item, then use the current 92 // two colors as our range 93 if( elevation < color_range[x+1].second ){ 94 idx=x; 95 t = (color_range[x+1].second - elevation)/ 96 (color_range[x+1].second - color_range[x].second); 97 98 break; 99 } 100 } 101 102 // interpolate the color 103 return lerp( color_range[idx].first, color_range[idx+1].first, t); 104 } 105 106 /* 107 * Given a pixel coordinate and the size of the input image, compute the pixel location 108 * on the DEM image. 109 */ 110 cv::Point2d world2dem( cv::Point2d const& coordinate, const cv::Size& dem_size ){ 111 112 113 // relate this to the dem points 114 // ASSUMING THAT DEM DATA IS ORTHORECTIFIED 115 double demRatioX = ((dem_tr.x - coordinate.x)/(dem_tr.x - dem_bl.x)); 116 double demRatioY = 1-((dem_tr.y - coordinate.y)/(dem_tr.y - dem_bl.y)); 117 118 cv::Point2d output; 119 output.x = demRatioX * dem_size.width; 120 output.y = demRatioY * dem_size.height; 121 122 return output; 123 } 124 125 /* 126 * Convert a pixel coordinate to world coordinates 127 */ 128 cv::Point2d pixel2world( const int& x, const int& y, const cv::Size& size ){ 129 130 // compute the ratio of the pixel location to its dimension 131 double rx = (double)x / size.width; 132 double ry = (double)y / size.height; 133 134 // compute LERP of each coordinate 135 cv::Point2d rightSide = lerp(tr, br, ry); 136 cv::Point2d leftSide = lerp(tl, bl, ry); 137 138 // compute the actual Lat/Lon coordinate of the interpolated coordinate 139 return lerp( leftSide, rightSide, rx ); 140 } 141 142 /* 143 * Add color to a specific pixel color value 144 */ 145 void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r ){ 146 147 if( pix[0] + b < 255 && pix[0] + b >= 0 ){ pix[0] += b; } 148 if( pix[1] + g < 255 && pix[1] + g >= 0 ){ pix[1] += g; } 149 if( pix[2] + r < 255 && pix[2] + r >= 0 ){ pix[2] += r; } 150 } 151 152 153 /* 154 * Main Function 155 */ 156 int main( int argc, char* argv[] ){ 157 158 /* 159 * Check input arguments 160 */ 161 if( argc < 3 ){ 162 cout << "usage: " << argv[0] << " <image> <dem>" << endl; 163 return 1; 164 } 165 166 // load the image (note that we don't have the projection information. You will 167 // need to load that yourself or use the full GDAL driver. The values are pre-defined 168 // at the top of this file 169 cv::Mat image = cv::imread(argv[1], cv::IMREAD_LOAD_GDAL | cv::IMREAD_COLOR ); 170 171 // load the dem model 172 cv::Mat dem = cv::imread(argv[2], cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH ); 173 174 // create our output products 175 cv::Mat output_dem( image.size(), CV_8UC3 ); 176 cv::Mat output_dem_flood( image.size(), CV_8UC3 ); 177 178 // for sanity sake, make sure GDAL Loads it as a signed short 179 if( dem.type() != CV_16SC1 ){ throw std::runtime_error("DEM image type must be CV_16SC1"); } 180 181 // define the color range to create our output DEM heat map 182 // Pair format ( Color, elevation ); Push from low to high 183 // Note: This would be perfect for a configuration file, but is here for a working demo. 184 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 188, 154, 46), -1)); 185 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 110, 220, 110), 0.25)); 186 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 150, 250, 230), 20)); 187 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 160, 220, 200), 75)); 188 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 220, 190, 170), 100)); 189 color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 250, 180, 140), 200)); 190 191 // define a minimum elevation 192 double minElevation = -10; 193 194 // iterate over each pixel in the image, computing the dem point 195 for( int y=0; y<image.rows; y++ ){ 196 for( int x=0; x<image.cols; x++ ){ 197 198 // convert the pixel coordinate to lat/lon coordinates 199 cv::Point2d coordinate = pixel2world( x, y, image.size() ); 200 201 // compute the dem image pixel coordinate from lat/lon 202 cv::Point2d dem_coordinate = world2dem( coordinate, dem.size() ); 203 204 // extract the elevation 205 double dz; 206 if( dem_coordinate.x >= 0 && dem_coordinate.y >= 0 && 207 dem_coordinate.x < dem.cols && dem_coordinate.y < dem.rows ){ 208 dz = dem.at<short>(dem_coordinate); 209 }else{ 210 dz = minElevation; 211 } 212 213 // write the pixel value to the file 214 output_dem_flood.at<cv::Vec3b>(y,x) = image.at<cv::Vec3b>(y,x); 215 216 // compute the color for the heat map output 217 cv::Vec3b actualColor = get_dem_color(dz); 218 output_dem.at<cv::Vec3b>(y,x) = actualColor; 219 220 // show effect of a 10 meter increase in ocean levels 221 if( dz < 10 ){ 222 add_color( output_dem_flood.at<cv::Vec3b>(y,x), 90, 0, 0 ); 223 } 224 // show effect of a 50 meter increase in ocean levels 225 else if( dz < 50 ){ 226 add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 90, 0 ); 227 } 228 // show effect of a 100 meter increase in ocean levels 229 else if( dz < 100 ){ 230 add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 0, 90 ); 231 } 232 233 }} 234 235 // print our heat map 236 cv::imwrite( "heat-map.jpg" , output_dem ); 237 238 // print the flooding effect image 239 cv::imwrite( "flooded.jpg", output_dem_flood); 240 241 return 0; 242 } 243