Introduction

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:

  1. EIDW: the ICAO code for Dublin Airport
  2. 031200Z: Day 3 of the month, 12:00 UTC [Z = Zulu Time]
  3. 26013KT: Wind direction: 260 degrees, speed: 13 knots
  4. 9999: Visibility is 10km or further
  5. FEW016: A few clouds at 1600 feet above airport
  6. BKN024: Broken cloud at 2400 feet above airport
  7. 04/01: Temperature 4C, with the dew point at 1C
  8. Q1030: Sea level air pressure (QNH) is 1030hPa
  9. NOSIG: No significant change in hear future

Preliminaries

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)

Processing loop

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")

Finally

There are plenty of other possibilities - precipitation and visibility are a little more complex.


  1. For example http://heras-gilsanz.com/manuel/METAR-Decoder.html

  2. For Dublin Airport: http://tgftp.nws.noaa.gov/data/observations/metar/stations/EIDW.TXT

  3. http://www.wrh.noaa.gov/wrh/metar_decode_key.pdf (with US codes)

  4. https://github.com/jwijffels/RMETAR

  5. http://www.ogimet.com/metars.phtml.en