GeocodeLib.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. <?php
  2. App::uses('String', 'Utility');
  3. App::uses('Xml', 'Utility');
  4. App::uses('HttpSocketLib', 'Tools.Lib');
  5. /**
  6. * geocode via google (UPDATE: api3)
  7. * @see DEPRECATED api2: http://code.google.com/intl/de-DE/apis/maps/articles/phpsqlgeocode.html
  8. * @sse http://code.google.com/intl/de/apis/maps/documentation/geocoding/#Types
  9. * 2010-06-25 ms
  10. */
  11. class GeocodeLib {
  12. const BASE_URL = 'http://{host}/maps/api/geocode/{output}?';
  13. const DEFAULT_HOST = 'de';
  14. const ACC_COUNTRY = 0;
  15. const ACC_AAL1 = 1;
  16. const ACC_AAL2 = 2;
  17. const ACC_AAL3 = 3;
  18. const ACC_POSTAL = 4;
  19. const ACC_LOC = 5;
  20. const ACC_SUBLOC = 6;
  21. const ACC_ROUTE = 7;
  22. const ACC_INTERSEC = 8;
  23. const ACC_STREET = 9;
  24. # First tries with curl, then cake, then php
  25. public $use = array('curl' => true, 'cake'=> true, 'php' => true);
  26. public $units = array('K' => 1.609344, 'N' => 0.868976242, 'F' => 5280, 'I' => 63360, 'M' => 1);
  27. /**
  28. * validation and retrieval options
  29. * - use:
  30. * - log: false logs only real errors, true all activities
  31. * - pause: timeout to prevent blocking
  32. * - ...
  33. *
  34. * 2010-06-25 ms
  35. */
  36. public $options = array(
  37. 'log' => false,
  38. 'pause' => 10000, # in ms
  39. 'min_accuracy' => self::ACC_COUNTRY,
  40. 'allow_inconclusive'=> true,
  41. 'expect' => array(), # see accuracyTypes for details
  42. # static url params
  43. 'output' => 'xml',
  44. 'host' => self::DEFAULT_HOST, # results in maps.google.com - use if you wish to obtain the closest address
  45. );
  46. /**
  47. * url params
  48. * 2010-06-25 ms
  49. */
  50. protected $params = array(
  51. 'address' => '', # either address or latlng required!
  52. 'latlng' => '',
  53. 'region' => '', # country tlds
  54. 'language' => 'de',
  55. 'bounds' => '',
  56. 'sensor' => 'false', # device with gps module sensor
  57. //'key' => '' # not neccessary anymore
  58. );
  59. protected $error = array();
  60. protected $result = null;
  61. /**
  62. * The Maps geocoder is programmed to bias its results depending on from which domain it receives requests. For example, entering "syracuse" in the search box on maps.google.com will geocode the city of "Syracuse, NY", while entering the same query on maps.google.it (Italy's domain) will find the city of "Siracusa" in Sicily. You would get the same results by sending that query through HTTP geocoding to maps.google.it instead of maps.google.com, which you can do by modifying the MAPS_HOST constant in the sample code below. Note: You cannot send a request to a non-existent maps.google.* server, so ensure that a country domain exists before redirecting your geocoding queries to it.
  63. */
  64. protected $hosts = array(
  65. 'us' => 'maps.google.com', # only one for "allow_inconclusive" = true
  66. 'gb' => 'maps.google.co.uk',
  67. 'de' => 'maps.google.de',
  68. 'ch' => 'maps.google.ch',
  69. 'at' => 'maps.google.at',
  70. 'it' => 'maps.google.it',
  71. //ADD MORE - The two-letter codes are iso2 country codes and are mapped to top level domains (ccTLDs)
  72. );
  73. protected $statusCodes = array(
  74. self::CODE_SUCCESS => 'Success',
  75. self::CODE_BAD_REQUEST => 'Sensor param missing',
  76. self::CODE_MISSING_QUERY => 'Adress/LatLng missing',
  77. self::CODE_UNKNOWN_ADDRESS => 'Success, but to address found',
  78. self::CODE_TOO_MANY_QUERIES => 'Limit exceeded',
  79. );
  80. protected $accuracyTypes = array(
  81. self::ACC_COUNTRY => 'country',
  82. self::ACC_AAL1 => 'administrative_area_level_1', # provinces/states
  83. self::ACC_AAL2 => 'administrative_area_level_2 ',
  84. self::ACC_AAL3 => 'administrative_area_level_3',
  85. self::ACC_POSTAL => 'postal_code',
  86. self::ACC_LOC => 'locality',
  87. self::ACC_SUBLOC => 'sublocality',
  88. self::ACC_ROUTE => 'route',
  89. self::ACC_INTERSEC => 'intersection',
  90. self::ACC_STREET => 'street_address'
  91. //neighborhood premise subpremise natural_feature airport park point_of_interest colloquial_area political ?
  92. );
  93. public function __construct($options = array()) {
  94. $this->defaultParams = $this->params;
  95. $this->defaultOptions = $this->options;
  96. if (Configure::read('debug') > 0) {
  97. $this->options['log'] = true;
  98. }
  99. $this->setOptions($options);
  100. }
  101. public function setParams($params) {
  102. foreach ($params as $key => $value) {
  103. if ($key == 'sensor' && $value != 'false' && $value != 'true') {
  104. $value = !empty($value) ? 'true' : 'false';
  105. }
  106. $this->params[$key] = urlencode((string)$value);
  107. }
  108. }
  109. public function setOptions($options) {
  110. foreach ($options as $key => $value) {
  111. if ($key == 'output' && $value != 'xml' && $value != 'json') {
  112. continue;
  113. }
  114. if ($key == 'host' && !array_key_exists($value, $this->hosts)) {
  115. continue;
  116. }
  117. $this->options[$key] = $value;
  118. }
  119. }
  120. public function setError($error) {
  121. if (empty($error)) {
  122. return;
  123. }
  124. $this->error[] = $error;
  125. }
  126. public function error($asString = true, $separator = ', ') {
  127. if (!$asString) {
  128. return $this->error;
  129. }
  130. return implode(', ', $this->error);
  131. }
  132. public function reset($full = true) {
  133. $this->error = array();
  134. $this->result = null;
  135. if ($full) {
  136. $this->params = $this->defaultParams;
  137. $this->options = $this->defaultOptions;
  138. }
  139. }
  140. /**
  141. * build and return url
  142. * 2010-06-29 ms
  143. */
  144. public function url() {
  145. $params = array(
  146. 'host' => $this->hosts[$this->options['host']],
  147. 'output' => $this->options['output']
  148. );
  149. $url = String::insert(self::BASE_URL, $params, array('before'=>'{', 'after'=>'}', 'clean'=>true));
  150. $params = array();
  151. foreach ($this->params as $key => $value) {
  152. if (!empty($value)) {
  153. $params[] = $key.'='.$value;
  154. }
  155. }
  156. return $url.implode('&', $params);
  157. }
  158. public function isInconclusive() {
  159. if ($this->result === null) {
  160. return null;
  161. }
  162. if (!isset($this->result[0])) {
  163. return false;
  164. }
  165. return count($this->result) > 0;
  166. }
  167. /**
  168. * @return array $result
  169. * 2010-06-25 ms
  170. */
  171. public function getResult() {
  172. if ($this->result !== null) {
  173. if (isset($this->result[0])) {
  174. $res = array();
  175. foreach ($this->result as $tmp) {
  176. $res[] = $this->options['output'] == 'json' ? $this->_transformJson($tmp) : $this->_transformXml($tmp);
  177. }
  178. return $res;
  179. }
  180. if ($this->options['output'] == 'json') {
  181. return $this->_transformJson($this->result);
  182. } else {
  183. return $this->_transformXml($this->result);
  184. }
  185. }
  186. return false;
  187. }
  188. /**
  189. * results usually from most accurate to least accurate result (street_address
  190. , ..., country)
  191. * @param float $lat
  192. * @param float $lng
  193. * @param array $options
  194. * - allow_inconclusive
  195. * - min_accuracy
  196. * @return boolean $success
  197. * 2010-06-29 ms
  198. */
  199. public function reverseGeocode($lat, $lng, $settings = array()) {
  200. $this->reset(false);
  201. $latlng = $lat.','.$lng;
  202. $this->setParams(array_merge($settings, array('latlng'=>$latlng)));
  203. $count = 0;
  204. $request_url = $this->url();
  205. while (true) {
  206. $result = $this->_fetch($request_url);
  207. if ($result === false || $result === null) {
  208. $this->setError('Could not retrieve url');
  209. CakeLog::write('geocode', 'Geocoder could not retrieve url with \''.$latlng.'\'');
  210. return false;
  211. }
  212. if ($this->options['output'] == 'json') {
  213. //$res = json_decode($result);
  214. } else {
  215. $res = Xml::build($result);
  216. }
  217. if (!is_object($res)) {
  218. $this->setError('XML parsing failed');
  219. CakeLog::write('geocode', 'Geocoder failed with XML parsing of \''.$latlng.'\'');
  220. return false;
  221. }
  222. $xmlArray = Xml::toArray($res);
  223. $xmlArray = $xmlArray['GeocodeResponse'];
  224. $status = $xmlArray['status'];
  225. if ($status == self::CODE_SUCCESS) {
  226. # validate
  227. if (isset($xmlArray['result'][0]) && !$this->options['allow_inconclusive']) {
  228. $this->setError('Inconclusive result (total of '.count($xmlArray['result']).')');
  229. $this->result = $xmlArray['result'];
  230. return false;
  231. }
  232. if (isset($xmlArray['result'][0])) {
  233. //$xmlArray['result'] = $xmlArray['result'][0];
  234. $accuracy = $this->_parse('type', $xmlArray['result'][0]);
  235. } else {
  236. $accuracy = $this->_parse('type', $xmlArray['result']);
  237. }
  238. if ($this->_isNotAccurateEnough($accuracy)) {
  239. $this->setError('Accuracy not good enough ('.implode(', ', (array)$accuracy).' instead of at least '.$this->accuracyTypes[$this->options['min_accuracy']].')');
  240. $this->result = $xmlArray['result'];
  241. return false;
  242. }
  243. # save Result
  244. if ($this->options['log']) {
  245. CakeLog::write('geocode', 'Address \''.$latlng.'\' has been geocoded');
  246. }
  247. break;
  248. } elseif ($status == self::CODE_TOO_MANY_QUERIES) {
  249. // sent geocodes too fast, delay +0.1 seconds
  250. if ($this->options['log']) {
  251. CakeLog::write('geocode', 'Delay neccessary for \''.$latlng.'\'');
  252. }
  253. $count++;
  254. } else {
  255. # something went wrong
  256. $this->setError('Error '.$status.(isset($this->statusCodes[$status]) ? ' ('.$this->statusCodes[$status].')' : ''));
  257. if ($this->options['log']) {
  258. CakeLog::write('geocode', 'Geocoder could not geocode \''.$latlng.'\'');
  259. }
  260. return false; # for now...
  261. }
  262. if ($count > 5) {
  263. if ($this->options['log']) {
  264. CakeLog::write('geocode', 'Geocoder aborted after too many trials with \''.$latlng.'\'');
  265. }
  266. $this->setError('Too many trials - abort');
  267. return false;
  268. }
  269. $this->pause(true);
  270. }
  271. $this->result = $xmlArray['result'];
  272. return true;
  273. }
  274. /**
  275. * trying to avoid "TOO_MANY_QUERIES" error
  276. * 2010-06-29 ms
  277. */
  278. public function pause($raise = false) {
  279. usleep($this->options['pause']);
  280. if ($raise) {
  281. $this->options['pause'] += 10000;
  282. }
  283. }
  284. /**
  285. * @param string $address
  286. * @param array $settings
  287. * - allow_inconclusive
  288. * - min_accuracy
  289. * @return boolean $success
  290. * 2010-06-25 ms
  291. */
  292. public function geocode($address, $settings = array()) {
  293. $this->reset(false);
  294. $this->setParams(array_merge($settings, array('address'=>$address)));
  295. if ($this->options['allow_inconclusive']) {
  296. # only host working with this setting
  297. //$this->options['host'] = self::DEFAULT_HOST;
  298. }
  299. $count = 0;
  300. $request_url = $this->url();
  301. while (true) {
  302. $result = $this->_fetch($request_url);
  303. if ($result === false || $result === null) {
  304. $this->setError('Could not retrieve url');
  305. CakeLog::write('geocode', 'Geocoder could not retrieve url with \''.$address.'\'');
  306. return false;
  307. }
  308. if ($this->options['output'] == 'json') {
  309. //$res = json_decode($result);
  310. } else {
  311. $res = Xml::build($result);
  312. }
  313. if (!is_object($res)) {
  314. $this->setError('XML parsing failed');
  315. CakeLog::write('geocode', 'Geocoder failed with XML parsing of \''.$address.'\'');
  316. return false;
  317. }
  318. $xmlArray = Xml::toArray($res);
  319. $xmlArray = $xmlArray['GeocodeResponse'];
  320. $status = $xmlArray['status'];
  321. if ($status == self::CODE_SUCCESS) {
  322. # validate
  323. if (isset($xmlArray['result'][0]) && !$this->options['allow_inconclusive']) {
  324. $this->setError('Inconclusive result (total of '.count($xmlArray['result']).')');
  325. $this->result = $xmlArray['result'];
  326. return false;
  327. }
  328. if (isset($xmlArray['result'][0])) {
  329. //$xmlArray['result'] = $xmlArray['result'][0];
  330. $accuracy = $this->_parse('type', $xmlArray['result'][0]);
  331. } else {
  332. $accuracy = $this->_parse('type', $xmlArray['result']);
  333. }
  334. //echo returns($accuracy);
  335. if ($this->_isNotAccurateEnough($accuracy)) {
  336. $this->setError('Accuracy not good enough ('.implode(', ', (array)$accuracy).' instead of at least '.$this->accuracyTypes[$this->options['min_accuracy']].')');
  337. $this->result = $xmlArray['result'];
  338. return false;
  339. }
  340. if (!empty($this->options['expect'])) {
  341. $types = array($accuracy); # TODO: maybe check more than just first?
  342. $validExpectation = false;
  343. foreach ($types as $type) {
  344. if (in_array($type, (array)$this->options['expect'])) {
  345. $validExpectation = true;
  346. break;
  347. }
  348. }
  349. if (!$validExpectation) {
  350. $this->setError('Expectation not good reached ('.$accuracy.' instead of '.implode(', ', (array)$this->options['expect']).')');
  351. $this->result = $xmlArray['result'];
  352. return false;
  353. }
  354. }
  355. # save Result
  356. if ($this->options['log']) {
  357. CakeLog::write('geocode', 'Address \''.$address.'\' has been geocoded');
  358. }
  359. break;
  360. } elseif ($status == self::CODE_TOO_MANY_QUERIES) {
  361. // sent geocodes too fast, delay +0.1 seconds
  362. if ($this->options['log']) {
  363. CakeLog::write('geocode', 'Delay neccessary for \''.$address.'\'');
  364. }
  365. $count++;
  366. } else {
  367. # something went wrong
  368. $this->setError('Error '.$status.(isset($this->statusCodes[$status]) ? ' ('.$this->statusCodes[$status].')' : ''));
  369. if ($this->options['log']) {
  370. CakeLog::write('geocode', 'Geocoder could not geocode \''.$address.'\'');
  371. }
  372. return false; # for now...
  373. }
  374. if ($count > 5) {
  375. if ($this->options['log']) {
  376. CakeLog::write('geocode', 'Geocoder aborted after too many trials with \''.$address.'\'');
  377. }
  378. $this->setError('Too many trials - abort');
  379. return false;
  380. }
  381. $this->pause(true);
  382. }
  383. $this->result = $xmlArray['result'];
  384. return true;
  385. }
  386. protected function _isNotAccurateEnough($accuracy = null) {
  387. if ($accuracy === null) {
  388. if (isset($this->result[0])) {
  389. $accuracy = $this->result[0]['type'];
  390. } else {
  391. $accuracy = $this->result['type'];
  392. }
  393. }
  394. if (is_array($accuracy)) {
  395. $accuracy = array_shift($accuracy);
  396. }
  397. if (!in_array($accuracy, $this->accuracyTypes)) {
  398. return null;
  399. }
  400. foreach ($this->accuracyTypes as $key => $type) {
  401. if ($type == $accuracy) {
  402. $accuracy = $key;
  403. break;
  404. }
  405. }
  406. //echo returns($accuracy);
  407. //echo returns('XXX'.$this->options['min_accuracy']);
  408. return $accuracy < $this->options['min_accuracy'];
  409. }
  410. protected function _transformJson($record) {
  411. $res = array();
  412. //TODO
  413. return $res;
  414. }
  415. /**
  416. * try to find the correct path
  417. * - type (string)
  418. * - Type (array[string, ...])
  419. * 2010-06-29 ms
  420. */
  421. protected function _parse($key, $array) {
  422. if (isset($array[$key])) {
  423. return $array[$key];
  424. }
  425. if (isset($array[($key = ucfirst($key))])) {
  426. return $array[$key][0];
  427. }
  428. return null;
  429. }
  430. /**
  431. * flattens result array and returns clean record
  432. * keys:
  433. * - formatted_address, type, country, country_code, country_province, country_province_code, locality, sublocality, postal_code, route, lat, lng, location_type, viewport, bounds
  434. * 2010-06-25 ms
  435. */
  436. protected function _transformXml($record) {
  437. $res = array();
  438. $components = array();
  439. if (!isset($record['address_component'][0])) {
  440. $record['address_component'] = array($record['address_component']);
  441. }
  442. foreach ($record['address_component'] as $c) {
  443. $types = array();
  444. if (isset($c['type'])) { //!is_array($c['Type'])
  445. if (!is_array($c['type'])) {
  446. $c['type'] = (array)$c['type'];
  447. }
  448. $type = $c['type'][0];
  449. array_shift($c['type']);
  450. $types = $c['type'];
  451. } elseif (isset($c['type'])) {
  452. $type = $c['type'];
  453. } else {
  454. # error?
  455. continue;
  456. }
  457. if (array_key_exists($type, $components)) {
  458. $components[$type]['name'] .= ' '.$c['long_name'];
  459. $components[$type]['abbr'] .= ' '.$c['short_name'];
  460. $components[$type]['types'] += $types;
  461. }
  462. $components[$type] = array('name'=>$c['long_name'], 'abbr'=>$c['short_name'], 'types'=>$types);
  463. }
  464. $res['formatted_address'] = $record['formatted_address'];
  465. $res['type'] = $this->_parse('type', $record);
  466. if (array_key_exists('country', $components)) {
  467. $res['country'] = $components['country']['name'];
  468. $res['country_code'] = $components['country']['abbr'];
  469. } else {
  470. $res['country'] = $res['country_code'] = '';
  471. }
  472. if (array_key_exists('administrative_area_level_1', $components)) {
  473. $res['country_province'] = $components['administrative_area_level_1']['name'];
  474. $res['country_province_code'] = $components['administrative_area_level_1']['abbr'];
  475. } else {
  476. $res['country_province'] = $res['country_province_code'] = '';
  477. }
  478. if (array_key_exists('postal_code', $components)) {
  479. $res['postal_code'] = $components['postal_code']['name'];
  480. } else {
  481. $res['postal_code'] = '';
  482. }
  483. if (array_key_exists('locality', $components)) {
  484. $res['locality'] = $components['locality']['name'];
  485. } else {
  486. $res['locality'] = '';
  487. }
  488. if (array_key_exists('sublocality', $components)) {
  489. $res['sublocality'] = $components['sublocality']['name'];
  490. } else {
  491. $res['sublocality'] = '';
  492. }
  493. if (array_key_exists('route', $components)) {
  494. $res['route'] = $components['route']['name'];
  495. if (array_key_exists('street_number', $components)) {
  496. $res['route'] .= ' '.$components['street_number']['name'];
  497. }
  498. } else {
  499. $res['route'] = '';
  500. }
  501. //TODO: add more
  502. $res['lat'] = $record['geometry']['location']['lat'];
  503. $res['lng'] = $record['geometry']['location']['lng'];
  504. $res['location_type'] = $record['geometry']['location_type'];
  505. if (!empty($record['geometry']['viewport'])) {
  506. $res['viewport'] = array('sw'=>$record['geometry']['viewport']['southwest'], 'ne'=>$record['geometry']['viewport']['northeast']);
  507. }
  508. if (!empty($record['geometry']['bounds'])) {
  509. $res['bounds'] = array('sw'=>$record['geometry']['bounds']['southwest'], 'ne'=>$record['geometry']['bounds']['northeast']);
  510. }
  511. # manuell corrections
  512. $array = array(
  513. 'Berlin' => 'BE',
  514. );
  515. if (!empty($res['country_province_code']) && array_key_exists($res['country_province_code'], $array)) {
  516. $res['country_province_code'] = $array[$res['country_province_code']];
  517. }
  518. return $res;
  519. }
  520. /**
  521. * fetches url with curl if available
  522. * fallbacks: cake and php
  523. * note: expects url with json encoded content
  524. * @access private
  525. **/
  526. protected function _fetch($url) {
  527. $this->HttpSocket = new HttpSocketLib($this->use);
  528. if ($res = $this->HttpSocket->fetch($url, 'CakePHP Geocode Lib')) {
  529. return $res;
  530. }
  531. $this->setError($this->HttpSocket->error());
  532. return false;
  533. }
  534. /**
  535. * debugging
  536. * 2009-11-27 ms
  537. */
  538. public function debug() {
  539. return $this->result;
  540. }
  541. /**
  542. * Calculates Distance between two points - each: array('lat'=>x,'lng'=>y)
  543. * DB:
  544. '6371.04 * ACOS( COS( PI()/2 - RADIANS(90 - Retailer.lat)) * ' .
  545. 'COS( PI()/2 - RADIANS(90 - '. $data['Location']['lat'] .')) * ' .
  546. 'COS( RADIANS(Retailer.lng) - RADIANS('. $data['Location']['lng'] .')) + ' .
  547. 'SIN( PI()/2 - RADIANS(90 - Retailer.lat)) * ' .
  548. 'SIN( PI()/2 - RADIANS(90 - '. $data['Location']['lat'] . '))) ' .
  549. 'AS distance'
  550. * @param array pointX
  551. * @param array pointY
  552. * @param float $unit (M=miles, K=kilometers, N=nautical miles, I=inches, F=feet)
  553. * @return int distance: in km
  554. * 2009-03-06 ms
  555. */
  556. public function distance($pointX, $pointY, $unit = null) {
  557. if (empty($unit) || !array_key_exists(($unit = strtoupper($unit)), $this->units)) {
  558. $unit = array_keys($this->units);
  559. $unit = $unit[0];
  560. }
  561. /*
  562. $res = 6371.04 * ACOS( COS( PI()/2 - rad2deg(90 - $pointX['lat'])) *
  563. COS( PI()/2 - rad2deg(90 - $pointY['lat'])) *
  564. COS( rad2deg($pointX['lng']) - rad2deg($pointY['lng'])) +
  565. SIN( PI()/2 - rad2deg(90 - $pointX['lat'])) *
  566. SIN( PI()/2 - rad2deg(90 - $pointY['lat'])));
  567. $res = 6371.04 * acos(sin($pointY['lat'])*sin($pointX['lat'])+cos($pointY['lat'])*cos($pointX['lat'])*cos($pointY['lng'] - $pointX['lng']));
  568. */
  569. # seems to be the only working one (although slightly incorrect...)
  570. $res = 69.09 * rad2deg(acos(sin(deg2rad($pointX['lat'])) * sin(deg2rad($pointY['lat'])) + cos(deg2rad($pointX['lat'])) * cos(deg2rad($pointY['lat'])) * cos(deg2rad($pointX['lng'] - $pointY['lng']))));
  571. if (isset($this->units[$unit])) {
  572. $res *= $this->units[$unit];
  573. }
  574. return ceil($res);
  575. }
  576. public function convert($value, $fromUnit, $toUnit) {
  577. if (!isset($this->units[($fromUnit = strtoupper($fromUnit))]) || !isset($this->units[($toUnit = strtoupper($toUnit))])) {
  578. return false;
  579. }
  580. if ($fromUnit == 'M') {
  581. $value *= $this->units[$toUnit];
  582. } elseif ($toUnit == 'M') {
  583. $value /= $this->units[$fromUnit];
  584. } else {
  585. $value /= $this->units[$fromUnit];
  586. $value *= $this->units[$toUnit];
  587. }
  588. return $value;
  589. }
  590. /**
  591. * fuzziness filter for coordinates (lat or lng)
  592. * @param float coord
  593. * @param int level (0 = nothing to 5 = extrem)
  594. * - 1:
  595. * - 2:
  596. * - 3:
  597. * - 4:
  598. * - 5:
  599. * @static
  600. * 2011-03-16 ms
  601. */
  602. public function blur($coord, $level = 0) {
  603. if (!$level) {
  604. return $coord;
  605. }
  606. //TODO:
  607. switch ($level) {
  608. case 1:
  609. break;
  610. case 2:
  611. break;
  612. case 3:
  613. break;
  614. case 4:
  615. break;
  616. case 5:
  617. break;
  618. default:
  619. # wrong level, return value
  620. trigger_error('Invalid level \''.h($level).'\'');
  621. return $coord;
  622. }
  623. $scrambleVal = 0.000001 * mt_rand(1000,2000) * (mt_rand(0,1) === 0 ? 1 : -1);
  624. return ($coord + $scrambleVal);
  625. //$scrambleVal *= (mt_rand(0,1) === 0 ? 1 : 2);
  626. //$scrambleVal *= (float)(2^$level);
  627. //die($scrambleVal.' '.$coord);
  628. # TODO: + - by chance!!!
  629. return $coord + $scrambleVal;
  630. }
  631. const TYPE_ROOFTOP = 'ROOFTOP';
  632. const TYPE_RANGE_INTERPOLATED = 'RANGE_INTERPOLATED';
  633. const TYPE_GEOMETRIC_CENTER = 'GEOMETRIC_CENTER';
  634. const TYPE_APPROXIMATE = 'APPROXIMATE';
  635. const CODE_SUCCESS = 'OK'; //200;
  636. const CODE_TOO_MANY_QUERIES = 'OVER_QUERY_LIMIT'; //620;
  637. const CODE_BAD_REQUEST = 'REQUEST_DENIED'; //400;
  638. const CODE_MISSING_QUERY = 'INVALID_REQUEST';//601;
  639. const CODE_UNKNOWN_ADDRESS = 'ZERO_RESULTS'; //602;
  640. /*
  641. const CODE_SERVER_ERROR = 500;
  642. const CODE_UNAVAILABLE_ADDRESS = 603;
  643. const CODE_UNKNOWN_DIRECTIONS = 604;
  644. const CODE_BAD_KEY = 610;
  645. */
  646. }
  647. /*
  648. TODO:
  649. http://code.google.com/intl/de-DE/apis/maps/documentation/geocoding/
  650. - whats the difference to "http://maps.google.com/maps/api/geocode/output?parameters"
  651. */
  652. /*
  653. Example: NEW:
  654. Array
  655. (
  656. [status] => OK
  657. [Result] => Array
  658. (
  659. [type] => postal_code
  660. [formatted_address] => 74523, Deutschland
  661. [AddressComponent] => Array
  662. (
  663. [0] => Array
  664. (
  665. [long_name] => 74523
  666. [short_name] => 74523
  667. [type] => postal_code
  668. )
  669. [1] => Array
  670. (
  671. [long_name] => Schwäbisch Hall
  672. [short_name] => SHA
  673. [Type] => Array
  674. (
  675. [0] => administrative_area_level_2
  676. [1] => political
  677. )
  678. )
  679. [2] => Array
  680. (
  681. [long_name] => Baden-Württemberg
  682. [short_name] => BW
  683. [Type] => Array
  684. (
  685. [0] => administrative_area_level_1
  686. [1] => political
  687. )
  688. )
  689. [3] => Array
  690. (
  691. [long_name] => Deutschland
  692. [short_name] => DE
  693. [Type] => Array
  694. (
  695. [0] => country
  696. [1] => political
  697. )
  698. )
  699. )
  700. [Geometry] => Array
  701. (
  702. [Location] => Array
  703. (
  704. [lat] => 49.1257616
  705. [lng] => 9.7544127
  706. )
  707. [location_type] => APPROXIMATE
  708. [Viewport] => Array
  709. (
  710. [Southwest] => Array
  711. (
  712. [lat] => 49.0451477
  713. [lng] => 9.6132550
  714. )
  715. [Northeast] => Array
  716. (
  717. [lat] => 49.1670260
  718. [lng] => 9.8756350
  719. )
  720. )
  721. [Bounds] => Array
  722. (
  723. [Southwest] => Array
  724. (
  725. [lat] => 49.0451477
  726. [lng] => 9.6132550
  727. )
  728. [Northeast] => Array
  729. (
  730. [lat] => 49.1670260
  731. [lng] => 9.8756350
  732. )
  733. )
  734. )
  735. )
  736. )
  737. Example OLD:
  738. Array
  739. (
  740. [name] => 74523 Deutschland
  741. [Status] => Array
  742. (
  743. [code] => 200
  744. [request] => geocode
  745. )
  746. [Result] => Array
  747. (
  748. [id] => p1
  749. [address] => 74523, Deutschland
  750. [AddressDetails] => Array
  751. (
  752. [Accuracy] => 5
  753. [xmlns] => urn:oasis:names:tc:ciq:xsdschema:xAL:2.0
  754. [Country] => Array
  755. (
  756. [CountryNameCode] => DE
  757. [CountryName] => Deutschland
  758. [AdministrativeArea] => Array
  759. (
  760. [AdministrativeAreaName] => Baden-Württemberg
  761. [SubAdministrativeArea] => Array
  762. (
  763. [SubAdministrativeAreaName] => Schwäbisch Hall
  764. [PostalCode] => Array
  765. (
  766. [PostalCodeNumber] => 74523
  767. )
  768. )
  769. )
  770. )
  771. )
  772. [ExtendedData] => Array
  773. (
  774. [LatLonBox] => Array
  775. (
  776. [north] => 49.1670260
  777. [south] => 49.0451477
  778. [east] => 9.8756350
  779. [west] => 9.6132550
  780. )
  781. )
  782. [Point] => Array
  783. (
  784. [coordinates] => 9.7544127,49.1257616,0
  785. )
  786. )
  787. )
  788. {
  789. "status": "OK",
  790. "results": [ {
  791. "types": [ "street_address" ],
  792. "formatted_address": "Krebenweg 20, 74523 Schwäbisch Hall, Deutschland",
  793. "address_components": [ {
  794. "long_name": "20",
  795. "short_name": "20",
  796. "types": [ "street_number" ]
  797. }, {
  798. "long_name": "Krebenweg",
  799. "short_name": "Krebenweg",
  800. "types": [ "route" ]
  801. }, {
  802. "long_name": "Bibersfeld",
  803. "short_name": "Bibersfeld",
  804. "types": [ "sublocality", "political" ]
  805. }, {
  806. "long_name": "Schwäbisch Hall",
  807. "short_name": "Schwäbisch Hall",
  808. "types": [ "locality", "political" ]
  809. }, {
  810. "long_name": "Schwäbisch Hall",
  811. "short_name": "SHA",
  812. "types": [ "administrative_area_level_2", "political" ]
  813. }, {
  814. "long_name": "Baden-Württemberg",
  815. "short_name": "BW",
  816. "types": [ "administrative_area_level_1", "political" ]
  817. }, {
  818. "long_name": "Deutschland",
  819. "short_name": "DE",
  820. "types": [ "country", "political" ]
  821. }, {
  822. "long_name": "74523",
  823. "short_name": "74523",
  824. "types": [ "postal_code" ]
  825. } ],
  826. "geometry": {
  827. "location": {
  828. "lat": 49.0817369,
  829. "lng": 9.6908451
  830. },
  831. "location_type": "RANGE_INTERPOLATED", //ROOFTOP //APPROXIMATE
  832. "viewport": {
  833. "southwest": {
  834. "lat": 49.0785954,
  835. "lng": 9.6876999
  836. },
  837. "northeast": {
  838. "lat": 49.0848907,
  839. "lng": 9.6939951
  840. }
  841. },
  842. "bounds": {
  843. "southwest": {
  844. "lat": 49.0817369,
  845. "lng": 9.6908451
  846. },
  847. "northeast": {
  848. "lat": 49.0817492,
  849. "lng": 9.6908499
  850. }
  851. }
  852. },
  853. "partial_match": true
  854. } ]
  855. }
  856. */