X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/02f6afa51bb9f25e562853d87d8eccaeaa3f0b6a..3cd85eaaf16f740bb984436184b37a84a94b2553:/lib-php/SearchDescription.php diff --git a/lib-php/SearchDescription.php b/lib-php/SearchDescription.php index 8da49a9f..b4a78eb8 100644 --- a/lib-php/SearchDescription.php +++ b/lib-php/SearchDescription.php @@ -152,23 +152,17 @@ class SearchDescription /** * 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) { @@ -177,19 +171,19 @@ class SearchDescription $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; @@ -202,7 +196,7 @@ class SearchDescription // 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++; @@ -214,34 +208,37 @@ class SearchDescription $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->iNamePhrase = -1; - $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 @@ -249,14 +246,14 @@ class SearchDescription && 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) { @@ -272,7 +269,7 @@ class SearchDescription $iOp = Operator::NEAR; } $oSearch->iSearchRank += 2; - } elseif (!$bFirstToken && !$bLastToken) { + } elseif (!$oPosition->isFirstToken() && !$oPosition->isLastToken()) { $oSearch->iSearchRank += 2; } if ($this->sHouseNumber) { @@ -286,7 +283,7 @@ class SearchDescription ); $aNewSearches[] = $oSearch; } - } elseif ($sPhraseType != 'country' + } elseif (!$oPosition->isPhrase('country') && is_a($oSearchTerm, '\Nominatim\Token\Word') ) { $iWordID = $oSearchTerm->iId; @@ -294,11 +291,13 @@ class SearchDescription // 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->iNamePhrase = -1; - $oSearch->iSearchRank += 3 * $oSearchTerm->iTermCount; + $oSearch->iSearchRank += 1; $oSearch->aAddress[$iWordID] = $iWordID; $aNewSearches[] = $oSearch; } @@ -313,6 +312,13 @@ class SearchDescription } $aNewSearches[] = $oSearch; } + } elseif (!$oPosition->isPhrase('country') + && is_a($oSearchTerm, '\Nominatim\Token\Partial') + ) { + $aNewSearches = $this->extendWithPartialTerm( + $oSearchTerm, + $oPosition + ); } return $aNewSearches; @@ -321,55 +327,43 @@ class SearchDescription /** * 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')) - || strpos($sToken, ' ') !== false - ) { - 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) { @@ -384,12 +378,9 @@ class SearchDescription } $oSearch->aName[$iWordID] = $iWordID; } else { - if (!empty($aFullTokens)) { - $oSearch->iSearchRank++; - } $oSearch->aNameNonSearch[$iWordID] = $iWordID; } - $oSearch->iNamePhrase = $iPhrase; + $oSearch->iNamePhrase = $oPosition->getPhrase(); $aNewSearches[] = $oSearch; } @@ -414,7 +405,6 @@ class SearchDescription public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit) { $aResults = array(); - $iHousenumber = -1; if ($this->sCountryCode && empty($this->aName) @@ -447,23 +437,24 @@ class SearchDescription // 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 @@ -743,16 +734,33 @@ class SearchDescription 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 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); @@ -764,11 +772,11 @@ class SearchDescription $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. @@ -792,9 +800,9 @@ class SearchDescription } // 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 {