if ($this->bIncludeNameDetails) $sSQL .= "name, ";
$sSQL .= " extratags->'place' ";
+ // postcode table
+ $sSQL .= "UNION ";
+ $sSQL .= "SELECT";
+ $sSQL .= " 'P' as osm_type,";
+ $sSQL .= " (SELECT osm_id from placex p WHERE p.place_id = parent_place_id) as osm_id,";
+ $sSQL .= " 'place' as class, 'postcode' as type,";
+ $sSQL .= " null as admin_level, rank_search, rank_address,";
+ $sSQL .= " place_id, parent_place_id, country_code,";
+ $sSQL .= " get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) AS langaddress,";
+ $sSQL .= " postcode as placename,";
+ $sSQL .= " postcode as ref,";
+ if ($this->bIncludeExtraTags) $sSQL .= "null AS extra,";
+ if ($this->bIncludeNameDetails) $sSQL .= "null AS names,";
+ $sSQL .= " ST_x(st_centroid(geometry)) AS lon, ST_y(st_centroid(geometry)) AS lat,";
+ $sSQL .= $sImportanceSQL."(0.75-(rank_search::float/40)) AS importance, ";
+ $sSQL .= " (";
+ $sSQL .= " SELECT max(p.importance*(p.rank_address+2))";
+ $sSQL .= " FROM ";
+ $sSQL .= " place_addressline s, ";
+ $sSQL .= " placex p";
+ $sSQL .= " WHERE s.place_id = parent_place_id";
+ $sSQL .= " AND p.place_id = s.address_place_id ";
+ $sSQL .= " AND s.isaddress";
+ $sSQL .= " AND p.importance is not null";
+ $sSQL .= " ) AS addressimportance, ";
+ $sSQL .= " null AS extra_place ";
+ $sSQL .= "FROM location_postcode";
+ $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
+
if (30 >= $this->iMinAddressRank && 30 <= $this->iMaxAddressRank) {
// only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines
// with start- and endnumber, the common osm housenumbers are usually saved as points
return $aSearchResults;
}
- public function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases)
+ public function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases, $sNormQuery)
{
/*
Calculate all searches using aValidTokens i.e.
Score how good the search is so they can be ordered
*/
- foreach ($aPhrases as $iPhrase => $sPhrase) {
+ foreach ($aPhrases as $iPhrase => $aPhrase) {
$aNewPhraseSearches = array();
if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase];
else $sPhraseType = '';
- foreach ($aPhrases[$iPhrase]['wordsets'] as $iWordSet => $aWordset) {
+ foreach ($aPhrase['wordsets'] as $iWordSet => $aWordset) {
// Too many permutations - too expensive
if ($iWordSet > 120) break;
);
if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch;
}
- } elseif ($sPhraseType == 'postalcode') {
+ } elseif ($sPhraseType == 'postalcode' || ($aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'postcode')) {
// We need to try the case where the postal code is the primary element (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) so try both
if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) {
- // If we already have a name try putting the postcode first
- if (sizeof($aSearch['aName'])) {
+ // If we have structured search or this is the first term,
+ // make the postcode the primary search element.
+ if ($aSearchTerm['operator'] == '' && ($sPhraseType == 'postalcode' || sizeof($aSearch['aName']) == 0)) {
$aNewSearch = $aSearch;
+ $aNewSearch['sOperator'] = 'postcode';
$aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']);
- $aNewSearch['aName'] = array();
- $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
+ $aNewSearch['aName'][$aSearchTerm['word_id']] = substr($aSearchTerm['word_token'], 1);
if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aNewSearch;
}
- if (sizeof($aSearch['aName'])) {
- if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) {
- $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
- } else {
- $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
- $aSearch['iSearchRank'] += 1000; // skip;
- }
- } else {
- $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id'];
- //$aSearch['iNamePhrase'] = $iPhrase;
+ // If we have a structured search or this is not the first term,
+ // add the postcode as an addendum.
+ if ($sPhraseType == 'postalcode' || sizeof($aSearch['aName'])) {
+ $aSearch['sPostcode'] = $aSearchTerm['word_token'];
+ if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch;
}
- if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch;
}
} elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') {
if ($aSearch['sHouseNumber'] === '') {
*/
}
} elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) {
- if ($aSearch['sClass'] === '') {
- $aSearch['sOperator'] = $aSearchTerm['operator'];
+ // require a normalized exact match of the term
+ // if we have the normalizer version of the query
+ // available
+ if ($aSearch['sClass'] === ''
+ && ($sNormQuery === null || !($aSearchTerm['word'] && strpos($sNormQuery, $aSearchTerm['word']) === false))) {
$aSearch['sClass'] = $aSearchTerm['class'];
$aSearch['sType'] = $aSearchTerm['type'];
- if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name';
- else $aSearch['sOperator'] = 'near'; // near = in for the moment
- if (strlen($aSearchTerm['operator']) == 0) $aSearch['iSearchRank'] += 1;
+ if ($aSearchTerm['operator'] == '') {
+ $aSearch['sOperator'] = sizeof($aSearch['aName']) ? 'name' : 'near';
+ $aSearch['iSearchRank'] += 2;
+ } else {
+ $aSearch['sOperator'] = 'near'; // near = in for the moment
+ }
if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch;
}
{
if (!$this->sQuery && !$this->aStructuredQuery) return array();
+ $oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
+ if ($oNormalizer !== null) {
+ $sNormQuery = $oNormalizer->transliterate($this->sQuery);
+ } else {
+ $sNormQuery = null;
+ }
+
$sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]";
$sCountryCodesSQL = false;
if ($this->aCountryCodes) {
'sClass' => '',
'sType' => '',
'sHouseNumber' => '',
+ 'sPostcode' => '',
'oNear' => $oNearPoint
)
);
// array with: placeid => -1 | tiger-housenumber
$aResultPlaceIDs = array();
- $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases);
+ $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases, $sNormQuery);
if ($this->bReverseInPlan) {
// Reverse phrase array and also reverse the order of the wordsets in
$aFinalPhrase = end($aPhrases);
$aPhrases[sizeof($aPhrases)-1]['wordsets'] = getInverseWordSets($aFinalPhrase['words'], 0);
}
- $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false);
+ $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false, $sNormQuery);
foreach ($aGroupedSearches as $aSearches) {
foreach ($aSearches as $aSearch) {
// If a coordinate is given, the search must either
// be for a name or a special search. Ignore everythin else.
$aPlaceIDs = array();
+ } elseif ($aSearch['sOperator'] == 'postcode') {
+ $sSQL = "SELECT p.place_id FROM location_postcode p ";
+ if (sizeof($aSearch['aAddress'])) {
+ $sSQL .= ", search_name s ";
+ $sSQL .= "WHERE s.place_id = p.parent_place_id ";
+ $sSQL .= "AND array_cat(s.nameaddress_vector, s.name_vector) @> ARRAY[".join($aSearch['aAddress'], ",")."] AND ";
+ } else {
+ $sSQL .= " WHERE ";
+ }
+ $sSQL .= "p.postcode = '".pg_escape_string(reset($aSearch['aName']))."'";
+ if ($aSearch['sCountryCode']) {
+ $sSQL .= " AND p.country_code = '".$aSearch['sCountryCode']."'";
+ }
+ if ($sCountryCodesSQL) {
+ $sSQL .= " AND p.country_code in ($sCountryCodesSQL)";
+ }
+ $sSQL .= " LIMIT $this->iLimit";
+ if (CONST_Debug) var_dump($sSQL);
+ $aPlaceIDs = chksql($this->oDB->getCol($sSQL));
} else {
$aPlaceIDs = array();
}
if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') { // & in
+ $sClassTable = 'place_classtype_'.$aSearch['sClass'].'_'.$aSearch['sType'];
$sSQL = "SELECT count(*) FROM pg_tables ";
- $sSQL .= "WHERE tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'";
+ $sSQL .= "WHERE tablename = '$sClassTable'";
$bCacheTable = chksql($this->oDB->getOne($sSQL));
$sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
$sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
}
- $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l";
+ $sSQL = "select distinct i.place_id".($sOrderBySQL?', i.order_term':'')." from (";
+ $sSQL .= "select l.place_id".($sOrderBySQL?','.$sOrderBySQL.' as order_term':'')." from ".$sClassTable." as l";
if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)";
if ($sPlaceIDs) {
$sSQL .= ",placex as f where ";
$sSQL .= " and l.place_id not in (".join(',', $this->aExcludePlaceIDs).")";
}
if ($sCountryCodesSQL) $sSQL .= " and lp.country_code in ($sCountryCodesSQL)";
- if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc";
+ $sSQL .= 'limit 300) i ';
+ if ($sOrderBySQL) $sSQL .= "order by order_term asc";
if ($this->iOffset) $sSQL .= " offset $this->iOffset";
$sSQL .= " limit $this->iLimit";
if (CONST_Debug) var_dump($sSQL);