«Яндекс.Погода» для сайта в деталях

в 8:20, , рубрики: php, погода, яндекс, Яндекс API

Доброго времени суток. Этот пост будет написан на основе Яндекс.ТвояПогода с тем отличием, что здесь будет пролит свет на некоторые данные возвращаемых сервисом Яндекс.Погода, и, будет чуть больше кода.

Список городов и их идентификаторы можно увидеть по ссылке.
В моем случае было необходимо написать универсальный блок погоды с авто определением города. Пришлось извлечь информацию из 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. Что у меня получилось в итоге.

image

Рабочий вариант можно увидеть на странице превью.

Исходник в один файл можно посмотреть здесь.

Благодарю за внимание и жду советов по оптимизации и дополнению.

Автор: Temirkhan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js