4 "cell_type": "markdown",
5 "id": "f03213ea-7260-4f78-9b3b-99cc2ac18358",
8 "# Geotiff Interpolation Tutorial\n",
10 "The purpose of this notebook is to demonstrate interpolation from geotiff files to lon/lat pairs. In this case, the geotiff files are bands from HRRR grib2 files collected using `wrfxpy` methods on Alderaan. The lon/lat pairs are collected from RAWS stations with `Mesopy`, and assumed to be in the WGS84 standard coordinate system AKA EPSG 4326.\n",
12 "The tiff files are saved with naming a convention that stores the UTC time info as well as the associated band. See:\n",
14 "https://www.nco.ncep.noaa.gov/pmb/products/hrrr/hrrr.t00z.wrfprsf00.grib2.shtml"
18 "cell_type": "markdown",
19 "id": "9f0e2346-5d88-4b9f-9b07-751238700af9",
27 "execution_count": null,
28 "id": "110e36b1-61ea-4578-afc1-f774b7f61ee2",
33 "import os.path as osp\n",
34 "import numpy as np\n",
35 "from osgeo import gdal, osr\n",
36 "import matplotlib.pyplot as plt\n",
37 "import pandas as pd\n",
39 "sys.path.append('..')\n",
40 "from utils import retrieve_url"
45 "execution_count": null,
46 "id": "3aa52e7f-fbb2-44ff-8308-a27ff93f8ef3",
52 "importlib.reload(utils)\n",
53 "from utils import retrieve_url"
58 "execution_count": null,
59 "id": "c30fdba7-5a53-4bb9-8723-ae9253ae8036",
63 "tfile = \"hrrr.t00z.wrfprsf00.585.tif\"\n",
65 " url = f\"https://demo.openwfm.org/web/data/fmda/tif/20240101/{tfile}\", \n",
66 " dest_path = f\"{tfile}\")"
71 "execution_count": null,
72 "id": "5eaf3f83-a862-488d-a860-b2301a35ead3",
78 "# Get RAWS Station lat/lon\n",
79 "sts = pd.read_csv(\"C:/Users/jhirs/Documents/Projects/openwfm/notebooks/fmda/data/raws_stations_CO.csv\")\n",
84 "cell_type": "markdown",
85 "id": "236d8863-309c-4cbc-8b34-bd5cddda420b",
93 "execution_count": null,
94 "id": "8135266a-3638-4332-b79e-58fbca955f01",
98 "# Extract data from tif file\n",
99 "ds = gdal.Open(tfile)\n",
100 "width = ds.RasterXSize\n",
101 "height = ds.RasterYSize\n",
102 "gt = ds.GetGeoTransform()\n",
103 "gp = ds.GetProjection()\n",
104 "# data = np.array(ds.ReadAsArray())"
109 "execution_count": null,
110 "id": "b1195375-c6e8-4d57-b296-a5ed3f467a39",
122 "execution_count": null,
123 "id": "c10688f1-9649-44b6-a32f-9ae794548da0",
132 "execution_count": null,
133 "id": "1bdb1170-6d6d-4dba-84a0-2dbe8ecac11c",
137 "print('Raster count: ' + str(ds.RasterCount))"
142 "execution_count": null,
143 "id": "7c9ac9cd-a4ec-4f95-923f-ba6ec1f51d30",
147 "band = ds.GetRasterBand(1)\n",
148 "data = band.ReadAsArray()"
152 "cell_type": "markdown",
153 "id": "74b529ac-9ad4-407c-bb92-31828f4b43d9",
156 "# Plot Raster File\n",
158 "Using `imshow`, add a point at 100,100 just to demonstrate image indexing.\n",
160 "source: https://www.geeksforgeeks.org/visualizing-tiff-file-using-matplotlib-and-gdal-using-python/"
165 "execution_count": null,
166 "id": "0dcb3b37-d7d9-41ac-871b-bcc064cb2207",
173 "plt.imshow(data)\n",
174 "plt.plot(100, 100, marker='o', color='blue', markersize=6)\n",
175 "plt.annotate(\"(100,100)\", (100,100), color='blue')"
179 "cell_type": "markdown",
180 "id": "87ba2725-1512-4218-9bf7-e5e3082d4bb8",
183 "# Nearest Neighbor Lat/Lon\n",
185 "Source (nearest neighbor method): https://stackoverflow.com/questions/69034965/given-a-geotiff-file-how-does-one-find-the-single-pixel-closest-to-a-given-lati"
190 "execution_count": null,
191 "id": "d332d1d5-4e2a-454c-bbf7-a8d5401296e7",
195 "point_srs = osr.SpatialReference()\n",
196 "point_srs.ImportFromEPSG(4326) # hardcode for lon/lat\n",
198 "# GDAL>=3: make sure it's x/y\n",
199 "# see https://trac.osgeo.org/gdal/wiki/rfc73_proj6_wkt2_srsbarn\n",
200 "point_srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) \n",
202 "file_srs = osr.SpatialReference()\n",
203 "file_srs.ImportFromWkt(gp)"
208 "execution_count": null,
209 "id": "3399032e-2832-473d-a0f9-701a107303e1",
213 "ct = osr.CoordinateTransformation(point_srs, file_srs)\n",
215 "point_x = -105.002133 # lon\n",
216 "point_y = 39.746153 # lat\n",
217 "mapx, mapy, z = ct.TransformPoint(point_x, point_y) # output: coordinate pair (m)"
222 "execution_count": null,
223 "id": "352bef20-7e53-4b80-8669-fea2b3c0767f",
232 "execution_count": null,
233 "id": "a4203c63-a0bb-421f-b476-b27ea9f6abbe",
242 "execution_count": null,
243 "id": "f93b0e4a-d632-4787-8edc-e736eb881e03",
252 "execution_count": null,
253 "id": "e7d90e9a-031f-49c9-9229-1da02125376f",
257 "gt_inv = gdal.InvGeoTransform(gt)\n",
258 "pixel_x, pixel_y = gdal.ApplyGeoTransform(gt_inv, mapx, mapy)"
262 "cell_type": "markdown",
263 "id": "b188fe33-3d41-437e-8eec-6b050a79bfb3",
266 "We plot the image with the pixel annotated. The lon/lat pair is from a RAWS station near Colorado Springs, which matches the image below."
271 "execution_count": null,
272 "id": "7bbfb4ef-11f9-4f01-a8cc-01ddd972a000",
276 "plt.imshow(data)\n",
278 "# Plot pixel translation with annotations\n",
279 "plt.plot(pixel_x,pixel_y, marker='o', color='blue', markersize=6)\n",
280 "plt.annotate(f\"Pixels: ({round(pixel_x, 2)}, {round(pixel_y, 2)})\", xy=(pixel_x, pixel_y),\n",
281 " xytext=(pixel_x-100, pixel_y-100), fontsize=8, color='lightblue')\n",
282 "plt.annotate(f\"Lon/Lat: ({round(point_x, 2)}, {round(point_y, 2)})\", xy=(pixel_x, pixel_y),\n",
283 " xytext=(pixel_x-100, pixel_y-50), fontsize=8, color='lightblue')"
287 "cell_type": "markdown",
288 "id": "19bc96c4-5b62-4484-bcec-94ce9bec27a5",
291 "After this point, the tutorial goes on to describe a method for nearest neighbor. This is just one form of interpolation, so various methods will be explored below.\n",
293 "We will plot a zoomed in version of the pixels to demonstrate this."
298 "execution_count": null,
299 "id": "242d8106-79c7-4948-a15d-6f4e4ddf0cec",
303 "plt.imshow(data)\n",
305 "# Plot pixel translation with annotations\n",
306 "plt.plot(pixel_x,pixel_y, marker='o', color='lightblue', markersize=6,\n",
307 " label=f\"({round(pixel_x, 2)}, {round(pixel_y, 2)})\")\n",
309 "# Zoom in, set limits from plotted pixel\n",
311 "plt.xlim(pixel_x-offset, pixel_x+offset) # Note different +/- bc origin is topright\n",
312 "plt.ylim(pixel_y+offset,pixel_y-offset)\n",
314 "# Plot 4 points bracketing target pixel\n",
315 "x1, y1=np.floor(pixel_x), np.floor(pixel_y)\n",
316 "x2, y2=np.floor(pixel_x), np.ceil(pixel_y)\n",
317 "x3, y3=np.ceil(pixel_x), np.floor(pixel_y)\n",
318 "x4, y4=np.ceil(pixel_x), np.ceil(pixel_y)\n",
320 "plt.plot(x1,y1, marker='o', color='red', markersize=4, label='Bracketing Points')\n",
321 "plt.plot(x2,y2, marker='o', color='red', markersize=4)\n",
322 "plt.plot(x3,y3, marker='o', color='red', markersize=4)\n",
323 "plt.plot(x4,y4, marker='o', color='red', markersize=4)\n",
329 "cell_type": "markdown",
330 "id": "a3362432-1469-47e2-aa77-d5eb4aede959",
333 "# Interpolation Methods"
337 "cell_type": "markdown",
338 "id": "c0b16dbc-d206-414f-aa55-9c91482f3489",
341 "## Nearest Neighbor\n",
343 "The tutorial linked above simply rounds the pixel x and y coordinates to the nearest pixels and takes the value from that grid location. This is mathematically equivalent to an L1 nearest neighbor, or manhattan distance minimization."
348 "execution_count": null,
349 "id": "7dc6b796-33fc-461b-be5c-5b175d8a1d1f",
353 "# round to pixel\n",
354 "x_l1 = round(pixel_x)\n",
355 "y_l1 = round(pixel_y)"
360 "execution_count": null,
361 "id": "e4bf3262-752d-46f5-a4f8-9f90a68a6b4c",
365 "plt.imshow(data)\n",
367 "# Plot pixel translation with annotations\n",
368 "plt.plot(pixel_x,pixel_y, marker='o', color='blue', markersize=6,\n",
369 " label=f\"({round(pixel_x, 2)}, {round(pixel_y, 2)})\")\n",
371 "# Zoom in, set limits from plotted pixel\n",
373 "plt.xlim(pixel_x-offset, pixel_x+offset) # Note different +/- bc origin is topright\n",
374 "plt.ylim(pixel_y+offset,pixel_y-offset)\n",
376 "# Plot 4 points bracketing target pixel\n",
377 "x1, y1=np.floor(pixel_x), np.floor(pixel_y)\n",
378 "x2, y2=np.floor(pixel_x), np.ceil(pixel_y)\n",
379 "x3, y3=np.ceil(pixel_x), np.floor(pixel_y)\n",
380 "x4, y4=np.ceil(pixel_x), np.ceil(pixel_y)\n",
382 "plt.plot(x1,y1, marker='o', color='red', markersize=4, label='Bracketing Points')\n",
383 "plt.plot(x2,y2, marker='o', color='red', markersize=4)\n",
384 "plt.plot(x3,y3, marker='o', color='red', markersize=4)\n",
385 "plt.plot(x4,y4, marker='o', color='red', markersize=4)\n",
387 "# Plot interpolated pixel\n",
388 "plt.plot(x_l1,y_l1, marker='o', color='purple', markersize=5,\n",
389 " label='Interpolated')\n",
390 "interp_val = data[y_l1, x_l1]\n",
391 "plt.annotate(\"Interp. Value=\"+str(round(interp_val, 5)), xy=(x_l1, y_l1), xytext=(x_l1-1, y_l1-2), color='purple')\n",
393 "plt.title(\"L1 Nearest Neighbor\")\n",
399 "execution_count": null,
400 "id": "f75be7c9-1957-4daf-8ec8-368aab6beb90",
406 "cell_type": "markdown",
407 "id": "ec7f3ce4-96b1-49b8-b1c5-46f31bad88c6",
410 "## Nearest Neighbor (Euclidean)\n",
412 "In `wrfxpy`, the function `find_closest_grid_point` is defined to find the L2 nearest neighbor, which finds the minimum sum of squared distance (Euclidean norm).\n",
414 "https://github.com/openwfm/wrfxpy/blob/master/src/utils.py#L529\n",
416 "NOTE: very slow implementation, but I wanted to reproduce the method clearly. In this case, the interpolated value is the same as L1."
421 "execution_count": null,
422 "id": "09d65190-a4d0-4e8a-b6cf-494b636ce37a",
426 "x = np.arange(0, band.XSize)\n",
427 "y = np.arange(0, band.YSize)\n",
428 "pixels = [(xx, yy) for xx in x for yy in y]\n",
429 "d = np.zeros(len(pixels))\n",
430 "for i in range(0, len(pixels)):\n",
432 " d[i] = (pixel_x - p[0])**2 + (pixel_y - p[1])**2\n",
434 "nearest = pixels[np.argmin(d)]"
439 "execution_count": null,
440 "id": "c8165a6e-f63b-4b9e-841d-27570829b3ee",
444 "plt.imshow(data)\n",
446 "# Plot pixel translation with annotations\n",
447 "plt.plot(pixel_x,pixel_y, marker='o', color='blue', markersize=6,\n",
448 " label=f\"({round(pixel_x, 2)}, {round(pixel_y, 2)})\")\n",
450 "# Zoom in, set limits from plotted pixel\n",
452 "plt.xlim(pixel_x-offset, pixel_x+offset) # Note different +/- bc origin is topright\n",
453 "plt.ylim(pixel_y+offset,pixel_y-offset)\n",
455 "# Plot 4 points bracketing target pixel\n",
456 "x1, y1=np.floor(pixel_x), np.floor(pixel_y)\n",
457 "x2, y2=np.floor(pixel_x), np.ceil(pixel_y)\n",
458 "x3, y3=np.ceil(pixel_x), np.floor(pixel_y)\n",
459 "x4, y4=np.ceil(pixel_x), np.ceil(pixel_y)\n",
461 "plt.plot(x1,y1, marker='o', color='red', markersize=4, label='Bracketing Pixels')\n",
462 "plt.plot(x2,y2, marker='o', color='red', markersize=4)\n",
463 "plt.plot(x3,y3, marker='o', color='red', markersize=4)\n",
464 "plt.plot(x4,y4, marker='o', color='red', markersize=4)\n",
466 "# find nearest L2 pixel\n",
467 "plt.plot(nearest[0],nearest[1], marker='o', color='purple', markersize=5,\n",
468 " label='Interpolated')\n",
469 "interp_val = data[nearest[1], nearest[0]]\n",
470 "plt.annotate(\"Interp. Value=\"+str(round(interp_val, 5)), xy=(x_l1, y_l1), xytext=(x_l1-1, y_l1-2), color='purple')\n",
473 "plt.title(\"L2 Nearest Neighbor\")\n",
478 "cell_type": "markdown",
479 "id": "a6c2987c-99ed-4313-85bc-47630f111775",
482 "## Average Bracketing Points\n",
484 "Given the 4 bracketing points, interpolate the mean value."
489 "execution_count": null,
490 "id": "8d0124de-dd5a-4913-acb9-b2b81ba2bfcc",
494 "plt.imshow(data)\n",
496 "# Plot pixel translation with annotations\n",
497 "plt.plot(pixel_x,pixel_y, marker='o', color='blue', markersize=6,\n",
498 " label=f\"({round(pixel_x, 2)}, {round(pixel_y, 2)})\")\n",
500 "# Zoom in, set limits from plotted pixel\n",
502 "plt.xlim(pixel_x-offset, pixel_x+offset) # Note different +/- bc origin is topright\n",
503 "plt.ylim(pixel_y+offset,pixel_y-offset)\n",
505 "# Plot 4 points bracketing target pixel\n",
506 "x1, y1=int(np.floor(pixel_x)), int(np.floor(pixel_y))\n",
507 "x2, y2=int(np.floor(pixel_x)), int(np.ceil(pixel_y))\n",
508 "x3, y3=int(np.ceil(pixel_x)), int(np.floor(pixel_y))\n",
509 "x4, y4=int(np.ceil(pixel_x)), int(np.ceil(pixel_y))\n",
511 "plt.plot(x1,y1, marker='o', color='red', markersize=4, label='Bracketing Points')\n",
512 "plt.plot(x2,y2, marker='o', color='red', markersize=4)\n",
513 "plt.plot(x3,y3, marker='o', color='red', markersize=4)\n",
514 "plt.plot(x4,y4, marker='o', color='red', markersize=4)\n",
516 "interp_val = np.mean([data[y1, x1], data[y2, x2], data[y3, x3], data[y4, 4]])\n",
517 "plt.annotate(\"Interp. Value=\"+str(round(interp_val, 5)), xy=(pixel_x, pixel_y), xytext=(pixel_x-1, pixel_y-2), color='purple')\n",
520 "plt.title(\"Average 4 Nearest Neighbors\")\n",
525 "cell_type": "markdown",
526 "id": "80322dc5-efa2-4241-8f08-ffc58828f6f2",
529 "## Scipy griddata interpolation package"
533 "cell_type": "markdown",
534 "id": "76f5146e-b6ed-4b2b-8da6-927a747c0ed0",
537 "https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html"
542 "execution_count": null,
543 "id": "361bdffc-13e8-4366-8b4e-4d9292f135ae",
547 "from scipy.interpolate import griddata"
551 "cell_type": "markdown",
552 "id": "5e969ec7-dd62-4a70-bfaf-76c70e70a02d",
555 "## WRF-Fire Method\n",
557 "The WRF-Fire interpolation method combines a nearest neighbors approach and a linear interpolation. The steps are:\n",
558 "* Find nearest neighbor\n",
559 "* Construct 3x3 array centered at nearest neighbor\n",
560 "* Run linear interpolation (matlab's `scatteredInterpolant` method)\n",
562 "The python equivalent of `scatteredInterpolant` is `scipy.interpolate.griddata` (according to StackOverflow and ChatGPT).\n",
564 "WRF-Fire Sources:\n",
566 "https://github.com/openwfm/wrf-fire-matlab/blob/master/vis/ts_at.m\n",
568 "https://github.com/openwfm/wrf-fire-matlab/blob/master/vis/ts_at_test.m\r\n"
572 "cell_type": "markdown",
573 "id": "6da0597f-b393-4d27-9e2a-e522bf8506f3",
576 "## Validate Geolocation\n",
580 "* create two fake geotiff\n",
581 "* create data array with known lon/lat values\n",
582 "* evaluate and you should get coord back exactly\n",
586 "* Convert pixel indices (e.g. (0,0), (0,1)...) to lat lon\n",
587 "* Save lats and lons in array of same dimension as raster band\n",
588 "* Save as geotiff\n",
589 "* Read that file back in to do steps above\n",
592 "Modified from Source: https://stackoverflow.com/questions/59052516/find-lat-long-coordinates-from-pixel-point-in-geotiff-using-python-and-gdaltly"
597 "execution_count": null,
598 "id": "7d3560ad-6e14-4686-8ca3-cac6816b77e4",
602 "# Generate Arrays of pixel indices\n",
603 "lons = np.zeros((1059, 1799))\n",
604 "lats = np.zeros((1059, 1799))"
609 "execution_count": null,
610 "id": "b356ed24-b99c-4a3c-b783-9b708a34555c",
614 "from osgeo import osr, ogr, gdal\n",
616 "def pixel_to_world(geo_matrix, x, y):\n",
617 " # Given geotransform info of a geotiff file and an (x,y) pixel coord pair, return the coord pair that matches the geotiff in meters\n",
619 " # geomatrix: output of ds.GetGeoTransform() for given geotiff file\n",
620 " # tuple of length 6 contains: \n",
621 " # A geotransform consists in a set of 6 coefficients\n",
622 " # GT(0) x-coordinate of the upper-left corner of the upper-left pixel.\n",
623 " # GT(1) w-e pixel resolution / pixel width.\n",
624 " # GT(2) row rotation (typically zero).\n",
625 " # GT(3) y-coordinate of the upper-left corner of the upper-left pixel.\n",
626 " # GT(4) column rotation (typically zero).\n",
627 " # GT(5) n-s pixel resolution / pixel height (negative value for a north-up image).\n",
628 " # x: pixel index x coord (1)\n",
629 " # y: pixel index y coord (1)\n",
630 " # Return: coordinates of same point as given x,y as offset from UL (m)\n",
631 " # Example: pixel_to_world(mat, 0, 0) returns UL x,y from geotiff\n",
633 " ul_x = geo_matrix[0]\n",
634 " ul_y = geo_matrix[3]\n",
635 " x_dist = geo_matrix[1]\n",
636 " y_dist = geo_matrix[5]\n",
637 " _x = x * x_dist + ul_x\n",
638 " _y = y * y_dist + ul_y\n",
642 "def build_transform_inverse(dataset, EPSG):\n",
643 " # Given gdal dataset and target EPSG, return transformation function that transforms meter coord pairs to pixel coord pairs \n",
645 " # dataset: geotiff file\n",
646 " # EPSG: integer\n",
647 " source = osr.SpatialReference(wkt=dataset.GetProjection())\n",
648 " target = osr.SpatialReference()\n",
649 " target.ImportFromEPSG(EPSG)\n",
650 " return osr.CoordinateTransformation(source, target)\n",
652 "def world_to_epsg(wx, wy, trans):\n",
654 " # wx, wy: output of build_transform_inverse\n",
655 " # wx: x coordinate (m) related to geotiff reference point\n",
656 " # wy: y coordinate (m) related to geotiff reference point\n",
657 " # transform: function to transform to given epsg, function type is osgeo.osr.CoordinateTransformation\n",
659 " # point from osgeo Geometry object\n",
660 " point = ogr.Geometry(ogr.wkbPoint)\n",
661 " point.AddPoint(wx, wy)\n",
662 " point.Transform(trans)\n",
665 "def find_spatial_coordinate_from_pixel(dataset, x, y, transform=None, epsg=4326):\n",
666 " # Given gdal dataset, target x y pixel pair, and EPSG, return the EPSG defined coordinate pair \n",
667 " # dataset: gdal dataset, from geotiff file\n",
668 " # x (int): pixel x index \n",
669 " # y (int): pixel y index \n",
670 " ## Upper left corner is often (0,0)\n",
671 " # transform: transform inverse. output of build_transform_inverse, default none and it calculates from epsg\n",
672 " # supply transform to save computational time\n",
673 " # epsg: default 4326 (WGS84)\n",
674 " # Return: coord pair in given epsg, eg lat/lon (floats)\n",
675 " if transform is None:\n",
676 " transform = build_transform_inverse(ds, epsg)\n",
677 " world_x, world_y = pixel_to_world(dataset.GetGeoTransform(), x, y)\n",
678 " point = world_to_epsg(world_x, world_y, transform)\n",
679 " return point.GetX(), point.GetY()\n",
681 "ds = gdal.Open(tfile)"
685 "cell_type": "markdown",
686 "id": "2f86b706-7eea-49d5-8e32-c5f332b7f439",
689 "We want this process to match information from the gdalinfo in the geotiff file. The command line `gdalinfo hrrr.t00z.wrfprsf00.616.tif` returns info on the corner coordinates and center (not (0,0) for some reason). The following output should thus match the return of that command.\n",
691 "`gdalinfo` returns coordinates in the \"degrees minute second\" format, so we need to convert decimal degrees to this format to make sure it matches. \n"
696 "execution_count": null,
697 "id": "9a278659-2196-4055-8f0f-9f79196d8c26",
701 "## Test world_to_epsg\n",
702 "## For 'center' in file (-520.143, -306.153), not zero for some reason\n",
703 "## Should return ( 97d30'21.52\"W, 38d29'50.09\"N)\n",
704 "trans = build_transform_inverse(ds, 4326)\n",
705 "pt = world_to_epsg(-520.143, -306.153, trans)\n",
706 "print(f\"({pt.GetX()},{pt.GetY()})\")"
711 "execution_count": null,
712 "id": "58d1ff37-dbc2-45dd-9b09-c13b6e39a6c6",
716 "## Test on a corner coords\n",
717 "print(find_spatial_coordinate_from_pixel(ds, 0, 0)) # upper left\n",
718 "print(find_spatial_coordinate_from_pixel(ds, 0, 1058)) # upper right\n",
719 "print(find_spatial_coordinate_from_pixel(ds, 1798, 0)) # lower left\n",
720 "print(find_spatial_coordinate_from_pixel(ds, 1798, 1058)) # lower right"
724 "cell_type": "markdown",
725 "id": "80950022-acbb-4c79-a544-a5c805e28da4",
728 "`gdalinfo` returns coordinates with the format \"degree minute second(direction)\", so we convert the values from `gdalinfo` to check...\n",
730 "Source: stackexchange ___"
735 "execution_count": null,
736 "id": "d0fc3849-1dac-4fc0-b2a6-9cf270932fff",
740 "def decdeg2dms(dd, nsec=4):\n",
741 " # Given decimal degree, turn Degree Minute Second format\n",
742 " # dd: decimal lat/lon coord\n",
743 " # nsec: number of digits to round second\n",
744 " mult = -1 if dd < 0 else 1\n",
745 " mnt,sec = divmod(abs(dd)*3600, 60)\n",
746 " deg,mnt = divmod(mnt, 60)\n",
749 " sec=np.round(mult*sec, nsec)\n",
751 " date_str = f\"{deg}d {mnt}'{sec}\"\n",
756 "cell_type": "markdown",
757 "id": "9050dd35-202d-44f8-ab6f-744910839515",
760 "Check on coordinates listed in `gdalinfo`\n",
762 "Using coords in meters, convert first with `world_to_epsg` then evaluate with `decdeg2dms`. Note: negative coordinates correspond to degrees West or degrees South from point of origin (in HRRR files the origin is in continental US)."
767 "execution_count": null,
768 "id": "6ab633c2-bfa4-41b3-b7b0-1f8646261414",
772 "# Print lon/lat coords\n",
773 "ul = world_to_epsg(-2699020.143, 1588193.847, trans) # Upper left\n",
774 "print(f\"({ul.GetX()},{ul.GetY()})\")\n",
775 "ll = world_to_epsg(-2699020.143,-1588806.153, trans) # Lower left\n",
776 "print(f\"({ll.GetX()},{ll.GetY()})\")\n",
777 "ur = world_to_epsg(2697979.857, 1588193.847, trans) # Upper right\n",
778 "print(f\"({ur.GetX()},{ur.GetY()})\")\n",
779 "lr = world_to_epsg(2697979.857,-1588806.153, trans) # Lower right\n",
780 "print(f\"({lr.GetX()},{lr.GetY()})\")\n",
781 "center = world_to_epsg(-520.143, -306.153, trans) # center\n",
782 "print(f\"({center.GetX()},{center.GetY()})\")"
787 "execution_count": null,
788 "id": "ab76e16c-c5ad-420a-b43a-5c63d1b6a20e",
792 "# Convert to dms\n",
793 "print(f\"({decdeg2dms(ul.GetY())}, {decdeg2dms(ul.GetX())})\")\n",
794 "print(f\"({decdeg2dms(ll.GetY())}, {decdeg2dms(ll.GetX())})\")\n",
795 "print(f\"({decdeg2dms(ur.GetY())}, {decdeg2dms(ur.GetX())})\")\n",
796 "print(f\"({decdeg2dms(lr.GetY())}, {decdeg2dms(lr.GetX())})\")\n",
797 "print(f\"({decdeg2dms(center.GetY())}, {decdeg2dms(center.GetX())})\")"
801 "cell_type": "markdown",
802 "id": "8c769665-6014-4f7c-b83b-31fc67e9a658",
805 "### Build dummy geotiff files\n",
807 "Create 2 geotiff files with latitude/longitude values corresponding to grid pixel locations."
812 "execution_count": null,
813 "id": "a9944588-50df-4f47-8af3-cdf4727cfd1a",
817 "# dimensions of numpy ndarray stored as values of geotiff file\n",
818 "print(np.shape(data)) "
823 "execution_count": null,
824 "id": "d084fb21-0de9-4b27-b7ce-aa9f70afba25",
828 "# Initialize empty arrays\n",
829 "lons=np.zeros(np.shape(data))\n",
830 "lats=np.zeros(np.shape(data))"
835 "execution_count": null,
836 "id": "d762a7d7-d322-4063-8704-2a5cd7ebb684",
840 "# get transformation once and reuse\n",
841 "transform = build_transform_inverse(ds, EPSG=4326)\n",
842 "# Loop over indices and fill\n",
843 "for i in range(0, np.shape(lons)[0]): # iterate i over x coord (longitude)\n",
844 " for j in range(0, np.shape(lons)[1]): # iterate j over y coord (latitude)\n",
845 " coord = find_spatial_coordinate_from_pixel(ds, j, i, transform=transform) # note order flip is intentional\n",
846 " lats[i,j]=coord[0]\n",
847 " lons[i,j]=coord[1]"
851 "cell_type": "markdown",
852 "id": "b4efef93-bee9-4c87-a353-f86eab767954",
855 "Function to write geotiff file.\n",
857 "source: https://here.isnew.info/how-to-save-a-numpy-array-as-a-geotiff-file-using-gdal.html"
862 "execution_count": null,
863 "id": "927270f5-af27-4bbd-a9a9-8f4f73ddfc16",
867 "def write_geotiff(filename, arr, in_ds): \n",
868 " # Given file name, data array, and reference gdal Dataset, write data array as geotiff file with geolocation from reference\n",
870 " # filename: output file name, expecting .tif extension\n",
871 " # arr: numpy ndarray\n",
872 " # in_ds: gdal dataset with geolocation info\n",
874 " print(\"Writing \"+filename)\n",
876 " if arr.dtype == np.float32:\n",
877 " arr_type = gdal.GDT_Float32\n",
879 " arr_type = gdal.GDT_Int32\n",
881 " driver = gdal.GetDriverByName(\"GTiff\")\n",
882 " out_ds = driver.Create(filename, arr.shape[1], arr.shape[0], 1, arr_type)\n",
883 " out_ds.SetProjection(in_ds.GetProjection())\n",
884 " out_ds.SetGeoTransform(in_ds.GetGeoTransform())\n",
885 " band = out_ds.GetRasterBand(1)\n",
886 " band.WriteArray(arr)\n",
887 " band.FlushCache()\n",
888 " band.ComputeStatistics(False)"
893 "execution_count": null,
894 "id": "c22e54fb-e65c-4cf7-87c8-0af326300943",
898 "# Write output as geotiff file\n",
899 "# osp.join(os.getcwd(), \"lons.tif\")\n",
900 "# osp.join(os.getcwd(), \"lats.tif\")\n",
901 "write_geotiff(osp.join(os.getcwd(), \"lons.tif\"), lons, ds)\n",
902 "write_geotiff(osp.join(os.getcwd(), \"lats.tif\"), lats, ds)"
906 "cell_type": "markdown",
907 "id": "6b2230db-0699-4152-aa53-fbc87a928139",
910 "### Validate geolocation procedure\n",
912 "Using geotiff files written above, use the procedure for plucking nearest points on the lat/lon pairs from RAWS stations. Given input lat/lon, expect the same values back."
917 "execution_count": null,
918 "id": "a4a4e32a-9131-412d-8fde-3a251ba465dd",
922 "ds_lon = gdal.Open(osp.join(os.getcwd(), \"lons.tif\"))\n",
923 "ds_lat = gdal.Open(osp.join(os.getcwd(), \"lats.tif\"))"
928 "execution_count": null,
929 "id": "d2664341-88d4-4a26-a417-eeb3f9322306",
933 "ds = gdal.Open(tfile)\n",
934 "gt = ds_lon.GetGeoTransform()\n",
935 "gp = ds_lon.GetProjection()\n",
937 "# Check values the same for lat file\n",
938 "print('Geotrans matches: '+str(gt == ds_lon.GetGeoTransform()))\n",
939 "print('Proj matches: '+str(gp == ds_lat.GetProjection()))"
944 "execution_count": null,
945 "id": "cb48ccd8-bb12-4e0d-81ea-48eeb29d7504",
949 "# Get Projection info\n",
950 "point_srs = osr.SpatialReference()\n",
951 "point_srs.ImportFromEPSG(4326) # hardcode for lon/lat\n",
952 "# GDAL>=3: make sure it's x/y\n",
953 "# see https://trac.osgeo.org/gdal/wiki/rfc73_proj6_wkt2_srsbarn\n",
954 "point_srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) \n",
955 "file_srs = osr.SpatialReference()\n",
956 "file_srs.ImportFromWkt(gp)\n",
957 "ct = osr.CoordinateTransformation(point_srs, file_srs)"
962 "execution_count": null,
963 "id": "e65462e0-7a33-40c8-9eb7-4a3c793652aa",
967 "lons = ds_lon.GetRasterBand(1).ReadAsArray()\n",
968 "lats = ds_lat.GetRasterBand(1).ReadAsArray()"
972 "cell_type": "markdown",
973 "id": "8140fcb7-fc99-425d-84bf-74e0014731a6",
976 "Loop through some RAWS sites. Print out known lat/lon coord and then the value from the geotiff files, expect the output to be the same."
981 "execution_count": null,
982 "id": "8e093ec2-2db2-4d6c-bd1c-db906312c99a",
986 "# Helper function to define nearest neighbor approach\n",
987 "def interp_l1(x, y):\n",
992 "# loop through first N station coords\n",
993 "for i in range(0, 10):\n",
994 " print(\"~\"*35)\n",
995 " point_x = sts['lon'][i] # lon\n",
996 " point_y = sts['lat'][i] # lat\n",
997 " print(f\"RAWS Station {sts['STID'][i]} lon/lat: ({point_x}, {point_y})\")\n",
999 " mapx, mapy, z = ct.TransformPoint(np.float64(point_x), np.float64(point_y))\n",
1000 " gt_inv = gdal.InvGeoTransform(gt)\n",
1001 " pixel_x, pixel_y = gdal.ApplyGeoTransform(gt_inv, mapx, mapy)\n",
1002 " x, y = interp_l1(pixel_x, pixel_y)\n",
1003 " print(f\"Fitted: ({lons[y, x]}, {lats[y,x]})\")\n"
1007 "cell_type": "code",
1008 "execution_count": null,
1009 "id": "119b2302-1aef-4d6c-bb82-fb512a58b9a4",
1015 "cell_type": "code",
1016 "execution_count": null,
1017 "id": "6e40fd62-31e2-461f-b32a-bdc5a26a9e2b",
1023 "cell_type": "code",
1024 "execution_count": null,
1025 "id": "aa402e2c-318a-4041-a20b-61f3d941aa54",
1033 "display_name": "Python 3 (ipykernel)",
1034 "language": "python",
1038 "codemirror_mode": {
1042 "file_extension": ".py",
1043 "mimetype": "text/x-python",
1045 "nbconvert_exporter": "python",
1046 "pygments_lexer": "ipython3",