Files
Nominatim/lib/ReverseGeocode.php
Sarah Hoffmann a86563cfb1 make tables for external data (Tiger and aux) configurable
Introduces two new settings CONST_Use_US_Tiger_Data and
CONST_Use_Aux_Location_data, which are disabled by default.
When false the corresponding tables are ignored in queries
and updates.

Aux and tiger tables are no longer created by default. This
has to be done by the corresponding import scripts. The former
aux table creation can be found in sql/aux_tables.sql for
reference.
2016-04-07 21:47:51 +02:00

228 lines
6.9 KiB
PHP

<?php
class ReverseGeocode
{
protected $oDB;
protected $fLat;
protected $fLon;
protected $iMaxRank = 28;
protected $aLangPrefOrder = array();
protected $bIncludePolygonAsPoints = false;
protected $bIncludePolygonAsText = false;
protected $bIncludePolygonAsGeoJSON = false;
protected $bIncludePolygonAsKML = false;
protected $bIncludePolygonAsSVG = false;
protected $fPolygonSimplificationThreshold = 0.0;
function ReverseGeocode(&$oDB)
{
$this->oDB =& $oDB;
}
function setLanguagePreference($aLangPref)
{
$this->aLangPrefOrder = $aLangPref;
}
function setLatLon($fLat, $fLon)
{
$this->fLat = (float)$fLat;
$this->fLon = (float)$fLon;
}
function setRank($iRank)
{
$this->iMaxRank = $iRank;
}
function setZoom($iZoom)
{
// Zoom to rank, this could probably be calculated but a lookup gives fine control
$aZoomRank = array(
0 => 2, // Continent / Sea
1 => 2,
2 => 2,
3 => 4, // Country
4 => 4,
5 => 8, // State
6 => 10, // Region
7 => 10,
8 => 12, // County
9 => 12,
10 => 17, // City
11 => 17,
12 => 18, // Town / Village
13 => 18,
14 => 22, // Suburb
15 => 22,
16 => 26, // Street, TODO: major street?
17 => 26,
18 => 30, // or >, Building
19 => 30, // or >, Building
);
$this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
}
function setIncludePolygonAsPoints($b = true)
{
$this->bIncludePolygonAsPoints = $b;
}
function getIncludePolygonAsPoints()
{
return $this->bIncludePolygonAsPoints;
}
function setIncludePolygonAsText($b = true)
{
$this->bIncludePolygonAsText = $b;
}
function getIncludePolygonAsText()
{
return $this->bIncludePolygonAsText;
}
function setIncludePolygonAsGeoJSON($b = true)
{
$this->bIncludePolygonAsGeoJSON = $b;
}
function setIncludePolygonAsKML($b = true)
{
$this->bIncludePolygonAsKML = $b;
}
function setIncludePolygonAsSVG($b = true)
{
$this->bIncludePolygonAsSVG = $b;
}
function setPolygonSimplificationThreshold($f)
{
$this->fPolygonSimplificationThreshold = $f;
}
// returns { place_id =>, type => '(osm|tiger)' }
// fails if no place was found
function lookup()
{
$sPointSQL = 'ST_SetSRID(ST_Point('.$this->fLon.','.$this->fLat.'),4326)';
$iMaxRank = $this->iMaxRank;
$iMaxRank_orig = $this->iMaxRank;
// Find the nearest point
$fSearchDiam = 0.0004;
$iPlaceID = null;
$aArea = false;
$fMaxAreaDistance = 1;
$bIsInUnitedStates = false;
$bPlaceIsTiger = false;
while(!$iPlaceID && $fSearchDiam < $fMaxAreaDistance)
{
$fSearchDiam = $fSearchDiam * 2;
// If we have to expand the search area by a large amount then we need a larger feature
// then there is a limit to how small the feature should be
if ($fSearchDiam > 2 && $iMaxRank > 4) $iMaxRank = 4;
if ($fSearchDiam > 1 && $iMaxRank > 9) $iMaxRank = 8;
if ($fSearchDiam > 0.8 && $iMaxRank > 10) $iMaxRank = 10;
if ($fSearchDiam > 0.6 && $iMaxRank > 12) $iMaxRank = 12;
if ($fSearchDiam > 0.2 && $iMaxRank > 17) $iMaxRank = 17;
if ($fSearchDiam > 0.1 && $iMaxRank > 18) $iMaxRank = 18;
if ($fSearchDiam > 0.008 && $iMaxRank > 22) $iMaxRank = 22;
if ($fSearchDiam > 0.001 && $iMaxRank > 26) $iMaxRank = 26;
$sSQL = 'select place_id,parent_place_id,rank_search,calculated_country_code';
$sSQL .= ' FROM placex';
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
$sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
$sSQL .= ' and (name is not null or housenumber is not null)';
$sSQL .= ' and class not in (\'waterway\',\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
$sSQL .= ' and indexed_status = 0 ';
$sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
$sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
$sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', geometry) ASC limit 1';
if (CONST_Debug) var_dump($sSQL);
$aPlace = $this->oDB->getRow($sSQL);
if (PEAR::IsError($aPlace))
{
failInternalError("Could not determine closest place.", $sSQL, $aPlace);
}
$iPlaceID = $aPlace['place_id'];
$iParentPlaceID = $aPlace['parent_place_id'];
$bIsInUnitedStates = ($aPlace['calculated_country_code'] == 'us');
}
// Only street found? If it's in the US we can check TIGER data for nearest housenumber
if (CONST_Use_US_Tiger_Data && $bIsInUnitedStates && $iMaxRank_orig >= 28 && $iPlaceID && ($aPlace['rank_search'] == 26 || $aPlace['rank_search'] == 27 ))
{
$fSearchDiam = 0.001;
$sSQL = 'SELECT place_id,parent_place_id,30 as rank_search, ST_line_locate_point(linegeo,'.$sPointSQL.') as fraction';
//if (CONST_Debug) { $sSQL .= ', housenumber, ST_distance('.$sPointSQL.', centroid) as distance, st_y(centroid) as lat, st_x(centroid) as lon'; }
$sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$iPlaceID;
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')'; //no centroid anymore in Tiger data, now we have lines
$sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
if (CONST_Debug)
{
$sSQL = preg_replace('/limit 1/', 'limit 100', $sSQL);
var_dump($sSQL);
$aAllHouses = $this->oDB->getAll($sSQL);
foreach($aAllHouses as $i)
{
echo $i['housenumber'] . ' | ' . $i['distance'] * 1000 . ' | ' . $i['lat'] . ' | ' . $i['lon']. ' | '. "<br>\n";
}
}
$aPlaceTiger = $this->oDB->getRow($sSQL);
if (PEAR::IsError($aPlace))
{
failInternalError("Could not determine closest Tiger place.", $sSQL, $aPlaceTiger);
}
if ($aPlaceTiger)
{
if (CONST_Debug) var_dump('found Tiger place', $aPlaceTiger);
$bPlaceIsTiger = true;
$aPlace = $aPlaceTiger;
$iPlaceID = $aPlaceTiger['place_id'];
$iParentPlaceID = $aPlaceTiger['parent_place_id']; // the street
$iFraction = $aPlaceTiger['fraction'];
}
}
// The point we found might be too small - use the address to find what it is a child of
if ($iPlaceID && $iMaxRank < 28)
{
if ($aPlace['rank_search'] > 28 && $iParentPlaceID && !$bPlaceIsTiger)
{
$iPlaceID = $iParentPlaceID;
}
$sSQL = 'select address_place_id';
$sSQL .= ' FROM place_addressline';
$sSQL .= " WHERE place_id = $iPlaceID";
$sSQL .= " ORDER BY abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc";
$sSQL .= ' LIMIT 1';
$iPlaceID = $this->oDB->getOne($sSQL);
if (PEAR::IsError($iPlaceID))
{
failInternalError("Could not get parent for place.", $sSQL, $iPlaceID);
}
if (!$iPlaceID)
{
$iPlaceID = $aPlace['place_id'];
}
}
return array('place_id' => $iPlaceID,
'type' => $bPlaceIsTiger ? 'tiger' : 'osm',
'fraction' => $bPlaceIsTiger ? $iFraction : -1);
}
}
?>