''' NWS_Radar_Weather_Station.py This code is designed to download near-real time L2 NEXRAD radar data for a station specified for the user in a near-real time capability and generate an animated gif that includes the L2 reflecitvity and current weather warnings ''' ################################################################## # Added modules and libraries needed by this program from matplotlib.colors import LinearSegmentedColormap from matplotlib.lines import Line2D from mpl_toolkits.axes_grid1.inset_locator import inset_axes from PIL import Image from zoneinfo import ZoneInfo import boto3 # Interface for accessing AWS services (e.g., S3 buckets) from botocore import UNSIGNED # Allows anonymous (unsigned) access to AWS resources from botocore.config import Config # Used to configure AWS client behavior import cartopy.crs as ccrs import cartopy.feature as cfeature import cartopy.io.shapereader as shpreader from cartopy.feature import ShapelyFeature import concurrent.futures # Supports launching parallel tasks using threads or processes import datetime as dt import fnmatch # Allows operating system to check for particular string (such as filenames) import geopandas as gpd import glob import os import pyart import numpy as np import matplotlib as mpl # Loads map plotting library import matplotlib.patheffects as PathEffects from matplotlib.lines import Line2D import matplotlib.pyplot as plt import shapely import urllib.request ################################################################## start = dt.datetime.now() # Gets current time #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # START USER SETTINGS XW_SHOW = 'N' # Show interactive plot (Y) or just save it (N) USE_CUSTOM_CENTER_LOC = 'Y' CENTER_LAT= 38.994083 # Map center location latitude (used if USED_CUSTOM_CENTER_LOC == 'Y') CENTER_LON = -76.837741 # Map center location longitude (used if USE_CUSTOM_CENTER_LOC == 'Y') EXTENT_KM_X = 230 #Sets the map extent west to east in km (actual distance is 2x's this value) EXTENT_KM_Y = 110 #Sets the map extent south to north in km (actual distance is 2x's this value) MIN_RAD_REFL = 0 # Min reflectivity to display (dBZ) MAX_RAD_REFL = 60 # Max reflectivity to display (dBZ) LOOP_SIZE = 8 # Number of radar frames for GIF animation RADAR_STATION = 'KLWX' # NEXRAD station identifier #RADAR_STATION = 'KCCX' # NEXRAD station identifier LOCAL_RADAR_DATA_DIR = f"{os.getcwd()}/{RADAR_STATION}" # Directory where NWS radar data will be stores SHOW_NWS_WARNINGS = 'Y' # Toggle whether to overlay NWS warnings LOCAL_WARNING_DATA_DIR = f"{os.getcwd()}/NWS_Warnings" # Directory where NWS warning data will be stored PLOT_AXIS_LABEL_SIZE = 15 # Font size for plot labels PLOT_TITLE_LABEL_SIZE = 20 # Font size for plot title OPLOT_DIR = os.getcwd() # Where to save the animated GIF OPLOT_DIR = '../QLOOK_Current_Weather' # Where to save the animated GIF OPLOT_FMT = '.jpg' # Output format for individual plots OPLOT_DPI = 300 # Resolution of plots # Set radar colormap try: radar_cmap = pyart.graph.cm_colorblind.ChaseSpectral except AttributeError: radar_cmap = pyart.graph.cmweather.cm_colorblind.ChaseSpectral RADAR_FST_CMAP_CLR = 40 # First color value to be used for colorbar (range 0 - 255) RADAR_LST_CMAP_CLR = 200 # Last color value to be used for colorbar (range 0 - 255) # END USER SETTINGS #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # START MAIN CODE # --------------------------------------- # Sets non-linear colormap for plots # --------------------------------------- class nlcmap(LinearSegmentedColormap): """A nonlinear colormap""" name = 'nlcmap' def __init__(self, cmap, levels): self.cmap = cmap # @MRR: Need to add N for backend self.N = cmap.N self.monochrome = self.cmap.monochrome self.levels = np.asarray(levels, dtype='float64') self._x = (self.levels-self.levels.min()) / (self.levels.max()-self.levels.min()) self._y = np.linspace(0.0, 1.0, len(self.levels)) #@MRR Need to add **kw for 'bytes' def __call__(self, xi, alpha=1.0, **kw): """docstring for fname""" # @MRR: Appears broken? # It appears something's wrong with the # dimensionality of a calculation intermediate #yi = stineman_interp(xi, self._x, self._y) yi = np.interp(xi, self._x, self._y) return self.cmap(yi, alpha) # --------------------------------------- # Extract datetime from filename # --------------------------------------- def extract_datetime_from_filename(key): if key[-3:] != 'V06': return None try: dt_str = key[-19:-4] time_out = dt.datetime.strptime(dt_str, "%Y%m%d_%H%M%S") time_out = time_out.replace(tzinfo=ZoneInfo("UTC")) return time_out except Exception as e: print(f"Error parsing datetime from {key}: {e}") return None # --------------------------------------- # Download a single file (used in parallel) # --------------------------------------- def download_file_if_needed(bucket_name, key, local_path): try: s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED)) if not os.path.exists(local_path): print(f"Downloading {os.path.basename(key)}") s3.download_file(bucket_name, key, local_path) else: print(f"File already exists: {os.path.basename(key)}") except Exception as e: print(f"Error downloading {key}: {e}") ''' OLD RETRIVAL METHOD # --------------------------------------- # NWS Data Download Function # --------------------------------------- def download_radar_data(local_data_dir, sdate, edate, cdata_statname): #> # Download all radar files from AWS for a station between sdate and edate print(f"Date range: {sdate} to {edate} UTC") cur_date = sdate while cur_date <= edate: print(f"\nFetching NEXRAD Radar Data for {cdata_statname} on {cur_date.strftime('%Y/%m/%d %H UTC')}") prefix = f"{cur_date.strftime('%Y/%m/%d')}/{cdata_statname}" cur_download_dir = local_data_dir os.makedirs(cur_download_dir, exist_ok=True) s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED)) continuation_token = None files_to_download = [] bucket_name = 'noaa-nexrad-level2' bucket_name = 'unidata-nexrad-level2' print (bucket_name,prefix) while True: # Request next page of radar files if continuation_token: response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix, ContinuationToken=continuation_token) else: response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix) if 'Contents' not in response: print(f"No files found for {cur_date}") break cur_sdate = cur_date cur_edate = cur_date + dt.timedelta(hours=1) count = 0 for obj in response['Contents']: key = obj['Key'] count += 1 print (f"{key} #{count}/{len(response['Contents'])}") ksajksa # Filter files in the correct time range for obj in response['Contents']: key = obj['Key'] file_time = extract_datetime_from_filename(key) print (' ') print (file_time, cur_sdate,cur_edate) if file_time and cur_sdate <= file_time <= cur_edate: file_name = os.path.basename(key) local_path = os.path.join(cur_download_dir, file_name) files_to_download.append((bucket_name, key, local_path)) if response.get("IsTruncated"): continuation_token = response["NextContinuationToken"] else: break files_to_download = sorted(files_to_download) #print (files_to_download) #blah9999 print(f"Downloading {len(files_to_download)} files in parallel...") # Download files in parallel with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: future_to_file = { executor.submit(download_file_if_needed, bucket, key, path): (bucket, key, path) for bucket, key, path in files_to_download } for future in concurrent.futures.as_completed(future_to_file): bucket, key, path = future_to_file[future] try: future.result() except Exception as e: print(f"Failed to download {key}: {e}") cur_date += dt.timedelta(hours=1) ''' # Updated pagnated retrieval method 08 Sept 2025 def download_radar_data(local_data_dir, sdate, edate, cdata_statname): print(f"Date range: {sdate} to {edate} UTC") cur_date = sdate #bucket_name = 'noaa-nexrad-level2' bucket_name = 'unidata-nexrad-level2' s3 = boto3.client('s3', config=Config(signature_version=UNSIGNED)) while cur_date <= edate: print(f"\nFetching NEXRAD Radar Data for {cdata_statname} on {cur_date.strftime('%Y/%m/%d %H UTC')}") prefix = f"{cur_date.strftime('%Y/%m/%d')}/{cdata_statname}" cur_download_dir = local_data_dir os.makedirs(cur_download_dir, exist_ok=True) files_to_download = [] paginator = s3.get_paginator('list_objects_v2') pages = paginator.paginate(Bucket=bucket_name, Prefix=prefix) cur_sdate = cur_date cur_edate = cur_date + dt.timedelta(hours=1) print (cur_sdate,cur_edate) for page in pages: if 'Contents' not in page: continue for obj in page['Contents']: key = obj['Key'] file_time = extract_datetime_from_filename(key) #print (file_time) if file_time and cur_sdate <= file_time < cur_edate: file_name = os.path.basename(key) local_path = os.path.join(cur_download_dir, file_name) files_to_download.append((bucket_name, key, local_path)) # Use a thread pool to download files in parallel if files_to_download: print(f"Found {len(files_to_download)} files to download. Starting download...") with concurrent.futures.ThreadPoolExecutor() as executor: executor.map(download_file_if_needed, [item[0] for item in files_to_download], [item[1] for item in files_to_download], [item[2] for item in files_to_download]) else: print(f"No files found for time range {cur_sdate} to {cur_edate}") cur_date += dt.timedelta(hours=1) print("All downloads complete.") #< # --------------------------------------- # Main Code Execution # --------------------------------------- # Current time in UTC utc_now = dt.datetime.now(tz=ZoneInfo("UTC")) ## Current time in U.S. Eastern Time (handles daylight saving automatically) #eastern_now = dt.datetime.now(tz=ZoneInfo("America/New_York")) #print("UTC Time: ", utc_now.strftime("%Y-%m-%d %H:%M:%S %Z")) #print("Eastern Time: ", eastern_now.strftime("%Y-%m-%d %H:%M:%S %Z")) sdate = utc_now - dt.timedelta(hours=2) edate = utc_now #sdate = dt.datetime(2025,9,8,20,0,0,tzinfo=dt.timezone.utc) #edate = sdate + dt.timedelta(hours=2) # Download and set colors for NWS warning data (if desired) if SHOW_NWS_WARNINGS == 'Y': os.makedirs(LOCAL_WARNING_DATA_DIR, exist_ok=True) from pathlib import Path for file in Path(LOCAL_WARNING_DATA_DIR).glob("current_warnings*"): file.unlink() url = "https://tgftp.nws.noaa.gov/SL.us008001/DF.sha/DC.cap/DS.WWA/current_warnings.tar.gz" output_file = f"{LOCAL_WARNING_DATA_DIR}/current_warnings.tar.gz" urllib.request.urlretrieve(url, output_file) import tarfile with tarfile.open(output_file, "r:gz") as tar: tar.extractall(path=f"{LOCAL_WARNING_DATA_DIR}") print("Extraction complete.") shapefile_path = "current_warnings" gdf = gpd.read_file(f"{LOCAL_WARNING_DATA_DIR}/{shapefile_path}.shp") #print(gdf.columns) #print(gdf['PHENOM'].unique()) # or try 'phenom' if lowercase #print(gdf['SIG'].unique()) #ksakaks warning_types = ['TO', 'SV', 'FF'] try: gdf = gdf[(gdf['PHENOM'].isin(warning_types)) & (gdf['SIG'] == 'W')] except: print (f"NOTICE: NWS warnings files does not contain any warning information, will not display on radar map") SHOW_NWS_WARNINGS = 'N' # Color mapping: PHENOM → color phenom_color = { 'TO': 'red', 'SV': 'yellow', 'FF': 'blue' } # Legend legend_labels = { 'TO': 'Tornado', 'SV': 'Severe T-storm', 'FF': 'Flash Flood' } download_radar_data(LOCAL_RADAR_DATA_DIR, sdate, edate, RADAR_STATION) # Download NWS Level 2 Radar Data from AWS # Identify and sort valid radar files radar_files = [] for f in os.listdir(LOCAL_RADAR_DATA_DIR): if fnmatch.fnmatch(f, f"{RADAR_STATION}**V06"): radar_files.append(f"{LOCAL_RADAR_DATA_DIR}/{f}") radar_files = sorted(radar_files) # Creates custom colorbar for radar plots clevs = np.arange(MIN_RAD_REFL,MAX_RAD_REFL,1) cmap = radar_cmap cmaplist = [cmap(i) for i in range(RADAR_FST_CMAP_CLR,RADAR_LST_CMAP_CLR)] # Extract colors from colormap cmap = cmap.from_list('Custom cmap', cmaplist, cmap.N) radar_cmap = nlcmap(cmap, clevs) # Cleans up radar data directory to remove any files that are too old to control disc space for cfile in radar_files: #print (cfile) file_time = cfile[-19:-4] ftime = dt.datetime.strptime(file_time, "%Y%m%d_%H%M%S") ftime = ftime.replace(tzinfo=ZoneInfo("UTC")) #print (ftime) dtime = (utc_now - ftime).total_seconds() #print (dtime) if dtime >= 3600.0*2: os.system(f"rm -rf {cfile}") # Read and store US county shapemaps for use in plots # Read shapefile from Natural Earth if available or local file shpfilename = shpreader.natural_earth(resolution='10m', category='cultural', name='admin_2_counties') reader = shpreader.Reader(shpfilename) counties = list(reader.geometries()) county_feature = ShapelyFeature(counties, ccrs.PlateCarree(), edgecolor='gray', facecolor='none') # Loops through each time in the radar loop and generated a .jpg file that will combined later into an animated gif for l in range(0,LOOP_SIZE): if l < 10: file_num = f"0{l}" else: file_num = str(l) # Load radar file filename = radar_files[-1*LOOP_SIZE+l] print (f"{filename} {l+1}/{LOOP_SIZE}: ") file_time = filename[-19:-4] ftime = dt.datetime.strptime(file_time, "%Y%m%d_%H%M%S") ftime_utc = ftime.replace(tzinfo=ZoneInfo("UTC")) ftime_local = ftime_utc.astimezone(ZoneInfo("America/New_York")) #print (ftime_utc, ftime_local) #print (dt.datetime.strftime(ftime_local,'%Y/%m/%d %H:%M')) radar = pyart.io.read_nexrad_archive(filename) # Read in radar data from NWS Level 2 radar file using Pyart functions # Select a sweep sweep = 0 # Set radar location as map center location if custom lat/lon is not specified if USE_CUSTOM_CENTER_LOC == 'N': CENTER_LAT= radar.latitude['data'][0] CENTER_LON = radar.longitude['data'][0] deg_per_km = 1 / 111.0 extent = [CENTER_LON - EXTENT_KM_X * deg_per_km, CENTER_LON + EXTENT_KM_X * deg_per_km, CENTER_LAT- EXTENT_KM_Y * deg_per_km, CENTER_LAT+ EXTENT_KM_Y * deg_per_km] # Sets map extent # Determine reflectivity field name refl_field = 'reflectivity' if refl_field not in radar.fields: refl_field = list(radar.fields.keys())[0] # Get reflectivity data and radar location refl = radar.get_field(sweep, refl_field) x, y, z = radar.get_gate_x_y_z(sweep) lat0 = radar.latitude['data'][0] lon0 = radar.longitude['data'][0] # Convert x/y to geographic lat/lon lon2d, lat2d = pyart.core.cartesian_to_geographic_aeqd(x, y, lon0, lat0) # Translates radar data from radial into Cartesian coordinates # Creates figure, axes, and subplots SUBPLOT_ARR = np.array([1,1]) SUBPLOT_ADJ = np.array([ 0.03, 0.97, 0.11, 0.95, 0.01, 0.01]) # 1 rows, 2 cols # Creates figure, axes, and subplots #fig, axes = mpl.pyplot.subplots(nrows=SUBPLOT_ARR[0], ncols=SUBPLOT_ARR[1],figsize=(12,8.5)) fig, axes = mpl.pyplot.subplots(nrows=SUBPLOT_ARR[0], ncols=SUBPLOT_ARR[1],figsize=(15,7.5)) mpl.rcParams.update({'font.size': PLOT_AXIS_LABEL_SIZE}) mpl.rcParams.update({'font.weight':'bold'}) # Adjusts size and scope of subplots to user specification # print 'SUBPLOT SETTINGS',SUBPLOT_ADJ[0],SUBPLOT_ADJ[1],SUBPLOT_ADJ[2],SUBPLOT_ADJ[3],SUBPLOT_ADJ[4],SUBPLOT_ADJ[5] fig.subplots_adjust(left=SUBPLOT_ADJ[0],right=SUBPLOT_ADJ[1],bottom=SUBPLOT_ADJ[2],top=SUBPLOT_ADJ[3],hspace=SUBPLOT_ADJ[4],wspace=SUBPLOT_ADJ[5]) axes.axis('off') #axes[0].axis('off') #axes[1].axis('off') # Plotting #ax = plt.axes(projection=ccrs.PlateCarree()) caxis = plt.subplot2grid((SUBPLOT_ARR[0], SUBPLOT_ARR[1]), (0,0), rowspan=1, colspan=1, projection=ccrs.PlateCarree()) #ax.set_extent([lon2d.min()-1, lon2d.max()+1, lat2d.min()-1, lat2d.max()+1]) refl[reflMAX_RAD_REFL] = MAX_RAD_REFL # Plot reflectivity data a1 = caxis.pcolormesh(lon2d, lat2d, refl, cmap=radar_cmap, shading='auto', vmin=clevs[0],vmax=clevs[-1], transform=ccrs.PlateCarree(),alpha=0.7,zorder=1) # Create bottom horizontal colorbar axins = inset_axes(caxis, width="90%", # Width of colorbar relative to axes height="2%", # Height of colorbar loc='lower center', bbox_to_anchor=(0.0, -0.04, 1, 1), bbox_transform=caxis.transAxes, borderpad=0) cbar = plt.colorbar(a1, cax=axins, orientation='horizontal') ''' axins = inset_axes(caxis, width="2%", # Width of the colorbar as a percentage of the main axes height="100%", # Height matches the main axes loc='upper left', # Position the inset axes' upper left corner at the bbox bbox_to_anchor=(1.05, 0, 1, 1), # Move it just outside the right edge of ax bbox_transform=caxis.transAxes, # Use ax coordinates for bbox_to_anchor borderpad=0) # No padding between ax and the inset axes cbar = plt.colorbar(a1, cax=axins) ''' #cbar.set_label(f"{refl_field} (dBZ)",fontsize=PLOT_AXIS_LABEL_SIZE,fontweight='bold') cbar.set_label(f"Base Radar Reflectivity (dBZ)",fontsize=PLOT_AXIS_LABEL_SIZE,fontweight='bold') # Set colorbar label # Adds colorbar labels to colorbar min_var = np.nanmin(MIN_RAD_REFL) max_var = np.nanmax(MAX_RAD_REFL) dvar = max_var - min_var #if dvar <= 1: dvar = 1 #min_var -= dvar/10 #max_var += dvar/10 max_num_ticks = 8 tick_int_cands = np.array([0.2,0.4,0.5,1,2,3,4,5,10,15,20,50,100,150,200,500,1000,2000]) for tc in tick_int_cands: nticks = dvar/tc #print (tc, nticks) if nticks < max_num_ticks: break else: tick_int = tc cbar.set_ticks(np.arange(min_var,max_var,tick_int)) # Adds colorbar ticks to colorbar if SHOW_NWS_WARNINGS == 'Y': # Plot each warning type using dual-layer outlines for code, color in phenom_color.items(): subset = gdf[gdf['PHENOM'] == code] if not subset.empty: for geom in subset.geometry: if geom.type == 'Polygon': geoms = [geom] elif geom.type == 'MultiPolygon': geoms = geom.geoms else: continue for poly in geoms: coords = np.array(poly.exterior.coords) # First layer: thick black outline caxis.plot( coords[:, 0], coords[:, 1], transform=ccrs.PlateCarree(), color='black', linewidth=4.5, zorder=4 ) # Second layer: thinner colored edge caxis.plot( coords[:, 0], coords[:, 1], transform=ccrs.PlateCarree(), color=color, linewidth=2.0, zorder=5 ) # Create custom legend using colored lines handles = [] for code, color in phenom_color.items(): handle = Line2D( [0], [0], color=color, linewidth=4, label=legend_labels[code], path_effects=[PathEffects.withStroke(linewidth=6, foreground='black')] ) handles.append(handle) legend = caxis.legend( handles=handles, title='Warnings', loc='upper right', bbox_to_anchor=(1.00, 1), frameon=True, facecolor='white', edgecolor='black', fontsize=12, title_fontsize=13 ) legend.get_frame().set_alpha(1.0) ''' # If user-specified warnings are desired adds these to the map and adds a legend to the map if SHOW_NWS_WARNINGS == 'Y': # Plot each warning type with specified color for code, color in phenom_color.items(): subset = gdf[gdf['PHENOM'] == code] if not subset.empty: #h1 = subset.plot(ax=caxis, color=color, edgecolor='black', alpha=0.4, label=code,linewidth=2.0) h1 = subset.plot(ax=caxis, color=color, edgecolor='black', alpha=0.4, label=code,linewidth=2.0) # Created the handle objects for the python legend handles = [] for code, color in phenom_color.items(): handle = Line2D( [0], [0], color=color, linewidth=4, label=legend_labels[code], path_effects=[PathEffects.withStroke(linewidth=6, foreground='black')] ) handles.append(handle) # Adds legend for warnings to map legend = caxis.legend( handles=handles, title='Warnings', loc='upper right', # or 'upper right', etc. bbox_to_anchor=(1.00, 1), frameon=True, facecolor='white', # ← white background edgecolor='black', # ← optional: add black border fontsize=12, title_fontsize=13 ) # Force background to be fully opaque white legend.get_frame().set_alpha(1.0) # Solid background ''' #plt.colorbar(pc, ax=ax, label=f"{refl_field} (dBZ)", orientation='horizontal') ## Mark user-specified location caxis.plot(CENTER_LON, CENTER_LAT, 'k+', markersize=12, markeredgewidth=2) caxis.text(CENTER_LON + 0.03, CENTER_LAT, 'GSFC', fontsize=15) # Add features caxis.set_extent(extent) caxis.coastlines(resolution='10m',linewidth=2,) caxis.add_feature(cfeature.STATES.with_scale('10m'), linewidth=2, edgecolor='black',zorder=2) caxis.add_feature(cfeature.BORDERS, edgecolor='black',linewidth=2, zorder=2) caxis.add_feature(county_feature, linewidth=1, edgecolor='black', zorder=2) caxis.add_feature(cfeature.NaturalEarthFeature('cultural', 'roads', '10m', edgecolor='black', facecolor='none',linewidth=0.5), zorder=2) caxis.set_title(f"NEXRAD {RADAR_STATION} on {dt.datetime.strftime(ftime_utc,'%Y/%m/%d %H:%M UTC')} ({dt.datetime.strftime(ftime_local,'%H:%M ET')})", fontsize=PLOT_TITLE_LABEL_SIZE,fontweight='bold') #plt.tight_layout() #print (ftime_utc, ftime_local) #print (dt.datetime.strftime(ftime_local,'%Y/%m/%d %H:%M')) oplot_curr = 'Weather_data04' if XW_SHOW == 'Y': print ('Now printing panel to screen. Image will not be saved') plt.show() else: print ('Now saving figure as '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+file_num+OPLOT_FMT) plt.savefig((LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+file_num+OPLOT_FMT),dpi=OPLOT_DPI) mpl.pyplot.clf() del fig, axes plt.close() # Find and sort all relevant JPG files image_files = sorted(glob.glob(f"{LOCAL_RADAR_DATA_DIR}/{oplot_curr}*.jpg")) # Load images into a list frames = [Image.open(img).convert("P") for img in image_files] # Output path output_gif_path = os.path.join(LOCAL_RADAR_DATA_DIR, f"{oplot_curr}.gif") # Save as animated GIF frames[0].save( output_gif_path, save_all=True, append_images=frames[1:], optimize=True, duration=333, # Duration per frame in ms (333 ms = ~3 fps) loop=0 # Loop forever ) print(f"Saved animated GIF to {output_gif_path}") #os.system(f"rm -rf {OPLOT_DIR}/{oplot_curr}**.gif") #print ('ffmpeg -y -i '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'%02d.jpg -vf palettegen palette.png') #os.system ('ffmpeg -y -i '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'%02d.jpg -vf palettegen palette.png') #print ('ffmpeg -stream_loop 0 -framerate 3 -thread_queue_size 12 -i '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'%02d.jpg -i palette.png -lavfi paletteuse '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'.gif') #os.system('ffmpeg -stream_loop 0 -framerate 3 -thread_queue_size 12 -i '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'%02d.jpg -i palette.png -lavfi paletteuse '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'.gif') os.system('mv -f '+LOCAL_RADAR_DATA_DIR+'/'+oplot_curr+'**.gif'+' '+OPLOT_DIR) # END MAIN CODE #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< elapsed = (dt.datetime.now()-start).total_seconds() print ('Program complete...total elapsed time is '+str(elapsed)+' seconds')