|
5 | 5 |
|
6 | 6 | @author: avanetten |
7 | 7 |
|
| 8 | +See: |
8 | 9 | https://github.com/SpaceNetChallenge/utilities/blob/spacenetV3/spacenetutilities/geoTools.py |
9 | | -
|
10 | 10 | https://gis.stackexchange.com/questions/127427/transforming-shapely-polygon-and-multipolygon-objects |
11 | | -
|
12 | 11 | """ |
13 | 12 |
|
14 | | - |
15 | | -import os |
16 | 13 | import gdal |
17 | 14 | import geopandas as gpd |
18 | 15 | import shapely |
|
23 | 20 | import pandas as pd |
24 | 21 | import numpy as np |
25 | 22 | import time |
26 | | -#import json |
| 23 | +# import json |
| 24 | +# import os |
| 25 | + |
| 26 | + |
| 27 | +############################################################################### |
| 28 | +def geomPixel2geomGeo(shapely_geom, affineObject=[], |
| 29 | + input_raster='', gdal_geomTransform=[]): |
| 30 | + |
| 31 | + """ |
| 32 | + Transform a shapely geometry from pixel to geospatial coordinates |
| 33 | +
|
| 34 | + Arguments |
| 35 | + --------- |
| 36 | + shapely_geom : shapely.geometry |
| 37 | + Input geometry in pixel coordinates |
| 38 | + affineObject : affine Object ? |
| 39 | + Object that is used to transform geometries (Preferred method). |
| 40 | + This argument supercedes input_raster. Defaults to ``[]``. |
| 41 | + input_raster : str |
| 42 | + Path to raster to gather georectification information. If string is |
| 43 | + empty, use gdal_geomTransform. Defaults to ``''``. |
| 44 | + gdal_geomTransform : gdal transform? |
| 45 | + The geographic transform of the image. Defaults to ``[]``. |
| 46 | +
|
| 47 | + Returns |
| 48 | + ------- |
| 49 | + geomTransform : shapely transform? |
| 50 | + Geometric transform. |
| 51 | + """ |
| 52 | + |
| 53 | + if not affineObject: |
| 54 | + if input_raster != '': |
| 55 | + affineObject = rio.open(input_raster).transform |
| 56 | + else: |
| 57 | + affineObject = af.Affine.from_gdal(gdal_geomTransform) |
| 58 | + |
| 59 | + if not gdal_geomTransform: |
| 60 | + gdal_geomTransform = gdal.Open(input_raster).GetGeoTransform() |
| 61 | + |
| 62 | + geomTransform = shapely.affinity.affine_transform(shapely_geom, |
| 63 | + [affineObject.a, |
| 64 | + affineObject.b, |
| 65 | + affineObject.d, |
| 66 | + affineObject.e, |
| 67 | + affineObject.xoff, |
| 68 | + affineObject.yoff] |
| 69 | + ) |
| 70 | + |
| 71 | + return geomTransform |
| 72 | + |
| 73 | + |
| 74 | +############################################################################### |
| 75 | +def get_row_geo_coords(row, affineObject=[], gdal_geomTransform=[], |
| 76 | + inProj_str='epsg:4326', outProj_str='epsg:3857', |
| 77 | + verbose=False): |
| 78 | + |
| 79 | + """ |
| 80 | + Get geo coords for SIMRSWN dataframe row containing pixel coords. |
| 81 | +
|
| 82 | + Arguments |
| 83 | + --------- |
| 84 | + row : pandas dataframe |
| 85 | + Row in the pandas dataframe |
| 86 | + affineObject : affine Object ? |
| 87 | + Object that is used to transform geometries. Defaults to ``[]``. |
| 88 | + gdal_geomTransform : gdal transform? |
| 89 | + The geographic transform of the image. Defaults to ``[]``. |
| 90 | + inProj_str : str |
| 91 | + Projection of input image. Defaults to ``'epsg:4326'`` (WGS84) |
| 92 | + outProj_str : str |
| 93 | + Desired projection of bounding box coordinate. |
| 94 | + Defaults to ``'epsg:3857'`` (WGS84 Web Mercator for mapping). |
| 95 | + verbose : boolean |
| 96 | + Switch to print values to screen. Defaults to ``False``. |
| 97 | +
|
| 98 | + Returns |
| 99 | + ------- |
| 100 | + out_arr, poly_geo : tuple |
| 101 | + out_arr is a list of geo coordinates computd from pixels coords: |
| 102 | + [lon0, lat0, lon1, lat1, x0_wmp, y0_wmp, x1_wmp, y1_wmp] |
| 103 | + poly_geo is the shapely polygon in geo coords of the box |
| 104 | + """ |
| 105 | + |
| 106 | + # convert latlon to wmp |
| 107 | + inProj = pyproj.Proj(init=inProj_str) # (init='epsg:4326') |
| 108 | + outProj = pyproj.Proj(init=outProj_str) # init='epsg:3857') |
| 109 | + |
| 110 | + raster_loc = row['Image_Path'] |
| 111 | + x0, y0 = row['Xmin_Glob'], row['Ymin_Glob'] |
| 112 | + x1, y1 = row['Xmax_Glob'], row['Ymax_Glob'] |
| 113 | + if verbose: |
| 114 | + print("idx, x0, y0, x1, y1:", row.values[0], x0, y0, x1, y1) |
| 115 | + |
| 116 | + poly = shapely.geometry.Polygon([(x0, y0), (x0, y1), (x1, y1), (x1, y0)]) |
| 117 | + # transform |
| 118 | + t01 = time.time() |
| 119 | + poly_geo = geomPixel2geomGeo(poly, input_raster=raster_loc, |
| 120 | + affineObject=affineObject, |
| 121 | + gdal_geomTransform=gdal_geomTransform) |
| 122 | + # print ("geo_coords.coords:", list(poly_geo.exterior.coords)) |
| 123 | + t02 = time.time() |
| 124 | + if verbose: |
| 125 | + print(" Time to compute transform:", t02-t01, "seconds") |
| 126 | + |
| 127 | + # x-y bounding box is a (minx, miny, maxx, maxy) tuple. |
| 128 | + lon0, lat0, lon1, lat1 = poly_geo.bounds |
| 129 | + # wkt_latlon = poly_geo.wkt |
| 130 | + if verbose: |
| 131 | + print(" lon0, lat0, lon1, lat1:", lon0, lat0, lon1, lat1) |
| 132 | + |
| 133 | + # convert to other coords?: |
| 134 | + # https://gis.stackexchange.com/questions/78838/converting-projected-coordinates-to-lat-lon-using-python |
| 135 | + # https://openmaptiles.com/coordinate-systems/ |
| 136 | + # https://ocefpaf.github.io/python4oceanographers/blog/2013/12/16/utm/ |
| 137 | + # Web Mercator projection (EPSG:3857) |
| 138 | + # convert to wmp |
| 139 | + x0_wmp, y0_wmp = pyproj.transform(inProj, outProj, lon0, lat0) |
| 140 | + x1_wmp, y1_wmp = pyproj.transform(inProj, outProj, lon1, lat1) |
| 141 | + |
| 142 | + # create data array |
| 143 | + out_arr = [lon0, lat0, lon1, lat1, x0_wmp, y0_wmp, x1_wmp, y1_wmp] |
| 144 | + |
| 145 | + return out_arr, poly_geo |
| 146 | + |
27 | 147 |
|
28 | 148 | ############################################################################### |
29 | | -def add_geo_coords_to_df(df_, create_geojson=False, |
30 | | - inProj_str='epsg:4326', outProj_str='epsg:3857', |
31 | | - verbose=False): |
| 149 | +def add_geo_coords_to_df(df_, inProj_str='epsg:4326', outProj_str='epsg:3857', |
| 150 | + create_geojson=False, verbose=False): |
32 | 151 | '''Determine geo coords (latlon + wmp) of the bounding boxes''' |
33 | 152 |
|
34 | | - |
35 | | - # Placeholder |
36 | | - return df_, [] |
37 | | - |
| 153 | + """ |
| 154 | + Get geo coords for SIMRSWN dataframe containing pixel coords. |
| 155 | +
|
| 156 | + Arguments |
| 157 | + --------- |
| 158 | + df_ : pandas dataframe |
| 159 | + Dataframe containing bounding box predictions |
| 160 | + inProj_str : str |
| 161 | + Projection of input image. Defaults to ``'epsg:4326'`` (WGS84) |
| 162 | + outProj_str : str |
| 163 | + Desired projection of bounding box coordinate. |
| 164 | + Defaults to ``'epsg:3857'`` (WGS84 Web Mercator for mapping). |
| 165 | + create_geojson : boolean |
| 166 | + Switch to output a geojson. If False, return []. |
| 167 | + Defaults to ``False``. |
| 168 | + verbose : boolean |
| 169 | + Switch to print values to screen. Defaults to ``False``. |
| 170 | +
|
| 171 | + Returns |
| 172 | + ------- |
| 173 | + df_, json : tuple |
| 174 | + df_ is the updated dataframe with geo coords. |
| 175 | + json is the optional GeoJSON corresponding to the boxes. |
| 176 | + """ |
| 177 | + |
| 178 | + t0 = time.time() |
| 179 | + print("Adding geo coords...") |
| 180 | + |
| 181 | + # first, create a dictionary of geo transforms |
| 182 | + raster_locs = np.unique(df_['Image_Path'].values) |
| 183 | + geo_dict = {} |
| 184 | + for raster_loc in raster_locs: |
| 185 | + # raster geo transform |
| 186 | + gdal_geomTransform = gdal.Open(raster_loc).GetGeoTransform() |
| 187 | + affineObject = rio.open(raster_loc).transform |
| 188 | + if verbose: |
| 189 | + print("raster_loc:", raster_loc) |
| 190 | + print("gdal_geomTransform:", gdal_geomTransform) |
| 191 | + print("affineObject:", affineObject) |
| 192 | + geo_dict[raster_loc] = [gdal_geomTransform, affineObject] |
| 193 | + |
| 194 | + # iterate through dataframe |
| 195 | + # columns = ['geometry'] |
| 196 | + out_arr_json = [] |
| 197 | + out_arr = [] |
| 198 | + for idx, row in df_.iterrows(): |
| 199 | + # get transform |
| 200 | + [gdal_geomTransform, affineObject] = geo_dict[row['Image_Path']] |
| 201 | + if verbose: |
| 202 | + print(idx, " row['Xmin_Glob'], row['Ymin_Glob'],", |
| 203 | + "row['Xmax_Glob'], row['Ymax_Glob']", |
| 204 | + row['Xmin_Glob'], row['Ymin_Glob'], |
| 205 | + row['Xmax_Glob'], row['Ymax_Glob']) |
| 206 | + |
| 207 | + out_arr_row, poly_geo = get_row_geo_coords( |
| 208 | + row, affineObject=affineObject, |
| 209 | + gdal_geomTransform=gdal_geomTransform, |
| 210 | + inProj_str=inProj_str, outProj_str=outProj_str, |
| 211 | + verbose=verbose) |
| 212 | + out_arr.append(out_arr_row) |
| 213 | + if create_geojson: |
| 214 | + out_arr_json.append(poly_geo) |
| 215 | + |
| 216 | + # update dataframe |
| 217 | + # [lon0, lat0, lon1, lat1, x0_wmp, y0_wmp, x1_wmp, y1_wmp] |
| 218 | + out_arr = np.array(out_arr) |
| 219 | + df_['lon0'] = out_arr[:, 0] |
| 220 | + df_['lat0'] = out_arr[:, 1] |
| 221 | + df_['lon1'] = out_arr[:, 2] |
| 222 | + df_['lat1'] = out_arr[:, 3] |
| 223 | + df_['x0_wmp'] = out_arr[:, 4] |
| 224 | + df_['y0_wmp'] = out_arr[:, 5] |
| 225 | + df_['x1_wmp'] = out_arr[:, 6] |
| 226 | + df_['y1_wmp'] = out_arr[:, 7] |
| 227 | + |
| 228 | + # geodataframe if desired |
| 229 | + # https://gis.stackexchange.com/questions/174159/convert-a-pandas-dataframe-to-a-geodataframe |
| 230 | + if create_geojson: |
| 231 | + crs_init = {'init': inProj_str} |
| 232 | + df_json = pd.DataFrame(out_arr_json, columns=['geometry']) |
| 233 | + # add important columns to df_json |
| 234 | + df_json['category'] = df_['Category'].values |
| 235 | + df_json['prob'] = df_['Prob'].values |
| 236 | + gdf = gpd.GeoDataFrame(df_json, crs=crs_init, geometry=out_arr_json) |
| 237 | + json_out = gdf |
| 238 | + # json_out = gdf.to_json() |
| 239 | + else: |
| 240 | + json_out = [] |
| 241 | + |
| 242 | + t1 = time.time() |
| 243 | + print("Time to add geo coords to df:", t1 - t0, "seconds") |
| 244 | + return df_, json_out |
0 commit comments