개발관련 정보/서버
기상청 동네예보 활용하기 php 소스
초록당근
2021. 5. 11. 09:24
data.go.kr 에서 제공하는 동네예보는 격자형 존을 기준으로 정보가 제공되는 시스템이다.
API 연동 가이드 하단에 C 소스를 php로 변환해 포함하였고,
전체적으로 위경도값만 제공하여 필요한 날씨정보를 가져오도록 클래스를 제작해보았다.
<?php
define ("XLIMIT", 149); /* X축 격자점 수 */
define ("YLIMIT", 253); /* Y축 격자점 수 */
define ("PI", asin(1.0)*2.0);
define ("DEGRAD", PI/180.0);
define ("RADDEG", 180.0/PI);
// data.go.kr
//define ("WEATHER_URL", "http://apis.data.go.kr/1360000/VilageFcstInfoService/getUltraSrtFcst"); // 초단기예보
//define ("WEATHER_URL", "http://apis.data.go.kr/1360000/VilageFcstInfoService/getVilageFcst"); // 동네예보
define ("WEATHER_URL", "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst"); // 새 동네예보(2021.11.1일자 변경)
define ("SERVICE_KEY", "********"); // 발급받은 API-KEY
class DataGoUtil {
public $map;
public function __construct() {
//
// 동네예보 지도 정보
//
$this->map['Re'] = 6371.00877; // 지도반경
$this->map['grid'] = 5.0; // 격자간격 (km)
$this->map['slat1'] = 30.0; // 표준위도 1
$this->map['slat2'] = 60.0; // 표준위도 2
$this->map['olon'] = 126.0; // 기준점 경도
$this->map['olat'] = 38.0; // 기준점 위도
$this->map['xo'] = 210 / $this->map['grid']; // 기준점 X좌표
$this->map['yo'] = 675 / $this->map['grid']; // 기준점 Y좌표
$this->map['first'] = 0;
}
public function requestForecast($lat, $lon, $time = 0) {
$time = $time == 0 ? time() : $time;
list($baseDate, $baseTime) = $this->getBaseDateTimeForecast($time);
list($nx, $ny) = $this->getXY($lat, $lon);
$request = $this->buildRequest($baseDate, $baseTime, $nx, $ny);
$response = $this->getResponseForecast($request);
return json_decode($response, true);
}
/**
* request build, numOfRows값이 100정도 되어야 TMX, TMN 값이 들어옴...
**/
public function buildRequest($baseDate, $baseTime, $nx, $ny) {
$request = array(
"serviceKey"=>SERVICE_KEY,
"numOfRows"=>100,
"pageNo"=>1,
"dataType"=>"JSON",
"base_date"=>$baseDate,
"base_time"=>$baseTime,
"nx"=>$nx,
"ny"=>$ny
);
return $request;
}
public function getResponseForecast($request) {
$ch = curl_init();
$queryParams = $this->buildUrlQuery($request);
curl_setopt($ch, CURLOPT_URL, WEATHER_URL."?".$queryParams);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_TIMEOUT, 3); // 타임아웃 추가
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
public function parseWeather($response, ...$categorys) {
$data = array();
try {
if(isset($response) && isset($response['response']['body']['items']['item'])) {
$list = $response['response']['body']['items']['item'];
if(isset($list) && count($list)>0) {
foreach($list as $row) {
foreach($categorys as $category) {
if($category == $row['category']) {
$data['date'] = $row['baseDate'];
$date = date_create_from_format('Ymd', $row['baseDate']);
$data['ts'] = $date->getTimestamp();
list($data['lat'], $data['lon']) = $this->getLatLng($row['nx'], $row['ny']);
switch($category) {
case "POP": // 강수확률 %
$data[$category] = $row['fcstValue']."%";
break;
case "PTY": // 강수형태
$data[$category] = $this->getPTYCode($row['fcstValue']);
break;
case "REH": // 습도 %
$data[$category] = $row['fcstValue']."%";
break;
case "SKY": // 하늘상태
$data[$category] = $this->getSKYCode($row['fcstValue']);
break;
case "T3H": // 3시간 기온
$data[$category] = $row['fcstValue']."℃";
break;
case "TMX": // 낮 최고기온
$data[$category] = $row['fcstValue']."℃";
break;
case "UUU": // 풍속(동서성분) m/s
$data[$category] = $row['fcstValue']."m/s";
break;
case "VEC": // 풍향
$data[$category] = $this->getWindDirection($row['fcstValue']);
break;
case "VVV": // 풍속(남북성분) m/s
$data[$category] = $row['fcstValue']."m/s";
break;
case "WSD": // 풍속
$data[$category] = $this->getWindSpeed($row['fcstValue']);
break;
case "TMN": // 아침 최저기온
$data[$category] = $row['fcstValue']."℃";
break;
case "TMX": // 낮 최고기온
$data[$category] = $row['fcstValue']."℃";
break;
case "R06": // 6시간 강수량
$data[$category] = $row['fcstValue']."mm";
break;
case "S06": // 6시간 적설량
$data[$category] = $row['fcstValue']."cm";
break;
case "T1H": // 1시간 기온
$data[$category] = $row['fcstValue']."℃";
break;
case "RN1": // 1시간 강수량
$data[$category] = $row['fcstValue']."mm";
break;
case "WAV": // 파고 M
$data[$category] = $row['fcstValue']."M";
break;
case "LGT": // 낙뇌
$data[$category] = $this->getLGTCode($row['fcstValue']);
break;
}
break;
}
}
}
}
}
} catch(exception $e) {
}
return $data;
}
public function buildUrlQuery($params) {
foreach($params as $param => $value) {
$paramsJoined[] = "$param=".urlencode($value);
}
$query = isset($paramsJoined) ? implode('&', $paramsJoined) : "";
return $query;
}
public function getBaseDateTimeRealtime($time = 0) {
$time = $time == 0 ? time() : $time;
$baseDate = date("Ymd", $time);
$baseTime = "0030";
$h = date("H", $time);
$m = date("i", $time);
if($h==0 && $m < 30) {
$baseDate = date("Ymd", $time-86400);
$baseTime = "2330";
} else if($m>=30) {
$baseTime = sprintf("%02d30", $h);
} else {
if($h==0) $h=23;
else $h = $h - 1;
$baseTime = sprintf("%02d30", $h);
}
return array($baseDate, $baseTime);
}
public function getBaseDateTimeForecast($time = 0) {
$time = $time == 0 ? time() : $time;
$baseTime = "2300";
$baseDate = date("Ymd", $time);
$tm = ceil(date("Hi", $time));
if($tm < 205) {
$baseDate = date("Ymd", $time-86400);
} else if($tm < 505) {
$baseTime = "0200";
} else if($tm < 805) {
$baseTime = "0500";
} else if($tm < 1105) {
$baseTime = "0800";
} else if($tm < 1405) {
$baseTime = "1100";
} else if($tm < 1705) {
$baseTime = "1400";
} else if($tm < 2005) {
$baseTime = "1700";
} else if($tm < 2305) {
$baseTime = "2000";
}
return array($baseDate, $baseTime);
}
/**
* RO6
**/
public function getRainString($v) {
if($v < 0.1) return " ";
else if($v >= 0.1 && $v < 1.0) return "1mm미만";
else if($v >= 1.0 && $v < 5.0) return "1~4mm";
else if($v >= 5.0 && $v < 10.0) return "5~9mm";
else if($v >= 10.0 && $v < 20.0) return "10~19mm";
else if($v >= 20.0 && $v < 40.0) return "20~39mm";
else if($v >= 40.0 && $v < 70.0) return "40~69mm";
else return "70mm이상";
}
/**
* PTY
**/
public function getPTYCode($code = 0) {
$result = "";
switch($code) {
case "0":
$result = "-";
break;
case "1":
$result = "비";
break;
case "2":
$result = "진눈개비";
break;
case "3":
$result = "눈";
break;
case "4":
$result = "소나기";
break;
case "5":
$result = "빗방울";
break;
case "6":
$result = "빗방울/눈날림";
break;
case "7":
$result = "눈날림";
break;
default:
$result = "-";
break;
}
return $result;
}
/**
* SKY 하늘
**/
public function getSKYCode($code) {
$result = "";
switch($code) {
case "1":
$result = "맑음";
break;
case "3":
$result = "구름많음";
break;
case "4":
$result = "흐림";
break;
default:
$result = "-";
break;
}
return $result;
}
public function getSkyStatus($v) {
if($v>=0 && $v<6) return "맑음";
else if($v<9) return "구름많음";
else if($v<11) return "흐림";
else return "";
}
public function getLGTCode($code) {
switch($code) {
case "0":
return "없음";
case "1":
return "낮음";
case "2":
return "보통";
case "3":
return "높음";
default:
return "";
}
}
/**
* WSD 풍속
**/
public function getWindSpeed($v) {
if($v<4) return ""; // 약한바람
else if($v<9) return "약간강";
else if($v<14) return "강";
else return "매우강";
}
/**
* VEC 풍향
**/
public function getWindDirection($v) {
$seq = ceil(($v + 22.5 * 0.5) / 22.5);
$windDirection = array("N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW",
"N");
$windDirectionString = array("북", "북북동", "북동", "동북동",
"동", "동남동", "남동", "남남동",
"남", "남남서", "남서", "서남서",
"서", "서북서", "북서", "북북서",
"북");
if($seq < count($windDirection) && $seq >= 0) return array($windDirection[$seq], $windDirectionString[$seq]);
else return array("", "");
}
/**
* 위경도 -> (X,Y)
**/
public function getXY($lat, $lon) {
$x = 0;
$y = 0;
$this->map_conv($lon, $lat, $x, $y, 0, $this->map);
return array($x, $y);
}
/**
* (X,Y) -> 위경도
**/
public function getLatLng($x, $y) {
$lat = 0;
$lon = 0;
if ($x < 1 || $x > XLIMIT || $y < 1 || $y > YLIMIT) {
return array($lat, $lon);
}
$this->map_conv($lon, $lat, $x, $y, 1, $this->map);
return array($lat, $lon);
}
public function map_conv(&$lon, &$lat, &$x, &$y, $code, &$map) {
$lon1 = $lat1 = $x1 = $y1 = 0.0;
switch($code) {
case 0:
//
// 위경도 -> (X,Y)
//
$lon1 = $lon;
$lat1 = $lat;
$this->lamcproj($lon1, $lat1, $x1, $y1, $code, $map);
$x = (int)($x1 + 1.5);
$y = (int)($y1 + 1.5);
break;
case 1:
//
// (X,Y) -> 위경도
//
$x1 = $x - 1;
$y1 = $y - 1;
$this->lamcproj($lon1, $lat1, $x1, $y1, $code, $map);
$lon = $lon1;
$lat = $lat1;
break;
}
}
public function lamcproj(&$lon, &$lat, &$x, &$y, $code, &$map) {
$re = $map['Re'] / $map['grid'];
$slat1 = $map['slat1'] * DEGRAD;
$slat2 = $map['slat2'] * DEGRAD;
$olon = $map['olon'] * DEGRAD;
$olat = $map['olat'] * DEGRAD;
$sn = tan(PI * 0.25 + $slat2*0.5)/tan(PI * 0.25 + $slat1 * 0.5);
$sn = log(cos($slat1) / cos($slat2)) / log($sn);
$sf = tan(PI * 0.25 + $slat1 * 0.5);
$sf = pow($sf,$sn) * cos($slat1) / $sn;
$ro = tan(PI * 0.25 + $olat * 0.5);
$ro = $re * $sf / pow($ro,$sn);
if ($code == 0) {
$ra = tan(PI * 0.25 + ($lat) * DEGRAD * 0.5);
// printf("* ra: %f, theta: %f, PI: %f, DEGRAD: %f\n", $ra, $theta, PI, DEGRAD);
$ra = $re * $sf / pow($ra,$sn);
$theta = $lon * DEGRAD - $olon;
if ($theta > PI) $theta -= 2.0 * PI;
if ($theta < -(PI)) $theta += 2.0 * PI;
$theta *= $sn;
$x = (float)($ra * sin($theta)) + $map['xo'];
$y = (float)($ro - $ra * cos($theta)) + $map['yo'];
} else {
$xn = $x - $map['xo'];
$yn = $ro - ($y) + $map['yo'];
$ra = sqrt($xn * $xn + $yn * $yn);
if ($sn< 0.0) $ra = -$ra;
$alat = pow(($re * $sf / $ra),(1.0 / $sn));
$alat = 2.0 * atan($alat) - PI * 0.5;
if (abs($xn) <= 0.0) {
$theta = 0.0;
} else {
if (abs($yn) <= 0.0) {
$theta = PI * 0.5;
if($xn< 0.0 ) $theta = -$theta;
} else
$theta = atan2($xn,$yn);
}
$alon = $theta / $sn + $olon;
$lat = $this->round_down((float)($alat * RADDEG), 7);
$lon = $this->round_down((float)($alon * RADDEG), 7);
}
}
public function round_up($number, $precision = 2) {
$fig = (int) str_pad('1', $precision, '0');
return (ceil($number * $fig) / $fig);
}
public function round_down($number, $precision = 2) {
$fig = (int) str_pad('1', $precision, '0');
return (floor($number * $fig) / $fig);
}
}
?>
여기까지 클래스고
사용법은 다음과 같다.
<?php
require 'DataGoUtil.php';
$dataGoUtil = new DataGoUtil;
list($lat, $lon) = $dataGoUtil->getLatLng(100, 100);
echo "lat: ".$lat."\n";
echo "lon: ".$lon."\n";
$response = $dataGoUtil->requestForecast($lat, $lon); //, $time);
$data = $dataGoUtil->parseWeather($response, "TMN", "TMX", "SKY", "PTY", "REH");
print_r($data);
?>
------------
Array
(
[date] => 20210511
[ts] => 1620706692
[lat] => 36.28812
[lon] => 129.251767
[PTY] => -
[REH] => 65%
[SKY] => 맑음
[TMX] => 18.0℃
[TMN] => 9.0℃
)