Доброго времени суток. Этот пост будет написан на основе Яндекс.ТвояПогода с тем отличием, что здесь будет пролит свет на некоторые данные возвращаемых сервисом Яндекс.Погода, и, будет чуть больше кода.
Список городов и их идентификаторы можно увидеть по ссылке.
В моем случае было необходимо написать универсальный блок погоды с авто определением города. Пришлось извлечь информацию из xml файла и записать ее в виде json массива с соответствиями.
Как выглядит список.
Для определения города посетителя используется ipGeoBase
function getGeo($ip)
{
if(!filter_var($ip, FILTER_VALIDATE_IP, array('flags' => FILTER_FLAG_IPV4)))
return FALSE;
$get = file_get_contents("http://ipgeobase.ru:7020/geo?ip={$ip}");
$xml = simplexml_load_string($get);
$city = isset($xml->ip->city) ? strtolower($xml->ip->city) : '';
return $city;
}
Здесь у меня вышла нестыковка. Сервис прекрасно работает в СНГ, но ни за что не определит, например, посетителя из Лондона.
Поэтому файл со списком городов можно было существенно сжать, но руки покамест не дошли.
Далее сам класс для работы с погодой. Вы спросите «зачем понадобился класс»? Компонент писался под движок InstantCMS в котором компонент инициализируется только через класс с соответствующим названием.
Оговорюсь заранее, некоторые значения переменных лишь предположительны($noon, $night)
class cms_model_weather{
private $cache_dir;
private $city_id;
private $noon; //Время дня. По-умолчанию 4 - основная часть дня 11:00-20:00
private $night; //Время ночи. По-умолчанию 5
//Инициализируем класс
public function __construct(){
$this->cache_dir=$_SERVER['DOCUMENT_ROOT'].'/habr/cache/weather';
$this->noon=4;
$this->night=5;
if(isset($_SESSION['weather_city'])){$cityname=$_SESSION['wether_city'];} //Берем название города из сессии
else{$cityname=getGeo($_SERVER['REMOTE_ADDR']); }// Получаем название города по автоопределению
$cities=json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'].'/habr/YandexCities.txt'));
if(isset($cities->$cityname)){
$this->city_id=$cities->$cityname;
} else {
$this->city_id=$cities->{'Москва'};
}
}
Если название города есть в списке яндекса, присваиваем его ид переменной.
Далее получаем xml файл с данными о погоде и парсим его.
Здесь для «локализации» даты я воспользовался массивом соответствий. Возможно, есть более адекватный способ, но те, которые предлагал мне гугл, не помогли. Можно было обойтись и без этого, но задача стояла конкретная.
//Получаем погоду для определенного города
public function getCityWeather(){
$_LANG['MONTHS'] = array(
'Jan'=>'январь', 'Feb'=>'февраль', 'Mar'=>'март',
'Apr'=>'апрель', 'May'=>'май','Jun'=>'июнь',
'Jul'=>'июль','Aug'=>'август', 'Sep'=>'сентябрь',
'Oct'=>'октябрь','Nov'=>'ноябрь','Dec'=>'декабрь');
$_LANG['WEEKDAYS'] = array(
'Sunday'=>'воскресенье', 'Monday'=>'понедельник',
'Tuesday'=>'вторник', 'Wednesday'=>'среда',
'Thursday'=>'четверг', 'Friday'=>'пятница',
'Saturday'=>'суббота');
$city_cache=$this->{cache_dir}.$this->{city_id};
//Если время последнего изменения кэш файла меньше шести минут, возвращаем данные из кэша
if(file_exists($city_cache) && filemtime($city_cache)>time()-360){
$jsoned_weather=json_decode(file_get_contents($city_cache),TRUE);
} else { //Если кэш отсутствует, получаем данные с яндекса
$weather=simplexml_load_file('http://export.yandex.ru/weather-ng/forecasts/'.$this->city_id.'.xml');
if($weather!=null){
//Погода на семь дней
for($i=1; $i<=7; $i++)
{
if($weather->day[$i]!=null){
$days['sevendays'][]=array(
'weekday' => strtr(date('l', strtotime($weather->day[$i]->attributes()->date)), $_LANG['WEEKDAYS']),
'data'=>strtr(date("j M",strtotime($weather->day[$i]->attributes()->date)), $_LANG['MONTHS']),
'temp_day' => $this->checkZeroMark($weather->day[$i]->day_part[$this->noon]->temperature),
'temp_night' => $this->checkZeroMark($weather->day[$i]->day_part[$this->night]->temperature),
'weather_type' => $weather->day[$i]->day_part[$this->noon]->weather_type,
'wind_speed' => $weather->day[$i]->day_part[$this->noon]->wind_speed,
'humidity' => $weather->day[$i]->day_part[$this->noon]->humidity,
'pressure' => $weather->day[$i]->day_part[$this->noon]->pressure,
'image' => $weather->day[$i]->day_part[$this->noon]->{'image-v3'}
);
}
}
//Погода на сегодня
$days['today']=array(
'weekday' => strtr(date('l'), $_LANG['WEEKDAYS']),
'data'=>strtr(date('j M Y, G:i'), $_LANG['MONTHS']),
'temperature' => $this->checkZeroMark($weather->fact->temperature),
'weather_type' => $weather->fact->weather_type,
'wind_speed' => $weather->fact->wind_speed,
'humidity' => $weather->fact->humidity,
'pressure' => $weather->fact->pressure,
'water_temp' => $this->checkZeroMark($weather->fact->water_temperature),
'sunrise' => $weather->day->sunrise,
'sunset' => $weather->day->sunset,
'image' => $weather->fact->{'image-v3'},
'city'=>$weather->attributes()->city,
'country' => $weather->attributes()->country
);
file_put_contents($city_cache, json_encode($days));
$jsoned_weather=json_decode(file_get_contents($city_cache), TRUE);
} else{
return null;
}
}
return $jsoned_weather;
}
//Функция для проверки знака температуры
//Возвращает плюс, минус или ноль в зависимости от температуры
public function checkZeroMark($temperature){
if($temperature>0){
return '+'.$temperature;
} elseif ($temperature<0){
return '-'.$temperature;
}
return $temperature;
}
}
В объекте $weather возвращается такое количество данных, что не влезает в var_dump. Так как требования были в том, чтобы сделать некий аналог погоды, который есть, не кидайте помидорами-воля начальства =), на майлру, пришлось провести достаточно нудный анализ всех данных.
Легко догадаться, что $weather->fact содержит информацию о погоде на сегодняшний день. Кстати говоря, интересный пункт $weather->fact->water_temperature обозначается только для тех городов, рядом с которыми есть море. Для остальных результат либо 0(неприятный сюрприз), либо null.
$weather->attributes() содержит информацию о ключевых параметрах, по которым была запрошена информация из сервиса. Здесь примечательно то, что названия некоторых городов возвращаются латиницей. Есть подозрения, что зависит от вашей геолокации.
$weather->fact->{'image-v3'} возвращает название миниатюры погоды. Список всех названий я перебрал прокручивая слот-машину погоды яндекса в разных городах. Имейте ввиду, на ночное время выдаются совершенно другие иконки. Те, которые я нашел(вполне возможно, далеко не все)
Skc_d — солнечно(ясно)
skc_n — то же самое, но для ночного времени
bkn_d — облачно, с прояснениями
bkn_-ra_d — облачно с прояснениями, возможен дождь
bkn_-ra_n — то же самое, но ночное время суток
ovc — облачно
ovc_ra — дождь
ovc_-ra — слабый дождь(не помню, как в оригинале)
jg_d — туман
bl — метель
ovc_sn — снег
ovc_ts_ra — гроза
Теперь о $weather->day[]. Здесь хранится информация о погоде на неделю вперед включительно с сегодняшнего дня. Однако информация разбиена на 6 периодов с детальным раскрытием каждого. Путем небольшой сводки я пришел к выводу, что за основную часть дня отвечает массив под четвертым значением. Логически, 24 часа разбито на 6 частей, но сводка показала, что каждая часть в часовом эквиваленте разнится с остальными. Поправьте, если я ошибаюсь.
В целом, яндекс порадовал обилием необходимых и не очень данных. Например я так и не понял, для чего может понадобиться moonrise/moonset. Что у меня получилось в итоге.
Рабочий вариант можно увидеть на странице превью.
Исходник в один файл можно посмотреть здесь.
Благодарю за внимание и жду советов по оптимизации и дополнению.
Автор: Temirkhan