/**
* Derive new searches by adding a full term to the existing search.
*
- * @param object $oSearchTerm Description of the token.
- * @param bool $bHasPartial True if there are also tokens of partial terms
- * with the same name.
- * @param string $sPhraseType Type of phrase the token is contained in.
- * @param bool $bFirstToken True if the token is at the beginning of the
- * query.
- * @param bool $bFirstPhrase True if the token is in the first phrase of
- * the query.
- * @param bool $bLastToken True if the token is at the end of the query.
+ * @param object $oSearchTerm Description of the token.
+ * @param object $oPosition Description of the token position within
+ the query.
*
* @return SearchDescription[] List of derived search descriptions.
*/
- public function extendWithFullTerm($oSearchTerm, $bHasPartial, $sPhraseType, $bFirstToken, $bFirstPhrase, $bLastToken)
+ public function extendWithSearchTerm($oSearchTerm, $oPosition)
{
$aNewSearches = array();
- if (($sPhraseType == '' || $sPhraseType == 'country')
+ if ($oPosition->maybePhrase('country')
&& is_a($oSearchTerm, '\Nominatim\Token\Country')
) {
if (!$this->sCountryCode) {
$oSearch->sCountryCode = $oSearchTerm->sCountryCode;
// Country is almost always at the end of the string
// - increase score for finding it anywhere else (optimisation)
- if (!$bLastToken) {
+ if (!$oPosition->isLastToken()) {
$oSearch->iSearchRank += 5;
+ $oSearch->iNamePhrase = -1;
}
$aNewSearches[] = $oSearch;
}
- } elseif (($sPhraseType == '' || $sPhraseType == 'postalcode')
+ } elseif ($oPosition->maybePhrase('postalcode')
&& is_a($oSearchTerm, '\Nominatim\Token\Postcode')
) {
if (!$this->sPostcode) {
// If we have structured search or this is the first term,
// make the postcode the primary search element.
- if ($this->iOperator == Operator::NONE && $bFirstToken) {
+ if ($this->iOperator == Operator::NONE && $oPosition->isFirstToken()) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->iOperator = Operator::POSTCODE;
// If we have a structured search or this is not the first term,
// add the postcode as an addendum.
if ($this->iOperator != Operator::POSTCODE
- && ($sPhraseType == 'postalcode' || !empty($this->aName))
+ && ($oPosition->isPhrase('postalcode') || !empty($this->aName))
) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
+ $oSearch->iNamePhrase = -1;
if (strlen($oSearchTerm->sPostcode) < 4) {
$oSearch->iSearchRank += 4 - strlen($oSearchTerm->sPostcode);
}
$aNewSearches[] = $oSearch;
}
}
- } elseif (($sPhraseType == '' || $sPhraseType == 'street')
+ } elseif ($oPosition->maybePhrase('street')
&& is_a($oSearchTerm, '\Nominatim\Token\HouseNumber')
) {
if (!$this->sHouseNumber && $this->iOperator != Operator::POSTCODE) {
- $oSearch = clone $this;
- $oSearch->iSearchRank++;
- $oSearch->sHouseNumber = $oSearchTerm->sToken;
- if ($this->iOperator != Operator::NONE) {
- $oSearch->iSearchRank++;
- }
// sanity check: if the housenumber is not mainly made
// up of numbers, add a penalty
- if (preg_match('/\\d/', $oSearch->sHouseNumber) === 0
- || preg_match_all('/[^0-9]/', $oSearch->sHouseNumber, $aMatches) > 2) {
- $oSearch->iSearchRank++;
+ $iSearchCost = 1;
+ if (preg_match('/\\d/', $oSearchTerm->sToken) === 0
+ || preg_match_all('/[^0-9]/', $oSearchTerm->sToken, $aMatches) > 2) {
+ $iSearchCost++;
+ }
+ if ($this->iOperator != Operator::NONE) {
+ $iSearchCost++;
}
if (empty($oSearchTerm->iId)) {
- $oSearch->iSearchRank++;
+ $iSearchCost++;
}
// also must not appear in the middle of the address
if (!empty($this->aAddress)
|| (!empty($this->aAddressNonSearch))
|| $this->sPostcode
) {
- $oSearch->iSearchRank++;
+ $iSearchCost++;
}
+
+ $oSearch = clone $this;
+ $oSearch->iSearchRank += $iSearchCost;
+ $oSearch->iNamePhrase = -1;
+ $oSearch->sHouseNumber = $oSearchTerm->sToken;
$aNewSearches[] = $oSearch;
+
// Housenumbers may appear in the name when the place has its own
// address terms.
if ($oSearchTerm->iId !== null
&& empty($this->aAddress)
) {
$oSearch = clone $this;
- $oSearch->iSearchRank++;
+ $oSearch->iSearchRank += $iSearchCost;
$oSearch->aAddress = $this->aName;
$oSearch->bRareName = false;
$oSearch->aName = array($oSearchTerm->iId => $oSearchTerm->iId);
$aNewSearches[] = $oSearch;
}
}
- } elseif ($sPhraseType == ''
+ } elseif ($oPosition->isPhrase('')
&& is_a($oSearchTerm, '\Nominatim\Token\SpecialTerm')
) {
if ($this->iOperator == Operator::NONE) {
$oSearch = clone $this;
$oSearch->iSearchRank += 2;
+ $oSearch->iNamePhrase = -1;
$iOp = $oSearchTerm->iOperator;
if ($iOp == Operator::NONE) {
$iOp = Operator::NEAR;
}
$oSearch->iSearchRank += 2;
- } else if (!$bFirstToken && !$bLastToken) {
+ } elseif (!$oPosition->isFirstToken() && !$oPosition->isLastToken()) {
$oSearch->iSearchRank += 2;
}
if ($this->sHouseNumber) {
);
$aNewSearches[] = $oSearch;
}
- } elseif ($sPhraseType != 'country'
+ } elseif (!$oPosition->isPhrase('country')
&& is_a($oSearchTerm, '\Nominatim\Token\Word')
) {
$iWordID = $oSearchTerm->iId;
// of the phrase. In structured search the name must forcably in
// the first phrase. In unstructured search it may be in a later
// phrase when the first phrase is a house number.
- if (!empty($this->aName) || !($bFirstPhrase || $sPhraseType == '')) {
- if (($sPhraseType == '' || !$bFirstPhrase) && !$bHasPartial) {
+ if (!empty($this->aName) || !($oPosition->isFirstPhrase() || $oPosition->isPhrase(''))) {
+ if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
+ && $oSearchTerm->iTermCount > 1
+ ) {
$oSearch = clone $this;
- $oSearch->iSearchRank += 3 * $oSearchTerm->iTermCount;
+ $oSearch->iNamePhrase = -1;
+ $oSearch->iSearchRank += 1;
$oSearch->aAddress[$iWordID] = $iWordID;
$aNewSearches[] = $oSearch;
}
- } else if (empty($this->aNameNonSearch)) {
+ } elseif (empty($this->aNameNonSearch)) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->aName = array($iWordID => $iWordID);
}
$aNewSearches[] = $oSearch;
}
+ } elseif (!$oPosition->isPhrase('country')
+ && is_a($oSearchTerm, '\Nominatim\Token\Partial')
+ ) {
+ $aNewSearches = $this->extendWithPartialTerm(
+ $oSearchTerm,
+ $oPosition
+ );
}
return $aNewSearches;
/**
* Derive new searches by adding a partial term to the existing search.
*
- * @param string $sToken Term for the token.
- * @param object $oSearchTerm Description of the token.
- * @param bool $bStructuredPhrases True if the search is structured.
- * @param integer $iPhrase Number of the phrase the token is in.
- * @param array[] $aFullTokens List of full term tokens with the
- * same name.
+ * @param object $oSearchTerm Description of the token.
+ * @param object $oPosition Description of the token position within
+ the query.
*
* @return SearchDescription[] List of derived search descriptions.
*/
- public function extendWithPartialTerm($sToken, $oSearchTerm, $bStructuredPhrases, $iPhrase, $aFullTokens)
+ private function extendWithPartialTerm($oSearchTerm, $oPosition)
{
- // Only allow name terms.
- if (!(is_a($oSearchTerm, '\Nominatim\Token\Word'))) {
- return array();
- }
-
$aNewSearches = array();
$iWordID = $oSearchTerm->iId;
- if ((!$bStructuredPhrases || $iPhrase > 0)
+ if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
&& (!empty($this->aName))
) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
- if (preg_match('#^[0-9 ]+$#', $sToken)) {
+ if (preg_match('#^[0-9 ]+$#', $oSearchTerm->sToken)) {
$oSearch->iSearchRank++;
}
if ($oSearchTerm->iSearchNameCount < CONST_Max_Word_Frequency) {
$oSearch->aAddress[$iWordID] = $iWordID;
} else {
$oSearch->aAddressNonSearch[$iWordID] = $iWordID;
- if (!empty($aFullTokens)) {
- $oSearch->iSearchRank++;
- }
}
$aNewSearches[] = $oSearch;
}
if ((!$this->sPostcode && !$this->aAddress && !$this->aAddressNonSearch)
- && ((empty($this->aName) && empty($this->aNameNonSearch)) || $this->iNamePhrase == $iPhrase)
+ && ((empty($this->aName) && empty($this->aNameNonSearch))
+ || $this->iNamePhrase == $oPosition->getPhrase())
) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
if (empty($this->aName) && empty($this->aNameNonSearch)) {
$oSearch->iSearchRank++;
}
- if (preg_match('#^[0-9 ]+$#', $sToken)) {
+ if (preg_match('#^[0-9 ]+$#', $oSearchTerm->sToken)) {
$oSearch->iSearchRank++;
}
if ($oSearchTerm->iSearchNameCount < CONST_Max_Word_Frequency) {
}
$oSearch->aName[$iWordID] = $iWordID;
} else {
- if (!empty($aFullTokens)) {
- $oSearch->iSearchRank++;
- }
$oSearch->aNameNonSearch[$iWordID] = $iWordID;
}
- $oSearch->iNamePhrase = $iPhrase;
+ $oSearch->iNamePhrase = $oPosition->getPhrase();
$aNewSearches[] = $oSearch;
}
public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit)
{
$aResults = array();
- $iHousenumber = -1;
if ($this->sCountryCode
&& empty($this->aName)
// Now search for housenumber, if housenumber provided. Can be zero.
if (($this->sHouseNumber || $this->sHouseNumber === '0') && !empty($aResults)) {
+ $aHnResults = $this->queryHouseNumber($oDB, $aResults);
+
// Downgrade the rank of the street results, they are missing
- // the housenumber.
+ // the housenumber. Also drop POI places (rank 30) here, they
+ // cannot be a parent place and therefore must not be shown
+ // as a result for a search with a missing housenumber.
foreach ($aResults as $oRes) {
- if ($oRes->iAddressRank >= 26) {
- $oRes->iResultRank++;
- } else {
- $oRes->iResultRank += 2;
+ if ($oRes->iAddressRank < 28) {
+ if ($oRes->iAddressRank >= 26) {
+ $oRes->iResultRank++;
+ } else {
+ $oRes->iResultRank += 2;
+ }
+ $aHnResults[$oRes->iId] = $oRes;
}
}
- $aHnResults = $this->queryHouseNumber($oDB, $aResults);
-
- if (!empty($aHnResults)) {
- foreach ($aHnResults as $oRes) {
- $aResults[$oRes->iId] = $oRes;
- }
- }
+ $aResults = $aHnResults;
}
// finally get POIs if requested
// too many results are expected for the street, i.e. if the result
// will be narrowed down by an address. Remeber that with ordering
// every single result has to be checked.
- if ($this->sHouseNumber && (!empty($this->aAddress) || $this->sPostcode)) {
+ if ($this->sHouseNumber && ($this->bRareName || !empty($this->aAddress) || $this->sPostcode)) {
$sHouseNumberRegex = '\\\\m'.$this->sHouseNumber.'\\\\M';
$aOrder[] = ' (';
$aOrder[0] .= 'EXISTS(';
$aOrder[0] .= ' SELECT place_id';
$aOrder[0] .= ' FROM placex';
$aOrder[0] .= ' WHERE parent_place_id = search_name.place_id';
- $aOrder[0] .= " AND transliteration(housenumber) ~* E'".$sHouseNumberRegex."'";
+ $aOrder[0] .= " AND housenumber ~* E'".$sHouseNumberRegex."'";
$aOrder[0] .= ' LIMIT 1';
$aOrder[0] .= ') ';
// also housenumbers from interpolation lines table are needed
private function queryHouseNumber(&$oDB, $aRoadPlaceIDs)
{
$aResults = array();
- $sPlaceIDs = Result::joinIdsByTable($aRoadPlaceIDs, Result::TABLE_PLACEX);
+ $sRoadPlaceIDs = Result::joinIdsByTableMaxRank(
+ $aRoadPlaceIDs,
+ Result::TABLE_PLACEX,
+ 27
+ );
+ $sPOIPlaceIDs = Result::joinIdsByTableMinRank(
+ $aRoadPlaceIDs,
+ Result::TABLE_PLACEX,
+ 30
+ );
- if (!$sPlaceIDs) {
+ $aIDCondition = array();
+ if ($sRoadPlaceIDs) {
+ $aIDCondition[] = 'parent_place_id in ('.$sRoadPlaceIDs.')';
+ }
+ if ($sPOIPlaceIDs) {
+ $aIDCondition[] = 'place_id in ('.$sPOIPlaceIDs.')';
+ }
+
+ if (empty($aIDCondition)) {
return $aResults;
}
$sHouseNumberRegex = '\\\\m'.$this->sHouseNumber.'\\\\M';
- $sSQL = 'SELECT place_id FROM placex ';
- $sSQL .= 'WHERE parent_place_id in ('.$sPlaceIDs.')';
- $sSQL .= " AND transliteration(housenumber) ~* E'".$sHouseNumberRegex."'";
+ $sSQL = 'SELECT place_id FROM placex WHERE';
+ $sSQL .= " housenumber ~* E'".$sHouseNumberRegex."'";
+ $sSQL .= ' AND ('.join(' OR ', $aIDCondition).')';
$sSQL .= $this->oContext->excludeSQL(' AND place_id');
Debug::printSQL($sSQL);
$bIsIntHouseNumber= (bool) preg_match('/[0-9]+/', $this->sHouseNumber);
$iHousenumber = intval($this->sHouseNumber);
- if ($bIsIntHouseNumber && empty($aResults)) {
+ if ($bIsIntHouseNumber && $sRoadPlaceIDs && empty($aResults)) {
// if nothing found, search in the interpolation line table
$sSQL = 'SELECT distinct place_id FROM location_property_osmline';
$sSQL .= ' WHERE startnumber is not NULL';
- $sSQL .= ' AND parent_place_id in ('.$sPlaceIDs.') AND (';
+ $sSQL .= ' AND parent_place_id in ('.$sRoadPlaceIDs.') AND (';
if ($iHousenumber % 2 == 0) {
// If housenumber is even, look for housenumber in streets
// with interpolationtype even or all.
}
}
- // If nothing found try the aux fallback table
- if (CONST_Use_Aux_Location_data && empty($aResults)) {
- $sSQL = 'SELECT place_id FROM location_property_aux';
- $sSQL .= ' WHERE parent_place_id in ('.$sPlaceIDs.')';
- $sSQL .= " AND housenumber = '".$this->sHouseNumber."'";
- $sSQL .= $this->oContext->excludeSQL(' AND place_id');
-
- Debug::printSQL($sSQL);
-
- foreach ($oDB->getCol($sSQL) as $iPlaceId) {
- $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_AUX);
- }
- }
-
// If nothing found then search in Tiger data (location_property_tiger)
- if (CONST_Use_US_Tiger_Data && $bIsIntHouseNumber && empty($aResults)) {
+ if (CONST_Use_US_Tiger_Data && $sRoadPlaceIDs && $bIsIntHouseNumber && empty($aResults)) {
$sSQL = 'SELECT place_id FROM location_property_tiger';
- $sSQL .= ' WHERE parent_place_id in ('.$sPlaceIDs.') and (';
+ $sSQL .= ' WHERE parent_place_id in ('.$sRoadPlaceIDs.') and (';
if ($iHousenumber % 2 == 0) {
$sSQL .= "interpolationtype='even'";
} else {