Weather has always held a certain fascination for us, shaping our daily decisions and guiding our plans. As technology continues to weave itself into every facet of our lives, it’s no surprise that it has also transformed how we interact with the weather. In this article, we will create a weather app that blends the power of the Python Delphi ecosystem with the rich data streams of the Weatherstack API.
Our destination? An elegant form housing a search bar that opens up a world of meteorological insight. With three distinct tabs – historical, current, and prediction – we’ll explore the weather’s past, present, and future, bringing it all to your fingertips. So, let’s dive into the world of weather forecasting with DelphiFMX and Weatherstack, and let the forecasts begin!
Table of Contents
How to Build a Feature-Packed Weather App With Delphi FMX?
Creating a weather application with DelphiFMX involves a series of key steps. First, install the Delphi Integrated Development Environment (IDE) and the Python FireMonkey GUI framework on your PC. These are the fundamental tools you’ll need to craft your weather app. Also, ensure you have the Delphi4PythonExporter tool set up along with a text editor or an IDE that supports Python, such as PyScripter. If you haven’t set up these essential tools yet, consider referring to the article “Setting Up a Robust Python GUI Project” for a head start.
Furthermore, it’s crucial to prepare for data retrieval from Weatherstack API. To do this, sign up and subscribe to a plan on Weatherstack’s website. While this tutorial uses the plan providing access to all endpoints, you can certainly begin with the free plan, utilizing the current weather endpoint to get started.
To access the source code and resources for this tutorial, you can download them from our GitHub repository using the following link:
https://github.com/Embarcadero/PythonBlogExamples/tree/main/WeatherApp
What Kind of a Form do I Need For this Python GUI App?
Let’s launch the Delphi application and set up a new project. Navigate to the File menu, choose New -> Multi-Device Application, and select Blank Application before clicking OK. For this tutorial, we’ve given our project the name “WeatherApp.”
If you’re not yet familiar with the different sections and features within the Delphi IDE, we recommend checking out our complimentary eBook bundle. This resource offers a comprehensive overview of the Delphi Python EcoSystem and various Python GUI offerings.
Now, let’s assign a name to our primary form. Right-click on the form and choose QuickEdit. In this case, we’ll name our form MainForm
and add the caption Weather App
.
To simplify file tracking, we’ll rename the source file for MainForm
to WeatherApp.pas
. Additionally, you can adjust the form’s dimensions using the Object Inspector, modifying ClientWidth
and ClientHeight
properties, or simply by dragging and resizing it with your mouse.
For this weather app, we’ll utilize the TTabControl
component in the IDE to create two tabs on our form: one for current weather and the other for weather predictions. You can find this component in the Palette, and it can be added by simply dragging and dropping it onto the form.
How To Create A Weather App With The Python Delphi Ecosystem and Weatherstack API
Expand the component to cover the entire form area. This is how our form looks like.
Right-click on the component and select AddTabItem to add tabs to the form. As previously mentioned, we will include three tabs: one for displaying current weather, one for historical weather data, and another for future weather predictions. Then, use QuickEdit to rename the tabs to Current, Historical, and Prediction respectively.
Before, adding components to our tabs, let’s add a drop down menu, using the TComboBox component to help the user select a city for the app, as well as a few TLabel components. We will also use the text settings property of each label to make them more visually appealing. Here is what our form looks like:
Now that we’ve completed the top of our form, let’s start adding components to each tab of our TTabControl component. We will start by opening up the prediction tab of our form. Here we will be adding three different components. The TMemo component will be used to show the user the weather data. A TSpinBox component will be used to ask the user the number of days after which the user will show the data. Finally a TButton component will be used to call on the API to retrieve the weather data. We will also add a few TLabel components to help the user navigate through the form. Here is what our form looks like:
Now that our prediction tab is completed let’s complete our Current weather tab. Start by opening up the tab, and just like the Prediction tab, we will be adding a TMemo component to display our results as well as a TButton component to get the data. In addition, we will also be adding a TImage component that will be used to display an image consistent with our current weather. Here is what our form looks like:
Finally, let’s add some components to our Historical tab. Similar to the Prediction and Current Tab’s we will be adding a TButton and TMemo component. However, we will also be adding a TCalender component to help the user select the data for a specific historical date. Here is what our form looks like:
Now that we are done with the form’s, let’s infuse some style to make them more appealing. To do this, you’ll need to obtain the free eBook bundle, which includes various styles. Download the eBook and extract the FMX styles ZIP file to your preferred directory. Now select the style you want to use, in this tutorial we’ll be using the Dark.style
from the bundle.
Now Go to the Palette and add the TStyleBook
component to your form.
Double-click on the StyleBook1
component to access the Style Designer. Now click the open button, which will prompt a dialog box to appear.
Navigate to the .style
file within the dialog box, select the file, and then click exit.
Now, select the form and access the Object Inspector. Locate the StyleBook property, choose the drop-down menu, and select StyleBook1.
Here’s what our form looks like:
With the forms now complete, the last step requires us to add functionality to each button of our form. To do this, navigate to the location of each button in the form and double-click on each button. This action creates an OnClick method for that button subsequently creating a procedure in the .pas file and corresponds it to the .fmx file. This will allow you to add unique functionalities to each button when clicked.
However, due to the Delphi4PythonExporter’s lack of runtime implementation, we need to include at least one comment (//
) in each procedure to preserve its function when exporting the form.
How Can I Export The Form Into a Python Script?
Now that our form is ready, we can export the project by navigating to Tools > Export To Python > Export Current Entire Project
.
Here, we will give the project the title Weather App
. Next, select the directory of your choosing and click on Export:
Because we had one form, Delphi generated two Python files and one .pyfmx
file. The WeatherApp.py
file will be used to run the application, whereas the Main.py
file contains the class for the MainForm
. The Main.pyfmx
file contains the visual information for the MainForm
. Here are the contents for each file.
WeatherApp.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from delphifmx import * from Main import MainForm def main(): Application.Initialize() Application.Title = 'Weather App' Application.MainForm = MainForm(Application) Application.MainForm.Show() Application.Run() Application.MainForm.Destroy() if __name__ == '__main__': main() |
Main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import os from delphifmx import * class MainForm(Form): def __init__(self, owner): self.TabControl1 = None self.HistoricalTab = None self.HistoryButton = None self.Calendar1 = None self.HistoricalMemo = None self.DateLabel = None self.CurrentTab = None self.CurrentButton = None self.Image1 = None self.CurrentMemo = None self.PredictionTab = None self.Edit2 = None self.TabControl2 = None self.TabItem1 = None self.Button1 = None self.Calendar2 = None self.Memo1 = None self.Label1 = None self.TabItem2 = None self.Button2 = None self.Image2 = None self.Memo2 = None self.TabItem3 = None self.Edit1 = None self.PredictButton = None self.SpinBox1 = None self.PredictLabel = None self.PredictLabel2 = None self.PredictMemo = None self.TitleLabel = None self.CityLabel = None self.CityCombo = None self.StyleBook1 = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) def HistoryButtonClick(self, Sender): pass def CurrentButtonClick(self, Sender): pass def PredictButtonClick(self, Sender): pass |
How Can I Use the Weatherstack API to Add Functionality to this Form?
Now the only thing that remains is to add functionality to our app. Let’s start by opening up the Main.py
file and importing some libraries.
1 2 3 4 5 6 |
import os from delphifmx import * # Additional Imports import requests |
Next, navigate to the __init__
function. Here we will populate the CityCombo
box using APILayer’s geolocation API. We will use the API to retrieve all the cities in the United States, and then populate the combo box. Here is what our __init__
funciton looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
def __init__(self, owner): self.TabControl1 = None self.HistoricalTab = None self.HistoryButton = None self.Calendar1 = None self.CurrentTab = None self.CurrentButton = None self.Image1 = None self.PredictionTab = None self.Edit2 = None self.PredictButton = None self.TitleLabel = None self.CityLabel = None self.HistoricalMemo = None self.DateLabel = None self.CurrentMemo = None self.SpinBox1 = None self.PredictLabel = None self.PredictLabel2 = None self.PredictMemo = None self.CityCombo = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) # Get all cities in a particular country url = "https://api.apilayer.com/geo/country/cities/US" payload = {} headers= { "apikey": "your_api_key" } response = requests.request("GET", url, headers=headers, data = payload) result = response.json() # Store all cities in a list and even populate the combo box self.cities = [] for city in result: self.CityCombo.Items.Add(city["name"]) self.cities.append(city["name"]) |
Now that our CityCombo
box is populated, let’s head over to the HistoryButtonClick
function. Here we will start off by retrieving the date from the TCalender
component and, using a combination of .format()
and .zfill()
, convert it to an appropriate format.
1 2 3 |
def HistoryButtonClick(self, Sender): date = '{}-{}-{}'.format(self.Calendar1.DateTime[0], str(self.Calendar1.DateTime[1]).zfill(2), str(self.Calendar1.DateTime[2]).zfill(2)) # Storing date from calendar in the required format |
Next, we will define some parameters for our API, then use the ‘historical
‘ endpoint of the weatherstack API to retrieve the historical data for a selected city. Here is what our final HistoryButtonClick
function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def HistoryButtonClick(self, Sender): date = '{}-{}-{}'.format(self.Calendar1.DateTime[0], str(self.Calendar1.DateTime[1]).zfill(2), str(self.Calendar1.DateTime[2]).zfill(2)) # Storing date from calendar in the required format params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex], 'historical_date': date } api_result = requests.get('http://api.weatherstack.com/historical', params) api_response = api_result.json() self.HistoricalMemo.Lines.Add("Location : " + api_response["location"]["name"]) self.HistoricalMemo.Lines.Add("Min Temperature : " + str(api_response["historical"][date]["mintemp"])) self.HistoricalMemo.Lines.Add("Max Temperature : " + str(api_response["historical"][date]["maxtemp"])) self.HistoricalMemo.Lines.Add("Avg Temperature : " + str(api_response["historical"][date]["avgtemp"])) self.HistoricalMemo.Lines.Add("UV_Index : " + str(api_response["historical"][date]["uv_index"])) |
Now similarly to the HistoryButtonClick
function, we will define our CurrentClickButton
. Here, instead of the ‘historical
‘ endpoint, we will be using the ‘current
‘ endpoint to retrieve the data weather data. We will then uses the .Add
method of the TMemo
component to display it on our form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def CurrentButtonClick(self, Sender): params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex] } api_result = requests.get('http://api.weatherstack.com/current', params) api_response = api_result.json() self.CurrentMemo.Lines.Add("Location : " + api_response["location"]["name"]) self.CurrentMemo.Lines.Add("Temperature: " + str(api_response["current"]["temperature"])) self.CurrentMemo.Lines.Add("Feels Like: " + str(api_response["current"]["feelslike"])) self.CurrentMemo.Lines.Add("Description: " + api_response["current"]["weather_descriptions"][0]) self.CurrentMemo.Lines.Add("Percipitation : " + str(api_response["current"]["precip"])) self.CurrentMemo.Lines.Add("Humidity: " + str(api_response["current"]["humidity"])) self.CurrentMemo.Lines.Add("Windspeed: " + str(api_response["current"]["wind_speed"])) self.CurrentMemo.Lines.Add("Pressure: " + str(api_response["current"]["pressure"])) self.CurrentMemo.Lines.Add("UV_Index: " + str(api_response["current"]["uv_index"])) self.CurrentMemo.Lines.Add("Visibility: " + str(api_response["current"]["visibility"])) |
In addition to this, we will also use the image_url
parameter generated by the API, to first save the image and then display the image on our Current tab. Here is what our final function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
def CurrentButtonClick(self, Sender): params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex] } api_result = requests.get('http://api.weatherstack.com/current', params) api_response = api_result.json() self.CurrentMemo.Lines.Add("Location : " + api_response["location"]["name"]) self.CurrentMemo.Lines.Add("Temperature: " + str(api_response["current"]["temperature"])) self.CurrentMemo.Lines.Add("Feels Like: " + str(api_response["current"]["feelslike"])) self.CurrentMemo.Lines.Add("Description: " + api_response["current"]["weather_descriptions"][0]) self.CurrentMemo.Lines.Add("Percipitation : " + str(api_response["current"]["precip"])) self.CurrentMemo.Lines.Add("Humidity: " + str(api_response["current"]["humidity"])) self.CurrentMemo.Lines.Add("Windspeed: " + str(api_response["current"]["wind_speed"])) self.CurrentMemo.Lines.Add("Pressure: " + str(api_response["current"]["pressure"])) self.CurrentMemo.Lines.Add("UV_Index: " + str(api_response["current"]["uv_index"])) self.CurrentMemo.Lines.Add("Visibility: " + str(api_response["current"]["visibility"])) # URL of the image you want to load image_url = api_response["current"]["weather_icons"][0] # Send a GET request to the URL to retrieve the image data response = requests.get(image_url) if response.status_code == 200: # Read the image data from the response content image_data = response.content # Local path to save the image local_path = "image.png" # Save the image locally with open(local_path, "wb") as image_file: image_file.write(image_data) self.Image1.Bitmap.LoadFromFile(local_path) |
Finally, navigate to the PredictButtonClick
function and, like the previous functions, we will use the ‘forecast
‘ endpoint to retrieve the future prediction. Additionally, we will also use the .Text
property of the TSpinBox
component to get the number of days. Here is what our function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def PredictButtonClick(self, Sender): params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex], 'forecast_days': self.SpinBox1.Text, } api_result = requests.get('http://api.weatherstack.com/forecast', params) api_response = api_result.json() forecast = api_response["forecast"] # Iterate through the forecast dates for date, date_data in forecast.items(): self.PredictMemo.Lines.Add("Date: " + date) self.PredictMemo.Lines.Add("Min Temperature: " + str(date_data["mintemp"])) self.PredictMemo.Lines.Add("Max Temperature: " + str(date_data["maxtemp"])) self.PredictMemo.Lines.Add("Avg Temperature: " + str(date_data["avgtemp"])) self.PredictMemo.Lines.Add("UV_Index: " + str(date_data["uv_index"])) self.PredictMemo.Lines.Add("") # Add an empty line for separation |
Here is what our final Main.py
file looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
import os from delphifmx import * # Additional Imports import requests class MainForm(Form): def __init__(self, owner): self.TabControl1 = None self.HistoricalTab = None self.HistoryButton = None self.Calendar1 = None self.CurrentTab = None self.CurrentButton = None self.Image1 = None self.PredictionTab = None self.Edit2 = None self.PredictButton = None self.TitleLabel = None self.CityLabel = None self.HistoricalMemo = None self.DateLabel = None self.CurrentMemo = None self.SpinBox1 = None self.PredictLabel = None self.PredictLabel2 = None self.PredictMemo = None self.CityCombo = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) # Get all cities in a particular country url = "https://api.apilayer.com/geo/country/cities/US" payload = {} headers= { "apikey": "'your_api_key'" } response = requests.request("GET", url, headers=headers, data = payload) result = response.json() # Store all cities in a list and even populate the combo box self.cities = [] for city in result: self.CityCombo.Items.Add(city["name"]) self.cities.append(city["name"]) def HistoryButtonClick(self, Sender): date = '{}-{}-{}'.format(self.Calendar1.DateTime[0], str(self.Calendar1.DateTime[1]).zfill(2), str(self.Calendar1.DateTime[2]).zfill(2)) # Storing date from calendar in the required format params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex], 'historical_date': date } api_result = requests.get('http://api.weatherstack.com/historical', params) api_response = api_result.json() self.HistoricalMemo.Lines.Add("Location : " + api_response["location"]["name"]) self.HistoricalMemo.Lines.Add("Min Temperature : " + str(api_response["historical"][date]["mintemp"])) self.HistoricalMemo.Lines.Add("Max Temperature : " + str(api_response["historical"][date]["maxtemp"])) self.HistoricalMemo.Lines.Add("Avg Temperature : " + str(api_response["historical"][date]["avgtemp"])) self.HistoricalMemo.Lines.Add("UV_Index : " + str(api_response["historical"][date]["uv_index"])) def CurrentButtonClick(self, Sender): params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex] } api_result = requests.get('http://api.weatherstack.com/current', params) api_response = api_result.json() self.CurrentMemo.Lines.Add("Location : " + api_response["location"]["name"]) self.CurrentMemo.Lines.Add("Temperature: " + str(api_response["current"]["temperature"])) self.CurrentMemo.Lines.Add("Feels Like: " + str(api_response["current"]["feelslike"])) self.CurrentMemo.Lines.Add("Description: " + api_response["current"]["weather_descriptions"][0]) self.CurrentMemo.Lines.Add("Percipitation : " + str(api_response["current"]["precip"])) self.CurrentMemo.Lines.Add("Humidity: " + str(api_response["current"]["humidity"])) self.CurrentMemo.Lines.Add("Windspeed: " + str(api_response["current"]["wind_speed"])) self.CurrentMemo.Lines.Add("Pressure: " + str(api_response["current"]["pressure"])) self.CurrentMemo.Lines.Add("UV_Index: " + str(api_response["current"]["uv_index"])) self.CurrentMemo.Lines.Add("Visibility: " + str(api_response["current"]["visibility"])) # URL of the image you want to load image_url = api_response["current"]["weather_icons"][0] # Send a GET request to the URL to retrieve the image data response = requests.get(image_url) if response.status_code == 200: # Read the image data from the response content image_data = response.content # Local path to save the image local_path = "image.png" # Save the image locally with open(local_path, "wb") as image_file: image_file.write(image_data) self.Image1.Bitmap.LoadFromFile(local_path) def PredictButtonClick(self, Sender): params = { 'access_key': 'your_api_access_key', 'query': self.cities[self.CityCombo.ItemIndex], 'forecast_days': self.SpinBox1.Text, } api_result = requests.get('http://api.weatherstack.com/forecast', params) api_response = api_result.json() forecast = api_response["forecast"] # Iterate through the forecast dates for date, date_data in forecast.items(): self.PredictMemo.Lines.Add("Date: " + date) self.PredictMemo.Lines.Add("Min Temperature: " + str(date_data["mintemp"])) self.PredictMemo.Lines.Add("Max Temperature: " + str(date_data["maxtemp"])) self.PredictMemo.Lines.Add("Avg Temperature: " + str(date_data["avgtemp"])) self.PredictMemo.Lines.Add("UV_Index: " + str(date_data["uv_index"])) self.PredictMemo.Lines.Add("") # Add an empty line for separation |
What Does the Completed Project Look Like?
Now that everything is complete, all we need to do is open up the WeatherApp.py
file and run it.
Let’s head over to the Current tab and select a city using the CityCombo
box. Next, click on Get Data to retrieve the current weather information.
Now, let’s head over to the Historical tab and select a date for historical data. Next, click on the Get History button to retrieve the data.
Finally, we can even get the predicted weather for “New York City.” Head over to the Prediction tab and select the number of days for prediction. Now click on the Predict button to get the predicted data.
Are You Ready to Create Your Own Amazing Apps?
Congratulations! Thanks to the dynamic duo of DelphiFMX and Weatherstack, you’ve gained the skills to craft your own weather app. The app development world is within your grasp, and the possibilities are endless. Now, you’re not just forecasting the weather; you’re shaping innovative applications that can revolutionize how we engage with the world. But this is only the start. The DelphiFMX ecosystem provides a vast range of tools and resources, all at your disposal.
Ready to explore the vast app development landscape and bring your unique ideas to life? Get started now and dive into the world of app creation.
What Are Some FAQs Related to this Post?
Do I need prior experience with Delphi to follow this tutorial?
While prior experience with Delphi could be helpful, the tutorial is designed to provide step-by-step guidance, making it accessible to both beginners and experienced developers.
Can I use Weatherstack for free, or are there subscription plans?
Weatherstack offers both free and paid subscription plans. In this tutorial, a subscription plan providing access to all endpoints is used, but you can start with the free plan, which includes access to the current weather endpoint.
Can I deploy a weather app created with DelphiFMX on multiple platforms?
Yes, one of the advantages of using DelphiFMX is its ability to create cross-platform applications. You can deploy your weather app on platforms like Windows and macOS.
Can I use the knowledge from this tutorial to create other types of apps?
Absolutely! The principles and techniques covered in this tutorial can be applied to a wide range of app development projects, not just weather apps. Delphi is a versatile tool for creating various types of applications.
Can I customize the design and features of my weather app beyond what’s covered in this tutorial?
Yes, you can further enhance your weather app by customizing its design and adding additional features to meet your specific requirements. DelphiFMX offers flexibility for creative app development.