Friday, December 23, 2022

A Fun Geeky Project during a Bomb Cyclone

We are apparently in the midst of a Bomb Cyclone. I thought it might be fun to compare the temperatures around the state during one. Well there is a Linux app called "weather-util" and when provided with a Weather Station it reports on the current conditions like so:


terry@CloudedLeopord:~/Documents/fpc/WeatherStations$ weather-util kdsm
Current conditions at Des Moines International, IA
Last updated Dec 23, 2022 - 01:57 PM EST / 2022.12.23 1857 UTC
   Temperature: -2.9 F (-19.4 C)
   Relative Humidity: 71%
   Wind: from the WNW (300 degrees) at 29 MPH (25 KT) gusting to 40 MPH (35 KT)
   Windchill: -29 F (-34 C)
   Weather: blowing snow
   Sky conditions: overcast
First I needed to extract all the weather stations for Iowa (That's my state). So I wrote the following little program.
Program CaptureWS;
Uses SysUtils, db, sqldb, mysql80conn, StrUtils;
{$I /home/terry/Documents/fpc/MysqlConnLib.inc}
{$I /home/terry/Documents/fpc/DbConst.inc}
Type
StationType = Record
StationCode: String[10];
StationDesc: String[80];
StationLoc: String[40];
StationMetar: String[80];
StationZone: String[40];
End;
Const
KkMax=5000;

Var
WSConn: TSqlConnector;
WSTran: TsqlTransaction;
WSQry: TSqlQuery;
IFile: Text;
i, Tst, Cnt: LongInt;
StationArray: Array[1..KkMax] of StationType;
StationBuffer: StationType;
Buffer, Work: String;
Sql: AnsiString;


Begin
Assign(IFile, 'stations.txt');
Reset(IFile);
ReadLn(IFile, Buffer);
i := 1;
Repeat
If Copy(Buffer,1,1) = '[' Then
Begin
StationBuffer.StationCode := Copy(Buffer,1,6);
ReadLn(IFile, Buffer);
Work := Copy(Buffer,1,80);
StationBuffer.StationDesc := Copy(Buffer,1,80);
ReadLn(IFile, Buffer);
StationBuffer.StationLoc := Copy(Buffer,1,40);
ReadLn(IFile, Buffer);
StationBuffer.StationMetar := Copy(Buffer,1,80);
ReadLn(IFile, Buffer);
StationBuffer.StationZone := Copy(Buffer,1,80);
Tst := Pos(', IA,', Work );
If Tst > 0 Then
Begin
StationArray[i] := StationBuffer;
i := i + 1;
End;
End;
ReadLn(IFile, Buffer);
Until Eof(IFile);
Cnt := i - 1;
WriteLn;

WSConn := CreateConnection('MySQL 8.0', '127.0.0.1', 'WeatherStation', MyUser, MyPass);
CreateTransaction(WSConn, WSTran);
WSQry := GetQuery(WSConn, WSTran);
WSConn.Open;
For i := 1 To Cnt Do
Begin
Sql := 'Insert Into WeatherStation(WSCode, WSDesc, WSLoc, WSMetar, WSZone) Values (' +
QuotedStr(StationArray[i].StationCode) + ', ' +
QuotedStr(StationArray[i].StationDesc) + ', ' +
QuotedStr(StationArray[i].StationLoc) + ', ' +
QuotedStr(StationArray[i].StationMetar) + ', ' +
QuotedStr(StationArray[i].StationZone) + ')';
WSQry.Sql.Clear;
WSQry.Sql.Add(Sql);
WSQry.ExecSql;
WSTran.Commit;
End;
WSConn.Close;
WSQry.Free;
WSConn.Free;
WSTran.Free;
End.

I am using a free compiler here called Free Pascal, it is relatively fast. It extracts the data from a text file (called stations.txt) that I found on the Interwebs. The data looks like the following:

[k1p1]
description = Plymouth, Plymouth Municipal Airport, NH, United States
location = (0.7640906, -1.2523368)
metar = http://tgftp.nws.noaa.gov/data/observations/metar/decoded/K1P1.TXT
zone = ('nhz005', 0.0023109)

[k1r7]
description = Brookhaven, Ms, US
location = (0.5515240, -1.5781267)
metar = http://tgftp.nws.noaa.gov/data/observations/metar/decoded/K1R7.TXT
zone = ('msz062', 0.0012836)

[k1s5]
description = Sunnyside Muni, Sunnyside, WA, United States of America
location = (0.8085601, -2.0938778)
metar = http://tgftp.nws.noaa.gov/data/observations/metar/decoded/K1S5.TXT
zone = ('waz027', 0.0045105)

So when I find a square left bracket in column one, I know I am on the first line of a new data item and I save that line and the next three lines to a data structure.  If the 2nd line of the data item contains a ", IA,", I know it is an Iowa Weather Station and the new data structure gets saved to an array.  Finally, I save all the data records in the array to a MySQL table for easy access.

Once I got the Weather Stations into a MySql table, I wrote another program to calculate the average and Standard Deviation for all of the Iowa Weather Stations.

Program ListIAWeather;
Uses Classes, SysUtils, db, sqldb, mysql80conn, StrUtils, Process, Math;
{$I /home/terry/Documents/fpc/MysqlConnLib.inc}
{$I /home/terry/Documents/fpc/DbConst.inc}

Type
	DblArrayType = Array[1..5000] of Double;

		
Var
	WSConn:					TSqlConnector;
	WSTran: 					TsqlTransaction;
	WSQry:					TSqlQuery;
	CityCode:					String;
	WeatherP: 					TProcess;
  	WeatherSL: 					TStringList;
  	i, j, k:					LongInt;
  	Temp, WChill:				DblArrayType;
  	AvgTemp, AvgWChill, StdTemp, 
  	StdWChill:					Double;
  	Work:						String;
  	
Function Avg(a: DblArrayType; n: LongInt): Double;
Var
	d, Cnt:	Double;
	i:		LongInt;
Begin
	d := 0; Cnt := n;
	For i := 1 To n Do
		d := d + a[i];
	Avg := d / Cnt;
End;

Function PopStd(a: DblArrayType; n: LongInt; AV: Double): Double;
Var
	d, Cnt:		Double;
	i:			LongInt;
Begin
	d := 0; Cnt := n;
	For i := 1 To n Do 
		d := d + Sqr(AV - a[i]);
	PopStd := Sqrt(d / Cnt);	
End;
	
Begin
	WSConn := CreateConnection('MySQL 8.0', '127.0.0.1', 'WeatherStation', MyUser, MyPass);
	CreateTransaction(WSConn, WSTran);
	WSQry := GetQuery(WSConn, WSTran);
	
	WSQry.SQL.Text := 'Select WSCode from WeatherStation;';
	WSConn.Open;
	WSQry.Open;
	WeatherP := TProcess.Create(nil);
	WeatherSL := TStringList.Create;
	WeatherP.Executable := 'weather-util';
	WeatherP.Options := WeatherP.Options + [poWaitOnExit, poUsePipes]; 
	j := 1; k := 1;
	If WSConn.Connected Then
		Repeat
			CityCode := Copy(WSQry.FieldByName('WSCode').AsString,2,4);
			WeatherP.Parameters.Clear; 
			WeatherP.Parameters.Add(CityCode); 
			WeatherP.Execute;
			WeatherSL.LoadFromStream(WeatherP.Output);
			For i := 0 To WeatherSL.Count-1 Do 
			Begin
				WriteLn(WeatherSL[i]);
				Work := WeatherSL[i];
				If Pos('Temperature', Work) > 0 Then
				Begin
					Temp[j] := StrToFloat(ExtractWord(2, Work, [' ']));
					j := j + 1;
				End;
				If Pos('Windchi', Work) > 0 Then
				Begin
					WChill[k] := StrToFloat(ExtractWord(2, Work, [' ']));
					k := k + 1;
				End;
			End;
			WriteLn;
			WSQry.Next
		Until WSQry.Eof;
	WSQry.Close;
	WSConn.Close;
	WSQry.Free;
	WSConn.Free;
	WSTran.Free;
	j := j - 1;
	k := k - 1;
	AvgTemp := Avg(Temp, j);
	StdTemp := PopStd(Temp, j, AvgTemp);
	
	AvgWChill := Avg(WChill, k);
	StdWChill := PopStd(WChill, k, AvgWChill);
	
	WriteLn('Avg. Temp: ', AvgTemp:6:2, '     Std: ', StdTemp:6:2);
	WriteLn('Avg. WChill: ', AvgWChill:6:2, '     Std: ', StdWChill:6:2);
End.

Ok, this program gets the weather station name from the mysql table and executes "weather-util" in the background.  The output from that program is captured and put in a TStringList(a sort of an array of strings) and from that each line is analyzed.  Each temperature and windchill are entered into an array.  Finally, each array the average and standard deviation is calculated.  

Standard Deviation is a number representing the spread of data items and is heavily used in business and science.  One deviation from mean should cover 68% of data items, 2 deviations should cover 95% and three deviations should cover 99.97% of all data items.


On 12/22 I got this result:

Avg. Temp:  -7.98     Std:   5.53
Avg. WChill: -33.43     Std:   7.42

So across the state 68 percent of the sites should have a temperature -13.51 to -2.45.

On 12/23, I reran it and got the following:

Avg. Temp:  -2.11     Std:   2.90
Avg. WChill: -27.81     Std:   4.47
 

A muuch smaller variation of the data. So the temperature was much more similar across the whole state than yesterday.

No comments: