* @package WB * @subpackage geo */ WBClass::load('WBGeo'); /** * Geo Information Tools: Address * * @todo only works for German addresses * @version 0.3.0 * @package WB * @subpackage geo */ class WBGeo_Address extends WBStdClass { const FORMAT_TYPE_NAME = 'name'; const FORMAT_TYPE_STREET = 'street'; const FORMAT_TYPE_CITY = 'city'; const FORMAT_TYPE_STATE = 'state'; const FORMAT_TYPE_COUNTRY = 'country'; /** * @var string */ private $class = ''; /** * @var string */ private $type = ''; /** * @var string */ private $name = ''; /** * @var float */ private $coordLat = 0; /** * @var float */ private $coordLon = 0; /** * @var string */ private $houseNo = ''; /** * @var string */ private $street = ''; /** * @var string */ private $suburb = ''; /** * @var string */ private $cityDistrict = ''; /** * @var string */ private $city = ''; /** * @var string */ private $state = ''; /** * @var string */ private $zip = ''; /** * @var string */ private $country = 'Deutschland'; /** * @var string */ private $countryCode = 'DE'; /** * Verify Address */ public function verifyAddress() { $this->fetchInfo(); return true; } /** * Get Coordinates * * @return array */ public function getCoordinates() { if (empty($this->coordLat)) { return array(); } return array( 'lat' => $this->coordLat, 'lon' => $this->coordLon ); } /** * Get Name * * Name is optional part of address * @return string */ public function getName() { return $this->name; } /** * Set Name * * @param string */ public function setName($name) { $this->name = $name; } /** * Get Street Name * * @return string */ public function getStreet() { return $this->street; } /** * Set Street * * @param string */ public function setStreet($street) { $this->street = $street; } /** * Get House Number * * @return string */ public function getHouseNo() { return $this->houseNo; } /** * Set House No * * @param string */ public function setHouseNo($houseNo) { $this->houseNo = $houseNo; } /** * Get Suburb * * @return string */ public function getSuburb() { return $this->suburb; } /** * Get ZIP Code * * @return string */ public function getZIP() { return $this->zip; } /** * Set ZIP * * @param string */ public function setZIP($zip) { $this->zip = $zip; } /** * Get City * * @return string */ public function getCity() { return $this->city; } /** * Set City * * @todo improve fuzzy ZIP / city detection * @param string * @param bool */ public function setCity($city, $fuzzy = false) { $this->city = ''; if ($fuzzy) { $this->zip = ''; $label = array($city); if ($this->extractCity($label) ) { return; } else if (preg_match('/(\d+)/', $city, $match)) { $this->zip = $match[1]; return; } } $this->city = $city; } /** * Get State * * @return string */ public function getState() { return $this->state; } /** * Set State * * @param string */ public function setState($state) { $this->state = $state; } /** * Get Country Name * * @return string */ public function getCountry() { return $this->country; } /** * Get Country Code * * @return string */ public function getCountryCode() { return $this->countryCode; } /** * Set Country Code * * @param string */ public function setCountryCode($code) { if ($this->countryCode == $code) { return; } WBClass::load('WBException_Argument'); /** @var WBDictionary_Country */ $dict = WBClass::create('WBDictionary_Country'); try { $dict->load($code); } catch (WBException_Argument $e) { return; } $this->countryCode = $code; $this->country = $dict->getWord(); } /** * Get Country Code * * @return string */ public function getAddressLabel() { $label = ''; $label .= sprintf($this->getFormat(self::FORMAT_TYPE_NAME), $this->name); $label .= sprintf($this->getFormat(self::FORMAT_TYPE_STREET), $this->street, $this->houseNo); $label .= sprintf($this->getFormat(self::FORMAT_TYPE_CITY), $this->zip, $this->city); $label .= sprintf($this->getFormat(self::FORMAT_TYPE_STATE), $this->state); $label .= sprintf($this->getFormat(self::FORMAT_TYPE_COUNTRY), $this->country); return $label; } private function flush() { $this->name = ''; $this->coordLat = 0; $this->coordLon = 0; $this->class = ''; $this->type = ''; $this->houseNo = ''; $this->street = ''; $this->suburb = ''; $this->city = ''; $this->zip = ''; $this->state = ''; //$this->country = ''; //$this->countryCode = ''; } /** * Set Address from Label * * Set internal label and parse it to extract street, house number etc. * * @todo become more flexible * @todo parse other countries addresses too * @param string */ public function setAddressLabel($label) { $this->flush(); $label = array_map('trim', explode("\n", $label)); $res = true; if (!$this->extractCity($label)) { $res = false; } if ($res && !$this->extractStreet($label)) { $res = false; } if (!empty($label)) { $this->name = implode("\n", $label); } return true; } /** * Extract Street and House Number * * @param array * @return true on success */ private function extractStreet(&$label) { $copy = $label; while (0 < count($copy)) { $tmp = trim(array_pop($copy)); $tmp = explode(" ", $tmp); $street = array(); while (0 < count($tmp)) { $t = array_shift($tmp); if (is_numeric($t[0])) { $this->street = implode(' ', $street); $this->houseNo = $t . implode(' ', $tmp); $label = $copy; return true; break; } $street[] = $t; } } return false; } /** * Extract City and ZIP * * @param array * @return bool true on success */ private function extractCity(&$label) { $copy = $label; while (0 < count($copy)) { $tmp = array_pop($copy); if (!preg_match('/(\d+)\s+(\w+)/', $tmp, $match)) { continue; } $this->city = $match[2]; $this->zip = $match[1]; $label = $copy; return true; } return false; } /** * Search on OpenStreetMap * * Requires at least country plus city and or postalcode. * * @todo improve translation of city/town/village/municipality * @return bool true on success */ private function fetchInfo() { $url = 'https://nominatim.openstreetmap.org/search'; $get = array( //'street' => $this->houseNo . ' ' . $this->street, //'city' => $this->city, //'postalcode' => $this->zip, 'country' => $this->country, 'format' => 'json', 'polygon' => 0, 'addressdetails' => 1 ); if (!empty($this->zip)) { $get['postalcode'] = $this->zip; } if (!empty($this->city)) { $get['city'] = $this->city; } if (!empty($this->street)) { if (!empty($this->houseNo)) { $get['street'] = $this->houseNo . ' ' . $this->street; } else { $get['street'] = $this->street; } } // not enough information to search anything if (empty($get['postalcode']) && empty($get['city'])) { return false; } $header = array( 'User-Agent: Wombat Geo_Address 0.1' ); // use key 'http' even if you send the request to 'https' $options = array( 'http' => array( 'method' => 'GET', 'header' => implode("\r\n", $header) ) ); $ctx = stream_context_create($options); $url = $url . '?' . http_build_query($get); $info = file_get_contents($url, false, $ctx); if (empty($info)) { return false; } $info = json_decode($info, true); if (empty($info)) { return false; } $info = array_shift($info); $this->coordLat = $info['lat']; $this->coordLon = $info['lon']; $this->class = $info['class']; $this->type = $info['type']; if (!empty($info['address']['house_number'])) { $this->houseNo = $info['address']['house_number']; } if (!empty($info['address']['road'])) { $this->street = $info['address']['road']; } else if (!empty($info['address']['city_district'])) { $this->suburb = $info['address']['city_district']; } if (!empty($info['address']['suburb'])) { $this->suburb = $info['address']['suburb']; } if (!empty($info['address']['city'])) { $this->city = $info['address']['city']; } else if (!empty($info['address']['municipality'])) { $this->city = $info['address']['municipality']; } else if (!empty($info['address']['town'])) { $this->city = $info['address']['town']; } else if (!empty($info['address']['village'])) { $this->city = $info['address']['village']; } if (!empty($info['address']['postcode'])) { $this->zip = $info['address']['postcode']; } if (!empty($info['address']['state'])) { $this->state = $info['address']['state']; } if (!empty($info['address']['country'])) { $this->country = $info['address']['country']; } if (!empty($info['address']['country_code'])) { $this->countryCode = $info['address']['country_code']; } return true; } /** * Get String Format For Type * * Format depends on country code * * @todo use address' country code to select proper format * @param string * @return string */ private function getFormat($type) { switch ($type) { case self::FORMAT_TYPE_STREET: $format = "%s %s\n"; break; case self::FORMAT_TYPE_CITY: $format = "%s %s\n"; break; case self::FORMAT_TYPE_STATE: $format = ''; break; default: $format = "%s\n"; break; } return $format; } }