oDB =& $oDB;
        $this->oPlaceLookup = new PlaceLookup($this->oDB);
        $this->oTokenizer = new \Nominatim\Tokenizer($this->oDB);
    }
    public function setLanguagePreference($aLangPref)
    {
        $this->aLangPrefOrder = $aLangPref;
    }
    public function getMoreUrlParams()
    {
        if ($this->aStructuredQuery) {
            $aParams = $this->aStructuredQuery;
        } else {
            $aParams = array('q' => $this->sQuery);
        }
        $aParams = array_merge($aParams, $this->oPlaceLookup->getMoreUrlParams());
        if ($this->aExcludePlaceIDs) {
            $aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
        }
        if ($this->bBoundedSearch) $aParams['bounded'] = '1';
        if ($this->aCountryCodes) {
            $aParams['countrycodes'] = implode(',', $this->aCountryCodes);
        }
        if ($this->aViewBox) {
            $aParams['viewbox'] = join(',', $this->aViewBox);
        }
        return $aParams;
    }
    public function setLimit($iLimit = 10)
    {
        if ($iLimit > 50) $iLimit = 50;
        if ($iLimit < 1) $iLimit = 1;
        $this->iFinalLimit = $iLimit;
        $this->iLimit = $iLimit + min($iLimit, 10);
    }
    public function setFeatureType($sFeatureType)
    {
        switch ($sFeatureType) {
            case 'country':
                $this->setRankRange(4, 4);
                break;
            case 'state':
                $this->setRankRange(8, 8);
                break;
            case 'city':
                $this->setRankRange(14, 16);
                break;
            case 'settlement':
                $this->setRankRange(8, 20);
                break;
        }
    }
    public function setRankRange($iMin, $iMax)
    {
        $this->iMinAddressRank = $iMin;
        $this->iMaxAddressRank = $iMax;
    }
    public function setViewbox($aViewbox)
    {
        $aBox = array_map('floatval', $aViewbox);
        $this->aViewBox[0] = max(-180.0, min($aBox[0], $aBox[2]));
        $this->aViewBox[1] = max(-90.0, min($aBox[1], $aBox[3]));
        $this->aViewBox[2] = min(180.0, max($aBox[0], $aBox[2]));
        $this->aViewBox[3] = min(90.0, max($aBox[1], $aBox[3]));
        if ($this->aViewBox[2] - $this->aViewBox[0] < 0.000000001
            || $this->aViewBox[3] - $this->aViewBox[1] < 0.000000001
        ) {
            userError("Bad parameter 'viewbox'. Not a box.");
        }
    }
    private function viewboxImportanceFactor($fX, $fY)
    {
        if (!$this->aViewBox) {
            return 1;
        }
        $fWidth = ($this->aViewBox[2] - $this->aViewBox[0])/2;
        $fHeight = ($this->aViewBox[3] - $this->aViewBox[1])/2;
        $fXDist = abs($fX - ($this->aViewBox[0] + $this->aViewBox[2])/2);
        $fYDist = abs($fY - ($this->aViewBox[1] + $this->aViewBox[3])/2);
        if ($fXDist <= $fWidth && $fYDist <= $fHeight) {
            return 1;
        }
        if ($fXDist <= $fWidth * 3 && $fYDist <= 3 * $fHeight) {
            return 0.5;
        }
        return 0.25;
    }
    public function setQuery($sQueryString)
    {
        $this->sQuery = $sQueryString;
        $this->aStructuredQuery = false;
    }
    public function getQueryString()
    {
        return $this->sQuery;
    }
    public function loadParamArray($oParams, $sForceGeometryType = null)
    {
        $this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch);
        $this->setLimit($oParams->getInt('limit', $this->iFinalLimit));
        $this->iOffset = $oParams->getInt('offset', $this->iOffset);
        $this->bFallback = $oParams->getBool('fallback', $this->bFallback);
        // List of excluded Place IDs - used for more acurate pageing
        $sExcluded = $oParams->getStringList('exclude_place_ids');
        if ($sExcluded) {
            foreach ($sExcluded as $iExcludedPlaceID) {
                $iExcludedPlaceID = (int)$iExcludedPlaceID;
                if ($iExcludedPlaceID)
                    $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
            }
            if (isset($aExcludePlaceIDs))
                $this->aExcludePlaceIDs = $aExcludePlaceIDs;
        }
        // Only certain ranks of feature
        $sFeatureType = $oParams->getString('featureType');
        if (!$sFeatureType) $sFeatureType = $oParams->getString('featuretype');
        if ($sFeatureType) $this->setFeatureType($sFeatureType);
        // Country code list
        $sCountries = $oParams->getStringList('countrycodes');
        if ($sCountries) {
            foreach ($sCountries as $sCountryCode) {
                if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) {
                    $aCountries[] = strtolower($sCountryCode);
                }
            }
            if (isset($aCountries))
                $this->aCountryCodes = $aCountries;
        }
        $aViewbox = $oParams->getStringList('viewboxlbrt');
        if ($aViewbox) {
            if (count($aViewbox) != 4) {
                userError("Bad parameter 'viewboxlbrt'. Expected 4 coordinates.");
            }
            $this->setViewbox($aViewbox);
        } else {
            $aViewbox = $oParams->getStringList('viewbox');
            if ($aViewbox) {
                if (count($aViewbox) != 4) {
                    userError("Bad parameter 'viewbox'. Expected 4 coordinates.");
                }
                $this->setViewBox($aViewbox);
            } else {
                $aRoute = $oParams->getStringList('route');
                $fRouteWidth = $oParams->getFloat('routewidth');
                if ($aRoute && $fRouteWidth) {
                    $this->aRoutePoints = $aRoute;
                    $this->aRouteWidth = $fRouteWidth;
                }
            }
        }
        $this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
        $this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
    }
    public function setQueryFromParams($oParams)
    {
        // Search query
        $sQuery = $oParams->getString('q');
        if (!$sQuery) {
            $this->setStructuredQuery(
                $oParams->getString('amenity'),
                $oParams->getString('street'),
                $oParams->getString('city'),
                $oParams->getString('county'),
                $oParams->getString('state'),
                $oParams->getString('country'),
                $oParams->getString('postalcode')
            );
        } else {
            $this->setQuery($sQuery);
        }
    }
    public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
    {
        $sValue = trim($sValue);
        if (!$sValue) return false;
        $this->aStructuredQuery[$sKey] = $sValue;
        if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
            $this->iMinAddressRank = $iNewMinAddressRank;
            $this->iMaxAddressRank = $iNewMaxAddressRank;
        }
        if ($aItemListValues) $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
        return true;
    }
    public function setStructuredQuery($sAmenity = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false)
    {
        $this->sQuery = false;
        // Reset
        $this->iMinAddressRank = 0;
        $this->iMaxAddressRank = 30;
        $this->aAddressRankList = array();
        $this->aStructuredQuery = array();
        $this->sAllowedTypesSQLList = false;
        $this->loadStructuredAddressElement($sAmenity, 'amenity', 26, 30, false);
        $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false);
        $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false);
        $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false);
        $this->loadStructuredAddressElement($sState, 'state', 8, 8, false);
        $this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11));
        $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false);
        if (!empty($this->aStructuredQuery)) {
            $this->sQuery = join(', ', $this->aStructuredQuery);
            if ($this->iMaxAddressRank < 30) {
                $this->sAllowedTypesSQLList = '(\'place\',\'boundary\')';
            }
        }
    }
    public function fallbackStructuredQuery()
    {
        if (!$this->aStructuredQuery) return false;
        $aParams = $this->aStructuredQuery;
        if (count($aParams) == 1) return false;
        $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
        foreach ($aOrderToFallback as $sType) {
            if (isset($aParams[$sType])) {
                unset($aParams[$sType]);
                $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']);
                return true;
            }
        }
        return false;
    }
    public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
    {
        /*
             Calculate all searches using oValidTokens i.e.
             'Wodsworth Road, Sheffield' =>
             Phrase Wordset
             0      0       (wodsworth road)
             0      1       (wodsworth)(road)
             1      0       (sheffield)
             Score how good the search is so they can be ordered
         */
        foreach ($aPhrases as $iPhrase => $oPhrase) {
            $aNewPhraseSearches = array();
            $sPhraseType = $oPhrase->getPhraseType();
            foreach ($oPhrase->getWordSets() as $aWordset) {
                $aWordsetSearches = $aSearches;
                // Add all words from this wordset
                foreach ($aWordset as $iToken => $sToken) {
                    //echo "
$sToken";
                    $aNewWordsetSearches = array();
                    foreach ($aWordsetSearches as $oCurrentSearch) {
                        //echo "";
                        //var_dump($oCurrentSearch);
                        //echo "";
                        // Tokens with full name matches.
                        foreach ($oValidTokens->get(' '.$sToken) as $oSearchTerm) {
                            $aNewSearches = $oCurrentSearch->extendWithFullTerm(
                                $oSearchTerm,
                                $oValidTokens->contains($sToken)
                                  && strpos($sToken, ' ') === false,
                                $sPhraseType,
                                $iToken == 0 && $iPhrase == 0,
                                $iPhrase == 0,
                                $iToken + 1 == count($aWordset)
                                  && $iPhrase + 1 == count($aPhrases)
                            );
                            foreach ($aNewSearches as $oSearch) {
                                if ($oSearch->getRank() < $this->iMaxRank) {
                                    $aNewWordsetSearches[] = $oSearch;
                                }
                            }
                        }
                        // Look for partial matches.
                        // Note that there is no point in adding country terms here
                        // because country is omitted in the address.
                        if ($sPhraseType != 'country') {
                            // Allow searching for a word - but at extra cost
                            foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
                                $aNewSearches = $oCurrentSearch->extendWithPartialTerm(
                                    $sToken,
                                    $oSearchTerm,
                                    (bool) $sPhraseType,
                                    $iPhrase,
                                    $oValidTokens->get(' '.$sToken)
                                );
                                foreach ($aNewSearches as $oSearch) {
                                    if ($oSearch->getRank() < $this->iMaxRank) {
                                        $aNewWordsetSearches[] = $oSearch;
                                    }
                                }
                            }
                        }
                    }
                    // Sort and cut
                    usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
                    $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
                }
                //var_Dump('