특정 버스 터미널에서 나머지 터미널로 운행하는 유효한 노선을 찾는 방법

· 조회수 12

공공데이터API의 고속버스터미널 정보를 이용해 특정 버스 터미널에서 나머지 터미널로 운행하는 유효한 노선을 찾는 방법입니다.
방문자가 출발지를 선택하고 도착지를 선택할때 없는 노선까지 선택하는 상황을 방지하는 것으로 사용자를 우선으로 하는 출력 방법입니다.
예제에서는 인천만 해당하는데 $excludeRegion 부분만 변경($terminal_list의 키값)해주면 됩니다.
이걸 응용해서 디비 테이블에서 넣어서 셀렉트 박스로 불러오게 하면 되겠습니다.
* curl로 돌리면 속도는 둘째치고 부하증가로 먹통이 될수 있어 curl을 병렬처리하게 됩니다.
 

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

set_time_limit(0); // 충분한 실행 시간 확보

$serviceKey = '인증키';
$endpoint = 'https://apis.data.go.kr/1613000/ExpBusInfoService/getStrtpntAlocFndExpbusInfo';
$testDate = '20251114'; // YYYYMMDD
$excludeRegion = '인천'; // 출발지는 인천
$totalValidRoutes = 0;
$validRoutesDetail = [];

$terminal_list = [
    '서울' => [
        'NAEK010' => '서울경부',
        'NAEK020' => '센트럴시티(서울)',
        'NAEK021' => '센트럴시티(서울)',
        'NAEK030' => '동서울(030)',
        'NAEK032' => '동서울',
        'NAEK040' => '상봉'
    ],
    '인천' => [
        'NAEK100' => '인천',
        'NAEK105' => '인천공항T1',
        'NAEK117' => '인천공항T2'
    ],
    '대전' => [
        'NAEK300' => '대전복합',
        'NAEK305' => '대전청사',
        'NAEK307' => '대전도룡',
        'NAEK360' => '유성'
    ],
    '대구' => [
        'NAEK801' => '동대구',
        'NAEK805' => '서대구',
        'NAEK807' => '대구용계'
    ],
    '부산' => [
        'NAEK700' => '부산',
        'NAEK703' => '서부산(사상)'
    ],
    '울산' => [
        'NAEK715' => '울산',
        'NAEK716' => '울산신복'
    ],
    '광주' => [
        'NAEK500' => '광주(유·스퀘어)',
        'NAEK503' => '광주비아'
    ],
    '세종' => [
        'NAEK351' => '세종연구단지',
        'NAEK352' => '세종시',
        'NAEK353' => '세종청사',
        'NAEK358' => '세종연구단지', // 중복 코드명
        'NAEK361' => '세종국무조정실',
        'NAEK362' => '세종시청'
    ],
    '경기' => [
        'NAEK101' => '부천', 'NAEK109' => '서수원', 'NAEK110' => '수원', 'NAEK111' => '용인신갈',
        'NAEK112' => '영통', 'NAEK114' => '신갈시외', 'NAEK115' => '고양화정', 'NAEK116' => '고양백석',
        'NAEK118' => '죽전', 'NAEK119' => '신갈영덕', 'NAEK120' => '성남(분당)', 'NAEK125' => '광명(KTX역)',
        'NAEK126' => '광명(철산역)', 'NAEK127' => '오산', 'NAEK130' => '안성', 'NAEK131' => '안성중대',
        'NAEK132' => '안성풍림', 'NAEK133' => '안성공도', 'NAEK134' => '안성한경', 'NAEK137' => '안성대림',
        'NAEK138' => '안성회관', 'NAEK139' => '여주대', 'NAEK140' => '여주', 'NAEK146' => '포천',
        'NAEK149' => '용인유림', 'NAEK150' => '용인', 'NAEK160' => '이천', 'NAEK161' => '1구간',
        'NAEK169' => '구리', 'NAEK170' => '의정부', 'NAEK171' => '이천마장', 'NAEK172' => '이천부발(신하리)',
        'NAEK173' => '의정부', 'NAEK174' => '평택용이동', 'NAEK175' => '평택대', 'NAEK176' => '안중오거리',
        'NAEK177' => '안중', 'NAEK180' => '평택', 'NAEK186' => '운천', 'NAEK190' => '안산',
        'NAEK195' => '시흥(시화)', 'NAEK477' => '죽전보정'
    ],
    '강원' => [ // 기존 '강원'
        'NAEK147' => '신철원', 'NAEK148' => '철원', 'NAEK200' => '강릉', 'NAEK210' => '동해',
        'NAEK219' => '강원대(삼척)', 'NAEK220' => '삼척', 'NAEK230' => '속초', 'NAEK238' => '횡성(휴)하행',
        'NAEK239' => '횡성(휴)상행', 'NAEK240' => '원주', 'NAEK244' => '원주혁신', 'NAEK245' => '원주문막',
        'NAEK246' => '원주기업', 'NAEK250' => '춘천', 'NAEK270' => '양양', 'NAEK272' => '영월'
    ],
    '충북' => [ // 기존 '충북'
        'NAEK400' => '청주(고속)', 'NAEK401' => '청주(센트럴)', 'NAEK405' => '북청주', 'NAEK406' => '북부오창',
        'NAEK407' => '청주공항', 'NAEK408' => '속리산', 'NAEK409' => '보은', 'NAEK419' => '중앙탑면',
        'NAEK420' => '충주', 'NAEK421' => '서충주', 'NAEK440' => '황간', 'NAEK449' => '제천하소',
        'NAEK450' => '제천', 'NAEK455' => '증평', 'NAEK457' => '괴산'
    ],
    '충남' => [ // 기존 '충남'
        'NAEK310' => '천안', 'NAEK312' => '당진', 'NAEK315' => '정안(휴)하행', 'NAEK316' => '정안(휴)상행',
        'NAEK320' => '공주', 'NAEK324' => '인삼랜드(휴)하행', 'NAEK325' => '인삼랜드(휴)상행', 'NAEK330' => '금산',
        'NAEK337' => '배방정류소', 'NAEK338' => '아산테크노밸리', 'NAEK340' => '아산온양', 'NAEK341' => '아산서부',
        'NAEK342' => '아산탕정', 'NAEK343' => '천안아산역', 'NAEK344' => '아산둔포', 'NAEK346' => '천안공단',
        'NAEK347' => '선문대', 'NAEK349' => '탕정삼성LCD', 'NAEK370' => '논산', 'NAEK380' => '연무대',
        'NAEK388' => '당진기지시', 'NAEK389' => '홍성', 'NAEK390' => '내포', 'NAEK391' => '청양',
        'NAEK392' => '정산', 'NAEK393' => '서산', 'NAEK394' => '태안', 'NAEK395' => '보령',
        'NAEK396' => '안면도', 'NAEK397' => '창기리', 'NAEK398' => '예산', 'NAEK399' => '덕산스파'
    ],
    '전북' => [ // 기존 '전북'
        'NAEK602' => '전주', 'NAEK605' => '전주호남제일문', 'NAEK610' => '군산', 'NAEK615' => '익산',
        'NAEK616' => '익산팔봉', 'NAEK619' => '애통리', 'NAEK620' => '김제', 'NAEK621' => '전북혁신',
        'NAEK622' => '자치인재원', 'NAEK625' => '남원', 'NAEK626' => '덕과', 'NAEK629' => '태인',
        'NAEK630' => '정읍', 'NAEK634' => '흥덕', 'NAEK635' => '고창', 'NAEK640' => '부안',
        'NAEK644' => '전북강진', 'NAEK645' => '순창', 'NAEK649' => '봉동', 'NAEK650' => '진안'
    ],
    '전남' => [ // 기존 '전남'
        'NAEK505' => '목포', 'NAEK509' => '여천', 'NAEK510' => '여수', 'NAEK512' => '순천신대지구CGV',
        'NAEK513' => '순천신대지구', 'NAEK515' => '순천', 'NAEK519' => '구례', 'NAEK520' => '광양',
        'NAEK525' => '동광양', 'NAEK528' => '섬진강(휴)하행', 'NAEK529' => '섬진강(휴)상행', 'NAEK530' => '나주',
        'NAEK535' => '강진', 'NAEK540' => '고흥', 'NAEK545' => '녹동', 'NAEK550' => '무안',
        'NAEK552' => '해제', 'NAEK554' => '보성', 'NAEK555' => '벌교', 'NAEK560' => '영광',
        'NAEK565' => '영산포', 'NAEK570' => '영암', 'NAEK575' => '완도', 'NAEK576' => '관산',
        'NAEK577' => '회진', 'NAEK578' => '원동', 'NAEK580' => '장흥', 'NAEK581' => '함평',
        'NAEK582' => '담양', 'NAEK583' => '장성', 'NAEK584' => '문장', 'NAEK585' => '지도',
        'NAEK586' => '화순', 'NAEK587' => '능주', 'NAEK588' => '옥과', 'NAEK589' => '곡성',
        'NAEK590' => '진도', 'NAEK592' => '남악', 'NAEK593' => '임자대광', 'NAEK594' => '임자진리',
        'NAEK595' => '해남'
    ],
    '경북' => [ // 기존 '경북'
        'NAEK810' => '구미', 'NAEK812' => '선산(휴)하행', 'NAEK813' => '선산(휴)상행', 'NAEK815' => '경주',
        'NAEK820' => '김천', 'NAEK821' => '김천혁신', 'NAEK823' => '낙동강(휴)하행', 'NAEK824' => '낙동강(휴)상행',
        'NAEK825' => '상주', 'NAEK828' => '포항시청', 'NAEK830' => '포항', 'NAEK834' => '풍기',
        'NAEK835' => '영주', 'NAEK840' => '안동', 'NAEK843' => '영덕', 'NAEK845' => '영천',
        'NAEK846' => '영천망정', 'NAEK850' => '점촌', 'NAEK851' => '예천', 'NAEK852' => '경북도청',
        'NAEK862' => '영주장수', 'NAEK863' => '영주꽃동산'
    ],
    '경남' => [ // 기존 '경남'
        'NAEK704' => '진해', 'NAEK705' => '마산', 'NAEK706' => '내서', 'NAEK710' => '창원',
        'NAEK711' => '창원역', 'NAEK722' => '진주', 'NAEK723' => '진주개양', 'NAEK724' => '진주혁신',
        'NAEK730' => '통영', 'NAEK735' => '김해', 'NAEK736' => '김해장유'
    ]
];

// 인천 출발 터미널
$departureTerminals = $terminal_list[$excludeRegion];

// 나머지 도착 터미널
$allArrivalTerminals = [];
foreach ($terminal_list as $region => $terminals) {
    if ($region !== $excludeRegion) {
        $allArrivalTerminals = array_merge($allArrivalTerminals, $terminals);
    }
}

$multi_handle = curl_multi_init();
$all_handles = [];
$requestCount = 0;

echo "=== 인천 출발 유효 행선지 검증 (병렬 처리) ===\n";
echo "테스트 날짜: {$testDate}\n";

// 요청 생성
foreach ($departureTerminals as $depId => $depName) {
    foreach ($allArrivalTerminals as $arrId => $arrName) {
        $queryParams = http_build_query([
            'serviceKey' => $serviceKey,
            'pageNo' => 1,
            'numOfRows' => 1,
            '_type' => 'xml',
            'depTerminalId' => $depId,
            'arrTerminalId' => $arrId,
            'depPlandTime' => $testDate
        ]);

        $url = $endpoint . '?' . $queryParams;

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);

        curl_multi_add_handle($multi_handle, $ch);

        $all_handles[$depId . '_' . $arrId] = [
            'handle' => $ch,
            'depName' => $depName,
            'arrName' => $arrName
        ];

        $requestCount++;
    }
}

echo "총 요청 수: {$requestCount}\n";
echo "========================================\n";

// 실행
$active = null;
do {
    $status = curl_multi_exec($multi_handle, $active);
    if ($active) {
        curl_multi_select($multi_handle, 0.5);
    }
} while ($active && $status == CURLM_OK);

// 결과 처리
foreach ($all_handles as $key => $data) {
    $ch = $data['handle'];
    $response = curl_multi_getcontent($ch);
    $totalCount = 0;

    if ($response !== false) {
        libxml_use_internal_errors(true);
        $xml = simplexml_load_string($response);
        libxml_clear_errors();

        if ($xml && isset($xml->body->totalCount)) {
            $totalCount = (int)$xml->body->totalCount;
        }
    }

    if ($totalCount > 0) {
        [$depId, $arrId] = explode('_', $key);
        if (!isset($validRoutesDetail[$depId])) $validRoutesDetail[$depId] = [];
        $validRoutesDetail[$depId][$arrId] = $data['arrName'];
        $totalValidRoutes++;
        echo "✅ 유효 노선: {$data['depName']} ({$depId}) -> {$data['arrName']} ({$arrId}) [{$totalCount}회]\n";
    }

    curl_multi_remove_handle($multi_handle, $ch);
    curl_close($ch);
}

curl_multi_close($multi_handle);

echo "\n========================================\n";
echo "⭐ 최종 결과: 인천 출발 유효 노선 총 {$totalValidRoutes}개 ⭐\n";
echo "========================================\n\n";

// 유효 노선 목록
foreach ($validRoutesDetail as $depId => $arrList) {
    echo "출발 터미널: {$departureTerminals[$depId]} ({$depId})\n";
    echo "  -> 유효 행선지 목록:\n";
    foreach ($arrList as $arrId => $arrName) {
        echo "     - {$arrId}: {$arrName}\n";
    }
    echo "\n";
}
?>



"문명이란 근로의 산물이다. - 스마일스"

- 문명, 근로 -

북마크목록