|
| 1 | +# IMPORTANT: Make sure you install below packages in advance |
| 2 | +#!pip install sentinelsat |
| 3 | +#!pip install rasterio |
| 4 | +#!apt install gdal-bin python-gdal python3-gdal |
| 5 | +#!apt install python3-rtree |
| 6 | +#!pip install git+git://github.com/geopandas/geopandas.git |
| 7 | +#!pip install descartes |
| 8 | +#!pip install shapely |
| 9 | +#!pip install six |
| 10 | +#!pip install pyproj |
| 11 | +#!pip install descartes |
| 12 | +#!pip install geopandas |
| 13 | +#!pip install folium |
| 14 | + |
| 15 | +import os |
| 16 | +import numpy as np |
| 17 | + |
| 18 | +from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt |
| 19 | +from shapely.geometry import MultiPolygon, Polygon |
| 20 | +from rasterio.plot import show |
| 21 | +from geojson import Polygon |
| 22 | +from osgeo import gdal |
| 23 | +from PIL import Image, ImageDraw, ImageFont |
| 24 | +import geopandas as gpd |
| 25 | +import pandas as pd |
| 26 | +import numpy as np |
| 27 | +import matplotlib.pyplot as plt |
| 28 | +import json |
| 29 | +import shutil |
| 30 | +import glob |
| 31 | + |
| 32 | +import rasterio as rio |
| 33 | +import rasterio.mask |
| 34 | +import fiona |
| 35 | +import folium |
| 36 | +import zipfile |
| 37 | + |
| 38 | +from notebook import notebookapp |
| 39 | +import urllib |
| 40 | +import ipykernel |
| 41 | + |
| 42 | +#Generate coordinate of Area of Interest by using tools from Keene University below |
| 43 | +#from IPython.display import HTML |
| 44 | +#HTML(r'<iframe width="960" height="480" src="https://www.keene.edu/campus/maps/tool/" frameborder="0"></iframe>') |
| 45 | + |
| 46 | +# Sentinel API credentials |
| 47 | +user = 'username' |
| 48 | +password = 'password' |
| 49 | +api = SentinelAPI(user, password, 'https://scihub.copernicus.eu/dhus') |
| 50 | + |
| 51 | + |
| 52 | +def sentinel2_hello(): |
| 53 | + print("Hello from Sentinel2") |
| 54 | + return |
| 55 | + |
| 56 | +# (JupyterNotebook only) Get notebookpath |
| 57 | +def notebook_path(): |
| 58 | + """Returns the absolute path of the Notebook or None if it cannot be determined |
| 59 | + NOTE: works only when the security is token-based or there is also no password |
| 60 | + """ |
| 61 | + connection_file = os.path.basename(ipykernel.get_connection_file()) |
| 62 | + kernel_id = connection_file.split('-', 1)[1].split('.')[0] |
| 63 | + |
| 64 | + for srv in notebookapp.list_running_servers(): |
| 65 | + try: |
| 66 | + if srv['token']=='' and not srv['password']: # No token and no password, ahem... |
| 67 | + req = urllib.request.urlopen(srv['url']+'api/sessions') |
| 68 | + else: |
| 69 | + req = urllib.request.urlopen(srv['url']+'api/sessions?token='+srv['token']) |
| 70 | + sessions = json.load(req) |
| 71 | + for sess in sessions: |
| 72 | + if sess['kernel']['id'] == kernel_id: |
| 73 | + return os.path.join(srv['notebook_dir'],sess['notebook']['path']) |
| 74 | + except: |
| 75 | + pass # There may be stale entries in the runtime directory |
| 76 | + return None |
| 77 | + |
| 78 | +# Convert the Polygon data to GeoJSON format |
| 79 | +def Sentinel2_convert_polygon_to_json(object_name, polygon_object): |
| 80 | + print("Converting polygon to GeoJSON...") |
| 81 | + with open(object_name +'.geojson', 'w') as f: |
| 82 | + json.dump(polygon_object, f) |
| 83 | + print(object_name +".geojson created") |
| 84 | + return |
| 85 | + |
| 86 | +## Get Sentinel Geodataframe |
| 87 | +def Sentinel2_get_productsgdf(footprint_geojson, start_date, end_date): |
| 88 | + print("Start Date: ", start_date) |
| 89 | + print("End Date: ", end_date) |
| 90 | + |
| 91 | + print("Querying Sentinel-2...") |
| 92 | + products = api.query(footprint_geojson, |
| 93 | + date = (start_date, end_date), #Desired date for the beginning and ending time of Sentinel-2 image |
| 94 | + platformname = 'Sentinel-2', |
| 95 | + processinglevel = 'Level-2A', |
| 96 | + cloudcoverpercentage = (0,70)) |
| 97 | + |
| 98 | + products_gdf = api.to_geodataframe(products) |
| 99 | + products_gdf_sorted_by_date = products_gdf.sort_values(['ingestiondate'], ascending=[True]) |
| 100 | + return products_gdf_sorted_by_date |
| 101 | + |
| 102 | +## Get Sentinel all scenes |
| 103 | +def Sentinel2_get_all_data(products_gdf, index, fontfile, object_name, start_date, resolution): |
| 104 | + print("\n---START---") |
| 105 | + products_gdf = products_gdf |
| 106 | + |
| 107 | + title = products_gdf.iloc[index]["title"] |
| 108 | + print(" Sentinel-2 title: \t", str(title)) |
| 109 | + |
| 110 | + uuid = products_gdf.iloc[index]["uuid"] |
| 111 | + product_title = products_gdf.iloc[index]["title"] |
| 112 | + |
| 113 | + ingestion_date = products_gdf.iloc[index]["ingestiondate"].strftime('%Y%m%d-%H:%M') |
| 114 | + print(" IngestionDate: \t", ingestion_date) |
| 115 | + |
| 116 | + cloudcoverpercentage = products_gdf.iloc[index]["cloudcoverpercentage"] |
| 117 | + print(" Cloud percentage: \t", cloudcoverpercentage) |
| 118 | + |
| 119 | + datasize = products_gdf.iloc[index]["size"] |
| 120 | + print(" Data size: \t", datasize) |
| 121 | + |
| 122 | + #Download Sentinel-2 data |
| 123 | + print(" Download data now...") |
| 124 | + api.download(uuid) |
| 125 | + file_name = str(product_title) +'.zip' |
| 126 | + print(" Successfully downloaded data as: \t", file_name) |
| 127 | + |
| 128 | + |
| 129 | + #Extract downloaded Sentinel-2 data |
| 130 | + print("\nExtracting data.zip file...") |
| 131 | + with zipfile.ZipFile(file_name) as zf: |
| 132 | + zf.extractall() |
| 133 | + |
| 134 | + #Get image's folder path |
| 135 | + print("\nFile identification...") |
| 136 | + print(" Resolution: \t", resolution) |
| 137 | + path = str(product_title) + '.SAFE/GRANULE' |
| 138 | + files = os.listdir(path) |
| 139 | + pathA = str(product_title) + '.SAFE/GRANULE/' + str(files[0]) |
| 140 | + files2 = os.listdir(pathA) |
| 141 | + pathB = str(product_title) + '.SAFE/GRANULE/' + str(files[0]) +'/' + str(files2[1]) +'/R' + resolution + 'm' |
| 142 | + files3 = os.listdir(pathB) |
| 143 | + |
| 144 | + |
| 145 | + path_b2 = str(product_title) + '.SAFE/GRANULE/' + str(files[0]) +'/' + str(files2[1]) +'/R' + resolution + 'm/' +str(files3[0][0:23] +'B02_'+resolution+'m.jp2') |
| 146 | + path_b3 = str(product_title) + '.SAFE/GRANULE/' + str(files[0]) +'/' + str(files2[1]) +'/R' + resolution + 'm/' +str(files3[0][0:23] +'B03_'+resolution+'m.jp2') |
| 147 | + path_b4 = str(product_title) + '.SAFE/GRANULE/' + str(files[0]) +'/' + str(files2[1]) +'/R' + resolution + 'm/' +str(files3[0][0:23] +'B04_'+resolution+'m.jp2') |
| 148 | + |
| 149 | + print(" ", path_b2) |
| 150 | + print(" ", path_b3) |
| 151 | + print(" ", path_b4) |
| 152 | + |
| 153 | + #Open Band4(Blue), 3(Green) and 2(Red) |
| 154 | + b4 = rio.open(path_b4) |
| 155 | + b3 = rio.open(path_b3) |
| 156 | + b2 = rio.open(path_b2) |
| 157 | + |
| 158 | + #RGB color compose (output file as GeoTiff: public domain metadata standard which allows georeferencing information to be embedded within a TIFF file) |
| 159 | + print("\nRGB Color composing...") |
| 160 | + with rio.open(object_name +'.tiff','w',driver='Gtiff', width=b4.width, height=b4.height, |
| 161 | + count=3,crs=b4.crs,transform=b4.transform, dtype=b4.dtypes[0]) as rgb: |
| 162 | + rgb.write(b4.read(1),1) |
| 163 | + rgb.write(b3.read(1),2) |
| 164 | + rgb.write(b2.read(1),3) |
| 165 | + rgb.close() |
| 166 | + |
| 167 | + #Read polygon from .geojson |
| 168 | + nReserve_geo = gpd.read_file(object_name +'.geojson') |
| 169 | + |
| 170 | + # Reference https://rasterio.readthedocs.io/en/latest/api/rasterio.crs.html#module-rasterio.crs |
| 171 | + # EPSGの326544というのは、地図投影する際によく利用される、WGS84のUTM座標系のことを表します。 |
| 172 | + epsg = b4.crs |
| 173 | + |
| 174 | + # Since this RGB image is large and huge you save both computing power and time to clip and use only the area of interest. |
| 175 | + # We will clip the Natural reserve area from the RGB image. |
| 176 | + nReserve_proj = nReserve_geo.to_crs({'init': str(epsg)}) |
| 177 | + |
| 178 | + #Create directory for temporary Masked Tiff |
| 179 | + print("Creating directory Image_tiff...") |
| 180 | + os.makedirs('./Image_tiff', exist_ok=True) |
| 181 | + |
| 182 | + #Extract image for Area of Interest from the composed color image |
| 183 | + print("\nCalculating area of interest...") |
| 184 | + with rio.open(object_name +'.tiff') as src: |
| 185 | + out_image, out_transform = rio.mask.mask(src, nReserve_proj.geometry,crop=True) |
| 186 | + out_meta = src.meta.copy() |
| 187 | + out_meta.update({"driver": "GTiff", |
| 188 | + "height": out_image.shape[1], |
| 189 | + "width": out_image.shape[2], |
| 190 | + "transform": out_transform}) |
| 191 | + |
| 192 | + with rasterio.open('./Image_tiff/' +'Masked_' + object_name +'.tif', "w", **out_meta) as dest: |
| 193 | + dest.write(out_image) |
| 194 | + |
| 195 | + #jpeg processing from the extracted images |
| 196 | + scale = '-scale 0 250 0 30' |
| 197 | + options_list = [ |
| 198 | + '-ot Byte', |
| 199 | + '-of JPEG', |
| 200 | + scale |
| 201 | + ] |
| 202 | + |
| 203 | + options_string = " ".join(options_list) |
| 204 | + |
| 205 | + #Create directory for jpeg processed image |
| 206 | + print("Creating Image_jpeg...") |
| 207 | + os.makedirs('./Image_jpeg_'+object_name, exist_ok=True) |
| 208 | + |
| 209 | + #Save jpeg image |
| 210 | + #https://pypi.org/project/GDAL/ |
| 211 | + #GDAL Geospatial Data Abstraction Library |
| 212 | + gdal.Translate('./Image_jpeg_'+object_name +'/' + str(ingestion_date) + 'Masked_' +object_name +'.jpg', |
| 213 | + './Image_tiff/Masked_' +object_name +'.tif', |
| 214 | + options=options_string) |
| 215 | + |
| 216 | + |
| 217 | + #Print the ingestion_date on the image |
| 218 | + img = Image.open('./Image_jpeg_'+object_name +'/' + str(ingestion_date) + 'Masked_' +object_name +'.jpg') |
| 219 | + #print(img.size) |
| 220 | + #print(img.size[0]) |
| 221 | + x = img.size[0]/100 #x coordinate for the print position |
| 222 | + y = img.size[1]/100 #y coordinate for the print position |
| 223 | + fs = img.size[0]/50 #font size |
| 224 | + fs1 = int(fs) |
| 225 | + #print(fs1) |
| 226 | + #print(type(fs1)) |
| 227 | + obj_draw = ImageDraw.Draw(img) |
| 228 | + obj_font = ImageFont.truetype(fontfile, fs1) |
| 229 | + obj_draw.text((x, y), '-- 75 Tahun INDONESIA MERDEKA! --\n' + str(ingestion_date) + 'produced from European Space Agency (ESA) remote sensing data', fill=(255, 255, 255), font=obj_font) |
| 230 | + #obj_draw.text((img.size[0]/2, img.size[1]-y - img.size[1]/20 ), 'produced from European Space Agency (ESA) remote sensing data', fill=(255, 255, 255), font=obj_font) |
| 231 | + |
| 232 | + |
| 233 | + img.save('./Image_jpeg_'+str(object_name) +'/' + str(ingestion_date) + 'Masked_' +object_name +'.jpg') |
| 234 | + |
| 235 | + #Remove downloaded files |
| 236 | + print("\nRemoving files...") |
| 237 | + shutil.rmtree( str(product_title) + '.SAFE') |
| 238 | + os.remove(str(product_title) +'.zip') |
| 239 | + |
| 240 | + print("---DONE---\n") |
| 241 | + return |
| 242 | + |
| 243 | + |
| 244 | + |
| 245 | +## Get Sentinel satellite scene |
| 246 | +def Sentinel2_get_sorted_data(index, fontfile, object_name, footprint_geojson, start_date, end_date, resolution): |
| 247 | + print("Querying Sentinel-2...") |
| 248 | + products = api.query(footprint_geojson, |
| 249 | + date = (start_date, end_date), #Desired date for the beginning and ending time of Sentinel-2 image |
| 250 | + platformname = 'Sentinel-2', |
| 251 | + processinglevel = 'Level-2A', |
| 252 | + cloudcoverpercentage = (0,100)) |
| 253 | + |
| 254 | + products_gdf = api.to_geodataframe(products) |
| 255 | + |
| 256 | + |
| 257 | + #Sort the value of cloud coverage percentage from the small one |
| 258 | + print("Sorting data from the lowest Cloud Coverage Percentage...") |
| 259 | + products_gdf_sorted = products_gdf.sort_values(['cloudcoverpercentage'], ascending=[True]) |
| 260 | + print(" Data sorted! Information as follow:") |
| 261 | + |
| 262 | + #Get Sentinel-2 data |
| 263 | + Sentinel2_get_all_data(products_gdf_sorted, index, fontfile, object_name, start_date, resolution) |
| 264 | + print("Get sorted data finished.") |
| 265 | + return |
0 commit comments