8 protected $iMaxRank = 28;
10 protected $aLangPrefOrder = array();
12 function ReverseGeocode(&$oDB)
17 function setLanguagePreference($aLangPref)
19 $this->aLangPrefOrder = $aLangPref;
22 function setLatLon($fLat, $fLon)
24 $this->fLat = (float)$fLat;
25 $this->fLon = (float)$fLon;
28 function setRank($iRank)
30 $this->iMaxRank = $iRank;
33 function setZoom($iZoom)
35 // Zoom to rank, this could probably be calculated but a lookup gives fine control
37 0 => 2, // Continent / Sea
49 12 => 18, // Town / Village
53 16 => 26, // Street, TODO: major street?
55 18 => 30, // or >, Building
56 19 => 30, // or >, Building
58 $this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
63 $sPointSQL = 'ST_SetSRID(ST_Point('.$this->fLon.','.$this->fLat.'),4326)';
64 $iMaxRank = $this->iMaxRank;
65 $iMaxRank_orig = $this->iMaxRank;
67 // Find the nearest point
68 $fSearchDiam = 0.0004;
71 $fMaxAreaDistance = 1;
72 $bIsInUnitedStates = false;
73 $bPlaceIsTiger = false;
74 while(!$iPlaceID && $fSearchDiam < $fMaxAreaDistance)
76 $fSearchDiam = $fSearchDiam * 2;
78 // If we have to expand the search area by a large amount then we need a larger feature
79 // then there is a limit to how small the feature should be
80 if ($fSearchDiam > 2 && $iMaxRank > 4) $iMaxRank = 4;
81 if ($fSearchDiam > 1 && $iMaxRank > 9) $iMaxRank = 8;
82 if ($fSearchDiam > 0.8 && $iMaxRank > 10) $iMaxRank = 10;
83 if ($fSearchDiam > 0.6 && $iMaxRank > 12) $iMaxRank = 12;
84 if ($fSearchDiam > 0.2 && $iMaxRank > 17) $iMaxRank = 17;
85 if ($fSearchDiam > 0.1 && $iMaxRank > 18) $iMaxRank = 18;
86 if ($fSearchDiam > 0.008 && $iMaxRank > 22) $iMaxRank = 22;
87 if ($fSearchDiam > 0.001 && $iMaxRank > 26) $iMaxRank = 26;
89 $sSQL = 'select place_id,parent_place_id,rank_search,calculated_country_code from placex';
90 $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
91 $sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
92 $sSQL .= ' and (name is not null or housenumber is not null)';
93 $sSQL .= ' and class not in (\'waterway\',\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
94 $sSQL .= ' and indexed_status = 0 ';
95 $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
96 $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
97 $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', geometry) ASC limit 1';
98 if (CONST_Debug) var_dump($sSQL);
99 $aPlace = $this->oDB->getRow($sSQL);
100 if (PEAR::IsError($aPlace))
102 failInternalError("Could not determine closest place.", $sSQL, $aPlace);
104 $iPlaceID = $aPlace['place_id'];
105 $iParentPlaceID = $aPlace['parent_place_id'];
106 $bIsInUnitedStates = ($aPlace['calculated_country_code'] == 'us');
109 // Only street found? If it's in the US we can check TIGER data for nearest housenumber
110 if ($bIsInUnitedStates && $iMaxRank_orig >= 28 && $iPlaceID && ($aPlace['rank_search'] == 26 || $aPlace['rank_search'] == 27 ))
112 $fSearchDiam = 0.001;
113 $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search, ST_line_locate_point(linegeo,'.$sPointSQL.') as fraction';
114 //if (CONST_Debug) { $sSQL .= ', housenumber, ST_distance('.$sPointSQL.', centroid) as distance, st_y(centroid) as lat, st_x(centroid) as lon'; }
115 $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$iPlaceID;
116 $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')'; //no centroid anymore in Tiger data, now we have lines
117 $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
120 // print all house numbers in the parent (street)
123 $sSQL = preg_replace('/limit 1/', 'limit 100', $sSQL);
126 $aAllHouses = $this->oDB->getAll($sSQL);
127 foreach($aAllHouses as $i)
129 echo $i['housenumber'] . ' | ' . $i['distance'] * 1000 . ' | ' . $i['lat'] . ' | ' . $i['lon']. ' | '. "<br>\n";
133 $aPlaceTiger = $this->oDB->getRow($sSQL);
134 if (PEAR::IsError($aPlace))
136 failInternalError("Could not determine closest Tiger place.", $sSQL, $aPlaceTiger);
140 if (CONST_Debug) var_dump('found Tiger place', $aPlaceTiger);
141 $bPlaceIsTiger = true;
142 $aPlace = $aPlaceTiger;
143 $iPlaceID = $aPlaceTiger['place_id'];
144 $iParentPlaceID = $aPlaceTiger['parent_place_id']; // the street
145 $iFraction = $aPlaceTiger['fraction'];
149 // The point we found might be too small - use the address to find what it is a child of
150 if ($iPlaceID && $iMaxRank < 28)
152 if ($aPlace['rank_search'] > 28 && $iParentPlaceID && !$bPlaceIsTiger)
154 $iPlaceID = $iParentPlaceID;
156 $sSQL = "select address_place_id from place_addressline where place_id = $iPlaceID order by abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc limit 1";
157 $iPlaceID = $this->oDB->getOne($sSQL);
158 if (PEAR::IsError($iPlaceID))
160 failInternalError("Could not get parent for place.", $sSQL, $iPlaceID);
164 $iPlaceID = $aPlace['place_id'];
168 return array('place_id' => $iPlaceID,
169 'type' => $bPlaceIsTiger ? 'tiger' : 'osm',
170 'fraction' => $bPlaceIsTiger ? $iFraction : -1 );