METARs are coded reports of current weather conditions at airports. They are often produced automatically, and there is a well defined structure to the encoded information. There are websites which, when given a METAR, will decode it1. Decoded METARs for a 24 hours period are available from the NOAA in the USA{^2]. Current METARS are also available2. The NOAA also publish detailed descriptions of METAR codes3
An example for Dublin Airport is shown below.
EIDW 031200Z 26013KT 9999 FEW016 BKN024 04/01 Q1030 NOSIG
If we break this down the decoding is relatively quick:
The METARs can be read and decoded in R. The RMETAR package4 has a decoder function. This note shows how the temperatures can be extracted to create a daily series. The data were obtained, for a month at a time, from OGIMET5.
Each month’s file has header information and the METARS. Each METAR is terminated with a = character. Some may stretch over two lines. If a station has not given a reading, then the value of the METAR is NIL=. Each METAR is date stamped with a 12 character string: yyyymmddhhmm and this is followed by the string METAR. The METAR data proper follows from the 20ht character.
A single month’s file can be read thus:
con <- file(fdname,"r")
x <- readLines(con)
close(con)
This creates a character vector x in which each element is a single line from the file.
The next task is to remove the header lines. The data lines begin with the year.
xx <- which(substr(x,1,4) == '2016')
x <- x[-(1:(xx[1]-1))]
Then those METARS split over two lines are joined into a single string.
xcon <- grep("=",x)
M <- length(xcon)
notin <- function(x,table) match(x,table, nomatch = 0) == 0
N <- length(x)
joinback <- which(notin(1:N,xcon))
for(i in joinback) x[i] <- paste(x[i],x[i+1],sep="")
x <- x[-(joinback+1)]
The lines with no data are then removed.
NILlines <- grep("NIL=",x)
x <- x[-NILlines]
Finally, the temperatures can be extracted. This needs a regular expression to identify the data string. The temperature are two digits, preceded by the letter M if the temperature is blow zero Celsius. The current temperature and the dew point temperature are separated by a slash. The regexp M?[0-9][0-9]/M?[0-9][0-9] works well. In the example, we are interested in the air temperatures, and not the dew point temperatures.
N <- length(x)
temps <- rep(NA,N)
tempInt <- rep(0,N)
for(i in 1:N){
metar <- substr(x[i],20,nchar(x[i])-1)
result <- regexpr(" M?[0-9][0-9]/M?[0-9][0-9] ",metar)
temps[i] <- substr(metar,result[1]+1,result[1]+attr(result,"match.length")-2)
temps[i] <- gsub("M","-",temps[i])
t2 <- strsplit(temps[i],"/")
tempInt[i] <- as.numeric(t2[[1]][1])
}
We might also want the temperatures summarized for each day. In this example, MonthAve is a list with twelve elements, indexed by Month.
Dates <- substr(x,1,8)
MonthAve[[Month]] <- tapply(tempInt,Dates,mean)
MonthMax[[Month]] <- tapply(tempInt,Dates,max)
MonthMin[[Month]] <- tapply(tempInt,Dates,min)
The opreceding can be gathered together in a loop to process several files - in this case there is one file for each month in 2016.
Months <- 1:12
NM <- length(Months)
MonthAve <- vector("list",NM)
MonthMin <- vector("list",NM)
MonthMax <- vector("list",NM)
MonthPrs <- vector("list",NM)
MonthWnd <- vector("list",NM)
for(Month in Months) {
###
### Open and read the next file
###
if (Month < 10) {
fdname <- paste("2016_0",Month,"_EGNT.txt",sep="")
} else {
fdname <- paste("2016_",Month,"_EGNT.txt",sep="")
}
con <- file(fdname,"r")
x <- readLines(con)
close(con)
print(fdname)
###
### Remove the header lines
###
N <- length(x)
xx <- which(substr(x,1,4) == '2016')
x <- x[xx[1]:N]
###
### Join split records
###
xcon <- grep("=",x)
M <- length(xcon)
notin <- function(x,table) match(x,table, nomatch = 0) == 0
N <- length(x)
joinback <- which(notin(1:N,xcon))
for(i in joinback) x[i] <- paste(x[i],x[i+1],sep="")
x <- x[-(joinback+1)]
###
### Remove NIL records
###
NILlines <- grep("NIL=",x)
x <- x[-NILlines]
N <- length(x)
temps <- rep(NA,N)
tempInt <- rep(0,N)
tempPrs <- rep(0,N)
tempWnd <- rep(0,N)
for(i in 1:N){
metar <- substr(x[i],20,nchar(x[i])-1)
result <- regexpr(" M?[0-9][0-9]/M?[0-9][0-9] ",metar)
temps[i] <- substr(metar,result[1]+1,result[1]+attr(result,"match.length")-2)
temps[i] <- gsub("M","-",temps[i])
t2 <- strsplit(temps[i],"/")
tempInt[i] <- as.numeric(t2[[1]][1])
result <- regexpr(" Q[0-9][0-9][0-9][0-9]",metar)
tmp.p <- substr(metar,result[1]+2,result[1]+attr(result,"match.length")-1)
tempPrs[i] <- as.numeric(tmp.p)
result <- regexpr("[0-9][0-9]KT",metar)
tmp.w <- substr(metar,result[1],result[1]+attr(result,"match.length")-3)
tempWnd[i] <- as.numeric(tmp.w)
}
Dates <- substr(x,1,8)
MonthAve[[Month]] <- tapply(tempInt,Dates,mean)
MonthMax[[Month]] <- tapply(tempInt,Dates,max)
MonthMin[[Month]] <- tapply(tempInt,Dates,min)
MonthWnd[[Month]] <- tapply(tempWnd,Dates,min)
MonthPrs[[Month]] <- tapply(tempPrs,Dates,min)
}
## [1] "2016_01_EGNT.txt"
## [1] "2016_02_EGNT.txt"
## [1] "2016_03_EGNT.txt"
## [1] "2016_04_EGNT.txt"
## [1] "2016_05_EGNT.txt"
## [1] "2016_06_EGNT.txt"
## [1] "2016_07_EGNT.txt"
## [1] "2016_08_EGNT.txt"
## [1] "2016_09_EGNT.txt"
## [1] "2016_10_EGNT.txt"
## [1] "2016_11_EGNT.txt"
## [1] "2016_12_EGNT.txt"
#
# Some processing for visualisation
#
MaxTemp <- MonthMax[[1]]
MinTemp <- MonthMin[[1]]
AveTemp <- MonthAve[[1]]
AvePres <- MonthPrs[[1]]
AveWind <- MonthWnd[[1]]
for (i in 2:NM) {
MaxTemp <- c(MaxTemp,MonthMax[[i]])
MinTemp <- c(MinTemp,MonthMin[[i]])
AveTemp <- c(AveTemp,MonthAve[[i]])
AveWind <- c(AveWind,MonthWnd[[i]])
AvePres <- c(AvePres,MonthPrs[[i]])
}
omits <- which(is.na(AveTemp))
AveTemp <- AveTemp[-omits]
MaxTemp <- MaxTemp[-omits]
MinTemp <- MinTemp[-omits]
AveWind <- AveWind[-omits]
AvePres <- AvePres[-omits]
Tmax <- max(MaxTemp,na.rm=T)
Tmin <- min(MinTemp,na.rm=T)
mLen <- c(31,29,31, 30,31,30, 31,31,30, 31,30,31)
mStart <- c(1,cumsum(mLen[1:(NM-1)])+1)
mMid <- (cumsum(mLen)+mStart)/2
mName <- c("Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec")
###
### Line plot of average temperature
###
plot(NA,ylim=c(Tmin,Tmax),xlim=c(1,366),xaxt="n",xlab="Month",ylab="Temperature (C)")
axis(1,at=mMid,labels=mName)
abline(h=0,col="lightblue")
abline(v=cumsum(mLen[1:(NM-1)])+0.5,col="lightgrey",lty=2)
abline(h=6,col="green",lty=2)
lines(AveTemp,)
#lines(MaxTemp,lty=1,col="darkgrey")
#lines(MinTemp,lty=1,col="darkgrey")
title("Diurnal Temperature Variation at EGNT 2016")
###
### Boxplot of monthly variation
###
MonthList <- rep(NA,sum(mLen))
for (i in 1:12) {
MonthList[mStart[i]:(mStart[i]+mLen[i]-1)] <- mName[i]
}
boxplot(AveTemp~MonthList,at=c(4,8,12,2,1,7,6,3,5,11,10,9),
xlab="Month",ylab="Temperature (C)")
title("Daily Average Temperatures by Month at EGNT 2016")
###
### Range by day
###
DailyRange <- MaxTemp - MinTemp
plot(DailyRange,type="l",xaxt="n",xlab="Month",ylab="Temperature Range (C)")
axis(1,at=mMid,labels=mName)
abline(v=cumsum(mLen[1:(NM-1)])+0.5,col="red",lty=2)
title("Diurnal Temperature Range at EGNT 2016")
###
### Boxplot of wind variation
###
boxplot(AveWind~MonthList,at=c(4,8,12,2,1,7,6,3,5,11,10,9),
xlab="Month",ylab="Windspeed (Kt)")
title("Daily Average Windspeed by Month at EGNT 2016")
###
### Boxplot of pressure variation
###
boxplot(AvePres~MonthList,at=c(4,8,12,2,1,7,6,3,5,11,10,9),
xlab="Month",ylab="Pressure (hPa)")
title("Daily Average Pressure by Month at EGNT 2016")
There are plenty of other possibilities - precipitation and visibility are a little more complex.