3
\$\begingroup\$

I am writing a python GUI application that plots weather data on a map for a specific region. It plots temperature, dewpoint, wind data, and icons that represent the sky conditions. It extracts the data for longitude/latitude from text files. I timed out how long it takes for the program to plot each type of weather data and I got the following results:

temperature: 10 seconds

dewpoint: 10 seconds

wind: 15 seconds

sky conditions: 90 seconds

So, it doesn't take too long for it to plot temperature, dewpoint, and wind. However, it takes a very long time for it to plot the sky conditions, which are icon images. My goal is for my application to display any kind of weather data in a few seconds. I am wondering what I can do to my python code to make it more efficient and run faster, so it meets that goal.

I use global variables, which I know is not good programming practice. This is likely one thing I am doing in my code that is slowing things down. I also use numerous function calls, which could also be slowing things down as well. I would like to rewrite my code, so that I don't use so many global variables and function calls.

The biggest problem I think is when it displays the icons, which are images as stated above. I am new at working with images in python, so I don't know if python is just slow with dealing with images or not. Any analysis of my code/suggestions to make it better would be greatly appreciated.

import tkinter as tk
from tkinter import ttk
import tkinter.font as tkFont
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.image as mpimg
from mpl_toolkits.basemap import Basemap
from PIL import Image
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.pyplot as plt
import numpy as np
import requests

def retrieve_filedata():
      file = area.get() + '.txt'
      f = open(file, 'r')
      line = f.readline()
      global minlon
      minlon = float(line.split(' ')[0])
      global minlat
      minlat = float(line.split(' ')[1])
      global maxlon
      maxlon = float(line.split(' ')[2])
      global maxlat
      maxlat = float(line.split()[3])
      global centerlat
      centerlat = float(line.split()[4]) 
      global centerlon
      centerlon = float(line.split()[5]) 
      
      lines = f.readlines()[1:] #put longitude and latitudes into array
      global lat
      lat = []
      global lon
      lon = []

      for x in lines:
         lat.append(float(x.split(' ')[0]))
         lon.append(float(x.split()[1]))
      f.close()

def current_weather():
       retrieve_filedata()
       key = '4a7a419e4e16e2629a4cedc37cbf7e50'
       url = 'https://api.openweathermap.org/data/2.5/weather'
       global observation
       observation = []
       global u
       u = []
       global v
       v = []
       global icon
       icon = []
       for i in range(0,len(lon)):
          params = {'lat': lat[i], 'lon': lon[i], 'appid': key, 'units': 'imperial'}
          response = requests.get(url, params = params)
          weather = response.json()
          airtemp = round(weather['main']['temp'])
          humidity = weather['main']['humidity'] #relative humidity
          kelvin = ((airtemp-32)/1.8)+273 #convert temperature from F to Kelvin
          #E = 0.611 * np.exp(5423 * ((1/273) - (1/dewpoint))) vapor pressure
          Es = 0.611 * np.exp(5423 * ((1/273) - (1/kelvin))) #saturation vapor pressure
          kelvindew = 5423/((5423/273)-np.log(((humidity/100)*Es)/0.611)) #use vapor pressure equation to find dewpoint in Kelvin
          dewpoint = round(((kelvindew-273)*1.8)+32) #convert dewpoint from Kelvin to F
          windspeed = round(weather['wind']['speed'])
          winddirection = weather['wind']['deg']
          u.append(-(windspeed)*np.sin(((np.pi)/180)*winddirection))
          v.append(-(windspeed)*np.cos(((np.pi)/180)*winddirection))
          icon.append(weather['weather'][0]['icon'])
          if data.get() == 'Temperature':
             observation.append(airtemp)
          if data.get() == 'Dewpoint':
             observation.append(dewpoint)

def display_winds():
   current_weather()
   map = Basemap(llcrnrlon= minlon, llcrnrlat= minlat, urcrnrlon= maxlon, urcrnrlat= maxlat ,resolution='i',
             lat_0 = centerlat, lon_0 = centerlon)
   for i in range(0, len(lon)):
      x,y = map(lon[i], lat[i])
      map.barbs(x, y, u[i], v[i], length = 5, pivot = 'middle', zorder = 100)
   plt.show()

def display_icons():
    map = Basemap(llcrnrlon= minlon, llcrnrlat= minlat, urcrnrlon= maxlon, urcrnrlat= maxlat ,resolution='i',
             lat_0 = centerlat, lon_0 = centerlon)
    image = []
    im = []
    ab = []
    x,y = map(lon, lat) 
    for i in range(0, len(lon)):
       image.append(Image.open(requests.get('http://openweathermap.org/img/wn/' + icon[i] + '@2x.png', stream=True).raw))
       im.append(OffsetImage(image[i], zoom = 0.25))
       ab.append(AnnotationBbox(im[i], (x[i],y[i]), frameon=False))
       map._check_ax().add_artist(ab[i])
plt.show()

def plot_data():     
    current_weather()
    map = Basemap(llcrnrlon= minlon, llcrnrlat= minlat, urcrnrlon= maxlon, urcrnrlat= maxlat ,resolution='i',
             lat_0 = centerlat, lon_0 = centerlon)
    map.drawmapboundary(fill_color='#A6CAE0')
    map.drawcounties(zorder = 20)
    map.drawstates()
    map.fillcontinents(color='#e6b800',lake_color='#A6CAE0') 
    for i in range(0, len(lon)):
         x,y = map(lon[i], lat[i])
         if data.get() == 'Temperature' or data.get() == 'Dewpoint':
             plt.text(x, y, observation[i], fontsize = 7)
         if data.get() == 'Wind':
             display_winds()
         if data.get() == 'Sky Conditions':
             display_icons()
    plt.show()

root = tk.Tk()
canvas = tk.Canvas(root, height = '300', width = '400')
canvas.pack()

#create widgets
titlefont = tkFont.Font(size = 20)
title = tk.Label(text = "Current Weather Map Data", font = titlefont)

arealabel = tk.Label(text = "Choose Region: ")
areavalue = tk.StringVar()
area = ttk.Combobox(root, width = '13', textvariable = areavalue)

datalabel = tk.Label(text = "Choose Data: ")
datavalue = tk.StringVar()
data = ttk.Combobox(root, width = '12', textvariable = datavalue)

button = tk.Button(root, text = "Plot Data", command = plot_data)

#organize widgets
title.place(relx = 0.1, rely = 0.05)
arealabel.place(relx = 0.05, rely = 0.25)
area.place(relx = 0.35, rely = 0.25)
datalabel.place(relx = 0.05, rely = 0.40)
data.place(relx = 0.35, rely = 0.40)
button.place(relx = 0.40, rely = 0.65)

#Insert Values into Area Combobox
area['values'] = ('Pennsylvania', 'Maryland', 'Delaware', 'New Jersey', 'Virginia', 'West Virginia', 'Ohio', 'New York',
                  'Rhode Island', 'Massachusetts', 'Connecticut', 'Vermont', 'New Hampshire', 'Maine', 'Mid Atlantic', 'New England')
area.current(0)

#Insert Values into Data Combobox
data['values'] = ('Temperature', 'Dewpoint', 'Wind', 'Sky Conditions')
data.current(0)

root.mainloop()
\$\endgroup\$
4
  • \$\begingroup\$ Looks like your internet is slow or that api's server is slow in serving you.Lots of get requests for image download, code has to be slow. \$\endgroup\$ Jul 23, 2020 at 17:16
  • \$\begingroup\$ Code can be made faster if you download images in async but I don't have experience with that. \$\endgroup\$ Jul 23, 2020 at 17:25
  • 1
    \$\begingroup\$ Please show a sample of your input text file. \$\endgroup\$
    – Reinderien
    Jul 26, 2020 at 2:39
  • \$\begingroup\$ The plt.show() under the definition of display_icons() is incorrectly indented. Ordinarily I'd say it's a copy-paste mistake, but this is a really odd place for such a mistake. Please clarify. \$\endgroup\$
    – Mast
    Dec 29, 2021 at 10:07

1 Answer 1

2
\$\begingroup\$

There are a couple of problems with your code we'll have to clear up before looking at performance. I'd like to write a comprehensive review about it, but I'd have to make too many assumptions. So instead it's going to be a more general one and one of the problems with that is that I can't touch the performance issue. You have not specified your Python version, how your input is structured (we know there'll be at least 8 values, but that's it), what your output looks like (at least one plot) and one of the libraries you're using is out-of-date (no longer readily available).

I'll look at your imports and your functions to see if we can make it readable. Once it's better readable either the performance issues will be much easier to profile for yourself so you can see what's going wrong, or you'll have to post a revised version in a new question for further review.

Imports

The following imports are unused and can be removed:

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.image as mpimg

A more glaring problem though is that people keep using Basemap. Yes, it's incredibly powerful. It's also very old and no longer supported since 2020. This means no updates and more hassle getting it installed on newer systems.

Luckily there's Cartopy. I know full well it's not always easy to migrate a project, but with the current code size it's still doable. If you wait till the project has grown further it will be much harder.

You won't be the first to migrate so there are plenty of people who've gone before and figured most of it out (examples: [1], [2]) already.

Functions

You say you've timed temperature, dewpoint, wind and sky conditions. However, there is no indication in the code that this is timed by the software or bound to a specific function. There's a display_winds function, but the rest is labelled wrong or inconsistently.

for i in range(0, len(lon)):
     x,y = map(lon[i], lat[i])
     if data.get() == 'Temperature' or data.get() == 'Dewpoint':
         plt.text(x, y, observation[i], fontsize = 7)
     if data.get() == 'Wind':
         display_winds()
     if data.get() == 'Sky Conditions':
         display_icons()

Why not make a display_temperature() and display_dewpoint as well? Should they even give the same output like they do now?

I won't overwhelm you with new terms, but I'd like to introduce you to the Single Responsibility Principle (SRP). Every class or function should have responsibility over a single part of the program. All of your functions are doing too much, making it unclear what's happening where and making it nearly impossible to re-use some of the code except by copy-pasting it around. Which you've done, since there are plenty of places where you're doing the same thing in multiple places, either exactly the same or slightly different.

Some of your actions happen in the global scope. It's customary to wrap all this in a main function. While Python doesn't require it unless some other languages, it does help with testing, debugging, profiling en re-use. Put everything that's not in its own function in def main() and end your code with:

if __name__ == "__name__":
    main()

This is particularly useful if you want to include functions from this file into a new project, since everything in main won't be automatically executed unless explicitly told to.

Remainder

There are a lot of other things that can be improved. Your for loops are ugly, you define map twice exactly the same way in different functions (re-use it, don't do the same work twice). All your globals can be removed, how you read your file is dangerous (use with open(area.get() + ".txt") as f: instead to have the file close automatically) and you can probably redo your file reading entirely if you show us how it's formatted. Currently you're using split all over the place (inconsistently too, sometimes with and sometimes without argument), if you know how the file is formatted you can do that all in one go. Your code is bordering on the edge of whether it makes sense to create a new class, another thing I can't get into details about without being able to reproduce the output.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.