|
| 1 | +import argparse |
| 2 | +import glob |
| 3 | +from importlib import import_module |
| 4 | +import os |
| 5 | +import sys |
| 6 | + |
| 7 | +named_libs = [('fiona', 'fiona'), ('geopandas', 'gpd'), ('pandas', 'pd')] |
| 8 | + |
| 9 | +for (name, short) in named_libs: |
| 10 | + try: |
| 11 | + lib = import_module(name) |
| 12 | + except: |
| 13 | + print(sys.exc_info()) |
| 14 | + else: |
| 15 | + globals()[short] = lib |
| 16 | + |
| 17 | +from osgeo import osr |
| 18 | +from shapely.geometry import Polygon |
| 19 | +from shapely import wkt |
| 20 | + |
| 21 | +from s1reader.s1_orbit import get_orbit_file_from_list |
| 22 | +from s1reader.s1_reader import load_bursts |
| 23 | + |
| 24 | + |
| 25 | +def command_line_parser(): |
| 26 | + ''' |
| 27 | + Command line parser |
| 28 | + ''' |
| 29 | + parser = argparse.ArgumentParser(description=""" |
| 30 | + Create a burst map for a single slc""", |
| 31 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| 32 | + parser.add_argument('-s', '--slc', type=str, action='store', |
| 33 | + dest='slc', |
| 34 | + help="slc to map") |
| 35 | + parser.add_argument('-d', '--orbit-dir', type=str, dest='orbit_dir', |
| 36 | + help="Directory containing orbit files") |
| 37 | + parser.add_argument('-x', '--x-spacing', type=float, default=5, |
| 38 | + dest='x_spacing', |
| 39 | + help='Spacing of the geogrid in x direction') |
| 40 | + parser.add_argument('-y', '--y-spacing', type=float, default=10, |
| 41 | + dest='y_spacing', |
| 42 | + help='Spacing of the geogrid in y direction') |
| 43 | + parser.add_argument('-e', '--epsg', type=int, dest='epsg', |
| 44 | + help='EPSG for output coordinates') |
| 45 | + parser.add_argument('-o', '--output', type=str, default='burst_map.gpkg', |
| 46 | + dest='output', |
| 47 | + help='Base filename for all output burst map products') |
| 48 | + return parser.parse_args() |
| 49 | + |
| 50 | + |
| 51 | +def burst_map(slc, orbit_dir, x_spacing, |
| 52 | + y_spacing, epsg, |
| 53 | + output_filename): |
| 54 | + """ |
| 55 | + Create a CSV of SLC metadata and plot bursts |
| 56 | + Parameters: |
| 57 | + slc: str |
| 58 | + Path to SLC file |
| 59 | + orbit_dir: str |
| 60 | + Path to directory containing orbit files |
| 61 | + x_spacing: float |
| 62 | + Spacing of the geogrid in the x direction |
| 63 | + y_spacing: float |
| 64 | + Spacing to the geogrid in the y direction |
| 65 | + epsg: int |
| 66 | + EPSG code for the output coodrdinates |
| 67 | + output_filename: str |
| 68 | + Filename used for the output CSV, shp, html, and kml files |
| 69 | + |
| 70 | + Returns: |
| 71 | + output_filename.csv, output_filename.shp, output_filename.html, output_filename.kml |
| 72 | + """ |
| 73 | + |
| 74 | + # Initialize dictionary that will contain all the info for geocoding |
| 75 | + burst_map = {'burst_id':[], 'length': [], 'width': [], |
| 76 | + 'spacing_x': [], 'spacing_y':[], 'min_x': [], |
| 77 | + 'max_x': [], 'min_y': [], 'max_y': [], 'first_valid_line': [], |
| 78 | + 'last_valid_line':[], 'first_valid_sample':[], 'last_valid_sample':[], |
| 79 | + 'border':[]} |
| 80 | + i_subswath = [1, 2, 3] |
| 81 | + pol = 'vv' |
| 82 | + orbit_list = glob.glob(f'{orbit_dir}/*EOF') |
| 83 | + orbit_path = get_orbit_file_from_list(slc, orbit_list) |
| 84 | + |
| 85 | + for subswath in i_subswath: |
| 86 | + ref_bursts = load_bursts(slc, orbit_path, subswath, pol) |
| 87 | + for burst in ref_bursts: |
| 88 | + burst_map['burst_id'].append(burst.burst_id) |
| 89 | + burst_map['length'].append(burst.shape[0]) |
| 90 | + burst_map['width'].append(burst.shape[1]) |
| 91 | + burst_map['spacing_x'].append(x_spacing) |
| 92 | + burst_map['spacing_y'].append(y_spacing) |
| 93 | + burst_map['first_valid_line'].append(burst.first_valid_line) |
| 94 | + burst_map['last_valid_line'].append(burst.last_valid_line) |
| 95 | + burst_map['first_valid_sample'].append(burst.first_valid_sample) |
| 96 | + burst_map['last_valid_sample'].append(burst.last_valid_sample) |
| 97 | + |
| 98 | + poly = burst.border[0] |
| 99 | + # Give some margin to the polygon |
| 100 | + margin = 0.001 |
| 101 | + poly = poly.buffer(margin) |
| 102 | + burst_map['border'].append(Polygon(poly.exterior.coords).wkt) |
| 103 | + |
| 104 | + # Transform coordinates from lat/long to EPSG |
| 105 | + llh = osr.SpatialReference() |
| 106 | + llh.ImportFromEPSG(4326) |
| 107 | + tgt = osr.SpatialReference() |
| 108 | + |
| 109 | + tgt_x, tgt_y = [], [] |
| 110 | + x, y = poly.exterior.coords.xy |
| 111 | + tgt.ImportFromEPSG(int(epsg)) |
| 112 | + trans = osr.CoordinateTransformation(llh, tgt) |
| 113 | + for lx, ly in zip(x, y): |
| 114 | + dummy_y, dummy_x, dummy_z = trans.TransformPoint(ly, lx, 0) |
| 115 | + tgt_x.append(dummy_x) |
| 116 | + tgt_y.append(dummy_y) |
| 117 | + |
| 118 | + if epsg == 4326: |
| 119 | + x_min = x_spacing * (min(tgt_x) / x_spacing) |
| 120 | + y_min = y_spacing * (min(tgt_y) / y_spacing) |
| 121 | + x_max = x_spacing * (max(tgt_x) / x_spacing) |
| 122 | + y_max = y_spacing * (max(tgt_y) / y_spacing) |
| 123 | + else: |
| 124 | + x_min = x_spacing * round(min(tgt_x) / x_spacing) |
| 125 | + y_min = y_spacing * round(min(tgt_y) / y_spacing) |
| 126 | + x_max = x_spacing * round(max(tgt_x) / x_spacing) |
| 127 | + y_max = y_spacing * round(max(tgt_y) / y_spacing) |
| 128 | + |
| 129 | + # Allocate coordinates inside the dictionary |
| 130 | + burst_map['min_x'].append(x_min) |
| 131 | + burst_map['min_y'].append(y_min) |
| 132 | + burst_map['max_x'].append(x_max) |
| 133 | + burst_map['max_y'].append(y_max) |
| 134 | + |
| 135 | + # Save generated burst map as csv |
| 136 | + data = pd.DataFrame.from_dict(burst_map) |
| 137 | + data.to_csv(f'{output_filename}.csv') |
| 138 | + |
| 139 | + # Create GeoDataFrame to plot bursts on a map |
| 140 | + df = data |
| 141 | + df['border'] = df['border'].apply(wkt.loads) |
| 142 | + gdf = gpd.GeoDataFrame(df, crs='epsg:4326') |
| 143 | + gdf = gdf.rename(columns={'border': 'geometry'}).set_geometry('geometry') |
| 144 | + |
| 145 | + # Save the GeoDataFrame as a shapefile (some people may prefer the format) |
| 146 | + gdf.to_file(f'{output_filename}.shp') |
| 147 | + |
| 148 | + # Save the GeoDataFrame as a kml |
| 149 | + kml_path = f'{output_filename}.kml' |
| 150 | + if os.path.isfile(kml_path): |
| 151 | + os.remove(kml_path) |
| 152 | + |
| 153 | + fiona.supported_drivers['KML'] = 'rw' |
| 154 | + gdf.to_file(f'{output_filename}.kml', driver='KML') |
| 155 | + |
| 156 | + |
| 157 | + # Plot bursts on an interactive map |
| 158 | + m = gdf.explore( |
| 159 | + column="burst_id", # make choropleth based on "Burst ID" column |
| 160 | + tooltip="burst_id", # show "Burst ID" value in tooltip (on hover) |
| 161 | + popup=True, # show all values in popup (on click) |
| 162 | + tiles="CartoDB positron", # use "CartoDB positron" tiles |
| 163 | + cmap="Set1", # use "Set1" matplotlib colormap |
| 164 | + style_kwds=dict(color="black") # use black outline |
| 165 | + ) |
| 166 | + |
| 167 | + m.save(f'{output_filename}.html') |
| 168 | + |
| 169 | + |
| 170 | +if __name__ == '__main__': |
| 171 | + cmd = command_line_parser() |
| 172 | + burst_map(cmd.slc, cmd.orbit_dir, |
| 173 | + cmd.x_spacing, cmd.y_spacing, |
| 174 | + cmd.epsg, cmd.output) |
0 commit comments