/*
###################################################################################
# This file is part of GraphWeather.
# Latest version is available on : http://guilmard.free.fr
# Copyright (C) 2006 Antoine Guilmard - guilmard@free.fr
#
# GraphWeather is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# GraphWeather is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with GraphWeather; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
###################################################################################
*/
#include "MonthStats.h"
#include "DayStats.h"
#include "GraphicContext.h"
//////////////////////////////////////////////////////////////////////////
// Constructors
//////////////////////////////////////////////////////////////////////////
CMonthStats::CMonthStats(CStatsContext* pStatsContext, int Year, int Month, CBaseStats* pParent/*=NULL*/)
:CBaseStats(pStatsContext, pParent)
{
m_StatsType = MONTH_STATS;
m_XMLDateFormat[0] = "%a %d - %H:%M";
m_XMLDateFormat[1] = "%a %d";
m_Year = Year;
m_Month = Month;
DaysOfRain002 = 0;
DaysOfRain020 = 0;
DaysOfRain200 = 0;
MaxPos32 = 0;
Max0 = 0;
Min0 = 0;
MinNeg18 = 0;
m_StartPeriod = GetStartPeriod(m_Year, m_Month);
m_EndPeriod = GetEndPeriod(m_Year, m_Month);
ResetStats();
}
//////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////
CMonthStats::~CMonthStats(void)
{
map<int,CBaseStats*>::const_iterator it=m_SubTimeStats.begin();
for (/*empty*/; it!=m_SubTimeStats.end(); it++)
delete it->second;
}
//////////////////////////////////////////////////////////////////////////
// Method: GetStatsPath
// FullName: CMonthStats::GetStatsPath
// Access: public
//
//! \param string& StatsPath
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::GetStatsPath(string& StatsPath)
{
stringstream ss;
ss << m_Year << "\\" ;
ss << setw(2) << setfill('0') << m_Month << "\\" ;
ss << m_Year << "_" ;
ss << setw(2) << setfill('0') << m_Month;
StatsPath = ss.str();
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateStatsTree
// FullName: CMonthStats::UpdateStatsTree
// Access: virtual public
//
//! \param int Year
//! \param int Month
//! \param int Day
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateStatsTree(int Year, int Month, int Day)
{
ASSERT(m_Month == Month);
if( m_SubTimeStats.find(Day) == m_SubTimeStats.end())
m_SubTimeStats.insert(pair<int,CBaseStats*>(Day, new CDayStats(m_pStatsContext, m_Year, m_Month, Day, this)));
m_SubTimeStats[Day]->UpdateStatsTree(Year, Month, Day);
m_DatabaseUpdateNeeded = TRUE;
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateDatabase
// FullName: CMonthStats::UpdateDatabase
// Access: public
//
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateDatabase()
{
if (m_DatabaseUpdateNeeded)
{
CBaseStats::UpdateDatabase();
// Override TMinMean and TMaxMean actually calculated for days not decade
int NumOfDays = 0;
TMaxMean = 0.0f;
TMinMean = 0.0f;
map<int,CBaseStats*>::iterator itday = m_SubTimeStats.begin();
for (; itday != m_SubTimeStats.end(); itday++)
{
CBaseStats* pDaySats = itday->second;
TMaxMean += pDaySats->ProbesStats[outdoor_temperature].Max;
TMinMean += pDaySats->ProbesStats[outdoor_temperature].Min;
NumOfDays++;
}
if (NumOfDays > 0)
{
TMaxMean /= (float)NumOfDays;
TMinMean /= (float)NumOfDays;
}
}
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateContent
// FullName: CMonthStats::UpdateContent
// Access: public
//
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateContent()
{
if (m_DatabaseUpdateNeeded)
{
CBaseStats::UpdateContent();
// Update XML if required
if (m_pStatsContext->m_GenerateXML)
{
UpdateFiles();
try
{
UpdateGraphics();
}
catch (...)
{
}
}
// Update NOAA if required
if (m_pStatsContext->m_GenerateNOAA)
{
UpdateNOAA();
}
m_DatabaseUpdateNeeded = FALSE;
}
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateFiles
// FullName: CMonthStats::UpdateFiles
// Access: public
//
//! \param DWORD Type
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateFiles()
{
string XslPath = "./../../month.xsl";
string Extension;
stringstream ss;
Extension=".xml";
ss << "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>" << endl;
ss << "<?xml-stylesheet href=\""<< XslPath << "\" type=\"text/xsl\"?>" << endl;
ss << "<statistics year=\"" << m_Year ;
ss << "\" month=\"" << setw(2) << setfill('0') << m_Month ;
tm Date;
memset(&Date, 0, sizeof(tm));
Date.tm_isdst = -1;
Date.tm_mday = 1;
Date.tm_mon = m_Month - 1;
Date.tm_year = m_Year - 1900;
mktime(&Date);
char StrMonth[32];
strftime(StrMonth, 32, "%B", &Date);
ss << "\" str-month=\"" << StrMonth << "\">" << endl;
// Number of days in month
int DaysInMonth = 31;
if (m_Month == 4 || m_Month == 6 || m_Month == 9 || m_Month == 11)
{
DaysInMonth = 30;
}
else if (m_Month == 2)
{
DaysInMonth = 28;
if ((m_Year % 4) == 0) // Valid from 1901 to 2099
DaysInMonth = 29;
}
// Map of available days in month
ss << "\t<map";
for (int i = 1; i <= DaysInMonth ; i++)
{
if (m_SubTimeStats.find(i) != m_SubTimeStats.end())
ss << " day-" << setw(2) << setfill('0') << i << "=\"1\"";
else
ss << " day-" << setw(2) << setfill('0') << i << "=\"0\"";
}
ss << "/>" << endl;
// Probes
AppendStatsString(ss, 1);
ss << "</statistics>" << endl;
// Write the stats file
string StatsString = ss.str();
string StatsPath;
GetStatsPath(StatsPath);
StatsPath += Extension;
WriteStatsFile(StatsPath, StatsString);
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateNOAA
// FullName: CMonthStats::UpdateNOAA
// Access: protected
//
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateNOAA()
{
SetLocalEn();
string BasePath;
GetContextOutputPath(BasePath);
if (!BasePath.empty())
{
string StatsPath;
GetStatsPath(StatsPath);
string FullPathName = BasePath;
FullPathName += StatsPath;
FullPathName += "_NOAA.txt";
string::size_type Index=FullPathName.rfind("\\");
string Directory(FullPathName.begin(), FullPathName.begin() + Index + 1);
CreateDir(Directory.c_str());
ofstream Output;
Output.open(FullPathName.c_str());
if(!Output.fail())
{
time_t Time = GetStartPeriod(m_Year, m_Month);
tm TimeInfo = *localtime(&Time);
char Buffer[64];
char Line[1024];
strftime(Buffer, 64, "%B %y", &TimeInfo);
// Number of days in month
int DaysInMonth = 31;
if (m_Month == 4 || m_Month == 6 || m_Month == 9 || m_Month == 11)
{
DaysInMonth = 30;
}
else if (m_Month == 2)
{
DaysInMonth = 28;
if ((m_Year % 4) == 0) // Valid from 1901 to 2099
DaysInMonth = 29;
}
///////////////////////////////////////////////////////////////////////////////////////
// Title
Output << " MONTHLY CLIMATOLOGICAL SUMMARY for " << Buffer << endl;
Output << endl;
///////////////////////////////////////////////////////////////////////////////////////
// Location
sprintf(Line, "NAME: %s CITY: %s STATE: %s",
m_pStatsContext->m_Station.c_str(),
m_pStatsContext->m_Country.c_str(),
m_pStatsContext->m_State.c_str());
Output << Line << endl;
// Latitude
float LatDegrees = fabs(m_pStatsContext->m_Latitude);
float LatMinutes = (LatDegrees - floorf(LatDegrees)) * 60.0f;
float LatSeconds = (LatMinutes - floorf(LatMinutes)) * 60.0f;
char LatDirection = 'N';
if (m_pStatsContext->m_Latitude < 0)
LatDirection = 'S';
// Longitude
float LonDegrees = fabs(m_pStatsContext->m_Longitude);
float LonMinutes = (LonDegrees - floorf(LonDegrees)) * 60.0f;
float LonSeconds = (LonMinutes - floorf(LonMinutes)) * 60.0f;
char LonDirection = 'W';
if (m_pStatsContext->m_Longitude < 0)
LonDirection = 'E';
// Complete line
sprintf(Line, "ELEV: %5d m LAT: %3d° %02d' %02d\" %c LONG: %3d° %02d' %02d\" %c",
(int)m_pStatsContext->m_Altitude,
(int)floor(LatDegrees),
(int)floor(LatMinutes),
(int)floor(LatSeconds),
LatDirection,
(int)floor(LonDegrees),
(int)floor(LonMinutes),
(int)floor(LonSeconds),
LonDirection);
Output << Line << endl;
///////////////////////////////////////////////////////////////////////////////////////
// Data header
string Legend = " TEMPERATURE ($Unit[Temperature]), RAIN ($Unit[Rainfall]), WIND SPEED ($Unit[Speed])";
m_StringManager.Parse(m_pStatsContext, m_pStatsContext, &Legend);
Output << endl << Legend.c_str() << endl << endl;
Output << " HEAT COOL AVG" << endl;
Output << " MEAN DEG DEG WIND DOM" << endl;
Output << "DAY TEMP HIGH TIME LOW TIME DAYS DAYS RAIN SPEED HIGH TIME DIR" << endl;
Output << "------------------------------------------------------------------------------------" << endl;
///////////////////////////////////////////////////////////////////////////////////////
// Data table
float TemperaturePos32 = (m_pStatsContext->m_Units & C_TEMPERATURE_MASK) == C_UNIT_CELSIUS ? 32.0f : 89.6f;
float Temperature0 = (m_pStatsContext->m_Units & C_TEMPERATURE_MASK) == C_UNIT_CELSIUS ? 0.0f : 32.0f;
float TemperatureNeg18 = (m_pStatsContext->m_Units & C_TEMPERATURE_MASK) == C_UNIT_CELSIUS ? -18.0f : -0.4f;
float TBaseHeatDegreeDays = (m_pStatsContext->m_Units & C_TEMPERATURE_MASK) == C_UNIT_CELSIUS ? m_pStatsContext->m_TBaseHeatDegreeDays : Units::C2F(m_pStatsContext->m_TBaseHeatDegreeDays);
float TBaseCoolDegreeDays = (m_pStatsContext->m_Units & C_TEMPERATURE_MASK) == C_UNIT_CELSIUS ? m_pStatsContext->m_TBaseCoolDegreeDays : Units::C2F(m_pStatsContext->m_TBaseCoolDegreeDays);
float Rainfall002 = (m_pStatsContext->m_Units & C_RAINFALL_MASK) == C_UNIT_MM ? 0.2f : 0.0787f;
unsigned long MaxRainfallDate = 0;
float MaxRainfall = 0;
string WindDirection;
float WindSpeedMax;
// Reset counters
DaysOfRain002 = 0;
DaysOfRain020 = 0;
DaysOfRain200 = 0;
MaxPos32 = 0;
Max0 = 0;
Min0 = 0;
MinNeg18 = 0;
// Loop days
for (int i=1; i <= DaysInMonth; i++)
{
// Print day
sprintf(Line, "%02d", i);
Output << Line;
// Find if the day data exists
map<int,CBaseStats*>::iterator itday = m_SubTimeStats.find(i);
if (itday == m_SubTimeStats.end())
{
Output << endl;
continue;
}
// Format data line
CDayStats* pDayStats = reinterpret_cast<CDayStats*>(itday->second);
char MaxTempTime[6];
Time = (time_t)pDayStats->ProbesStats[outdoor_temperature].MaxDate;
tm TimeInfo = *localtime(&Time);
strftime(MaxTempTime, 6, "%H:%M", &TimeInfo);
char MinTempTime[6];
Time = (time_t)pDayStats->ProbesStats[outdoor_temperature].MinDate;
TimeInfo = *localtime(&Time);
strftime(MinTempTime, 6, "%H:%M", &TimeInfo);
char MaxWindTime[6];
if (pDayStats->ProbesStats[wind_gust].Max != -FLT_MAX)
{
WindSpeedMax = pDayStats->ProbesStats[wind_gust].Max;
Time = (time_t)pDayStats->ProbesStats[wind_gust].MaxDate;
}
else
{
WindSpeedMax = pDayStats->ProbesStats[wind_speed].Max;
Time = (time_t)pDayStats->ProbesStats[wind_speed].MaxDate;
}
TimeInfo = *localtime(&Time);
strftime(MaxWindTime, 6, "%H:%M", &TimeInfo);
WindDirection = FormatWindDirection(pDayStats->ProbesStats[wind_direction].TrueMean, TRUE);
sprintf(Line, " %5.1f %5.1f %s %5.1f %s %5.1f %5.1f %5.1f %5.1f %5.1f %s %s",
pDayStats->ProbesStats[outdoor_temperature].TrueMean,
pDayStats->ProbesStats[outdoor_temperature].Max,
MaxTempTime,
pDayStats->ProbesStats[outdoor_temperature].Min,
MinTempTime,
pDayStats->HeatDegreeDays,
pDayStats->CoolDegreeDays,
pDayStats->TotalRainfall,
pDayStats->ProbesStats[wind_speed].TrueMean,
WindSpeedMax,
MaxWindTime,
WindDirection.c_str());
Output << Line << endl;
if (pDayStats->ProbesStats[outdoor_temperature].Max >= TemperaturePos32)
MaxPos32++;
if (pDayStats->ProbesStats[outdoor_temperature].Max <= Temperature0)
Max0++;
if (pDayStats->ProbesStats[outdoor_temperature].Min <= Temperature0)
Min0++;
if (pDayStats->ProbesStats[outdoor_temperature].Min <= TemperatureNeg18)
MinNeg18++;
if (pDayStats->TotalRainfall > Rainfall002)
DaysOfRain002++;
if (pDayStats->TotalRainfall > (10 * Rainfall002))
DaysOfRain020++;
if (pDayStats->TotalRainfall > (100 * Rainfall002))
DaysOfRain200++;
}
///////////////////////////////////////////////////////////////////////////////////////
// Data footer
Output << "-------------------------------------------------------------------------------------" << endl;
Time = (time_t)ProbesStats[outdoor_temperature].MaxDate;
TimeInfo = *localtime(&Time);
int MaxTempDay = TimeInfo.tm_mday;
Time = (time_t)ProbesStats[outdoor_temperature].MinDate;
TimeInfo = *localtime(&Time);
int MinTempDay = TimeInfo.tm_mday;
if (ProbesStats[wind_gust].Max != -FLT_MAX)
{
WindSpeedMax = ProbesStats[wind_gust].Max;
Time = (time_t)ProbesStats[wind_gust].MaxDate;
}
else
{
WindSpeedMax = ProbesStats[wind_speed].Max;
Time = (time_t)ProbesStats[wind_speed].MaxDate;
}
TimeInfo = *localtime(&Time);
int MaxWindDay = TimeInfo.tm_mday;
WindDirection = FormatWindDirection(ProbesStats[wind_direction].TrueMean, TRUE);
sprintf(Line, " %5.1f %5.1f %2d %5.1f %2d %5.1f %5.1f %5.1f %5.1f %5.1f %2d %s",
ProbesStats[outdoor_temperature].TrueMean,
ProbesStats[outdoor_temperature].Max,
MaxTempDay,
ProbesStats[outdoor_temperature].Min,
MinTempDay,
HeatDegreeDays,
CoolDegreeDays,
TotalRainfall,
ProbesStats[wind_speed].TrueMean,
WindSpeedMax,
MaxWindDay,
WindDirection.c_str());
Output << Line << endl << endl;
///////////////////////////////////////////////////////////////////////////////////////
// Supplemental data
sprintf(Line, "Max >= %5.1f: %2d", TemperaturePos32, MaxPos32);
Output << Line << endl;
sprintf(Line, "Max <= %5.1f: %2d", Temperature0, Max0);
Output << Line << endl;
sprintf(Line, "Min <= %5.1f: %2d", Temperature0, Min0);
Output << Line << endl;
sprintf(Line, "Min <= %5.1f: %2d", TemperatureNeg18, MinNeg18);
Output << Line << endl;
char MaxRainfallDayTime[9];
Time = (time_t)MaxRainfallDayDate;
TimeInfo = *localtime(&Time);
strftime(MaxRainfallDayTime, 9, "%d/%m/%y", &TimeInfo);
if (MaxRainfallDay > 0.0f)
sprintf(Line, "Max Rain: %.2f ON %s", MaxRainfallDay, MaxRainfallDayTime);
else
sprintf(Line, "Max Rain: 0.0");
Output << Line << endl;
sprintf(Line, "Days of Rain: %d (> .2 mm) %d (> 2 mm) %d (> 20 mm)", DaysOfRain002, DaysOfRain020, DaysOfRain200);
Output << Line << endl;
sprintf(Line, "Heat Base: %.1f Cool Base: %.1f Method: Integration", TBaseHeatDegreeDays, TBaseCoolDegreeDays);
Output << Line << endl;
Output.close();
}
}
RestoreLocal();
}
//////////////////////////////////////////////////////////////////////////
// Method: UpdateGraphics
// FullName: CMonthStats::UpdateGraphics
// Access: protected
//
//
//! \return void
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
void CMonthStats::UpdateGraphics()
{
CGraphicContext* pGraphicContext = m_pStatsContext->GetGraphicContext();
if (pGraphicContext != NULL)
{
// Set period in context
pGraphicContext->Control(C_PERIOD, C_START_PERIOD, &m_StartPeriod);
pGraphicContext->Control(C_PERIOD, C_END_PERIOD, &m_EndPeriod);
// Draw graphs
for (int k=1; k<100; k++)
{
stringstream ss;
ss << "graph-month-" << k << ".xml";
string Stylesheet = m_pStatsContext->GetOutputPath();
Stylesheet += ss.str();
ifstream File;
File.open(Stylesheet.c_str());
if(File.is_open())
{
File.close();
pGraphicContext->Control(C_STYLESHEET, C_NULL, (void*)Stylesheet.c_str());
pGraphicContext->Process(C_SAVE_GRAPHIC_IN_MEMORY|C_JPEG);
string BasePath;
GetContextOutputPath(BasePath);
stringstream GraphicPathName;
GraphicPathName << BasePath << m_Year << "\\" << setw(2) << setfill('0') ;
GraphicPathName << m_Month << "\\graph-month-" << setw(1) << k << ".jpg";
pGraphicContext->GetResult(C_SAVE_GRAPHIC, C_DEFAULT, (void*)GraphicPathName.str().c_str());
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// Read stream
//////////////////////////////////////////////////////////////////////////
BOOL CMonthStats::ReadStream(ifstream& DBStream)
{
m_DatabaseUpdateNeeded = FALSE;
m_StatsPos=DBStream.tellg();
short Header;
DBStream.read((char*)&Header,sizeof(short));
if(Header != C_STATS_DB_MONTH_HEADER)
return C_STATS_DB_CORRUPTED;
short Month=0;
DBStream.read((char*)&Month,sizeof(short));
m_Month=Month;
m_StartPeriod = GetStartPeriod(m_Year, m_Month);
m_EndPeriod = GetEndPeriod(m_Year, m_Month);
short NbSubTimeStats;
DBStream.read((char*)&NbSubTimeStats,sizeof(short));
BOOL Success = CBaseStats::ReadStream(DBStream);
for (short i=0; i<NbSubTimeStats && Success==C_OK; i++)
{
CDayStats* pDayStats=new CDayStats(m_pStatsContext, m_Year, m_Month, 0, this);
Success = pDayStats->ReadStream(DBStream);
if (Success != C_OK)
delete pDayStats;
else
m_SubTimeStats.insert(pair<int,CBaseStats*>(pDayStats->GetDay(),pDayStats));
}
return Success;
}
//////////////////////////////////////////////////////////////////////////
// Write stream
//////////////////////////////////////////////////////////////////////////
BOOL CMonthStats::WriteStream(ofstream& DBStream)
{
short Header=C_STATS_DB_MONTH_HEADER;
DBStream.write((char*)&Header,sizeof(short));
short Month=(short)m_Month;
DBStream.write((char*)&Month,sizeof(short));
short NbSubTimeStats=(short)m_SubTimeStats.size();
DBStream.write((char*)&NbSubTimeStats,sizeof(short));
CBaseStats::WriteStream(DBStream);
map<int,CBaseStats*>::const_iterator it=m_SubTimeStats.begin();
for (/*empty*/; it!=m_SubTimeStats.end(); it++)
(it->second)->WriteStream(DBStream);
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
// Method: GetDay
// FullName: CMonthStats::GetDay
// Access: public
//
//! \param int Day
//
//! \return CDayStats*
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
CDayStats* CMonthStats::GetDay(int Day)
{
SubTimeMap::iterator DayIt = m_SubTimeStats.find(Day);
if (DayIt != m_SubTimeStats.end())
{
ASSERT(DayIt->second->GetStatsType() == DAY_STATS);
return static_cast<CDayStats*>(DayIt->second);
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
// Method: GetTimestamp
// FullName: CMonthStats::GetTimestamp
// Access: public const
//
//
//! \return ULONG
//
//! \brief
//
//////////////////////////////////////////////////////////////////////////
ULONG CMonthStats::GetTimestamp() const
{
struct tm TmStruct;
memset(&TmStruct,0,sizeof(tm));
TmStruct.tm_mon = m_Month - 1;
TmStruct.tm_year = m_Year - 1900;
TmStruct.tm_isdst = -1;
return (ULONG)(mktime(&TmStruct));
}