Stations Database
An online database of GHCN stations with meta-data will be useful for extracting station lists for analysis.
Discuss…
An Open Analysis of the Historical Temperature Record
{ Monthly Archives }
An online database of GHCN stations with meta-data will be useful for extracting station lists for analysis.
Discuss…
/// <summary>
/// Generate a mesh of the USA lower 48.
/// </summary>
/// <returns>Number of mesh regions if successful; -ve for error code</returns>
protected int _MeshUSALower48()
{
OpenTemp.WriteLine(string.Format("Generating mesh for the USA lower 48...")); // Define the perimter points
// (points obtained from drawing the perimiter in Google Earth and parsing the KML file)
PointF[] points = new PointF[111];
int iPoint = 0;
points[iPoint].X = -122.799f; points[iPoint].Y = 49f; iPoint++;
points[iPoint].X = -122.758f; points[iPoint].Y = 47.166f; iPoint++;
points[iPoint].X = -122.92f; points[iPoint].Y = 48.053f; iPoint++;
points[iPoint].X = -124.641f; points[iPoint].Y = 48.226f; iPoint++;
points[iPoint].X = -123.983f; points[iPoint].Y = 46.284f; iPoint++;
points[iPoint].X = -124.269f; points[iPoint].Y = 42.051f; iPoint++;
points[iPoint].X = -124.315f; points[iPoint].Y = 40.391f; iPoint++;
points[iPoint].X = -122.305f; points[iPoint].Y = 37.157f; iPoint++;
points[iPoint].X = -120.561f; points[iPoint].Y = 34.563f; iPoint++;
points[iPoint].X = -118.464f; points[iPoint].Y = 33.971f; iPoint++;
points[iPoint].X = -117.129f; points[iPoint].Y = 32.593f; iPoint++;
points[iPoint].X = -114.738f; points[iPoint].Y = 32.719f; iPoint++;
points[iPoint].X = -114.805f; points[iPoint].Y = 32.488f; iPoint++;
points[iPoint].X = -110.992f; points[iPoint].Y = 31.296f; iPoint++;
points[iPoint].X = -108.176f; points[iPoint].Y = 31.294f; iPoint++;
points[iPoint].X = -108.17f; points[iPoint].Y = 31.805f; iPoint++;
points[iPoint].X = -106.427f; points[iPoint].Y = 31.768f; iPoint++;
points[iPoint].X = -104.887f; points[iPoint].Y = 30.546f; iPoint++;
points[iPoint].X = -104.48f; points[iPoint].Y = 29.601f; iPoint++;
points[iPoint].X = -103.153f; points[iPoint].Y = 28.972f; iPoint++;
points[iPoint].X = -102.659f; points[iPoint].Y = 29.731f; iPoint++;
points[iPoint].X = -101.421f; points[iPoint].Y = 29.775f; iPoint++;
points[iPoint].X = -99.543f; points[iPoint].Y = 27.593f; iPoint++;
points[iPoint].X = -99.078f; points[iPoint].Y = 26.403f; iPoint++;
points[iPoint].X = -97.691f; points[iPoint].Y = 26.008f; iPoint++;
points[iPoint].X = -97.407f; points[iPoint].Y = 25.834f; iPoint++;
points[iPoint].X = -97.145f; points[iPoint].Y = 25.944f; iPoint++;
points[iPoint].X = -97.38f; points[iPoint].Y = 27.001f; iPoint++;
points[iPoint].X = -97.164f; points[iPoint].Y = 27.683f; iPoint++;
points[iPoint].X = -96.331f; points[iPoint].Y = 28.391f; iPoint++;
points[iPoint].X = -94.743f; points[iPoint].Y = 29.311f; iPoint++;
points[iPoint].X = -93.831f; points[iPoint].Y = 29.659f; iPoint++;
points[iPoint].X = -92.273f; points[iPoint].Y = 29.522f; iPoint++;
points[iPoint].X = -90.747f; points[iPoint].Y = 29.061f; iPoint++;
points[iPoint].X = -89.839f; points[iPoint].Y = 29.305f; iPoint++;
points[iPoint].X = -89.309f; points[iPoint].Y = 28.976f; iPoint++;
points[iPoint].X = -89.079f; points[iPoint].Y = 29.226f; iPoint++;
points[iPoint].X = -89.576f; points[iPoint].Y = 29.498f; iPoint++;
points[iPoint].X = -89.427f; points[iPoint].Y = 30.137f; iPoint++;
points[iPoint].X = -87.71f; points[iPoint].Y = 30.212f; iPoint++;
points[iPoint].X = -86.243f; points[iPoint].Y = 30.33f; iPoint++;
points[iPoint].X = -85.298f; points[iPoint].Y = 29.639f; iPoint++;
points[iPoint].X = -84.21f; points[iPoint].Y = 30.072f; iPoint++;
points[iPoint].X = -82.675f; points[iPoint].Y = 28.854f; iPoint++;
points[iPoint].X = -82.693f; points[iPoint].Y = 27.45f; iPoint++;
points[iPoint].X = -81.68f; points[iPoint].Y = 25.911f; iPoint++;
points[iPoint].X = -81.082f; points[iPoint].Y = 25.109f; iPoint++;
points[iPoint].X = -80.36f; points[iPoint].Y = 25.239f; iPoint++;
points[iPoint].X = -80.032f; points[iPoint].Y = 26.798f; iPoint++;
points[iPoint].X = -80.549f; points[iPoint].Y = 28.413f; iPoint++;
points[iPoint].X = -81.438f; points[iPoint].Y = 30.695f; iPoint++;
points[iPoint].X = -80.637f; points[iPoint].Y = 32.287f; iPoint++;
points[iPoint].X = -79.198f; points[iPoint].Y = 33.116f; iPoint++;
points[iPoint].X = -78.749f; points[iPoint].Y = 33.753f; iPoint++;
points[iPoint].X = -78.03f; points[iPoint].Y = 33.89f; iPoint++;
points[iPoint].X = -77.09f; points[iPoint].Y = 34.644f; iPoint++;
points[iPoint].X = -76.524f; points[iPoint].Y = 34.656f; iPoint++;
points[iPoint].X = -75.722f; points[iPoint].Y = 35.704f; iPoint++;
points[iPoint].X = -76.004f; points[iPoint].Y = 36.895f; iPoint++;
points[iPoint].X = -75.036f; points[iPoint].Y = 38.439f; iPoint++;
points[iPoint].X = -74.243f; points[iPoint].Y = 39.562f; iPoint++;
points[iPoint].X = -73.965f; points[iPoint].Y = 40.476f; iPoint++;
points[iPoint].X = -71.931f; points[iPoint].Y = 41f; iPoint++;
points[iPoint].X = -73.57f; points[iPoint].Y = 40.944f; iPoint++;
points[iPoint].X = -71.566f; points[iPoint].Y = 41.323f; iPoint++;
points[iPoint].X = -69.922f; points[iPoint].Y = 41.632f; iPoint++;
points[iPoint].X = -70.187f; points[iPoint].Y = 42.075f; iPoint++;
points[iPoint].X = -70.1f; points[iPoint].Y = 41.816f; iPoint++;
points[iPoint].X = -70.524f; points[iPoint].Y = 41.804f; iPoint++;
points[iPoint].X = -70.946f; points[iPoint].Y = 42.439f; iPoint++;
points[iPoint].X = -70.213f; points[iPoint].Y = 43.625f; iPoint++;
points[iPoint].X = -66.879f; points[iPoint].Y = 44.794f; iPoint++;
points[iPoint].X = -67.396f; points[iPoint].Y = 45.143f; iPoint++;
points[iPoint].X = -67.441f; points[iPoint].Y = 45.569f; iPoint++;
points[iPoint].X = -67.815f; points[iPoint].Y = 45.669f; iPoint++;
points[iPoint].X = -67.786f; points[iPoint].Y = 47.055f; iPoint++;
points[iPoint].X = -68.276f; points[iPoint].Y = 47.361f; iPoint++;
points[iPoint].X = -68.919f; points[iPoint].Y = 47.182f; iPoint++;
points[iPoint].X = -69.065f; points[iPoint].Y = 47.269f; iPoint++;
points[iPoint].X = -69.054f; points[iPoint].Y = 47.426f; iPoint++;
points[iPoint].X = -69.269f; points[iPoint].Y = 47.434f; iPoint++;
points[iPoint].X = -70.018f; points[iPoint].Y = 46.641f; iPoint++;
points[iPoint].X = -70.694f; points[iPoint].Y = 45.393f; iPoint++;
points[iPoint].X = -71.571f; points[iPoint].Y = 44.993f; iPoint++;
points[iPoint].X = -74.783f; points[iPoint].Y = 44.976f; iPoint++;
points[iPoint].X = -76.457f; points[iPoint].Y = 44.097f; iPoint++;
points[iPoint].X = -76.813f; points[iPoint].Y = 43.656f; iPoint++;
points[iPoint].X = -78.651f; points[iPoint].Y = 43.617f; iPoint++;
points[iPoint].X = -79.172f; points[iPoint].Y = 43.441f; iPoint++;
points[iPoint].X = -78.938f; points[iPoint].Y = 42.84f; iPoint++;
points[iPoint].X = -80.058f; points[iPoint].Y = 42.366f; iPoint++;
points[iPoint].X = -81.215f; points[iPoint].Y = 42.228f; iPoint++;
points[iPoint].X = -82.411f; points[iPoint].Y = 41.667f; iPoint++;
points[iPoint].X = -82.668f; points[iPoint].Y = 41.648f; iPoint++;
points[iPoint].X = -83.047f; points[iPoint].Y = 41.854f; iPoint++;
points[iPoint].X = -83.112f; points[iPoint].Y = 42.259f; iPoint++;
points[iPoint].X = -82.538f; points[iPoint].Y = 42.634f; iPoint++;
points[iPoint].X = -82.111f; points[iPoint].Y = 43.595f; iPoint++;
points[iPoint].X = -82.486f; points[iPoint].Y = 45.331f; iPoint++;
points[iPoint].X = -83.59f; points[iPoint].Y = 45.795f; iPoint++;
points[iPoint].X = -84.766f; points[iPoint].Y = 46.6f; iPoint++;
points[iPoint].X = -84.841f; points[iPoint].Y = 46.871f; iPoint++;
points[iPoint].X = -88.364f; points[iPoint].Y = 48.285f; iPoint++;
points[iPoint].X = -89.363f; points[iPoint].Y = 47.973f; iPoint++;
points[iPoint].X = -91.249f; points[iPoint].Y = 48.051f; iPoint++;
points[iPoint].X = -92.989f; points[iPoint].Y = 48.585f; iPoint++;
points[iPoint].X = -93.744f; points[iPoint].Y = 48.5f; iPoint++;
points[iPoint].X = -94.623f; points[iPoint].Y = 48.712f; iPoint++;
points[iPoint].X = -94.809f; points[iPoint].Y = 49.286f; iPoint++;
points[iPoint].X = -95.14f; points[iPoint].Y = 49.368f; iPoint++;
points[iPoint].X = -95.199f; points[iPoint].Y = 49f; iPoint++;
// Define the graphics path
_regionGraphicsPath = new GraphicsPath();
_regionGraphicsPath.AddLines(points);
_regionGraphicsPath.CloseAllFigures();
// Generate a list of rectangles that are contained by the perimeter
RectangleF bounds = _regionGraphicsPath.GetBounds();
for (float longitude = (float)Math.Floor(bounds.Left) - 0.5f * _cellSize;
longitude <= (float)Math.Ceiling(bounds.Right) + 0.5f * _cellSize;
longitude += _cellSize)
{
for (float latitude = (float)Math.Floor(bounds.Top) - 0.5f * _cellSize;
latitude <= (float)Math.Ceiling(bounds.Bottom) + 0.5f * _cellSize;
latitude += _cellSize)
{
if (_regionGraphicsPath.IsVisible(longitude, latitude))
{
_cells.Add(new Cell(latitude, longitude, _cellSize, _cellSize));
}
}
}
// Sanity check: are all stations contained in the mesh?
foreach (Station station in _stations.Values)
{
if (!_regionGraphicsPath.IsVisible(station.Longitude, station.Latitude))
{
OpenTemp.WriteLine(string.Format(" Station {0}{1} is not contained in the mesh", station.Country, station.StationID));
}
}
// Define stations that can affect every cell
foreach (Cell cell in _cells)
{
int nStations = cell.DefineStations(_stations.Values, _averagingRadius);
if (nStations < 3)
{
OpenTemp.WriteLine(string.Format(" Cell at <{0:F2}, {1:F2}> is affected by {2} stations (prefer at least 3)", cell.Latitude, cell.Longitude, nStations));
}
}
// Log
OpenTemp.WriteLine(string.Format(" Mesh generation finished: {0} mesh squares", _cells.Count));
// Cleanup and return
_regionGraphicsPath.Dispose();
return _cells.Count;
}
First things first. The code base needs to be reviewed and validated for accuracy.
A link to a summary page for every version of the code will be placed here. The summary page will include links to pages with source code. Code review should be done on those pages:
This was the first release of the source code. It was initially uploaded to a file hosting site, but is now available here:
It is a C# project created in Microsoft Visual Studio 2005. It requires the Microsoft .Net Framework v2.0. (The easiest way to get it is with Windows Update or Microsoft Update).
If you want to play with the code, you should consider downloading Microsoft Visual C# 2005 Express. It’s free.
The code is implemented in three files. Use the links below to discuss the code:
I would like to break it up into smaller files but that can wait until later.
// =====
// Copyright 2007 John Van Vliet, all rights reserved
// Here's my simple license, soon to be replaced by a proper license.
// What you can do:
// - read the code
// - play with the code
// - run the code
// - modify the code
// What you can't do:
// - re-publish the original code or any modified versions
// =====using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTemp
{
/// <summary>
/// Definition of a single station and its weight in determining the temperature for this cell.
/// </summary>
struct CellStation
{
internal float weight;
internal Station station;
}
/// <summary>
/// Definition of a single cell on the earth's surface
/// </summary>
class Cell
{ Continue Reading »
// =====
// Copyright 2007 John Van Vliet, all rights reserved
// Here's my simple license, soon to be replaced by a proper license.
// What you can do:
// - read the code
// - play with the code
// - run the code
// - modify the code
// What you can't do:
// - re-publish the original code or any modified versions
// =====using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTemp
{
class Station
{ Continue Reading »
This page contains the main body and utility methods of OpenTemp.cs
Use the links below for the input methods:
Use the links below for the analysis methods:
CalculateMonthlyAverages()
CalculateSeriesOffsets()
_MeshUSALower48()
Use these links for the ouput methods:
WriteStationResults()
WriteCellStationResults()
WriteCellInfo()
WriteSeriesOffsets()
WriteMonthlyResults()
WriteYearlyResults()
// ===== Continue Reading »
/// <summary>
/// Read the sttaions file and parse into the list of stations.
/// </summary>
/// <remarks>The stations file should be a CSV format with one line per station.
/// Each line should consist of the station ID (including the country code), the latitude, and the longitude.</remarks>
/// <returns>Number of stations if successful; -ve for error code</returns>
protected int ParseStations()
{
OpenTemp.WriteLine(string.Format("Reading the stations file '{0}'...", _stationsFile)); // Open the file
StreamReader sr;
try
{
sr = new StreamReader(_stationsFile, true);
OpenTemp.WriteLine(" File opened for reading");
}
catch (Exception ex)
{
OpenTemp.WriteLine(string.Format(" Exception thrown when opening file: {0}", ex.ToString()));
return -1;
}
if (sr == null)
{
OpenTemp.WriteLine(string.Format(" Failed to open file (check that it exists and the path is correct)"));
return -2;
}
// Parse one line at a time
int nLines = 1;
while (!sr.EndOfStream)
{
nLines++;
string line = sr.ReadLine();
if (line.Length == 0) continue;
string[] entries = line.Split(',');
if (entries.Length != 3)
{
OpenTemp.WriteLine(string.Format(" Line {0}: Wrong number of entries ({1}, expected {2})", nLines, entries.Length, 3));
sr.Dispose();
return -3;
}
string countryStation = entries[0];
if (!_stations.ContainsKey(countryStation))
{
float latitude = float.Parse(entries[1]); // TODO: Add error handling
float longitude = float.Parse(entries[2]); // TODO: Add error handling
_stations.Add(countryStation, new Station(countryStation, longitude, latitude));
}
Station station = _stations[countryStation];
// Increment and continue
if ((nLines % 100) == 0)
{
OpenTemp.WriteLine(string.Format(" {0} stations parsed", nLines));
}
}
// Cleanup
sr.Dispose();
// Done
OpenTemp.WriteLine(string.Format(" Parsing finished: {0} stations parsed", nLines));
return nLines;
}
/// <summary>
/// Read the data file and parse into the list of samples.
/// </summary>
/// <returns>Number of samples parsed if successful; -ve for error code</returns>
protected int ParseGHCNv2()
{
OpenTemp.WriteLine(string.Format("Reading the data file '{0}'...", _dataFile)); // Open the file
StreamReader sr;
try
{
sr = new StreamReader(_dataFile, true);
OpenTemp.WriteLine(" File opened for reading");
}
catch (Exception ex)
{
OpenTemp.WriteLine(string.Format(" Exception thrown when opening file: {0}", ex.ToString()));
return -1;
}
if (sr == null)
{
OpenTemp.WriteLine(" Failed to open file (check that it exists and the path is correct)");
return -2;
}
// Parse one line at a time
int nLinesTotal = 0;
int nLinesParsed = 0;
int expectedLength = 76;
while (!sr.EndOfStream)
{
nLinesTotal++;
string line = sr.ReadLine();
if (line.Length != expectedLength)
{
OpenTemp.WriteLine(string.Format(" Line {0}: Wrong number of characters ({1}, expected {2})", nLinesTotal + 1, line.Length, expectedLength));
sr.Dispose();
return -4;
}
string countryStation = line.Substring(0, 11);
if (!_stations.ContainsKey(countryStation)) continue;
Station station = _stations[countryStation];
string seriesText = line.Substring(11, 1);
byte series;
bool seriesOk = byte.TryParse(seriesText, out series);
if (!seriesOk)
{
OpenTemp.WriteLine(string.Format(" Line {0}: Failed to parse series # {1}", nLinesTotal + 1, seriesText));
sr.Dispose();
return -5;
}
if (!station.ContainsSeries(series))
{
station.AddSeries(series);
}
SortedList<DateTime, float?> values = station.GetReadings(series);
int year = 0;
string yearString = line.Substring(12, 4);
bool yearOk = int.TryParse(yearString, out year);
if (!yearOk)
{
OpenTemp.WriteLine(string.Format(" Line {0}: Error parsing year '{1}'", nLinesTotal + 1, yearString));
sr.Dispose();
return -6;
}
// Loop over all months
for (int iMonth = 1; iMonth <= 12; iMonth++)
{
DateTime date = new DateTime(year, iMonth, 1);
// Parse value
short value = -9999;
string valueString = line.Substring(16 + (iMonth - 1) * 5, 5);
bool valueOk = short.TryParse(valueString, out value);
if (!valueOk)
{
OpenTemp.WriteLine(string.Format(" Line {0}: Failed to parse value '{1}'", nLinesTotal + 1, valueString));
sr.Dispose();
return -7;
}
// Store sample
try
{
if ((value > -1000) && (value < +1000))
{
values.Add(new DateTime(year, iMonth, 1), 0.1f * (float)value); // Convert from tenths to real degrees
}
else
{
values.Add(new DateTime(year, iMonth, 1), null); // No reading
}
}
catch
{
OpenTemp.WriteLine(string.Format(" DUPLICATE station year: Station {0}, Series {1}, Year {2}", countryStation, series, year));
break;
}
}
// Increment and continue
nLinesParsed++;
if ((nLinesTotal % 100000) == 0)
{
OpenTemp.WriteLine(string.Format(" Parsing progress: {0} of {1} lines parsed", nLinesParsed, nLinesTotal));
}
}
// Cleanup
sr.Dispose();
// Done
OpenTemp.WriteLine(string.Format(" Parsing finished: {0} of {1} lines parsed", nLinesParsed, nLinesTotal));
return 12*nLinesParsed;
}
/// <summary>
/// Write the station results to a data file in CSV format.
/// </summary>
/// <returns>0 if successful; -ve for error code</returns>
protected int WriteStationResults()
{
OpenTemp.WriteLine(string.Format("Writing the station results file '{0}'...", _outputStationFile)); // Open the file
StreamWriter sw;
try
{
sw = new StreamWriter(_outputStationFile, false); // Overwrite (do not append)
OpenTemp.WriteLine(" File opened for writing");
}
catch (Exception ex)
{
OpenTemp.WriteLine(string.Format(" Exception thrown when opening file: {0}", ex.ToString()));
return -1;
}
if (sw == null)
{
OpenTemp.WriteLine(" Failed to open file (check that it exists and the path is correct)");
return -2;
}
// Write header
sw.Write("YEAR,MONTH");
foreach (string countryStation in _stations.Keys)
{
sw.Write(",");
sw.Write(countryStation);
}
sw.WriteLine();
// Write one line at a time (starting in 1880)
DateTime date = new DateTime(1880, 1, 1);
DateTime today = DateTime.Today;
int nMonths = 0;
while (date < today)
{
// Any values for this month?
int nValues = 0;
foreach (Station station in _stations.Values)
{
foreach (byte series in station.SeriesNumbers)
{
SortedList<DateTime, float?> values = station.GetReadings(series);
if (values.ContainsKey(date))
{
if (values[date].HasValue) nValues++;
}
}
}
// Write output if values found
if (nValues > 0)
{
// Write month label
sw.Write(date.ToString("yyyy"));
sw.Write(",");
sw.Write(date.ToString("MM"));
// Write values for this month
foreach (Station station in _stations.Values)
{
foreach (byte series in station.SeriesNumbers)
{
sw.Write(",");
SortedList<DateTime, float?> values = station.GetReadings(series);
if (values.ContainsKey(date))
{
if (!values[date].HasValue) continue;
sw.Write(string.Format("{0:F2}", values[date].Value));
}
}
}
sw.WriteLine();
nMonths++;
}
// Next month
date = date.AddMonths(1);
}
// Done
sw.Close();
// Done
OpenTemp.WriteLine(string.Format(" Writing finished: {0} months written", nMonths));
return 0;
}