buildMetarDB.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #!/usr/local/bin/php
  2. <?php
  3. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  4. // +----------------------------------------------------------------------+
  5. // | PHP version 4 |
  6. // +----------------------------------------------------------------------+
  7. // | Copyright (c) 1997-2004 The PHP Group |
  8. // +----------------------------------------------------------------------+
  9. // | This source file is subject to version 2.0 of the PHP license, |
  10. // | that is bundled with this package in the file LICENSE, and is |
  11. // | available through the world-wide-web at |
  12. // | http://www.php.net/license/2_02.txt. |
  13. // | If you did not receive a copy of the PHP license and are unable to |
  14. // | obtain it through the world-wide-web, please send a note to |
  15. // | license@php.net so we can mail you a copy immediately. |
  16. // +----------------------------------------------------------------------+
  17. // | Authors: Alexander Wirtz <alex@pc4p.net> |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: buildMetarDB.php,v 1.16 2004/01/06 19:36:28 eru Exp $
  21. require_once "DB.php";
  22. // {{{ constants
  23. // {{{ natural constants and measures
  24. define("SERVICES_WEATHER_RADIUS_EARTH", 6378.15);
  25. // }}}
  26. // }}}
  27. /**
  28. * This script downloads, saves and processes the textfiles needed for
  29. * the building the databases to enable searching for METAR stations.
  30. *
  31. * You can download the locations, which is a database of about 12000 world-
  32. * wide locations, which can be used to determine the coordinates of your
  33. * city or you can download a file with 6500 airports providing the metar
  34. * data. This database is used for the next-METAR-station search. Please see
  35. * the apropriate documentation in the Services_Weather_Metar class.
  36. *
  37. * For usage of this script, invoke with '-h'.
  38. *
  39. * @author Alexander Wirtz <alex@pc4p.net>
  40. * @link http://weather.noaa.gov/tg/site.shtml
  41. * @package Services_Weather
  42. * @version 1.2
  43. */
  44. // {{{ Services_Weather_checkData()
  45. /**
  46. * Services_Weather_checkData
  47. *
  48. * Checks the data for a certain string-length and if it either consists of
  49. * a certain char-type or a string of "-" as replacement.
  50. *
  51. * @param array $data The data to be checked
  52. * @param array $dataOrder Because the data is in different locations, we provide this
  53. * @return bool
  54. */
  55. function Services_Weather_checkData($data, $dataOrder)
  56. {
  57. $return = true;
  58. foreach ($dataOrder as $type => $idx) {
  59. switch (strtolower($type)) {
  60. case "b":
  61. $len = 2;
  62. $func = "ctype_digit";
  63. break;
  64. case "s":
  65. $len = 3;
  66. $func = "ctype_digit";
  67. break;
  68. case "i":
  69. $len = 4;
  70. $func = "ctype_alnum";
  71. break;
  72. default:
  73. break;
  74. }
  75. if ((strlen($data[$idx]) != $len) || (!$func($data[$idx]) && ($data[$idx] != str_repeat("-", $len)))) {
  76. $return = false;
  77. break;
  78. }
  79. }
  80. return $return;
  81. }
  82. // }}}
  83. // {{{ Services_Weather_getNextArg()
  84. /**
  85. * Services_Weather_getNextArg
  86. *
  87. * Checks, if the next argument is a parameter to a predecessing option.
  88. * Returns either that parameter or false, if the next argument is an option
  89. *
  90. * @param int $c Internal argument counter
  91. * @return string|bool
  92. */
  93. function Services_Weather_getNextArg(&$c)
  94. {
  95. if ((($c + 1) < $_SERVER["argc"]) && ($_SERVER["argv"][$c + 1]{0} != "-")) {
  96. $c++;
  97. return $_SERVER["argv"][$c];
  98. } else {
  99. return false;
  100. }
  101. }
  102. // }}}
  103. // First set a few variables for processing the options
  104. $modeSet = false;
  105. $saveFile = false;
  106. $printHelp = false;
  107. $invOpt = false;
  108. $verbose = 0;
  109. $dbType = "mysql";
  110. $dbProt = "unix";
  111. $dbName = "servicesWeatherDB";
  112. $dbUser = "root";
  113. $dbPass = "";
  114. $dbHost = "localhost";
  115. $dbOptions = array();
  116. $userFile = "";
  117. // Iterate through the arguments and check their validity
  118. for ($c = 1; $c < $_SERVER["argc"]; $c++) {
  119. switch ($_SERVER["argv"][$c]{1}) {
  120. case "l":
  121. // location-mode, if another mode is set, bail out
  122. if ($modeSet) {
  123. $printHelp = true;
  124. } else {
  125. $modeSet = true;
  126. $filePart = "bbsss";
  127. $tableName = "metarLocations";
  128. $dataOrder = array("b" => 0, "s" => 1, "i" => 2);
  129. }
  130. break;
  131. case "a":
  132. // dito for airport-mode
  133. if ($modeSet) {
  134. $printHelp = true;
  135. } else {
  136. $modeSet = true;
  137. $filePart = "cccc";
  138. $tableName = "metarAirports";
  139. $dataOrder = array("b" => 1, "s" => 2, "i" => 0);
  140. }
  141. break;
  142. case "f":
  143. // file-flag was provided, check if next argument is a string
  144. if (($userFile = Services_Weather_getNextArg($c)) === false) {
  145. $printHelp = true;
  146. }
  147. break;
  148. case "s":
  149. // If you download the file, it will be saved to disk
  150. $saveFile = true;
  151. break;
  152. case "t":
  153. // The type of the DB to be used
  154. if (($dbType = Services_Weather_getNextArg($c)) === false) {
  155. $printHelp = true;
  156. }
  157. break;
  158. case "r":
  159. // The protocol of the DB to be used
  160. if (($dbProt = Services_Weather_getNextArg($c)) === false) {
  161. $printHelp = true;
  162. }
  163. break;
  164. case "d":
  165. // The name of the DB to be used
  166. if (($dbName = Services_Weather_getNextArg($c)) === false) {
  167. $printHelp = true;
  168. }
  169. break;
  170. case "u":
  171. // The user of the DB to be used
  172. if (($dbUser = Services_Weather_getNextArg($c)) === false) {
  173. $printHelp = true;
  174. }
  175. break;
  176. case "p":
  177. // The password of the DB to be used
  178. if (($dbPass = Services_Weather_getNextArg($c)) === false) {
  179. $printHelp = true;
  180. }
  181. break;
  182. case "h":
  183. // The host of the DB to be used
  184. if (($dbHost = Services_Weather_getNextArg($c)) === false) {
  185. $printHelp = true;
  186. }
  187. break;
  188. case "o":
  189. // Options for the DB
  190. if (($options = Services_Weather_getNextArg($c)) === false) {
  191. $printHelp = true;
  192. } else {
  193. $options = explode(",", $options);
  194. foreach ($options as $option) {
  195. $optPair = explode("=", $option);
  196. $dbOptions[$optPair[0]] = $optPair[1];
  197. }
  198. }
  199. break;
  200. case "v":
  201. // increase verbosity
  202. for ($i = 1; $i < strlen($_SERVER["argv"][$c]); $i++) {
  203. if ($_SERVER["argv"][$c]{$i} == "v") {
  204. $verbose++;
  205. } else {
  206. $invOpt = true;
  207. break;
  208. }
  209. }
  210. break;
  211. default:
  212. // argument not valid, bail out
  213. $invOpt = true;
  214. break;
  215. }
  216. if ($invOpt) {
  217. // see above
  218. $printHelp = true;
  219. echo "Invalid option: '".$_SERVER["argv"][$c]."'\n";
  220. break;
  221. }
  222. }
  223. // help-message
  224. if (!$modeSet || $printHelp) {
  225. echo "Usage: ".basename($_SERVER["argv"][0], ".php")." -l|-a [options]\n";
  226. echo "Options:\n";
  227. echo " -l build locationsDB\n";
  228. echo " -a build airportsDB\n";
  229. echo " -f <file> use <file> as input\n";
  230. echo " -s save downloaded file to disk\n";
  231. echo " -t <dbtype> type of the DB to be used\n";
  232. echo " -r <dbprotocol> protocol -----\"----------\n";
  233. echo " -d <dbname> name ---------\"----------\n";
  234. echo " -u <dbuser> user ---------\"----------\n";
  235. echo " -p <dbpass> pass ---------\"----------\n";
  236. echo " -h <dbhost> host ---------\"----------\n";
  237. echo " -o <dboptions> options ------\"----------\n";
  238. echo " in the notation option=value,...\n";
  239. echo " -v display verbose debugging messages\n";
  240. echo " multiple -v increases verbosity\n";
  241. exit(255);
  242. }
  243. // check, if zlib is available
  244. if (extension_loaded("zlib")) {
  245. $open = "gzopen";
  246. $close = "gzclose";
  247. $files = array(
  248. $userFile, "nsd_".$filePart, "nsd_".$filePart.".txt",
  249. "nsd_".$filePart.".gz", "http://weather.noaa.gov/data/nsd_".$filePart.".gz"
  250. );
  251. } else {
  252. $open = "fopen";
  253. $close = "fclose";
  254. $files = array(
  255. $userFile, "nsd_".$filePart, "nsd_".$filePart.".txt",
  256. "http://weather.noaa.gov/data/nsd_".$filePart.".txt"
  257. );
  258. }
  259. // then try to open a source in the given order
  260. foreach ($files as $file) {
  261. $fp = @$open($file, "rb");
  262. if ($fp) {
  263. // found a valid source
  264. if ($verbose > 0) {
  265. echo "Services_Weather: Using '".$file."' as source.\n";
  266. }
  267. if ($saveFile && !file_exists($file)) {
  268. // apparently we want to save the file, and it's a remote file
  269. $file = basename($file);
  270. $fps = @$open($file, "wb");
  271. if (!$fps) {
  272. echo "Services_Weather: Couldn't save to '".$file."'!\n";
  273. } else {
  274. if ($verbose > 0) {
  275. echo "Services_Weather: Saving source to '".$file."'.\n";
  276. }
  277. // read from filepointer and save to disk
  278. while ($line = fread($fp, 1024)) {
  279. fwrite($fps, $line, strlen($line));
  280. }
  281. // unfortunately zlib does not support r/w on a resource,
  282. // so no rewind -> move $fp to new file on disk
  283. $close($fp);
  284. $close($fps);
  285. $fp = @$open($file, "rb");
  286. }
  287. }
  288. break;
  289. }
  290. }
  291. if (!$fp) {
  292. // no files found, or connection not available... bail out
  293. die("Services_Weather: Sourcefile nsd_".$filePart." not found!\n");
  294. }
  295. $dsn = $dbType."://".$dbUser.":".$dbPass."@".$dbProt."+".$dbHost."/".$dbName;
  296. $dsninfo = array(
  297. "phptype" => $dbType,
  298. "protocol" => $dbProt,
  299. "username" => $dbUser,
  300. "password" => $dbPass,
  301. "hostspec" => $dbHost,
  302. "database" => $dbName,
  303. "mode" => 0644
  304. );
  305. $db = DB::connect($dsninfo, $dbOptions);
  306. if (DB::isError($db)) {
  307. echo "Services_Weather: Connection to DB with '".$dbType."://".$dbUser.":PASS@".$dbHost."/".$dbName."' failed!\n";
  308. die($db->getMessage()."\n");
  309. } else {
  310. // Test, if we have to swipe or create the table first
  311. $select = "SELECT * FROM ".$tableName;
  312. $result = $db->query($select);
  313. if (DB::isError($result)) {
  314. // Create new table
  315. $create = "CREATE TABLE ".$tableName."(id int,block int,station int,icao varchar(4),name varchar(80),state varchar(2),country varchar(50),wmo int,latitude float,longitude float,elevation float,x float,y float,z float)";
  316. if ($verbose > 0) {
  317. echo "Services_Weather: Creating table '".$tableName."'.\n";
  318. }
  319. $result = $db->query($create);
  320. if (DB::isError($result)) {
  321. die($result->getMessage()."\n");
  322. }
  323. } else {
  324. // Delete the old stuff
  325. $delete = "DELETE FROM ".$tableName;
  326. if ($verbose > 0) {
  327. echo "Services_Weather: Deleting from table '".$tableName."'.\n";
  328. }
  329. $result = $db->query($delete);
  330. if (DB::isError($result)) {
  331. die($result->getMessage()."\n");
  332. }
  333. }
  334. // Ok, DB should be up and running now, let's shove in the data
  335. $line = 0;
  336. $error = 0;
  337. // read data from file
  338. while ($data = fgetcsv($fp, 1000, ";")) {
  339. // Check for valid data
  340. if ((sizeof($data) < 9) || !Services_Weather_checkData($data, $dataOrder)) {
  341. echo "Services_Weather: Invalid data in file!\n";
  342. echo "\tLine ".($line + 1).": ".implode(";", $data)."\n";
  343. $error++;
  344. } else {
  345. // calculate latitude and longitude
  346. // it comes in a ddd-mm[-ss]N|S|E|W format
  347. $coord = array( "latitude" => 7, "longitude" => 8);
  348. foreach ($coord as $latlon => $aId) {
  349. preg_match("/^(\d{1,3})-(\d{1,2})(-(\d{1,2}))?([NSEW])$/", $data[$aId], $result);
  350. ${$latlon} = 0; $factor = 1;
  351. foreach ($result as $var) {
  352. if ((strlen($var) > 0) && ctype_digit($var)) {
  353. ${$latlon} += $var / $factor;
  354. $factor *= 60;
  355. } elseif (ctype_alpha($var) && in_array($var, array("S", "W"))) {
  356. ${$latlon} *= (-1);
  357. }
  358. }
  359. }
  360. // Calculate the cartesian coordinates for latitude and longitude
  361. $theta = deg2rad($latitude);
  362. $phi = deg2rad($longitude);
  363. $x = SERVICES_WEATHER_RADIUS_EARTH * cos($phi) * cos($theta);
  364. $y = SERVICES_WEATHER_RADIUS_EARTH * sin($phi) * cos($theta);
  365. $z = SERVICES_WEATHER_RADIUS_EARTH * sin($theta);
  366. // Check for elevation in data
  367. $elevation = is_numeric($data[11]) ? $data[11] : 0;
  368. // integers: convert "--" fields to null, empty fields to 0
  369. foreach (array($dataOrder["b"], $dataOrder["s"], 6) as $i) {
  370. if (strpos($data[$i], "--") !== false) {
  371. $data[$i] = "null";
  372. } elseif ($data[$i] == "") {
  373. $data[$i] = 0;
  374. }
  375. }
  376. // strings: quote
  377. foreach (array($dataOrder["i"], 3, 4, 5) as $i) {
  378. $data[$i] = $db->quote($data[$i]);
  379. }
  380. // insert data
  381. $insert = "INSERT INTO ".$tableName." VALUES(".($line - $error).",";
  382. $insert .= $data[$dataOrder["b"]].",".$data[$dataOrder["s"]].",";
  383. $insert .= $data[$dataOrder["i"]].",".$data[3].",".$data[4].",";
  384. $insert .= $data[5].",".$data[6].",".round($latitude, 4).",";
  385. $insert .= round($longitude, 4).",".$elevation.",".round($x, 4).",";
  386. $insert .= round($y, 4).",".round($z, 4).")";
  387. $result = $db->query($insert);
  388. if (DB::isError($result)) {
  389. echo "\tLine ".($line + 1).": ".$insert."\n";
  390. echo $result->getMessage()."\n";
  391. $error++;
  392. } elseif($verbose > 2) {
  393. echo $insert."\n";
  394. }
  395. }
  396. $line++;
  397. }
  398. // commit and close
  399. $db->disconnect();
  400. if ($verbose > 0 || $error > 0) {
  401. echo "Services_Weather: ".($line - $error)." ".$tableName." added ";
  402. echo "to database '".$dbName."' (".$error." error(s)).\n";
  403. }
  404. }
  405. $close($fp);
  406. ?>