PBKK B - ETS - Membuat Aplikasi Prakiraan Cuaca

ETS - Membuat Aplikasi Prakiraan Cuaca

4 Maret 2023

Pada ETS kali ini, saya membuat Aplikasi Prakiraan Cuaca berbasis dekstop menggunakan teknologi Windows Presentation Form (WPF) dengan .NET Framework. Untuk data cuaca, saya menggunakan API https://openweathermap.org/.

Fitur-fitur yang ada pada aplikasi ini antara lain.
  • Cari Kota (Default = Surabaya)
  • Menampilkan Hari dan Jam
  • Menampilkan Data Cuaca
    • Temperature
    • Weather
    • Cloudiness
    • Wind status
    • Sunrise & sunset
    • Geo Location
    • Humidity
    • Visibility, dan
    • Pressure
Berikut beberapa referensi yang saya gunakan
Repository lengkap dapat diakses melalui Github

Video Demonstrasi

Dokumentasi

  • Tampilan awal aplikasi
  • Tampilan setelah melakukan pencarian kota



Source Code

<Window x:Class="WeatherApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WeatherApp"
mc:Ignorable="d"
Title="Weather App" Height="685" Width="995" Background="White"
WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen" FontFamily="Cambria">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="260"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--Left Side-->
<Border CornerRadius="30 0 0 30" Background="#FFFFFF">
<StackPanel>
<!--TextBox Search-->
<Border BorderBrush="#d9d9d9" BorderThickness="1" CornerRadius="15" HorizontalAlignment="Center" Width="200" Margin="0 35 0 0">
<Grid Margin="7 7">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="/Resources/explore.png" Height="18" Margin="5 0 0 0"/>
<TextBlock x:Name="textSearch" MouseDown="textSearch_MouseDown" Text="Search ..." Style="{StaticResource textHint}"/>
<TextBox Name="txtSearch" TextChanged="txtSearch_TextChanged" PreviewKeyDown="txtSearch_PreviewKeyDown" Style="{StaticResource textBox}"/>
</Grid>
</Border>
<StackPanel Margin="50 60 0 0">
<Image Source="/Resources/weather-app.png" Width="140" HorizontalAlignment="Left" />
<TextBlock x:Name="label_temp" Text="12°c" FontSize="46" Margin="0 20 0 0"/>
<TextBlock x:Name="label_datetime" Text="Monday, 16:00" FontSize="18" FontWeight="SemiBold" Margin="0 15 0 0"/>
<Separator Background="#dadada" Margin="3 30 40 30" Height="0.8"/>
<StackPanel Orientation="Horizontal">
<Image x:Name="image_weather" Source="/Resources/cloud.png" Width="20" Height="20"></Image>
<TextBlock x:Name="label_weather" Text="Mostly Cloudy" FontSize="14" FontWeight="SemiBold" Margin="10 0 0 0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 15 0 77">
<Image Source="/Resources/cloud.png" Width="20" Height="20"/>
<TextBlock x:Name="label_cloudiness" Text="Cloudiness - 30%" FontSize="14" FontWeight="SemiBold" Margin="10 0 0 0" VerticalAlignment="Center"/>
</StackPanel>
</StackPanel>
<Border CornerRadius="15" Background="Black" Margin="30 0">
<Border CornerRadius="15" Padding="0 30">
<Border.Background>
<ImageBrush ImageSource="/Resources/q1.jpg" Stretch="Fill" Opacity="0.65"/>
</Border.Background>
<TextBlock x:Name="label_city" Text="Surabaya" Foreground="#FFFFFF" VerticalAlignment="Center" TextAlignment="Center" FontWeight="Bold" FontSize="14"/>
</Border>
</Border>
</StackPanel>
</Border>
<!--Right Side-->
<Border Grid.Column="1" CornerRadius="0 30 30 0" MouseDown="Border_MouseDown" Background="#F6F6F6" >
<StackPanel>
<!--Title-->
<Grid Margin="40 30 40 10">
<TextBlock Text="Today's Highlights" FontSize="16" FontWeight="SemiBold" Margin="40 10 0 10"/>
<Button HorizontalAlignment="Right" BorderThickness="0" Width="30" Height="30" Click="btnExit_Click">
<Button.Background>
<ImageBrush ImageSource="/Resources/close.png"/>
</Button.Background>
</Button>
</Grid>
<!--Widget Section-->
<WrapPanel Margin="40 0 0 0">
<!--Widget 1-->
<Border Style="{StaticResource widgetBorder}">
<Grid>
<TextBlock Text="Wind Status" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock x:Name="label_windSpeed" Text="7.70" FontSize="34" FontWeight="SemiBold"/>
<TextBlock Text="km/h" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<Image Source="/Resources/dir.png" Width="25" Height="25" Margin="0 0 10 0"/>
<TextBlock x:Name="label_windDirection" Text="WSW" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</Border>
<!--Widget 2-->
<Border Style="{StaticResource widgetBorder}" BorderBrush="#03A9F4">
<StackPanel>
<TextBlock Text="Sunrise Sunset" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 25 0 0">
<Image Source="/Resources/sunrise.png" Width="40" Height="40" Margin="0 0 10 0"/>
<StackPanel VerticalAlignment="Bottom">
<TextBlock x:Name="label_sunrise" Text="6:35 AM" FontSize="16" FontWeight="SemiBold"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 10 0 0">
<Image Source="/Resources/sunset.png" Width="40" Height="40" Margin="0 0 10 0"/>
<StackPanel VerticalAlignment="Bottom">
<TextBlock x:Name="label_sunset" Text="5:12 AM" FontSize="16" FontWeight="SemiBold"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!--Widget 3-->
<Border Style="{StaticResource widgetBorder}">
<StackPanel>
<TextBlock Text="Geo Location" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 25 0 0">
<Image Source="/Resources/latitude.png" Width="40" Height="40" Margin="0 0 10 0"/>
<StackPanel>
<TextBlock x:Name="label_latitude" Text="6:35 AM" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="latitude" FontSize="12" Margin="0 2 0 0" Foreground="#a0a0a0"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 10 0 0">
<Image Source="/Resources/longitude.png" Width="40" Height="40" Margin="0 0 10 0"/>
<StackPanel>
<TextBlock x:Name="label_longitude" Text="5:12 AM" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="longitude" FontSize="12" Margin="0 2 0 0" Foreground="#a0a0a0"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!--Widget 4-->
<Border Style="{StaticResource widgetBorder}">
<Grid>
<TextBlock Text="Humidity" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock x:Name="label_humidity" Text="56" FontSize="34" FontWeight="SemiBold"/>
<TextBlock Text="%" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Name="label_humidity_rate" Text="Normal" FontWeight="SemiBold"/>
<Image Source="/Resources/humidity.png" Width="18" Height="18" Margin="10 0 0 0"/>
</StackPanel>
<Slider x:Name="slider_humidity" Style="{DynamicResource SliderStyle1}" Value="5.5" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90" IsEnabled="false"/>
</Grid>
</Border>
<!--Widget 5-->
<Border Style="{StaticResource widgetBorder}">
<Grid>
<TextBlock Text="Visibility" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock x:Name="label_visibility" Text="5.2" FontSize="34" FontWeight="SemiBold"/>
<TextBlock Text="km" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Name="label_visibility_rate" Text="Average" FontWeight="SemiBold"/>
<Image Source="/Resources/smog.png" Width="18" Height="18" Margin="10 0 0 0"/>
</StackPanel>
<Slider x:Name="slider_visibility" IsEnabled="false" Style="{DynamicResource SliderStyle1}" Value="4" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90"/>
</Grid>
</Border>
<!--Widget 6-->
<Border Style="{StaticResource widgetBorder}">
<Grid>
<TextBlock Text="Atmospheric Pressure" Style="{StaticResource titleText}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock x:Name="label_pressure" Text="105" FontSize="34" FontWeight="SemiBold"/>
<TextBlock Text="hPa" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Name="label_pressure_rate" Text="Unhealthy" FontWeight="SemiBold"/>
<Image Source="/Resources/atmospheric.png" Width="18" Height="18" Margin="10 0 0 0"/>
</StackPanel>
<Slider x:Name="slider_pressure" IsEnabled="false" Style="{DynamicResource SliderStyle1}" Value="7" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90"/>
</Grid>
</Border>
</WrapPanel>
</StackPanel>
</Border>
</Grid>
</Window>
view raw MainWindow.xaml hosted with ❤ by GitHub
using System;
using System.Windows;
using System.Windows.Input;
using Newtonsoft.Json;
using System.Net;
using System.Data.SqlTypes;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace WeatherApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
string city = "Surabaya";
string api_key = "0a2e2179a81ac8b8d5037759ebaa6562";
private DispatcherTimer timer;
public MainWindow()
{
InitializeComponent();
// Create and start the timer
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += Timer_Tick;
timer.Start();
getData_fromAPI();
}
private void Timer_Tick(object sender, EventArgs e)
{
// Get the current time
DateTime currentTime = DateTime.Now;
// Format the time as a string in the format "09:00 PM"
string timeString = currentTime.ToString("dddd,\nhh:mm:ss tt");
// Update the label with the time string
label_datetime.Text = timeString;
}
private void Border_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
this.DragMove();
}
private void textSearch_MouseDown(object sender, MouseButtonEventArgs e)
{
txtSearch.Focus();
}
private void txtSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
if (!string.IsNullOrEmpty(txtSearch.Text) && txtSearch.Text.Length > 0)
textSearch.Visibility = Visibility.Collapsed;
else
textSearch.Visibility = Visibility.Visible;
}
private void txtSearch_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.city = txtSearch.Text.ToString();
getData_fromAPI();
}
}
private async void getData_fromAPI()
{
try
{
using (WebClient webClient = new WebClient())
{
string url = string.Format("https://api.openweathermap.org/data/2.5/weather?q={0}&appid={1}", this.city, this.api_key);
string json = webClient.DownloadString(url);
WeatherData.root weatherData = new WeatherData.root();
weatherData = JsonConvert.DeserializeObject<WeatherData.root>(json);
// update data
label_temp.Text = Math.Floor(weatherData.main.temp - 273.15).ToString() + "°c";
label_weather.Text = toCamelCase((weatherData.weather[0].description).ToString());
image_weather.Source = new BitmapImage(new Uri("https://openweathermap.org/img/w/" + weatherData.weather[0].icon + ".png"));
label_cloudiness.Text = String.Format("Cloudiness - {0}%", weatherData.clouds.all.ToString());
label_city.Text = toCamelCase(this.city);
label_windSpeed.Text = (weatherData.wind.speed * 3.6).ToString("F2");
label_windDirection.Text = ConvertWindDirection(weatherData.wind.deg);
label_sunrise.Text = UnixTimestampToLocalDateTime(weatherData.sys.sunrise).ToString("h:mm tt");
label_sunset.Text = UnixTimestampToLocalDateTime(weatherData.sys.sunset).ToString("h:mm tt");
label_humidity.Text = weatherData.main.humidity.ToString();
label_humidity_rate.Text = GetHumidityCategory(weatherData.main.humidity);
slider_humidity.Value = weatherData.main.humidity / 10;
label_visibility.Text = (weatherData.visibility / 1000).ToString();
label_visibility_rate.Text = GetVisibilityCategory(weatherData.visibility / 1000);
slider_visibility.Value = weatherData.visibility / 1000;
label_pressure.Text = weatherData.main.pressure.ToString();
label_pressure_rate.Text = GetPressureCategory(weatherData.main.pressure);
slider_pressure.Value = 1 + (weatherData.main.pressure - 980) * (10 - 1) / (1030 - 980);
label_latitude.Text = weatherData.coord.lat.ToString();
label_longitude.Text = weatherData.coord.lat.ToString();
}
}
catch (Exception ex)
{
// handle the exception
MessageBox.Show($"An error occurred: {ex.Message}");
}
}
private string toCamelCase(string str)
{
string[] words = str.Split(' ');
for (int i = 0; i < words.Length; i++)
{
words[i] = char.ToUpper(words[i][0]) + words[i].Substring(1);
}
string camelCaseString = string.Join(" ", words);
return camelCaseString;
}
public static string ConvertWindDirection(double windDirectionInDegrees)
{
string windDirectionCategory;
switch ((int)((windDirectionInDegrees / 22.5) + 0.5) % 16)
{
case 0:
windDirectionCategory = "North";
break;
case 1:
windDirectionCategory = "North-northeast";
break;
case 2:
windDirectionCategory = "Northeast";
break;
case 3:
windDirectionCategory = "East-northeast";
break;
case 4:
windDirectionCategory = "East";
break;
case 5:
windDirectionCategory = "East-southeast";
break;
case 6:
windDirectionCategory = "Southeast";
break;
case 7:
windDirectionCategory = "South-southeast";
break;
case 8:
windDirectionCategory = "South";
break;
case 9:
windDirectionCategory = "South-southwest";
break;
case 10:
windDirectionCategory = "Southwest";
break;
case 11:
windDirectionCategory = "West-southwest";
break;
case 12:
windDirectionCategory = "West";
break;
case 13:
windDirectionCategory = "West-northwest";
break;
case 14:
windDirectionCategory = "Northwest";
break;
case 15:
windDirectionCategory = "North-northwest";
break;
default:
windDirectionCategory = "Unknown";
break;
}
return windDirectionCategory;
}
public static DateTime UnixTimestampToLocalDateTime(double unixTimestamp)
{
DateTimeOffset utcDateTimeOffset = DateTimeOffset.FromUnixTimeSeconds((long)unixTimestamp);
return utcDateTimeOffset.ToLocalTime().DateTime;
}
public static string GetHumidityCategory(double humidityPercentage)
{
string category;
switch (humidityPercentage)
{
case var h when h < 0 || h > 100:
category = "Invalid humidity percentage";
break;
case var h when h <= 30:
category = "Dry";
break;
case var h when h <= 60:
category = "Comfortable/Normal";
break;
case var h when h <= 90:
category = "Humid";
break;
default:
category = "Very humid";
break;
}
return category;
}
public static string GetVisibilityCategory(double distanceInKilometers)
{
string category;
switch (distanceInKilometers)
{
case var d when d >= 0 && d < 0.1:
category = "Poor";
break;
case var d when d >= 0.1 && d < 1:
category = "Moderate";
break;
case var d when d >= 1 && d < 2:
category = "Fairly good";
break;
case var d when d >= 2 && d < 5:
category = "Good";
break;
case var d when d >= 5 && d < 10:
category = "Very good";
break;
case var d when d >= 10:
category = "Excellent";
break;
default:
category = "Invalid distance";
break;
}
return category;
}
public static string GetPressureCategory(double pressureInHpa)
{
string category;
switch (pressureInHpa)
{
case var p when p < 980:
category = "Low";
break;
case var p when p >= 980 && p < 1013:
category = "Normal";
break;
case var p when p >= 1013 && p < 1030:
category = "High";
break;
case var p when p >= 1030:
category = "Very high";
break;
default:
category = "Invalid pressure";
break;
}
return category;
}
private void btnExit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}

Nama : Arief Badrus Sholeh
NRP : 5025201228
Kelas : PBKK B
Tahun : 2022/2023

Komentar