diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 1cfaf61681..21d506ae81 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -131,99 +131,6 @@ jobs: working-directory: Nominatim if: matrix.flavour != 'oldstuff' - legacy-test: - needs: create-archive - runs-on: ubuntu-20.04 - - strategy: - matrix: - postgresql: ["13", "16"] - - steps: - - uses: actions/download-artifact@v4 - with: - name: full-source - - - name: Unpack Nominatim - run: tar xf nominatim-src.tar.bz2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - - - uses: ./Nominatim/.github/actions/setup-postgresql - with: - postgresql-version: ${{ matrix.postgresql }} - postgis-version: 3 - - - name: Install Postgresql server dev - run: sudo apt-get install postgresql-server-dev-$PGVER - env: - PGVER: ${{ matrix.postgresql }} - - - uses: ./Nominatim/.github/actions/build-nominatim - with: - cmake-args: -DBUILD_MODULE=on - - - name: Install test prerequisites - run: sudo apt-get install -y -qq python3-behave - - - name: BDD tests (legacy tokenizer) - run: | - export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH - python3 -m behave -DREMOVE_TEMPLATE=1 -DSERVER_MODULE_PATH=$GITHUB_WORKSPACE/build/module -DAPI_ENGINE=php -DTOKENIZER=legacy --format=progress3 - working-directory: Nominatim/test/bdd - - - php-test: - needs: create-archive - runs-on: ubuntu-22.04 - - steps: - - uses: actions/download-artifact@v4 - with: - name: full-source - - - name: Unpack Nominatim - run: tar xf nominatim-src.tar.bz2 - - - uses: ./Nominatim/.github/actions/setup-postgresql - with: - postgresql-version: 15 - postgis-version: 3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.1 - tools: phpunit:9, phpcs, composer - ini-values: opcache.jit=disable - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: PHP linting - run: phpcs --report-width=120 . - working-directory: Nominatim - - - name: PHP unit tests - run: phpunit ./ - working-directory: Nominatim/test/php - - - uses: ./Nominatim/.github/actions/build-nominatim - with: - flavour: 'ubuntu-22' - - - name: Install test prerequisites - run: sudo apt-get install -y -qq python3-behave - - - name: BDD tests (php) - run: | - export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH - python3 -m behave -DREMOVE_TEMPLATE=1 -DAPI_ENGINE=php --format=progress3 - working-directory: Nominatim/test/bdd - - install: runs-on: ubuntu-latest needs: create-archive diff --git a/CMakeLists.txt b/CMakeLists.txt index 19bf6655c3..11f7f9297c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,25 +74,6 @@ if (BUILD_IMPORTER OR BUILD_API) find_package(PythonInterp 3.7 REQUIRED) endif() -#----------------------------------------------------------------------------- -# PHP -#----------------------------------------------------------------------------- - -# Setting PHP binary variable as to command line (prevailing) or auto detect - -if (BUILD_API) - if (NOT PHP_BIN) - find_program (PHP_BIN php) - endif() - # sanity check if PHP binary exists - if (NOT EXISTS ${PHP_BIN}) - message(WARNING "PHP binary not found. Only Python frontend can be used.") - set(PHP_BIN "") - else() - message (STATUS "Using PHP binary " ${PHP_BIN}) - endif() -endif() - #----------------------------------------------------------------------------- # import scripts and utilities (importer only) #----------------------------------------------------------------------------- @@ -125,8 +106,6 @@ if (BUILD_TESTS) find_program(PYTHON_BEHAVE behave) find_program(PYLINT NAMES pylint3 pylint) find_program(PYTEST NAMES pytest py.test-3 py.test) - find_program(PHPCS phpcs) - find_program(PHPUNIT phpunit) if (PYTHON_BEHAVE) message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}") @@ -141,24 +120,6 @@ if (BUILD_TESTS) message(WARNING "behave not found. BDD tests disabled." ) endif() - if (PHPUNIT) - message(STATUS "Using phpunit binary ${PHPUNIT}") - add_test(NAME php - COMMAND ${PHPUNIT} ./ - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php) - else() - message(WARNING "phpunit not found. PHP unit tests disabled." ) - endif() - - if (PHPCS) - message(STATUS "Using phpcs binary ${PHPCS}") - add_test(NAME phpcs - COMMAND ${PHPCS} --report-width=120 --colors lib-php - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) - else() - message(WARNING "phpcs not found. PHP linting tests disabled." ) - endif() - if (PYLINT) message(STATUS "Using pylint binary ${PYLINT}") add_test(NAME pylint @@ -203,11 +164,7 @@ if (BUILD_IMPORTER) DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME nominatim) - if (EXISTS ${PHP_BIN}) - configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed) - else() - configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed) - endif() + configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed) foreach (submodule nominatim_db nominatim_api) install(DIRECTORY src/${submodule} @@ -243,10 +200,6 @@ if (BUILD_MODULE) DESTINATION ${NOMINATIM_LIBDIR}/module) endif() -if (BUILD_API AND EXISTS ${PHP_BIN}) - install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR}) -endif() - install(FILES settings/env.defaults settings/address-levels.json settings/phrase-settings.json diff --git a/cmake/paths-py-no-php.tmpl b/cmake/paths-py-no-php.tmpl index 36856bf3c3..a95cb6645b 100644 --- a/cmake/paths-py-no-php.tmpl +++ b/cmake/paths-py-no-php.tmpl @@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim (installed version). """ from pathlib import Path -PHPLIB_DIR = None SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve() DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve() CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve() diff --git a/cmake/paths-py.tmpl b/cmake/paths-py.tmpl deleted file mode 100644 index 372a454671..0000000000 --- a/cmake/paths-py.tmpl +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# This file is part of Nominatim. (https://nominatim.org) -# -# Copyright (C) 2022 by the Nominatim developer community. -# For a full list of authors see the git log. -""" -Path settings for extra data used by Nominatim (installed version). -""" -from pathlib import Path - -PHPLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-php').resolve() -SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve() -DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve() -CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve() diff --git a/docs/admin/Advanced-Installations.md b/docs/admin/Advanced-Installations.md index f8232fb29c..ac8da274c6 100644 --- a/docs/admin/Advanced-Installations.md +++ b/docs/admin/Advanced-Installations.md @@ -194,7 +194,7 @@ On the client side you now need to configure the import to point to the correct location of the library **on the database server**. Add the following line to your your `.env` file: -```php +``` NOMINATIM_DATABASE_MODULE_PATH="" ``` diff --git a/docs/admin/Deployment-PHP.md b/docs/admin/Deployment-PHP.md deleted file mode 100644 index 9416c53eef..0000000000 --- a/docs/admin/Deployment-PHP.md +++ /dev/null @@ -1,151 +0,0 @@ -# Deploying Nominatim using the PHP frontend - -!!! danger - The PHP frontend is deprecated and will be removed in Nominatim 5.0. - -The Nominatim API is implemented as a PHP application. The `website/` directory -in the project directory contains the configured website. You can serve this -in a production environment with any web server that is capable to run -PHP scripts. - -This section gives a quick overview on how to configure Apache and Nginx to -serve Nominatim. It is not meant as a full system administration guide on how -to run a web service. Please refer to the documentation of -[Apache](https://httpd.apache.org/docs/current/) and -[Nginx](https://nginx.org/en/docs/) -for background information on configuring the services. - -!!! Note - Throughout this page, we assume your Nominatim project directory is - located in `/srv/nominatim-project` and you have installed Nominatim - using the default installation prefix `/usr/local`. If you have put it - somewhere else, you need to adjust the commands and configuration - accordingly. - - We further assume that your web server runs as user `www-data`. Older - versions of CentOS may still use the user name `apache`. You also need - to adapt the instructions in this case. - -## Making the website directory accessible - -You need to make sure that the `website` directory is accessible for the -web server user. You can check that the permissions are correct by accessing -on of the php files as the web server user: - -``` sh -sudo -u www-data head -n 1 /srv/nominatim-project/website/search.php -``` - -If this shows a permission error, then you need to adapt the permissions of -each directory in the path so that it is executable for `www-data`. - -If you have SELinux enabled, further adjustments may be necessary to give the -web server access. At a minimum the following SELinux labelling should be done -for Nominatim: - -``` sh -sudo semanage fcontext -a -t httpd_sys_content_t "/usr/local/nominatim/lib/lib-php(/.*)?" -sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim-project/website(/.*)?" -sudo semanage fcontext -a -t lib_t "/srv/nominatim-project/module/nominatim.so" -sudo restorecon -R -v /usr/local/lib/nominatim -sudo restorecon -R -v /srv/nominatim-project -``` - -## Nominatim with Apache - -### Installing the required packages - -With Apache you can use the PHP module to run Nominatim. - -Under Ubuntu/Debian install them with: - -``` sh -sudo apt install apache2 libapache2-mod-php -``` - -### Configuring Apache - -Make sure your Apache configuration contains the required permissions for the -directory and create an alias: - -``` apache - - Options FollowSymLinks MultiViews - AddType text/html .php - DirectoryIndex search.php - Require all granted - -Alias /nominatim /srv/nominatim-project/website -``` - -After making changes in the apache config you need to restart apache. -The website should now be available on `http://localhost/nominatim`. - -## Nominatim with Nginx - -### Installing the required packages - -Nginx has no built-in PHP interpreter. You need to use php-fpm as a daemon for -serving PHP cgi. - -On Ubuntu/Debian install nginx and php-fpm with: - -``` sh -sudo apt install nginx php-fpm -``` - -### Configure php-fpm and Nginx - -By default php-fpm listens on a network socket. If you want it to listen to a -Unix socket instead, change the pool configuration -(`/etc/php//fpm/pool.d/www.conf`) as follows: - -``` ini -; Replace the tcp listener and add the unix socket -listen = /var/run/php-fpm-nominatim.sock - -; Ensure that the daemon runs as the correct user -listen.owner = www-data -listen.group = www-data -listen.mode = 0666 -``` - -Tell nginx that php files are special and to fastcgi_pass to the php-fpm -unix socket by adding the location definition to the default configuration. - -``` nginx -root /srv/nominatim-project/website; -index search.php; -location / { - try_files $uri $uri/ @php; -} - -location @php { - fastcgi_param SCRIPT_FILENAME "$document_root$uri.php"; - fastcgi_param PATH_TRANSLATED "$document_root$uri.php"; - fastcgi_param QUERY_STRING $args; - fastcgi_pass unix:/var/run/php-fpm-nominatim.sock; - fastcgi_index index.php; - include fastcgi_params; -} - -location ~ [^/]\.php(/|$) { - fastcgi_split_path_info ^(.+?\.php)(/.*)$; - if (!-f $document_root$fastcgi_script_name) { - return 404; - } - fastcgi_pass unix:/var/run/php-fpm-nominatim.sock; - fastcgi_index search.php; - include fastcgi.conf; -} -``` - -Restart the nginx and php-fpm services and the website should now be available -at `http://localhost/`. - -## Nominatim with other webservers - -Users have created instructions for other webservers: - -* [Caddy](https://github.com/osm-search/Nominatim/discussions/2580) - diff --git a/docs/admin/Import.md b/docs/admin/Import.md index 5a365b236b..3f248b0e3e 100644 --- a/docs/admin/Import.md +++ b/docs/admin/Import.md @@ -257,8 +257,8 @@ successfully. nominatim admin --check-database ``` -Now you can try out your installation by executing a simple query on the -command line: +If you have installed the `nominatim-api` package, then you can try out +your installation by executing a simple query on the command line: ``` sh nominatim search --query Berlin @@ -270,10 +270,8 @@ or, when you have a reverse-only installation: nominatim reverse --lat 51 --lon 45 ``` -If you want to run Nominatim as a service, you need to make a choice between -running the modern Python frontend and the legacy PHP frontend. -Make sure you have installed the right packages as per -[Installation](Installation.md#software). +If you want to run Nominatim as a service, make sure you have installed +the right packages as per [Installation](Installation.md#software). #### Testing the Python frontend @@ -291,36 +289,15 @@ or, if you prefer to use Starlette instead of Falcon as webserver, nominatim serve --engine starlette ``` -Go to `http://localhost:8088/status.php` and you should see the message `OK`. -You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin` +Go to `http://localhost:8088/status` and you should see the message `OK`. +You can also run a search query, e.g. `http://localhost:8088/search?q=Berlin` or, for reverse-only installations a reverse query, -e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`. +e.g. `http://localhost:8088/reverse?lat=27.1750090510034&lon=78.04209025`. Do not use this test server in production. To run Nominatim via webservers like Apache or nginx, please continue reading [Deploy the Python frontend](Deployment-Python.md). -#### Testing the PHP frontend - -!!! danger - The PHP fronted is deprecated and will be removed in Nominatim 5.0. - -You can run a small test server with the PHP frontend like this: - -```sh -nominatim serve --engine php -``` - -Go to `http://localhost:8088/status.php` and you should see the message `OK`. -You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin` -or, for reverse-only installations a reverse query, -e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`. - -Do not use this test server in production. -To run Nominatim via webservers like Apache or nginx, please continue reading -[Deploy the PHP frontend](Deployment-PHP.md). - - ## Enabling search by category phrases diff --git a/docs/admin/Installation.md b/docs/admin/Installation.md index e67371bd9d..38c4d6017d 100644 --- a/docs/admin/Installation.md +++ b/docs/admin/Installation.md @@ -72,13 +72,6 @@ For running the Python frontend: * [starlette](https://www.starlette.io/) * [uvicorn](https://www.uvicorn.org/) -For running the legacy PHP frontend (deprecated, will be removed in Nominatim 5.0): - - * [PHP](https://php.net) (7.3+) - * PHP-pgsql - * PHP-intl (bundled with PHP) - - For dependencies for running tests and building documentation, see the [Development section](../develop/Development-Environment.md). diff --git a/docs/admin/Migration.md b/docs/admin/Migration.md index b407d2ef39..17019da4c3 100644 --- a/docs/admin/Migration.md +++ b/docs/admin/Migration.md @@ -20,6 +20,18 @@ breaking changes. **Please read them before running the migration.** If you are migrating from a version <3.6, then you still have to follow the manual migration steps up to 3.6. +## 4.5.0 -> master + +### PHP frontend removed + +The PHP frontend has been completely removed. Please switch to the Python +frontend. + +Without the PHP code, the `nominatim refresh --website` command is no longer +needed. It currently omits a warning and does otherwise nothing. It will be +removed in later versions of Nominatim. So make sure you remove it from your +scripts. + ## 4.4.0 -> 4.5.0 ### New structure for Python packages diff --git a/docs/api/Details.md b/docs/api/Details.md index c50378c5a6..b836efd30e 100644 --- a/docs/api/Details.md +++ b/docs/api/Details.md @@ -59,13 +59,6 @@ When set, then JSON output will be wrapped in a callback function with the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more information. -| Parameter | Value | Default | -|-----------| ----- | ------- | -| pretty | 0 or 1 | 0 | - -`[PHP-only]` Add indentation to the output to make it more human-readable. - - ### Output details | Parameter | Value | Default | @@ -95,10 +88,8 @@ members. |-----------| ----- | ------- | | hierarchy | 0 or 1 | 0 | -Include details of places lower in the address hierarchy. - -`[Python-only]` will only return properly parented places. These are address -or POI-like places that reuse the address of their parent street or place. +Include details of POIs and address that depend on the place. Only POIs +that use this place to determine their address will be returned. | Parameter | Value | Default | |-----------| ----- | ------- | @@ -129,7 +120,7 @@ as the ["Accept-Language" HTTP header](https://developer.mozilla.org/en-US/docs/ ##### JSON -[https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json) +[https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json) ```json diff --git a/docs/api/Output.md b/docs/api/Output.md index 029f78bc5e..75220cf510 100644 --- a/docs/api/Output.md +++ b/docs/api/Output.md @@ -168,7 +168,7 @@ Additional information requested with `addressdetails=1`, `extratags=1` and + more_url="https://nominatim.openstreetmap.org/search?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3"> "" Request time is the time when the request was started. The execution time is -given in seconds and corresponds to the time the query took executing in PHP. +given in seconds and includes the entire time the query was queued and executed +in the frontend. type contains the name of the endpoint used. Can be used as the same time as NOMINATIM_LOG_DB. diff --git a/docs/develop/Development-Environment.md b/docs/develop/Development-Environment.md index fd7820c6ab..441556ff20 100644 --- a/docs/develop/Development-Environment.md +++ b/docs/develop/Development-Environment.md @@ -26,12 +26,9 @@ following packages should get you started: ## Prerequisites for testing and documentation The Nominatim test suite consists of behavioural tests (using behave) and -unit tests (using PHPUnit for PHP code and pytest for Python code). -It has the following additional requirements: +unit tests (using pytest). It has the following additional requirements: * [behave test framework](https://behave.readthedocs.io) >= 1.2.6 -* [phpunit](https://phpunit.de) (9.5 is known to work) -* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) * [Pylint](https://pylint.org/) (CI always runs the latest version from pip) * [mypy](http://mypy-lang.org/) (plus typing information for external libs) * [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9) @@ -63,7 +60,7 @@ The easiest way, to handle these Python dependencies is to run your development from within a virtual environment. ```sh -sudo apt install libsqlite3-mod-spatialite php-cli +sudo apt install libsqlite3-mod-spatialite ``` To set up the virtual environment with all necessary packages run: @@ -86,28 +83,6 @@ Now enter the virtual environment whenever you want to develop: . ~/nominatim-dev-venv/bin/activate ``` -For installing the PHP development tools, run: - -```sh -sudo apt install php-cgi phpunit php-codesniffer -``` - -If your distribution does not have PHPUnit 7.3+, you can install it (as well -as CodeSniffer) via composer: - -``` -sudo apt-get install composer -composer global require "squizlabs/php_codesniffer=*" -composer global require "phpunit/phpunit=8.*" -``` - -The binaries are found in `.config/composer/vendor/bin`. You need to add this -to your PATH: - -``` -echo 'export PATH=~/.config/composer/vendor/bin:$PATH' > ~/.profile -``` - ### Running Nominatim during development The source code for Nominatim can be found in the `src` directory and can diff --git a/docs/develop/Testing.md b/docs/develop/Testing.md index c220f4e4e1..d57ab3199a 100644 --- a/docs/develop/Testing.md +++ b/docs/develop/Testing.md @@ -8,7 +8,7 @@ the tests, see the [Development setup chapter](Development-Environment.md). There are two kind of tests in this test suite. There are functional tests which test the API interface using a BDD test framework and there are unit -tests for specific PHP functions. +tests for the Python code. This test directory is structured as follows: @@ -20,28 +20,11 @@ This test directory is structured as follows: | +- db Tests for internal data processing on import and update | +- api Tests for API endpoints (search, reverse, etc.) | - +- php PHP unit tests +- python Python unit tests +- testdb Base data for generating API test database +- testdata Additional test data used by unit tests ``` -## PHP Unit Tests (`test/php`) - -Unit tests for PHP code can be found in the `php/` directory. They test selected -PHP functions. Very low coverage. - -To execute the test suite run - - cd test/php - UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../ - -It will read phpunit.xml which points to the library, test path, bootstrap -strip and sets other parameters. - -It will use (and destroy) a local database 'nominatim_unit_tests'. You can set -a different connection string with e.g. UNIT_TEST_DSN='pgsql:dbname=foo_unit_tests'. - ## Python Unit Tests (`test/python`) Unit tests for Python code can be found in the `python/` directory. The goal is @@ -118,7 +101,7 @@ and compromises the following data: * extract of Autauga country, Alabama, US (for tests against Tiger data) * additional data from `test/testdb/additional_api_test.data.osm` -API tests should only be testing the functionality of the website PHP code. +API tests should only be testing the functionality of the website frontend code. Most tests should be formulated as BDD DB creation tests (see below) instead. ### DB Creation Tests (`test/bdd/db`) diff --git a/docs/develop/Tokenizers.md b/docs/develop/Tokenizers.md index 03988ce068..f4a55adcae 100644 --- a/docs/develop/Tokenizers.md +++ b/docs/develop/Tokenizers.md @@ -91,14 +91,9 @@ for a custom tokenizer implementation. ### Directory Structure -Nominatim expects two files for a tokenizer: - -* `nominatim/tokenizer/_tokenizer.py` containing the Python part of the - implementation -* `lib-php/tokenizer/_tokenizer.php` with the PHP part of the - implementation - -where `` is a unique name for the tokenizer consisting of only lower-case +Nominatim expects a single file `src/nominatim_db/tokenizer/_tokenizer.py` +containing the Python part of the implementation. +`` is a unique name for the tokenizer consisting of only lower-case letters, digits and underscore. A tokenizer also needs to install some SQL functions. By convention, these should be placed in `lib-sql/tokenizer`. @@ -282,73 +277,3 @@ permanently. The indexer calls this function when all processing is done and replaces the content of the `token_info` column with the returned value before the trigger stores the information in the database. May return NULL if no information should be stored permanently. - -### PHP Tokenizer class - -The PHP tokenizer class is instantiated once per request and responsible for -analyzing the incoming query. Multiple requests may be in flight in -parallel. - -The class is expected to be found under the -name of `\Nominatim\Tokenizer`. To find the class the PHP code includes the file -`tokenizer/tokenizer.php` in the project directory. This file must be created -when the tokenizer is first set up on import. The file should initialize any -configuration variables by setting PHP constants and then require the file -with the actual implementation of the tokenizer. - -The tokenizer class must implement the following functions: - -```php -public function __construct(object &$oDB) -``` - -The constructor of the class receives a database connection that can be used -to query persistent data in the database. - -```php -public function checkStatus() -``` - -Check that the tokenizer can access its persistent data structures. If there -is an issue, throw an `\Exception`. - -```php -public function normalizeString(string $sTerm) : string -``` - -Normalize string to a form to be used for comparisons when reordering results. -Nominatim reweighs results how well the final display string matches the actual -query. Before comparing result and query, names and query are normalised against -this function. The tokenizer can thus remove all properties that should not be -taken into account for reweighing, e.g. special characters or case. - -```php -public function tokensForSpecialTerm(string $sTerm) : array -``` - -Return the list of special term tokens that match the given term. - -```php -public function extractTokensFromPhrases(array &$aPhrases) : TokenList -``` - -Parse the given phrases, splitting them into word lists and retrieve the -matching tokens. - -The phrase array may take on two forms. In unstructured searches (using `q=` -parameter) the search query is split at the commas and the elements are -put into a sorted list. For structured searches the phrase array is an -associative array where the key designates the type of the term (street, city, -county etc.) The tokenizer may ignore the phrase type at this stage in parsing. -Matching phrase type and appropriate search token type will be done later -when the SearchDescription is built. - -For each phrase in the list of phrases, the function must analyse the phrase -string and then call `setWordSets()` to communicate the result of the analysis. -A word set is a list of strings, where each string refers to a search token. -A phrase may have multiple interpretations. Therefore a list of word sets is -usually attached to the phrase. The search tokens themselves are returned -by the function in an associative array, where the key corresponds to the -strings given in the word sets. The value is a list of search tokens. Thus -a single string in the list of word sets may refer to multiple search tokens. - diff --git a/docs/develop/overview.md b/docs/develop/overview.md index b5625a883c..aedce9908c 100644 --- a/docs/develop/overview.md +++ b/docs/develop/overview.md @@ -20,5 +20,5 @@ and can be found in the files in the `sql/functions/` directory. The __search frontend__ implements the actual API. It takes search and reverse geocoding queries from the user, looks up the data and -returns the results in the requested format. This part is written in PHP -and can be found in the `lib/` and `website/` directories. +returns the results in the requested format. This part is located in the +`nominatim-api` package. The source code can be found in `src/nominatim_api`. diff --git a/lib-php/AddressDetails.php b/lib-php/AddressDetails.php deleted file mode 100644 index cfdd04167c..0000000000 --- a/lib-php/AddressDetails.php +++ /dev/null @@ -1,191 +0,0 @@ -iPlaceID = $iPlaceID; - - if (is_array($mLangPref)) { - $mLangPref = $oDB->getArraySQL($oDB->getDBQuotedList($mLangPref)); - } - - if (!isset($sHousenumber)) { - $sHousenumber = -1; - } - - $sSQL = 'SELECT *,'; - $sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname'; - $sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')'; - $sSQL .= ' ORDER BY rank_address DESC, isaddress DESC'; - - $this->aAddressLines = $oDB->getAll($sSQL); - } - - private static function isAddress($aLine) - { - return $aLine['isaddress'] || $aLine['type'] == 'country_code'; - } - - public function getAddressDetails($bAll = false) - { - if ($bAll) { - return $this->aAddressLines; - } - - return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress')); - } - - public function getLocaleAddress() - { - $aParts = array(); - $sPrevResult = ''; - - foreach ($this->aAddressLines as $aLine) { - if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) { - $sPrevResult = $aLine['localname']; - $aParts[] = $sPrevResult; - } - } - - return join(', ', $aParts); - } - - public function getAddressNames() - { - $aAddress = array(); - - foreach ($this->aAddressLines as $aLine) { - if (!self::isAddress($aLine)) { - continue; - } - - $sTypeLabel = ClassTypes\getLabelTag($aLine); - - $sName = null; - if (isset($aLine['localname']) && $aLine['localname']!=='') { - $sName = $aLine['localname']; - } elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') { - $sName = $aLine['housenumber']; - } - - if (isset($sName) - && (!isset($aAddress[$sTypeLabel]) - || $aLine['class'] == 'place') - ) { - $aAddress[$sTypeLabel] = $sName; - - if (!empty($aLine['name'])) { - $this->addSubdivisionCode($aAddress, $aLine['admin_level'], $aLine['name']); - } - } - } - - return $aAddress; - } - - /** - * Annotates the given json with geocodejson address information fields. - * - * @param array $aJson Json hash to add the fields to. - * - * Geocodejson has the following fields: - * street, locality, postcode, city, district, - * county, state, country - * - * Postcode and housenumber are added by type, district is not used. - * All other fields are set according to address rank. - */ - public function addGeocodeJsonAddressParts(&$aJson) - { - foreach (array_reverse($this->aAddressLines) as $aLine) { - if (!$aLine['isaddress']) { - continue; - } - - if (!isset($aLine['localname']) || $aLine['localname'] == '') { - continue; - } - - if ($aLine['type'] == 'postcode' || $aLine['type'] == 'postal_code') { - $aJson['postcode'] = $aLine['localname']; - continue; - } - - if ($aLine['type'] == 'house_number') { - $aJson['housenumber'] = $aLine['localname']; - continue; - } - - if ($this->iPlaceID == $aLine['place_id']) { - continue; - } - - $iRank = (int)$aLine['rank_address']; - - if ($iRank > 25 && $iRank < 28) { - $aJson['street'] = $aLine['localname']; - } elseif ($iRank >= 22 && $iRank <= 25) { - $aJson['locality'] = $aLine['localname']; - } elseif ($iRank >= 17 && $iRank <= 21) { - $aJson['district'] = $aLine['localname']; - } elseif ($iRank >= 13 && $iRank <= 16) { - $aJson['city'] = $aLine['localname']; - } elseif ($iRank >= 10 && $iRank <= 12) { - $aJson['county'] = $aLine['localname']; - } elseif ($iRank >= 5 && $iRank <= 9) { - $aJson['state'] = $aLine['localname']; - } elseif ($iRank == 4) { - $aJson['country'] = $aLine['localname']; - } - } - } - - public function getAdminLevels() - { - $aAddress = array(); - foreach (array_reverse($this->aAddressLines) as $aLine) { - if (self::isAddress($aLine) - && isset($aLine['admin_level']) - && $aLine['admin_level'] < 15 - && !isset($aAddress['level'.$aLine['admin_level']]) - ) { - $aAddress['level'.$aLine['admin_level']] = $aLine['localname']; - } - } - return $aAddress; - } - - public function debugInfo() - { - return $this->aAddressLines; - } - - private function addSubdivisionCode(&$aAddress, $iAdminLevel, $nameDetails) - { - if (is_string($nameDetails)) { - $nameDetails = json_decode('{' . str_replace('"=>"', '":"', $nameDetails) . '}', true); - } - if (!empty($nameDetails['ISO3166-2'])) { - $aAddress["ISO3166-2-lvl$iAdminLevel"] = $nameDetails['ISO3166-2']; - } - } -} diff --git a/lib-php/ClassTypes.php b/lib-php/ClassTypes.php deleted file mode 100644 index 0561f482e7..0000000000 --- a/lib-php/ClassTypes.php +++ /dev/null @@ -1,576 +0,0 @@ - array ( - 1 => 'Continent', - 2 => 'Country', - 3 => 'Region', - 4 => 'State', - 5 => 'State District', - 6 => 'County', - 7 => 'Municipality', - 8 => 'City', - 9 => 'City District', - 10 => 'Suburb', - 11 => 'Neighbourhood', - 12 => 'City Block' - ), - 'no' => array ( - 3 => 'State', - 4 => 'County' - ), - 'se' => array ( - 3 => 'State', - 4 => 'County' - ) - ); - - if (isset($aBoundaryList[$sCountry]) - && isset($aBoundaryList[$sCountry][$iAdminLevel]) - ) { - return $aBoundaryList[$sCountry][$iAdminLevel]; - } - - return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback; -} - -/** - * Return an estimated radius of how far the object node extends. - * - * @param array[] $aPlace Information about the place. This must be a node - * feature. - * - * @return float The radius around the feature in degrees. - */ -function getDefRadius($aPlace) -{ - $aSpecialRadius = array( - 'place:continent' => 25, - 'place:country' => 7, - 'place:state' => 2.6, - 'place:province' => 2.6, - 'place:region' => 1.0, - 'place:county' => 0.7, - 'place:city' => 0.16, - 'place:municipality' => 0.16, - 'place:island' => 0.32, - 'place:postcode' => 0.16, - 'place:town' => 0.04, - 'place:village' => 0.02, - 'place:hamlet' => 0.02, - 'place:district' => 0.02, - 'place:borough' => 0.02, - 'place:suburb' => 0.02, - 'place:locality' => 0.01, - 'place:neighbourhood'=> 0.01, - 'place:quarter' => 0.01, - 'place:city_block' => 0.01, - 'landuse:farm' => 0.01, - 'place:farm' => 0.01, - 'place:airport' => 0.015, - 'aeroway:aerodrome' => 0.015, - 'railway:station' => 0.005 - ); - - $sClassPlace = $aPlace['class'].':'.$aPlace['type']; - - return $aSpecialRadius[$sClassPlace] ?? 0.00005; -} - -/** - * Get the icon to use with the given object. - */ -function getIcon($aPlace) -{ - $aIcons = array( - 'boundary:administrative' => 'poi_boundary_administrative', - 'place:city' => 'poi_place_city', - 'place:town' => 'poi_place_town', - 'place:village' => 'poi_place_village', - 'place:hamlet' => 'poi_place_village', - 'place:suburb' => 'poi_place_village', - 'place:locality' => 'poi_place_village', - 'place:airport' => 'transport_airport2', - 'aeroway:aerodrome' => 'transport_airport2', - 'railway:station' => 'transport_train_station2', - 'amenity:place_of_worship' => 'place_of_worship_unknown3', - 'amenity:pub' => 'food_pub', - 'amenity:bar' => 'food_bar', - 'amenity:university' => 'education_university', - 'tourism:museum' => 'tourist_museum', - 'amenity:arts_centre' => 'tourist_art_gallery2', - 'tourism:zoo' => 'tourist_zoo', - 'tourism:theme_park' => 'poi_point_of_interest', - 'tourism:attraction' => 'poi_point_of_interest', - 'leisure:golf_course' => 'sport_golf', - 'historic:castle' => 'tourist_castle', - 'amenity:hospital' => 'health_hospital', - 'amenity:school' => 'education_school', - 'amenity:theatre' => 'tourist_theatre', - 'amenity:library' => 'amenity_library', - 'amenity:fire_station' => 'amenity_firestation3', - 'amenity:police' => 'amenity_police2', - 'amenity:bank' => 'money_bank2', - 'amenity:post_office' => 'amenity_post_office', - 'tourism:hotel' => 'accommodation_hotel2', - 'amenity:cinema' => 'tourist_cinema', - 'tourism:artwork' => 'tourist_art_gallery2', - 'historic:archaeological_site' => 'tourist_archaeological2', - 'amenity:doctors' => 'health_doctors', - 'leisure:sports_centre' => 'sport_leisure_centre', - 'leisure:swimming_pool' => 'sport_swimming_outdoor', - 'shop:supermarket' => 'shopping_supermarket', - 'shop:convenience' => 'shopping_convenience', - 'amenity:restaurant' => 'food_restaurant', - 'amenity:fast_food' => 'food_fastfood', - 'amenity:cafe' => 'food_cafe', - 'tourism:guest_house' => 'accommodation_bed_and_breakfast', - 'amenity:pharmacy' => 'health_pharmacy_dispensing', - 'amenity:fuel' => 'transport_fuel', - 'natural:peak' => 'poi_peak', - 'natural:wood' => 'landuse_coniferous_and_deciduous', - 'shop:bicycle' => 'shopping_bicycle', - 'shop:clothes' => 'shopping_clothes', - 'shop:hairdresser' => 'shopping_hairdresser', - 'shop:doityourself' => 'shopping_diy', - 'shop:estate_agent' => 'shopping_estateagent2', - 'shop:car' => 'shopping_car', - 'shop:garden_centre' => 'shopping_garden_centre', - 'shop:car_repair' => 'shopping_car_repair', - 'shop:bakery' => 'shopping_bakery', - 'shop:butcher' => 'shopping_butcher', - 'shop:apparel' => 'shopping_clothes', - 'shop:laundry' => 'shopping_laundrette', - 'shop:beverages' => 'shopping_alcohol', - 'shop:alcohol' => 'shopping_alcohol', - 'shop:optician' => 'health_opticians', - 'shop:chemist' => 'health_pharmacy', - 'shop:gallery' => 'tourist_art_gallery2', - 'shop:jewelry' => 'shopping_jewelry', - 'tourism:information' => 'amenity_information', - 'historic:ruins' => 'tourist_ruin', - 'amenity:college' => 'education_school', - 'historic:monument' => 'tourist_monument', - 'historic:memorial' => 'tourist_monument', - 'historic:mine' => 'poi_mine', - 'tourism:caravan_site' => 'accommodation_caravan_park', - 'amenity:bus_station' => 'transport_bus_station', - 'amenity:atm' => 'money_atm2', - 'tourism:viewpoint' => 'tourist_view_point', - 'tourism:guesthouse' => 'accommodation_bed_and_breakfast', - 'railway:tram' => 'transport_tram_stop', - 'amenity:courthouse' => 'amenity_court', - 'amenity:recycling' => 'amenity_recycling', - 'amenity:dentist' => 'health_dentist', - 'natural:beach' => 'tourist_beach', - 'railway:tram_stop' => 'transport_tram_stop', - 'amenity:prison' => 'amenity_prison', - 'highway:bus_stop' => 'transport_bus_stop2' - ); - - $sClassPlace = $aPlace['class'].':'.$aPlace['type']; - - return $aIcons[$sClassPlace] ?? null; -} - -/** - * Get an icon for the given object with its full URL. - */ -function getIconFile($aPlace) -{ - if (CONST_MapIcon_URL === false) { - return null; - } - - $sIcon = getIcon($aPlace); - - if (!isset($sIcon)) { - return null; - } - - return CONST_MapIcon_URL.'/'.$sIcon.'.p.20.png'; -} - -/** - * Return a class importance value for the given place. - * - * @param array[] $aPlace Information about the place. - * - * @return int An importance value. The lower the value, the more - * important the class. - */ -function getImportance($aPlace) -{ - static $aWithImportance = null; - - if ($aWithImportance === null) { - $aWithImportance = array_flip(array( - 'boundary:administrative', - 'place:country', - 'place:state', - 'place:province', - 'place:county', - 'place:city', - 'place:region', - 'place:island', - 'place:town', - 'place:village', - 'place:hamlet', - 'place:suburb', - 'place:locality', - 'landuse:farm', - 'place:farm', - 'highway:motorway_junction', - 'highway:motorway', - 'highway:trunk', - 'highway:primary', - 'highway:secondary', - 'highway:tertiary', - 'highway:residential', - 'highway:unclassified', - 'highway:living_street', - 'highway:service', - 'highway:track', - 'highway:road', - 'highway:byway', - 'highway:bridleway', - 'highway:cycleway', - 'highway:pedestrian', - 'highway:footway', - 'highway:steps', - 'highway:motorway_link', - 'highway:trunk_link', - 'highway:primary_link', - 'landuse:industrial', - 'landuse:residential', - 'landuse:retail', - 'landuse:commercial', - 'place:airport', - 'aeroway:aerodrome', - 'railway:station', - 'amenity:place_of_worship', - 'amenity:pub', - 'amenity:bar', - 'amenity:university', - 'tourism:museum', - 'amenity:arts_centre', - 'tourism:zoo', - 'tourism:theme_park', - 'tourism:attraction', - 'leisure:golf_course', - 'historic:castle', - 'amenity:hospital', - 'amenity:school', - 'amenity:theatre', - 'amenity:public_building', - 'amenity:library', - 'amenity:townhall', - 'amenity:community_centre', - 'amenity:fire_station', - 'amenity:police', - 'amenity:bank', - 'amenity:post_office', - 'leisure:park', - 'amenity:park', - 'landuse:park', - 'landuse:recreation_ground', - 'tourism:hotel', - 'tourism:motel', - 'amenity:cinema', - 'tourism:artwork', - 'historic:archaeological_site', - 'amenity:doctors', - 'leisure:sports_centre', - 'leisure:swimming_pool', - 'shop:supermarket', - 'shop:convenience', - 'amenity:restaurant', - 'amenity:fast_food', - 'amenity:cafe', - 'tourism:guest_house', - 'amenity:pharmacy', - 'amenity:fuel', - 'natural:peak', - 'waterway:waterfall', - 'natural:wood', - 'natural:water', - 'landuse:forest', - 'landuse:cemetery', - 'landuse:allotments', - 'landuse:farmyard', - 'railway:rail', - 'waterway:canal', - 'waterway:river', - 'waterway:stream', - 'shop:bicycle', - 'shop:clothes', - 'shop:hairdresser', - 'shop:doityourself', - 'shop:estate_agent', - 'shop:car', - 'shop:garden_centre', - 'shop:car_repair', - 'shop:newsagent', - 'shop:bakery', - 'shop:furniture', - 'shop:butcher', - 'shop:apparel', - 'shop:electronics', - 'shop:department_store', - 'shop:books', - 'shop:yes', - 'shop:outdoor', - 'shop:mall', - 'shop:florist', - 'shop:charity', - 'shop:hardware', - 'shop:laundry', - 'shop:shoes', - 'shop:beverages', - 'shop:dry_cleaning', - 'shop:carpet', - 'shop:computer', - 'shop:alcohol', - 'shop:optician', - 'shop:chemist', - 'shop:gallery', - 'shop:mobile_phone', - 'shop:sports', - 'shop:jewelry', - 'shop:pet', - 'shop:beauty', - 'shop:stationery', - 'shop:shopping_centre', - 'shop:general', - 'shop:electrical', - 'shop:toys', - 'shop:jeweller', - 'shop:betting', - 'shop:household', - 'shop:travel_agency', - 'shop:hifi', - 'amenity:shop', - 'tourism:information', - 'place:house', - 'place:house_name', - 'place:house_number', - 'place:country_code', - 'leisure:pitch', - 'highway:unsurfaced', - 'historic:ruins', - 'amenity:college', - 'historic:monument', - 'railway:subway', - 'historic:memorial', - 'leisure:nature_reserve', - 'leisure:common', - 'waterway:lock_gate', - 'natural:fell', - 'amenity:nightclub', - 'highway:path', - 'leisure:garden', - 'landuse:reservoir', - 'leisure:playground', - 'leisure:stadium', - 'historic:mine', - 'natural:cliff', - 'tourism:caravan_site', - 'amenity:bus_station', - 'amenity:kindergarten', - 'highway:construction', - 'amenity:atm', - 'amenity:emergency_phone', - 'waterway:lock', - 'waterway:riverbank', - 'natural:coastline', - 'tourism:viewpoint', - 'tourism:hostel', - 'tourism:bed_and_breakfast', - 'railway:halt', - 'railway:platform', - 'railway:tram', - 'amenity:courthouse', - 'amenity:recycling', - 'amenity:dentist', - 'natural:beach', - 'place:moor', - 'amenity:grave_yard', - 'waterway:drain', - 'landuse:grass', - 'landuse:village_green', - 'natural:bay', - 'railway:tram_stop', - 'leisure:marina', - 'highway:stile', - 'natural:moor', - 'railway:light_rail', - 'railway:narrow_gauge', - 'natural:land', - 'amenity:village_hall', - 'waterway:dock', - 'amenity:veterinary', - 'landuse:brownfield', - 'leisure:track', - 'railway:historic_station', - 'landuse:construction', - 'amenity:prison', - 'landuse:quarry', - 'amenity:telephone', - 'highway:traffic_signals', - 'natural:heath', - 'historic:house', - 'amenity:social_club', - 'landuse:military', - 'amenity:health_centre', - 'historic:building', - 'amenity:clinic', - 'highway:services', - 'amenity:ferry_terminal', - 'natural:marsh', - 'natural:hill', - 'highway:raceway', - 'amenity:taxi', - 'amenity:take_away', - 'amenity:car_rental', - 'place:islet', - 'amenity:nursery', - 'amenity:nursing_home', - 'amenity:toilets', - 'amenity:hall', - 'waterway:boatyard', - 'highway:mini_roundabout', - 'historic:manor', - 'tourism:chalet', - 'amenity:bicycle_parking', - 'amenity:hotel', - 'waterway:weir', - 'natural:wetland', - 'natural:cave_entrance', - 'amenity:crematorium', - 'tourism:picnic_site', - 'landuse:wood', - 'landuse:basin', - 'natural:tree', - 'leisure:slipway', - 'landuse:meadow', - 'landuse:piste', - 'amenity:care_home', - 'amenity:club', - 'amenity:medical_centre', - 'historic:roman_road', - 'historic:fort', - 'railway:subway_entrance', - 'historic:yes', - 'highway:gate', - 'leisure:fishing', - 'historic:museum', - 'amenity:car_wash', - 'railway:level_crossing', - 'leisure:bird_hide', - 'natural:headland', - 'tourism:apartments', - 'amenity:shopping', - 'natural:scrub', - 'natural:fen', - 'building:yes', - 'mountain_pass:yes', - 'amenity:parking', - 'highway:bus_stop', - 'place:postcode', - 'amenity:post_box', - 'place:houses', - 'railway:preserved', - 'waterway:derelict_canal', - 'amenity:dead_pub', - 'railway:disused_station', - 'railway:abandoned', - 'railway:disused' - )); - } - - $sClassPlace = $aPlace['class'].':'.$aPlace['type']; - - return $aWithImportance[$sClassPlace] ?? null; -} diff --git a/lib-php/DB.php b/lib-php/DB.php deleted file mode 100644 index 553d9452e2..0000000000 --- a/lib-php/DB.php +++ /dev/null @@ -1,362 +0,0 @@ -sDSN = $sDSN ?? getSetting('DATABASE_DSN'); - } - - public function connect($bNew = false, $bPersistent = true) - { - if (isset($this->connection) && !$bNew) { - return true; - } - $aConnOptions = array( - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, - \PDO::ATTR_PERSISTENT => $bPersistent - ); - - // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php - try { - $this->connection = new \PDO($this->sDSN, null, null, $aConnOptions); - } catch (\PDOException $e) { - $sMsg = 'Failed to establish database connection:' . $e->getMessage(); - throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage()); - } - - $this->connection->exec("SET DateStyle TO 'sql,european'"); - $this->connection->exec("SET client_encoding TO 'utf-8'"); - // Disable JIT and parallel workers. They interfere badly with search SQL. - $this->connection->exec('SET max_parallel_workers_per_gather TO 0'); - if ($this->getPostgresVersion() >= 11) { - $this->connection->exec('SET jit_above_cost TO -1'); - } - - $iMaxExecution = ini_get('max_execution_time'); - if ($iMaxExecution > 0) { - $this->connection->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds - } - - return true; - } - - // returns the number of rows that were modified or deleted by the SQL - // statement. If no rows were affected returns 0. - public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - $val = null; - try { - if (isset($aInputVars)) { - $stmt = $this->connection->prepare($sSQL); - $stmt->execute($aInputVars); - } else { - $val = $this->connection->exec($sSQL); - } - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $val; - } - - /** - * Executes query. Returns first row as array. - * Returns false if no result found. - * - * @param string $sSQL - * - * @return array[] - */ - public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - try { - $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage); - $row = $stmt->fetch(); - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $row; - } - - /** - * Executes query. Returns first value of first result. - * Returns false if no results found. - * - * @param string $sSQL - * - * @return array[] - */ - public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - try { - $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage); - $row = $stmt->fetch(\PDO::FETCH_NUM); - if ($row === false) { - return false; - } - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $row[0]; - } - - /** - * Executes query. Returns array of results (arrays). - * Returns empty array if no results found. - * - * @param string $sSQL - * - * @return array[] - */ - public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - try { - $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage); - $rows = $stmt->fetchAll(); - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $rows; - } - - /** - * Executes query. Returns array of the first value of each result. - * Returns empty array if no results found. - * - * @param string $sSQL - * - * @return array[] - */ - public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - $aVals = array(); - try { - $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage); - - while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false - $aVals[] = $val; - } - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $aVals; - } - - /** - * Executes query. Returns associate array mapping first value to second value of each result. - * Returns empty array if no results found. - * - * @param string $sSQL - * - * @return array[] - */ - public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - try { - $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage); - - $aList = array(); - while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) { - $aList[$aRow[0]] = $aRow[1]; - } - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $aList; - } - - /** - * Executes query. Returns a PDO statement to iterate over. - * - * @param string $sSQL - * - * @return PDOStatement - */ - public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed') - { - try { - if (isset($aInputVars)) { - $stmt = $this->connection->prepare($sSQL); - $stmt->execute($aInputVars); - } else { - $stmt = $this->connection->query($sSQL); - } - } catch (\PDOException $e) { - throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL); - } - return $stmt; - } - - /** - * St. John's Way => 'St. John\'s Way' - * - * @param string $sVal Text to be quoted. - * - * @return string - */ - public function getDBQuoted($sVal) - { - return $this->connection->quote($sVal); - } - - /** - * Like getDBQuoted, but takes an array. - * - * @param array $aVals List of text to be quoted. - * - * @return array[] - */ - public function getDBQuotedList($aVals) - { - return array_map(function ($sVal) { - return $this->getDBQuoted($sVal); - }, $aVals); - } - - /** - * [1,2,'b'] => 'ARRAY[1,2,'b']'' - * - * @param array $aVals List of text to be quoted. - * - * @return string - */ - public function getArraySQL($a) - { - return 'ARRAY['.join(',', $a).']'; - } - - /** - * Check if a table exists in the database. Returns true if it does. - * - * @param string $sTableName - * - * @return boolean - */ - public function tableExists($sTableName) - { - $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename'; - return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1); - } - - /** - * Deletes a table. Returns true if deleted or didn't exist. - * - * @param string $sTableName - * - * @return boolean - */ - public function deleteTable($sTableName) - { - return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0; - } - - /** - * Tries to connect to the database but on failure doesn't throw an exception. - * - * @return boolean - */ - public function checkConnection() - { - $bExists = true; - try { - $this->connect(true); - } catch (\Nominatim\DatabaseError $e) { - $bExists = false; - } - return $bExists; - } - - /** - * e.g. 9.6, 10, 11.2 - * - * @return float - */ - public function getPostgresVersion() - { - $sVersionString = $this->getOne('SHOW server_version_num'); - preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches); - return (float) ($aMatches[1].'.'.$aMatches[2]); - } - - /** - * e.g. 2, 2.2 - * - * @return float - */ - public function getPostgisVersion() - { - $sVersionString = $this->getOne('select postgis_lib_version()'); - preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches); - return (float) ($aMatches[1].'.'.$aMatches[2]); - } - - /** - * Returns an associate array of postgresql database connection settings. Keys can - * be 'database', 'hostspec', 'port', 'username', 'password'. - * Returns empty array on failure, thus check if at least 'database' is set. - * - * @return array[] - */ - public static function parseDSN($sDSN) - { - // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php - $aInfo = array(); - if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) { - foreach (explode(';', $aMatches[1]) as $sKeyVal) { - list($sKey, $sVal) = explode('=', $sKeyVal, 2); - if ($sKey == 'host') { - $sKey = 'hostspec'; - } elseif ($sKey == 'dbname') { - $sKey = 'database'; - } elseif ($sKey == 'user') { - $sKey = 'username'; - } - $aInfo[$sKey] = $sVal; - } - } - return $aInfo; - } - - /** - * Takes an array of settings and return the DNS string. Key names can be - * 'database', 'hostspec', 'port', 'username', 'password' but aliases - * 'dbname', 'host' and 'user' are also supported. - * - * @return string - * - */ - public static function generateDSN($aInfo) - { - $sDSN = sprintf( - 'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;', - $aInfo['host'] ?? $aInfo['hostspec'] ?? '', - $aInfo['port'] ?? '', - $aInfo['dbname'] ?? $aInfo['database'] ?? '', - $aInfo['user'] ?? '', - $aInfo['password'] ?? '' - ); - $sDSN = preg_replace('/\b\w+=;/', '', $sDSN); - $sDSN = preg_replace('/;\Z/', '', $sDSN); - - return $sDSN; - } -} diff --git a/lib-php/DatabaseError.php b/lib-php/DatabaseError.php deleted file mode 100644 index 68f1efe6fd..0000000000 --- a/lib-php/DatabaseError.php +++ /dev/null @@ -1,42 +0,0 @@ -oPDOErr = $oPDOErr; - $this->sSql = $sSql; - } - - public function __toString() - { - return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; - } - - public function getSqlError() - { - return $this->oPDOErr->getMessage(); - } - - public function getSqlDebugDump() - { - if (CONST_Debug) { - return var_export($this->oPDOErr, true); - } else { - return $this->sSql; - } - } -} diff --git a/lib-php/DebugHtml.php b/lib-php/DebugHtml.php deleted file mode 100644 index 7b0cba2d0e..0000000000 --- a/lib-php/DebugHtml.php +++ /dev/null @@ -1,189 +0,0 @@ -

Debug output for $sHeading

\n"; - } - - public static function newSection($sHeading) - { - echo "

$sHeading

\n"; - } - - public static function printVar($sHeading, $mVar) - { - echo '
'.$sHeading. ':  ';
-        Debug::outputVar($mVar, str_repeat(' ', strlen($sHeading) + 3));
-        echo "
\n"; - } - - public static function fmtArrayVals($aArr) - { - return array('__debug_format' => 'array_vals', 'data' => $aArr); - } - - public static function printDebugArray($sHeading, $oVar) - { - - if ($oVar === null) { - Debug::printVar($sHeading, 'null'); - } else { - Debug::printVar($sHeading, $oVar->debugInfo()); - } - } - - public static function printDebugTable($sHeading, $aVar) - { - echo ''.$sHeading.":\n"; - echo "\n"; - if (!empty($aVar)) { - echo " \n"; - $aKeys = array(); - $aInfo = reset($aVar); - if (!is_array($aInfo)) { - $aInfo = $aInfo->debugInfo(); - } - foreach ($aInfo as $sKey => $mVal) { - echo ' '."\n"; - $aKeys[] = $sKey; - } - echo " \n"; - foreach ($aVar as $oRow) { - $aInfo = $oRow; - if (!is_array($oRow)) { - $aInfo = $oRow->debugInfo(); - } - echo " \n"; - foreach ($aKeys as $sKey) { - echo ' '."\n"; - } - echo " \n"; - } - } - echo "
'.$sKey.'
';
-                    if (isset($aInfo[$sKey])) {
-                        Debug::outputVar($aInfo[$sKey], '');
-                    }
-                    echo '
\n"; - } - - public static function printGroupedSearch($aSearches, $aWordsIDs) - { - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - foreach ($aSearches as $aRankedSet) { - foreach ($aRankedSet as $aRow) { - $aRow->dumpAsHtmlTableRow($aWordsIDs); - } - } - echo '
rankName TokensName NotAddress TokensAddress Notcountryoperatorclasstypepostcodehousenumber
'; - } - - public static function printGroupTable($sHeading, $aVar) - { - echo ''.$sHeading.":\n"; - echo "\n"; - if (!empty($aVar)) { - echo " \n"; - echo ' '."\n"; - $aKeys = array(); - $aInfo = reset($aVar)[0]; - if (!is_array($aInfo)) { - $aInfo = $aInfo->debugInfo(); - } - foreach ($aInfo as $sKey => $mVal) { - echo ' '."\n"; - $aKeys[] = $sKey; - } - echo " \n"; - foreach ($aVar as $sGrpKey => $aGroup) { - foreach ($aGroup as $oRow) { - $aInfo = $oRow; - if (!is_array($oRow)) { - $aInfo = $oRow->debugInfo(); - } - echo " \n"; - echo ' '."\n"; - foreach ($aKeys as $sKey) { - echo ' '."\n"; - } - echo " \n"; - } - } - } - echo "
Group'.$sKey.'
'.$sGrpKey.'
';
-                        if (!empty($aInfo[$sKey])) {
-                            Debug::outputVar($aInfo[$sKey], '');
-                        }
-                        echo '
\n"; - } - - public static function printSQL($sSQL) - { - echo '

'.date('c').' '.htmlspecialchars($sSQL, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401).'

'."\n"; - } - - private static function outputVar($mVar, $sPreNL) - { - if (is_array($mVar) && !isset($mVar['__debug_format'])) { - $sPre = ''; - foreach ($mVar as $mKey => $aValue) { - echo $sPre; - $iKeyLen = Debug::outputSimpleVar($mKey); - echo ' => '; - Debug::outputVar( - $aValue, - $sPreNL.str_repeat(' ', $iKeyLen + 4) - ); - $sPre = "\n".$sPreNL; - } - } elseif (is_array($mVar) && isset($mVar['__debug_format'])) { - if (!empty($mVar['data'])) { - $sPre = ''; - foreach ($mVar['data'] as $mValue) { - echo $sPre; - Debug::outputSimpleVar($mValue); - $sPre = ', '; - } - } - } elseif (is_object($mVar) && method_exists($mVar, 'debugInfo')) { - Debug::outputVar($mVar->debugInfo(), $sPreNL); - } elseif (is_a($mVar, 'stdClass')) { - Debug::outputVar(json_decode(json_encode($mVar), true), $sPreNL); - } else { - Debug::outputSimpleVar($mVar); - } - } - - private static function outputSimpleVar($mVar) - { - if (is_bool($mVar)) { - echo ''.($mVar ? 'True' : 'False').''; - return $mVar ? 4 : 5; - } - - if (is_string($mVar)) { - $sOut = "'$mVar'"; - } else { - $sOut = (string)$mVar; - } - - echo htmlspecialchars($sOut, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401); - return strlen($sOut); - } -} diff --git a/lib-php/DebugNone.php b/lib-php/DebugNone.php deleted file mode 100644 index 818cc0867a..0000000000 --- a/lib-php/DebugNone.php +++ /dev/null @@ -1,19 +0,0 @@ -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; - } elseif ($iLimit < 1) { - $iLimit = 1; - } - - $this->iFinalLimit = $iLimit; - $this->iLimit = $iLimit + max($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 accurate 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() - { - $aParams = $this->aStructuredQuery; - - if (!$aParams || 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(); - $oPosition = new SearchPosition( - $oPhrase->getPhraseType(), - $iPhrase, - count($aPhrases) - ); - - foreach ($oPhrase->getWordSets() as $aWordset) { - $aWordsetSearches = $aSearches; - - // Add all words from this wordset - foreach ($aWordset as $iToken => $sToken) { - $aNewWordsetSearches = array(); - $oPosition->setTokenPosition($iToken, count($aWordset)); - - foreach ($aWordsetSearches as $oCurrentSearch) { - foreach ($oValidTokens->get($sToken) as $oSearchTerm) { - if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) { - $aNewSearches = $oSearchTerm->extendSearch( - $oCurrentSearch, - $oPosition - ); - - 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); - } - - $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches); - usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank')); - - $aSearchHash = array(); - foreach ($aNewPhraseSearches as $iSearch => $aSearch) { - $sHash = serialize($aSearch); - if (isset($aSearchHash[$sHash])) { - unset($aNewPhraseSearches[$iSearch]); - } else { - $aSearchHash[$sHash] = 1; - } - } - - $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50); - } - - // Re-group the searches by their score, junk anything over 20 as just not worth trying - $aGroupedSearches = array(); - foreach ($aNewPhraseSearches as $aSearch) { - $iRank = $aSearch->getRank(); - if ($iRank < $this->iMaxRank) { - if (!isset($aGroupedSearches[$iRank])) { - $aGroupedSearches[$iRank] = array(); - } - $aGroupedSearches[$iRank][] = $aSearch; - } - } - ksort($aGroupedSearches); - - $iSearchCount = 0; - $aSearches = array(); - foreach ($aGroupedSearches as $aNewSearches) { - $iSearchCount += count($aNewSearches); - $aSearches = array_merge($aSearches, $aNewSearches); - if ($iSearchCount > 50) { - break; - } - } - } - - // Revisit searches, drop bad searches and give penalty to unlikely combinations. - $aGroupedSearches = array(); - foreach ($aSearches as $oSearch) { - if (!$oSearch->isValidSearch()) { - continue; - } - - $iRank = $oSearch->getRank(); - if (!isset($aGroupedSearches[$iRank])) { - $aGroupedSearches[$iRank] = array(); - } - $aGroupedSearches[$iRank][] = $oSearch; - } - ksort($aGroupedSearches); - - return $aGroupedSearches; - } - - /* Perform the actual query lookup. - - Returns an ordered list of results, each with the following fields: - osm_type: type of corresponding OSM object - N - node - W - way - R - relation - P - postcode (internally computed) - osm_id: id of corresponding OSM object - class: general object class (corresponds to tag key of primary OSM tag) - type: subclass of object (corresponds to tag value of primary OSM tag) - admin_level: see https://wiki.openstreetmap.org/wiki/Admin_level - rank_search: rank in search hierarchy - (see also https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level) - rank_address: rank in address hierarchy (determines orer in address) - place_id: internal key (may differ between different instances) - country_code: ISO country code - langaddress: localized full address - placename: localized name of object - ref: content of ref tag (if available) - lon: longitude - lat: latitude - importance: importance of place based on Wikipedia link count - addressimportance: cumulated importance of address elements - extra_place: type of place (for admin boundaries, if there is a place tag) - aBoundingBox: bounding Box - label: short description of the object class/type (English only) - name: full name (currently the same as langaddress) - foundorder: secondary ordering for places with same importance - */ - - - public function lookup() - { - Debug::newFunction('Geocode::lookup'); - if (!$this->sQuery && !$this->aStructuredQuery) { - return array(); - } - - Debug::printDebugArray('Geocode', $this); - - $oCtx = new SearchContext(); - - if ($this->aRoutePoints) { - $oCtx->setViewboxFromRoute( - $this->oDB, - $this->aRoutePoints, - $this->aRouteWidth, - $this->bBoundedSearch - ); - } elseif ($this->aViewBox) { - $oCtx->setViewboxFromBox($this->aViewBox, $this->bBoundedSearch); - } - if ($this->aExcludePlaceIDs) { - $oCtx->setExcludeList($this->aExcludePlaceIDs); - } - if ($this->aCountryCodes) { - $oCtx->setCountryList($this->aCountryCodes); - } - - Debug::newSection('Query Preprocessing'); - - $sQuery = $this->sQuery; - if (!preg_match('//u', $sQuery)) { - userError('Query string is not UTF-8 encoded.'); - } - - // Do we have anything that looks like a lat/lon pair? - $sQuery = $oCtx->setNearPointFromQuery($sQuery); - - if ($sQuery || $this->aStructuredQuery) { - // Start with a single blank search - $aSearches = array(new SearchDescription($oCtx)); - - if ($sQuery) { - $sQuery = $aSearches[0]->extractKeyValuePairs($sQuery); - } - - $sSpecialTerm = ''; - if ($sQuery) { - preg_match_all( - '/\\[([\\w ]*)\\]/u', - $sQuery, - $aSpecialTermsRaw, - PREG_SET_ORDER - ); - if (!empty($aSpecialTermsRaw)) { - Debug::printVar('Special terms', $aSpecialTermsRaw); - } - - foreach ($aSpecialTermsRaw as $aSpecialTerm) { - $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); - if (!$sSpecialTerm) { - $sSpecialTerm = $aSpecialTerm[1]; - } - } - } - if (!$sSpecialTerm && $this->aStructuredQuery - && isset($this->aStructuredQuery['amenity'])) { - $sSpecialTerm = $this->aStructuredQuery['amenity']; - unset($this->aStructuredQuery['amenity']); - } - - if ($sSpecialTerm && !$aSearches[0]->hasOperator()) { - $aTokens = $this->oTokenizer->tokensForSpecialTerm($sSpecialTerm); - - if (!empty($aTokens)) { - $aNewSearches = array(); - $oPosition = new SearchPosition('', 0, 1); - $oPosition->setTokenPosition(0, 1); - - foreach ($aSearches as $oSearch) { - foreach ($aTokens as $oToken) { - $aNewSearches = array_merge( - $aNewSearches, - $oToken->extendSearch($oSearch, $oPosition) - ); - } - } - $aSearches = $aNewSearches; - } - } - - // Split query into phrases - // Commas are used to reduce the search space by indicating where phrases split - $aPhrases = array(); - if ($this->aStructuredQuery) { - foreach ($this->aStructuredQuery as $iPhrase => $sPhrase) { - $aPhrases[] = new Phrase($sPhrase, $iPhrase); - } - } else { - foreach (explode(',', $sQuery) as $sPhrase) { - $aPhrases[] = new Phrase($sPhrase, ''); - } - } - - Debug::printDebugArray('Search context', $oCtx); - Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]); - - Debug::newSection('Tokenization'); - $oValidTokens = $this->oTokenizer->extractTokensFromPhrases($aPhrases); - - if ($oValidTokens->count() > 0) { - $oCtx->setFullNameWords($oValidTokens->getFullWordIDs()); - - $aPhrases = array_filter($aPhrases, function ($oPhrase) { - return $oPhrase->getWordSets() !== null; - }); - - // Any words that have failed completely? - // TODO: suggestions - - Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo()); - Debug::printDebugTable('Phrases', $aPhrases); - - Debug::newSection('Search candidates'); - - $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens); - - if (!$this->aStructuredQuery) { - // Reverse phrase array and also reverse the order of the wordsets in - // the first and final phrase. Don't bother about phrases in the middle - // because order in the address doesn't matter. - $aPhrases = array_reverse($aPhrases); - $aPhrases[0]->invertWordSets(); - if (count($aPhrases) > 1) { - $aPhrases[count($aPhrases)-1]->invertWordSets(); - } - $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens); - - foreach ($aReverseGroupedSearches as $aSearches) { - foreach ($aSearches as $aSearch) { - if (!isset($aGroupedSearches[$aSearch->getRank()])) { - $aGroupedSearches[$aSearch->getRank()] = array(); - } - $aGroupedSearches[$aSearch->getRank()][] = $aSearch; - } - } - - ksort($aGroupedSearches); - } - } else { - // Re-group the searches by their score, junk anything over 20 as just not worth trying - $aGroupedSearches = array(); - foreach ($aSearches as $aSearch) { - if ($aSearch->getRank() < $this->iMaxRank) { - if (!isset($aGroupedSearches[$aSearch->getRank()])) { - $aGroupedSearches[$aSearch->getRank()] = array(); - } - $aGroupedSearches[$aSearch->getRank()][] = $aSearch; - } - } - ksort($aGroupedSearches); - } - - // Filter out duplicate searches - $aSearchHash = array(); - foreach ($aGroupedSearches as $iGroup => $aSearches) { - foreach ($aSearches as $iSearch => $aSearch) { - $sHash = serialize($aSearch); - if (isset($aSearchHash[$sHash])) { - unset($aGroupedSearches[$iGroup][$iSearch]); - if (empty($aGroupedSearches[$iGroup])) { - unset($aGroupedSearches[$iGroup]); - } - } else { - $aSearchHash[$sHash] = 1; - } - } - } - - Debug::printGroupedSearch( - $aGroupedSearches, - $oValidTokens->debugTokenByWordIdList() - ); - - // Start the search process - $iGroupLoop = 0; - $iQueryLoop = 0; - $aNextResults = array(); - foreach ($aGroupedSearches as $iGroupedRank => $aSearches) { - $iGroupLoop++; - $aResults = $aNextResults; - foreach ($aSearches as $oSearch) { - $iQueryLoop++; - - Debug::newSection("Search Loop, group $iGroupLoop, loop $iQueryLoop"); - Debug::printGroupedSearch( - array($iGroupedRank => array($oSearch)), - $oValidTokens->debugTokenByWordIdList() - ); - - $aNewResults = $oSearch->query( - $this->oDB, - $this->iMinAddressRank, - $this->iMaxAddressRank, - $this->iLimit - ); - - // The same result may appear in different rounds, only - // use the one with minimal rank. - foreach ($aNewResults as $iPlace => $oRes) { - if (!isset($aResults[$iPlace]) - || $aResults[$iPlace]->iResultRank > $oRes->iResultRank) { - $aResults[$iPlace] = $oRes; - } - } - - if ($iQueryLoop > 20) { - break; - } - } - - if (!empty($aResults)) { - $aSplitResults = Result::splitResults($aResults); - Debug::printVar('Split results', $aSplitResults); - if ($iGroupLoop <= 4 - && reset($aSplitResults['head'])->iResultRank > 0 - && $iGroupedRank !== array_key_last($aGroupedSearches)) { - // Haven't found an exact match for the query yet. - // Therefore add result from the next group level. - $aNextResults = $aSplitResults['head']; - foreach ($aNextResults as $oRes) { - $oRes->iResultRank--; - } - foreach ($aSplitResults['tail'] as $oRes) { - $oRes->iResultRank--; - $aNextResults[$oRes->iId] = $oRes; - } - $aResults = array(); - } else { - $aResults = $aSplitResults['head']; - } - } - - if (!empty($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) { - // Need to verify passes rank limits before dropping out of the loop (yuk!) - // reduces the number of place ids, like a filter - // rank_address is 30 for interpolated housenumbers - $aFilterSql = array(); - $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX); - if ($sPlaceIds) { - $sSQL = 'SELECT place_id FROM placex '; - $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') '; - $sSQL .= ' AND ('; - $sSQL .= " placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; - $sSQL .= " OR placex.rank_search between $this->iMinAddressRank and $this->iMaxAddressRank "; - if ($this->aAddressRankList) { - $sSQL .= ' OR placex.rank_address in ('.join(',', $this->aAddressRankList).')'; - } - $sSQL .= ')'; - $aFilterSql[] = $sSQL; - } - $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE); - if ($sPlaceIds) { - $sSQL = ' SELECT place_id FROM location_postcode lp '; - $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') '; - $sSQL .= " AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank "; - if ($this->aAddressRankList) { - $sSQL .= ' OR lp.rank_address in ('.join(',', $this->aAddressRankList).')'; - } - $sSQL .= ') '; - $aFilterSql[] = $sSQL; - } - - $aFilteredIDs = array(); - if ($aFilterSql) { - $sSQL = join(' UNION ', $aFilterSql); - Debug::printSQL($sSQL); - $aFilteredIDs = $this->oDB->getCol($sSQL); - } - - $tempIDs = array(); - foreach ($aResults as $oResult) { - if (($this->iMaxAddressRank == 30 && - ($oResult->iTable == Result::TABLE_OSMLINE - || $oResult->iTable == Result::TABLE_TIGER)) - || in_array($oResult->iId, $aFilteredIDs) - ) { - $tempIDs[$oResult->iId] = $oResult; - } - } - $aResults = $tempIDs; - } - - if (!empty($aResults) || $iGroupLoop > 4 || $iQueryLoop > 30) { - break; - } - } - } else { - // Just interpret as a reverse geocode - $oReverse = new ReverseGeocode($this->oDB); - $oReverse->setZoom(18); - - $oLookup = $oReverse->lookupPoint($oCtx->sqlNear, false); - - Debug::printVar('Reverse search', $oLookup); - - if ($oLookup) { - $aResults = array($oLookup->iId => $oLookup); - } - } - - // No results? Done - if (empty($aResults)) { - if ($this->bFallback && $this->fallbackStructuredQuery()) { - return $this->lookup(); - } - - return array(); - } - - if ($this->aAddressRankList) { - $this->oPlaceLookup->setAddressRankList($this->aAddressRankList); - } - $this->oPlaceLookup->setAllowedTypesSQLList($this->sAllowedTypesSQLList); - $this->oPlaceLookup->setLanguagePreference($this->aLangPrefOrder); - if ($oCtx->hasNearPoint()) { - $this->oPlaceLookup->setAnchorSql($oCtx->sqlNear); - } - - $aSearchResults = $this->oPlaceLookup->lookup($aResults); - - $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery); - foreach ($aRecheckWords as $i => $sWord) { - if (!preg_match('/[\pL\pN]/', $sWord)) { - unset($aRecheckWords[$i]); - } - } - - Debug::printVar('Recheck words', $aRecheckWords); - - foreach ($aSearchResults as $iIdx => $aResult) { - $fRadius = ClassTypes\getDefRadius($aResult); - - $aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius); - if ($aOutlineResult) { - $aResult = array_merge($aResult, $aOutlineResult); - } - - // Is there an icon set for this type of result? - $sIcon = ClassTypes\getIconFile($aResult); - if (isset($sIcon)) { - $aResult['icon'] = $sIcon; - } - - $sLabel = ClassTypes\getLabel($aResult); - if (isset($sLabel)) { - $aResult['label'] = $sLabel; - } - $aResult['name'] = $aResult['langaddress']; - - if ($oCtx->hasNearPoint()) { - $aResult['importance'] = 0.001; - $aResult['foundorder'] = $aResult['addressimportance']; - } else { - if ($aResult['importance'] == 0) { - $aResult['importance'] = 0.0001; - } - $aResult['importance'] *= $this->viewboxImportanceFactor( - $aResult['lon'], - $aResult['lat'] - ); - - // secondary ordering (for results with same importance (the smaller the better): - // - approximate importance of address parts - if (isset($aResult['addressimportance']) && $aResult['addressimportance']) { - $aResult['foundorder'] = -$aResult['addressimportance']/10; - } else { - $aResult['foundorder'] = -$aResult['importance']; - } - // - number of exact matches from the query - $aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches; - // - importance of the class/type - $iClassImportance = ClassTypes\getImportance($aResult); - if (isset($iClassImportance)) { - $aResult['foundorder'] += 0.0001 * $iClassImportance; - } else { - $aResult['foundorder'] += 0.01; - } - // - rank - $aResult['foundorder'] -= 0.00001 * (30 - $aResult['rank_search']); - - // Adjust importance for the number of exact string matches in the result - $iCountWords = 0; - $sAddress = $aResult['langaddress']; - foreach ($aRecheckWords as $i => $sWord) { - if (grapheme_stripos($sAddress, $sWord)!==false) { - $iCountWords++; - if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) { - $iCountWords += 0.1; - } - } - } - - // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right - $aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1); - } - $aSearchResults[$iIdx] = $aResult; - } - uasort($aSearchResults, 'byImportance'); - Debug::printVar('Pre-filter results', $aSearchResults); - - $aOSMIDDone = array(); - $aClassTypeNameDone = array(); - $aToFilter = $aSearchResults; - $aSearchResults = array(); - - foreach ($aToFilter as $aResult) { - $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id']; - if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) - && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])) - ) { - $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; - $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true; - $aSearchResults[] = $aResult; - } - - // Absolute limit on number of results - if (count($aSearchResults) >= $this->iFinalLimit) { - break; - } - } - - Debug::printVar('Post-filter results', $aSearchResults); - return $aSearchResults; - } // end lookup() - - public function debugInfo() - { - return array( - 'Query' => $this->sQuery, - 'Structured query' => $this->aStructuredQuery, - 'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder), - 'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs), - 'Limit (for searches)' => $this->iLimit, - 'Limit (for results)'=> $this->iFinalLimit, - 'Country codes' => Debug::fmtArrayVals($this->aCountryCodes), - 'Bounded search' => $this->bBoundedSearch, - 'Viewbox' => Debug::fmtArrayVals($this->aViewBox), - 'Route points' => Debug::fmtArrayVals($this->aRoutePoints), - 'Route width' => $this->aRouteWidth, - 'Max rank' => $this->iMaxRank, - 'Min address rank' => $this->iMinAddressRank, - 'Max address rank' => $this->iMaxAddressRank, - 'Address rank list' => Debug::fmtArrayVals($this->aAddressRankList) - ); - } -} // end class diff --git a/lib-php/ParameterParser.php b/lib-php/ParameterParser.php deleted file mode 100644 index a4936d376d..0000000000 --- a/lib-php/ParameterParser.php +++ /dev/null @@ -1,157 +0,0 @@ -aParams = ($aParams === null) ? $_GET : $aParams; - } - - public function getBool($sName, $bDefault = false) - { - if (!isset($this->aParams[$sName]) - || !is_string($this->aParams[$sName]) - || strlen($this->aParams[$sName]) == 0 - ) { - return $bDefault; - } - - return (bool) $this->aParams[$sName]; - } - - public function getInt($sName, $bDefault = false) - { - if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) { - return $bDefault; - } - - if (!preg_match('/^[+-]?[0-9]+$/', $this->aParams[$sName])) { - userError("Integer number expected for parameter '$sName'"); - } - - return (int) $this->aParams[$sName]; - } - - public function getFloat($sName, $bDefault = false) - { - if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) { - return $bDefault; - } - - if (!preg_match('/^[+-]?[0-9]*\.?[0-9]+$/', $this->aParams[$sName])) { - userError("Floating-point number expected for parameter '$sName'"); - } - - return (float) $this->aParams[$sName]; - } - - public function getString($sName, $bDefault = false) - { - if (!isset($this->aParams[$sName]) - || !is_string($this->aParams[$sName]) - || strlen($this->aParams[$sName]) == 0 - ) { - return $bDefault; - } - - return $this->aParams[$sName]; - } - - public function getSet($sName, $aValues, $sDefault = false) - { - if (!isset($this->aParams[$sName]) - || !is_string($this->aParams[$sName]) - || strlen($this->aParams[$sName]) == 0 - ) { - return $sDefault; - } - - if (!in_array($this->aParams[$sName], $aValues, true)) { - userError("Parameter '$sName' must be one of: ".join(', ', $aValues)); - } - - return $this->aParams[$sName]; - } - - public function getStringList($sName, $aDefault = false) - { - $sValue = $this->getString($sName); - - if ($sValue) { - // removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values - return array_values(array_filter(explode(',', $sValue), 'strlen')); - } - - return $aDefault; - } - - public function getPreferredLanguages($sFallback = null) - { - if ($sFallback === null && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $sFallback = $_SERVER['HTTP_ACCEPT_LANGUAGE']; - } - - $aLanguages = array(); - $sLangString = $this->getString('accept-language', $sFallback); - - if ($sLangString - && preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER) - ) { - foreach ($aLanguagesParse as $iLang => $aLanguage) { - $aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100); - if (!isset($aLanguages[$aLanguage[2]])) { - $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10; - } - } - arsort($aLanguages); - } - if (empty($aLanguages) && CONST_Default_Language) { - $aLanguages[CONST_Default_Language] = 1; - } - - foreach ($aLanguages as $sLanguage => $fLanguagePref) { - $this->addNameTag($aLangPrefOrder, 'name:'.$sLanguage); - } - $this->addNameTag($aLangPrefOrder, 'name'); - $this->addNameTag($aLangPrefOrder, 'brand'); - foreach ($aLanguages as $sLanguage => $fLanguagePref) { - $this->addNameTag($aLangPrefOrder, 'official_name:'.$sLanguage); - $this->addNameTag($aLangPrefOrder, 'short_name:'.$sLanguage); - } - $this->addNameTag($aLangPrefOrder, 'official_name'); - $this->addNameTag($aLangPrefOrder, 'short_name'); - $this->addNameTag($aLangPrefOrder, 'ref'); - $this->addNameTag($aLangPrefOrder, 'type'); - return $aLangPrefOrder; - } - - private function addNameTag(&$aLangPrefOrder, $sTag) - { - $aLangPrefOrder[$sTag] = $sTag; - $aLangPrefOrder['_place_'.$sTag] = '_place_'.$sTag; - } - - public function hasSetAny($aParamNames) - { - foreach ($aParamNames as $sName) { - if ($this->getBool($sName)) { - return true; - } - } - - return false; - } -} diff --git a/lib-php/Phrase.php b/lib-php/Phrase.php deleted file mode 100644 index 4ed4d402f6..0000000000 --- a/lib-php/Phrase.php +++ /dev/null @@ -1,89 +0,0 @@ -sPhrase = trim($sPhrase); - $this->sPhraseType = $sPhraseType; - } - - /** - * Get the original phrase of the string. - */ - public function getPhrase() - { - return $this->sPhrase; - } - - /** - * Return the element type of the phrase. - * - * @return string Pharse type if the phrase comes from a structured query - * or empty string otherwise. - */ - public function getPhraseType() - { - return $this->sPhraseType; - } - - public function setWordSets($aWordSets) - { - $this->aWordSets = $aWordSets; - } - - /** - * Return the array of possible segmentations of the phrase. - * - * @return string[][] Array of segmentations, each consisting of an - * array of terms. - */ - public function getWordSets() - { - return $this->aWordSets; - } - - /** - * Invert the set of possible segmentations. - * - * @return void - */ - public function invertWordSets() - { - foreach ($this->aWordSets as $i => $aSet) { - $this->aWordSets[$i] = array_reverse($aSet); - } - } - - public function debugInfo() - { - return array( - 'Type' => $this->sPhraseType, - 'Phrase' => $this->sPhrase, - 'WordSets' => $this->aWordSets - ); - } -} diff --git a/lib-php/PlaceLookup.php b/lib-php/PlaceLookup.php deleted file mode 100644 index 895a30dfb8..0000000000 --- a/lib-php/PlaceLookup.php +++ /dev/null @@ -1,615 +0,0 @@ -oDB =& $oDB; - } - - public function doDeDupe() - { - return $this->bDeDupe; - } - - public function setIncludeAddressDetails($b) - { - $this->bAddressDetails = $b; - } - - public function loadParamArray($oParams, $sGeomType = null) - { - $aLangs = $oParams->getPreferredLanguages(); - $this->aLangPrefOrderSql = - 'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']'; - - $this->bExtraTags = $oParams->getBool('extratags', false); - $this->bNameDetails = $oParams->getBool('namedetails', false); - - $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe); - - if ($sGeomType === null || $sGeomType == 'geojson') { - $this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson'); - } - - if ($oParams->getString('format', '') !== 'geojson') { - if ($sGeomType === null || $sGeomType == 'text') { - $this->bIncludePolygonAsText = $oParams->getBool('polygon_text'); - } - if ($sGeomType === null || $sGeomType == 'kml') { - $this->bIncludePolygonAsKML = $oParams->getBool('polygon_kml'); - } - if ($sGeomType === null || $sGeomType == 'svg') { - $this->bIncludePolygonAsSVG = $oParams->getBool('polygon_svg'); - } - } - $this->fPolygonSimplificationThreshold - = $oParams->getFloat('polygon_threshold', 0.0); - - $iWantedTypes = - ($this->bIncludePolygonAsText ? 1 : 0) + - ($this->bIncludePolygonAsGeoJSON ? 1 : 0) + - ($this->bIncludePolygonAsKML ? 1 : 0) + - ($this->bIncludePolygonAsSVG ? 1 : 0); - if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) { - if (CONST_PolygonOutput_MaximumTypes) { - userError('Select only '.CONST_PolygonOutput_MaximumTypes.' polygon output option'); - } else { - userError('Polygon output is disabled'); - } - } - } - - public function getMoreUrlParams() - { - $aParams = array(); - - if ($this->bAddressDetails) { - $aParams['addressdetails'] = '1'; - } - if ($this->bExtraTags) { - $aParams['extratags'] = '1'; - } - if ($this->bNameDetails) { - $aParams['namedetails'] = '1'; - } - - if ($this->bIncludePolygonAsText) { - $aParams['polygon_text'] = '1'; - } - if ($this->bIncludePolygonAsGeoJSON) { - $aParams['polygon_geojson'] = '1'; - } - if ($this->bIncludePolygonAsKML) { - $aParams['polygon_kml'] = '1'; - } - if ($this->bIncludePolygonAsSVG) { - $aParams['polygon_svg'] = '1'; - } - - if ($this->fPolygonSimplificationThreshold > 0.0) { - $aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold; - } - - if (!$this->bDeDupe) { - $aParams['dedupe'] = '0'; - } - - return $aParams; - } - - public function setAnchorSql($sPoint) - { - $this->sAnchorSql = $sPoint; - } - - public function setAddressRankList($aList) - { - $this->sAddressRankListSql = '('.join(',', $aList).')'; - } - - public function setAllowedTypesSQLList($sSql) - { - $this->sAllowedTypesSQLList = $sSql; - } - - public function setLanguagePreference($aLangPrefOrder) - { - $this->aLangPrefOrderSql = $this->oDB->getArraySQL( - $this->oDB->getDBQuotedList($aLangPrefOrder) - ); - } - - private function addressImportanceSql($sGeometry, $sPlaceId) - { - if ($this->sAnchorSql) { - $sSQL = 'ST_Distance('.$this->sAnchorSql.','.$sGeometry.')'; - } else { - $sSQL = '(SELECT max(ai_p.importance * (ai_p.rank_address + 2))'; - $sSQL .= ' FROM place_addressline ai_s, placex ai_p'; - $sSQL .= ' WHERE ai_s.place_id = '.$sPlaceId; - $sSQL .= ' AND ai_p.place_id = ai_s.address_place_id '; - $sSQL .= ' AND ai_s.isaddress '; - $sSQL .= ' AND ai_p.importance is not null)'; - } - - return $sSQL.' AS addressimportance,'; - } - - private function langAddressSql($sHousenumber) - { - if ($this->bAddressDetails) { - return ''; // langaddress will be computed from address details - } - - return 'get_address_by_language(place_id,'.$sHousenumber.','.$this->aLangPrefOrderSql.') AS langaddress,'; - } - - public function lookupOSMID($sType, $iID) - { - $sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id'; - $iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID)); - - if (!$iPlaceID) { - return null; - } - - $aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)), 0, 30, true); - - return empty($aResults) ? null : reset($aResults); - } - - public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30, $bAllowLinked = false) - { - Debug::newFunction('Place lookup'); - - if (empty($aResults)) { - return array(); - } - $aSubSelects = array(); - - $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX); - if ($sPlaceIDs) { - Debug::printVar('Ids from placex', $sPlaceIDs); - $sSQL = 'SELECT '; - $sSQL .= ' osm_type,'; - $sSQL .= ' osm_id,'; - $sSQL .= ' class,'; - $sSQL .= ' type,'; - $sSQL .= ' admin_level,'; - $sSQL .= ' rank_search,'; - $sSQL .= ' rank_address,'; - $sSQL .= ' min(place_id) AS place_id,'; - $sSQL .= ' min(parent_place_id) AS parent_place_id,'; - $sSQL .= ' -1 as housenumber,'; - $sSQL .= ' country_code,'; - $sSQL .= $this->langAddressSql('-1'); - $sSQL .= ' get_name_by_language(name,'.$this->aLangPrefOrderSql.') AS placename,'; - $sSQL .= " get_name_by_language(name, ARRAY['ref']) AS ref,"; - if ($this->bExtraTags) { - $sSQL .= 'hstore_to_json(extratags)::text AS extra,'; - } - if ($this->bNameDetails) { - $sSQL .= 'hstore_to_json(name)::text AS names,'; - } - $sSQL .= ' avg(ST_X(centroid)) AS lon, '; - $sSQL .= ' avg(ST_Y(centroid)) AS lat, '; - $sSQL .= ' COALESCE(importance,0.75-(rank_search::float/40)) AS importance, '; - $sSQL .= $this->addressImportanceSql( - 'ST_Collect(centroid)', - 'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)' - ); - $sSQL .= " COALESCE(extratags->'place', extratags->'linked_place') AS extra_place "; - $sSQL .= ' FROM placex'; - $sSQL .= " WHERE place_id in ($sPlaceIDs) "; - $sSQL .= ' AND ('; - $sSQL .= " placex.rank_address between $iMinRank and $iMaxRank "; - if (14 >= $iMinRank && 14 <= $iMaxRank) { - $sSQL .= " OR (extratags->'place') = 'city'"; - } - if ($this->sAddressRankListSql) { - $sSQL .= ' OR placex.rank_address in '.$this->sAddressRankListSql; - } - $sSQL .= ' ) '; - if ($this->sAllowedTypesSQLList) { - $sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList; - } - if (!$bAllowLinked) { - $sSQL .= ' AND linked_place_id is null '; - } - $sSQL .= ' GROUP BY '; - $sSQL .= ' osm_type, '; - $sSQL .= ' osm_id, '; - $sSQL .= ' class, '; - $sSQL .= ' type, '; - $sSQL .= ' admin_level, '; - $sSQL .= ' rank_search, '; - $sSQL .= ' rank_address, '; - $sSQL .= ' housenumber,'; - $sSQL .= ' country_code, '; - $sSQL .= ' importance, '; - if (!$this->bDeDupe) { - $sSQL .= 'place_id,'; - } - if (!$this->bAddressDetails) { - $sSQL .= 'langaddress, '; - } - $sSQL .= ' placename, '; - $sSQL .= ' ref, '; - if ($this->bExtraTags) { - $sSQL .= 'extratags, '; - } - if ($this->bNameDetails) { - $sSQL .= 'name, '; - } - $sSQL .= ' extra_place '; - - $aSubSelects[] = $sSQL; - } - - // postcode table - $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE); - if ($sPlaceIDs) { - Debug::printVar('Ids from location_postcode', $sPlaceIDs); - $sSQL = 'SELECT'; - $sSQL .= " 'P' as osm_type,"; - $sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,'; - $sSQL .= " 'place' as class, 'postcode' as type,"; - $sSQL .= ' null::smallint as admin_level, rank_search, rank_address,'; - $sSQL .= ' place_id, parent_place_id,'; - $sSQL .= ' -1 as housenumber,'; - $sSQL .= ' country_code,'; - $sSQL .= $this->langAddressSql('-1'); - $sSQL .= ' postcode as placename,'; - $sSQL .= ' postcode as ref,'; - if ($this->bExtraTags) { - $sSQL .= 'null::text AS extra,'; - } - if ($this->bNameDetails) { - $sSQL .= 'null::text AS names,'; - } - $sSQL .= ' ST_x(geometry) AS lon, ST_y(geometry) AS lat,'; - $sSQL .= ' (0.75-(rank_search::float/40)) AS importance, '; - $sSQL .= $this->addressImportanceSql('geometry', 'lp.parent_place_id'); - $sSQL .= ' null::text AS extra_place '; - $sSQL .= 'FROM location_postcode lp'; - $sSQL .= " WHERE place_id in ($sPlaceIDs) "; - $sSQL .= " AND lp.rank_address between $iMinRank and $iMaxRank"; - - $aSubSelects[] = $sSQL; - } - - // All other tables are rank 30 only. - if ($iMaxRank == 30) { - // TIGER table - if (CONST_Use_US_Tiger_Data) { - $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_TIGER); - if ($sPlaceIDs) { - Debug::printVar('Ids from Tiger table', $sPlaceIDs); - $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_TIGER); - // Tiger search only if a housenumber was searched and if it was found - // (realized through a join) - $sSQL = ' SELECT '; - $sSQL .= " 'T' AS osm_type, "; - $sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id=blub.parent_place_id) as osm_id, '; - $sSQL .= " 'place' AS class, "; - $sSQL .= " 'house' AS type, "; - $sSQL .= ' null::smallint AS admin_level, '; - $sSQL .= ' 30 AS rank_search, '; - $sSQL .= ' 30 AS rank_address, '; - $sSQL .= ' place_id, '; - $sSQL .= ' parent_place_id, '; - $sSQL .= ' housenumber_for_place as housenumber,'; - $sSQL .= " 'us' AS country_code, "; - $sSQL .= $this->langAddressSql('housenumber_for_place'); - $sSQL .= ' null::text AS placename, '; - $sSQL .= ' null::text AS ref, '; - if ($this->bExtraTags) { - $sSQL .= 'null::text AS extra,'; - } - if ($this->bNameDetails) { - $sSQL .= 'null::text AS names,'; - } - $sSQL .= ' st_x(centroid) AS lon, '; - $sSQL .= ' st_y(centroid) AS lat,'; - $sSQL .= ' -1.15 AS importance, '; - $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id'); - $sSQL .= ' null::text AS extra_place '; - $sSQL .= ' FROM ('; - $sSQL .= ' SELECT place_id, '; // interpolate the Tiger housenumbers here - $sSQL .= ' CASE WHEN startnumber != endnumber'; - $sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float)'; - $sSQL .= ' ELSE ST_LineInterpolatePoint(linegeo, 0.5) END AS centroid, '; - $sSQL .= ' parent_place_id, '; - $sSQL .= ' housenumber_for_place'; - $sSQL .= ' FROM ('; - $sSQL .= ' location_property_tiger '; - $sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)) '; - $sSQL .= ' WHERE '; - $sSQL .= ' housenumber_for_place >= startnumber'; - $sSQL .= ' AND housenumber_for_place <= endnumber'; - $sSQL .= ' ) AS blub'; //postgres wants an alias here - - $aSubSelects[] = $sSQL; - } - } - - // osmline - interpolated housenumbers - $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_OSMLINE); - if ($sPlaceIDs) { - Debug::printVar('Ids from interpolation', $sPlaceIDs); - $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_OSMLINE); - // interpolation line search only if a housenumber was searched - // (realized through a join) - $sSQL = 'SELECT '; - $sSQL .= " 'W' AS osm_type, "; - $sSQL .= ' osm_id, '; - $sSQL .= " 'place' AS class, "; - $sSQL .= " 'house' AS type, "; - $sSQL .= ' null::smallint AS admin_level, '; - $sSQL .= ' 30 AS rank_search, '; - $sSQL .= ' 30 AS rank_address, '; - $sSQL .= ' place_id, '; - $sSQL .= ' parent_place_id, '; - $sSQL .= ' housenumber_for_place as housenumber,'; - $sSQL .= ' country_code, '; - $sSQL .= $this->langAddressSql('housenumber_for_place'); - $sSQL .= ' null::text AS placename, '; - $sSQL .= ' null::text AS ref, '; - if ($this->bExtraTags) { - $sSQL .= 'null::text AS extra, '; - } - if ($this->bNameDetails) { - $sSQL .= 'null::text AS names, '; - } - $sSQL .= ' st_x(centroid) AS lon, '; - $sSQL .= ' st_y(centroid) AS lat, '; - // slightly smaller than the importance for normal houses - $sSQL .= ' -0.1 AS importance, '; - $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id'); - $sSQL .= ' null::text AS extra_place '; - $sSQL .= ' FROM ('; - $sSQL .= ' SELECT '; - $sSQL .= ' osm_id, '; - $sSQL .= ' place_id, '; - $sSQL .= ' country_code, '; - $sSQL .= ' CASE '; // interpolate the housenumbers here - $sSQL .= ' WHEN startnumber != endnumber '; - $sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) '; - $sSQL .= ' ELSE linegeo '; - $sSQL .= ' END as centroid, '; - $sSQL .= ' parent_place_id, '; - $sSQL .= ' housenumber_for_place '; - $sSQL .= ' FROM ('; - $sSQL .= ' location_property_osmline '; - $sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)'; - $sSQL .= ' ) '; - $sSQL .= ' WHERE housenumber_for_place >= 0 '; - $sSQL .= ' ) as blub'; //postgres wants an alias here - - $aSubSelects[] = $sSQL; - } - } - - if (empty($aSubSelects)) { - return array(); - } - - $sSQL = join(' UNION ', $aSubSelects); - Debug::printSQL($sSQL); - $aPlaces = $this->oDB->getAll($sSQL, null, 'Could not lookup place'); - - foreach ($aPlaces as &$aPlace) { - $aPlace['importance'] = (float) $aPlace['importance']; - if ($this->bAddressDetails) { - // to get addressdetails for tiger data, the housenumber is needed - $aPlace['address'] = new AddressDetails( - $this->oDB, - $aPlace['place_id'], - $aPlace['housenumber'], - $this->aLangPrefOrderSql - ); - $aPlace['langaddress'] = $aPlace['address']->getLocaleAddress(); - } - - if ($this->bExtraTags) { - if ($aPlace['extra']) { - $aPlace['sExtraTags'] = json_decode($aPlace['extra'], true); - } else { - $aPlace['sExtraTags'] = (object) array(); - } - } - - if ($this->bNameDetails) { - $aPlace['sNameDetails'] = $this->extractNames($aPlace['names']); - } - - $aPlace['addresstype'] = ClassTypes\getLabelTag( - $aPlace, - $aPlace['country_code'] - ); - - $aResults[$aPlace['place_id']] = $aPlace; - } - - $aResults = array_filter( - $aResults, - function ($v) { - return !($v instanceof Result); - } - ); - - Debug::printVar('Places', $aResults); - - return $aResults; - } - - - private function extractNames($sNames) - { - if (!$sNames) { - return (object) array(); - } - - $aFullNames = json_decode($sNames, true); - $aNames = array(); - - foreach ($aFullNames as $sKey => $sValue) { - if (strpos($sKey, '_place_') === 0) { - $sSubKey = substr($sKey, 7); - if (array_key_exists($sSubKey, $aFullNames)) { - $aNames[$sKey] = $sValue; - } else { - $aNames[$sSubKey] = $sValue; - } - } else { - $aNames[$sKey] = $sValue; - } - } - - return $aNames; - } - - - /* returns an array which will contain the keys - * aBoundingBox - * and may also contain one or more of the keys - * asgeojson - * askml - * assvg - * astext - * lat - * lon - */ - public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null, $fLonReverse = null, $fLatReverse = null) - { - - $aOutlineResult = array(); - if (!$iPlaceID) { - return $aOutlineResult; - } - - // Get the bounding box and outline polygon - $sSQL = 'select place_id,0 as numfeatures,st_area(geometry) as area,'; - $sSQL .= ' ST_Y(centroid) as centrelat, ST_X(centroid) as centrelon,'; - $sSQL .= ' ST_YMin(geometry) as minlat,ST_YMax(geometry) as maxlat,'; - $sSQL .= ' ST_XMin(geometry) as minlon,ST_XMax(geometry) as maxlon'; - if ($this->bIncludePolygonAsGeoJSON) { - $sSQL .= ',ST_AsGeoJSON(geometry) as asgeojson'; - } - if ($this->bIncludePolygonAsKML) { - $sSQL .= ',ST_AsKML(geometry) as askml'; - } - if ($this->bIncludePolygonAsSVG) { - $sSQL .= ',ST_AsSVG(geometry) as assvg'; - } - if ($this->bIncludePolygonAsText) { - $sSQL .= ',ST_AsText(geometry) as astext'; - } - - $sSQL .= ' FROM (SELECT place_id'; - if ($fLonReverse != null && $fLatReverse != null) { - $sSQL .= ',CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN '; - $sSQL .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))'; - $sSQL .=' ELSE centroid END AS centroid'; - } else { - $sSQL .= ',centroid'; - } - if ($this->fPolygonSimplificationThreshold > 0) { - $sSQL .= ',ST_SimplifyPreserveTopology(geometry,'.$this->fPolygonSimplificationThreshold.') as geometry'; - } else { - $sSQL .= ',geometry'; - } - $sSQL .= ' FROM placex where place_id = '.$iPlaceID.') as plx'; - - $aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline'); - - if ($aPointPolygon && $aPointPolygon['place_id']) { - if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) { - $aOutlineResult['lat'] = $aPointPolygon['centrelat']; - $aOutlineResult['lon'] = $aPointPolygon['centrelon']; - } - - if ($this->bIncludePolygonAsGeoJSON) { - $aOutlineResult['asgeojson'] = $aPointPolygon['asgeojson']; - } - if ($this->bIncludePolygonAsKML) { - $aOutlineResult['askml'] = $aPointPolygon['askml']; - } - if ($this->bIncludePolygonAsSVG) { - $aOutlineResult['assvg'] = $aPointPolygon['assvg']; - } - if ($this->bIncludePolygonAsText) { - $aOutlineResult['astext'] = $aPointPolygon['astext']; - } - - if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) { - $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius; - $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius; - } - - if (abs($aPointPolygon['minlon'] - $aPointPolygon['maxlon']) < 0.0000001) { - $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius; - $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius; - } - - $aOutlineResult['aBoundingBox'] = array( - (string)$aPointPolygon['minlat'], - (string)$aPointPolygon['maxlat'], - (string)$aPointPolygon['minlon'], - (string)$aPointPolygon['maxlon'] - ); - } - - // as a fallback we generate a bounding box without knowing the size of the geometry - if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) { - $aBounds = array( - 'minlat' => $fLat - $fRadius, - 'maxlat' => $fLat + $fRadius, - 'minlon' => $fLon - $fRadius, - 'maxlon' => $fLon + $fRadius - ); - - $aOutlineResult['aBoundingBox'] = array( - (string)$aBounds['minlat'], - (string)$aBounds['maxlat'], - (string)$aBounds['minlon'], - (string)$aBounds['maxlon'] - ); - } - return $aOutlineResult; - } -} diff --git a/lib-php/Result.php b/lib-php/Result.php deleted file mode 100644 index 4b244d1d2d..0000000000 --- a/lib-php/Result.php +++ /dev/null @@ -1,129 +0,0 @@ - $this->iTable, - 'ID' => $this->iId, - 'House number' => $this->iHouseNumber, - 'Exact Matches' => $this->iExactMatches, - 'Result rank' => $this->iResultRank - ); - } - - - public function __construct($sId, $iTable = Result::TABLE_PLACEX) - { - $this->iTable = $iTable; - $this->iId = (int) $sId; - } - - public static function joinIdsByTable($aResults, $iTable) - { - return join(',', array_keys(array_filter( - $aResults, - function ($aValue) use ($iTable) { - return $aValue->iTable == $iTable; - } - ))); - } - - public static function joinIdsByTableMinRank($aResults, $iTable, $iMinAddressRank) - { - return join(',', array_keys(array_filter( - $aResults, - function ($aValue) use ($iTable, $iMinAddressRank) { - return $aValue->iTable == $iTable && $aValue->iAddressRank >= $iMinAddressRank; - } - ))); - } - - public static function joinIdsByTableMaxRank($aResults, $iTable, $iMaxAddressRank) - { - return join(',', array_keys(array_filter( - $aResults, - function ($aValue) use ($iTable, $iMaxAddressRank) { - return $aValue->iTable == $iTable && $aValue->iAddressRank <= $iMaxAddressRank; - } - ))); - } - - public static function sqlHouseNumberTable($aResults, $iTable) - { - $sHousenumbers = ''; - $sSep = ''; - foreach ($aResults as $oResult) { - if ($oResult->iTable == $iTable) { - $sHousenumbers .= $sSep.'('.$oResult->iId.','; - $sHousenumbers .= $oResult->iHouseNumber.')'; - $sSep = ','; - } - } - - return $sHousenumbers; - } - - /** - * Split a result array into highest ranked result and the rest - * - * @param object[] $aResults List of results to split. - * - * @return array[] - */ - public static function splitResults($aResults) - { - $aHead = array(); - $aTail = array(); - $iMinRank = 10000; - - foreach ($aResults as $oRes) { - if ($oRes->iResultRank < $iMinRank) { - $aTail += $aHead; - $aHead = array($oRes->iId => $oRes); - $iMinRank = $oRes->iResultRank; - } elseif ($oRes->iResultRank == $iMinRank) { - $aHead[$oRes->iId] = $oRes; - } else { - $aTail[$oRes->iId] = $oRes; - } - } - - return array('head' => $aHead, 'tail' => $aTail); - } -} diff --git a/lib-php/ReverseGeocode.php b/lib-php/ReverseGeocode.php deleted file mode 100644 index f6ea590fb7..0000000000 --- a/lib-php/ReverseGeocode.php +++ /dev/null @@ -1,401 +0,0 @@ -oDB =& $oDB; - } - - - public 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 - 13 => 19, // Village - 14 => 22, // Neighbourhood - 15 => 25, // Locality - 16 => 26, // major street - 17 => 27, // minor street - 18 => 30, // or >, Building - 19 => 30, // or >, Building - ); - $this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28; - } - - /** - * Find the closest interpolation with the given search diameter. - * - * @param string $sPointSQL Reverse geocoding point as SQL - * @param float $fSearchDiam Search diameter - * - * @return Record of the interpolation or null. - */ - protected function lookupInterpolation($sPointSQL, $fSearchDiam) - { - Debug::newFunction('lookupInterpolation'); - $sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,'; - $sSQL .= ' (CASE WHEN endnumber != startnumber'; - $sSQL .= ' THEN (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.')'; - $sSQL .= ' ELSE startnumber END) as fhnr,'; - $sSQL .= ' startnumber, endnumber, step,'; - $sSQL .= ' ST_Distance(linegeo,'.$sPointSQL.') as distance'; - $sSQL .= ' FROM location_property_osmline'; - $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')'; - $sSQL .= ' and indexed_status = 0 and startnumber is not NULL '; - $sSQL .= ' and parent_place_id != 0'; - $sSQL .= ' ORDER BY distance ASC limit 1'; - Debug::printSQL($sSQL); - - return $this->oDB->getRow( - $sSQL, - null, - 'Could not determine closest housenumber on an osm interpolation line.' - ); - } - - protected function lookupLargeArea($sPointSQL, $iMaxRank) - { - $sCountryCode = $this->getCountryCode($sPointSQL); - if (CONST_Search_WithinCountries and $sCountryCode == null) { - return null; - } - - if ($iMaxRank > 4) { - $aPlace = $this->lookupPolygon($sPointSQL, $iMaxRank); - if ($aPlace) { - return new Result($aPlace['place_id']); - } - } - - // If no polygon which contains the searchpoint is found, - // searches in the country_osm_grid table for a polygon. - return $this->lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode); - } - - protected function getCountryCode($sPointSQL) - { - Debug::newFunction('getCountryCode'); - // searches for polygon in table country_osm_grid which contains the searchpoint - // and searches for the nearest place node to the searchpoint in this polygon - $sSQL = 'SELECT country_code FROM country_osm_grid'; - $sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1'; - Debug::printSQL($sSQL); - - $sCountryCode = $this->oDB->getOne( - $sSQL, - null, - 'Could not determine country polygon containing the point.' - ); - return $sCountryCode; - } - - protected function lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode) - { - Debug::newFunction('lookupInCountry'); - if ($sCountryCode) { - if ($iMaxRank > 4) { - // look for place nodes with the given country code - $sSQL = 'SELECT place_id FROM'; - $sSQL .= ' (SELECT place_id, rank_search,'; - $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance'; - $sSQL .= ' FROM placex'; - $sSQL .= ' WHERE osm_type = \'N\''; - $sSQL .= ' AND country_code = \''.$sCountryCode.'\''; - $sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index - $sSQL .= ' AND rank_search between 5 and ' .min(25, $iMaxRank); - $sSQL .= ' AND type != \'postcode\''; - $sSQL .= ' AND name IS NOT NULL '; - $sSQL .= ' and indexed_status = 0 and linked_place_id is null'; - $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL; - $sSQL .= ') as a '; - $sSQL .= 'WHERE distance <= reverse_place_diameter(rank_search)'; - $sSQL .= ' ORDER BY rank_search DESC, distance ASC'; - $sSQL .= ' LIMIT 1'; - Debug::printSQL($sSQL); - - $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.'); - Debug::printVar('Country node', $aPlace); - - if ($aPlace) { - return new Result($aPlace['place_id']); - } - } - - // still nothing, then return the country object - $sSQL = 'SELECT place_id, ST_distance('.$sPointSQL.', centroid) as distance'; - $sSQL .= ' FROM placex'; - $sSQL .= ' WHERE country_code = \''.$sCountryCode.'\''; - $sSQL .= ' AND rank_search = 4 AND rank_address = 4'; - $sSQL .= ' AND class in (\'boundary\', \'place\')'; - $sSQL .= ' AND linked_place_id is null'; - $sSQL .= ' ORDER BY distance ASC'; - Debug::printSQL($sSQL); - - $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.'); - Debug::printVar('Country place', $aPlace); - if ($aPlace) { - return new Result($aPlace['place_id']); - } - } - - return null; - } - - /** - * Search for areas or nodes for areas or nodes between state and suburb level. - * - * @param string $sPointSQL Search point as SQL string. - * @param int $iMaxRank Maximum address rank of the feature. - * - * @return Record of the found feature or null. - * - * Searches first for polygon that contains the search point. - * If such a polygon is found, place nodes with a higher rank are - * searched inside the polygon. - */ - protected function lookupPolygon($sPointSQL, $iMaxRank) - { - Debug::newFunction('lookupPolygon'); - // polygon search begins at suburb-level - if ($iMaxRank > 25) { - $iMaxRank = 25; - } - // no polygon search over country-level - if ($iMaxRank < 5) { - $iMaxRank = 5; - } - // search for polygon - $sSQL = 'SELECT place_id, parent_place_id, rank_address, rank_search FROM'; - $sSQL .= '(select place_id, parent_place_id, rank_address, rank_search, country_code, geometry'; - $sSQL .= ' FROM placex'; - $sSQL .= ' WHERE ST_GeometryType(geometry) in (\'ST_Polygon\', \'ST_MultiPolygon\')'; - // Ensure that query planner doesn't use the index on rank_search. - $sSQL .= ' AND coalesce(rank_search, 0) between 5 and ' .$iMaxRank; - $sSQL .= ' AND rank_address between 4 and 25'; // needed for index selection - $sSQL .= ' AND geometry && '.$sPointSQL; - $sSQL .= ' AND type != \'postcode\' '; - $sSQL .= ' AND name is not null'; - $sSQL .= ' AND indexed_status = 0 and linked_place_id is null'; - $sSQL .= ' ORDER BY rank_search DESC LIMIT 50 ) as a'; - $sSQL .= ' WHERE ST_Contains(geometry, '.$sPointSQL.' )'; - $sSQL .= ' ORDER BY rank_search DESC LIMIT 1'; - Debug::printSQL($sSQL); - - $aPoly = $this->oDB->getRow($sSQL, null, 'Could not determine polygon containing the point.'); - Debug::printVar('Polygon result', $aPoly); - - if ($aPoly) { - // if a polygon is found, search for placenodes begins ... - $iRankAddress = $aPoly['rank_address']; - $iRankSearch = $aPoly['rank_search']; - $iPlaceID = $aPoly['place_id']; - - if ($iRankSearch != $iMaxRank) { - $sSQL = 'SELECT place_id FROM '; - $sSQL .= '(SELECT place_id, rank_search, country_code, geometry,'; - $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance'; - $sSQL .= ' FROM placex'; - $sSQL .= ' WHERE osm_type = \'N\''; - $sSQL .= ' AND rank_search > '.$iRankSearch; - $sSQL .= ' AND rank_search <= '.$iMaxRank; - $sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index - $sSQL .= ' AND type != \'postcode\''; - $sSQL .= ' AND name IS NOT NULL '; - $sSQL .= ' AND indexed_status = 0 AND linked_place_id is null'; - $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL; - $sSQL .= ' ORDER BY rank_search DESC, distance ASC'; - $sSQL .= ' limit 100) as a'; - $sSQL .= ' WHERE ST_Contains((SELECT geometry FROM placex WHERE place_id = '.$iPlaceID.'), geometry )'; - $sSQL .= ' AND distance <= reverse_place_diameter(rank_search)'; - $sSQL .= ' ORDER BY rank_search DESC, distance ASC'; - $sSQL .= ' LIMIT 1'; - Debug::printSQL($sSQL); - - $aPlaceNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.'); - Debug::printVar('Nearest place node', $aPlaceNode); - if ($aPlaceNode) { - return $aPlaceNode; - } - } - } - return $aPoly; - } - - - public function lookup($fLat, $fLon, $bDoInterpolation = true) - { - return $this->lookupPoint( - 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)', - $bDoInterpolation - ); - } - - public function lookupPoint($sPointSQL, $bDoInterpolation = true) - { - Debug::newFunction('lookupPoint'); - // Find the nearest point - $fSearchDiam = 0.006; - $oResult = null; - $aPlace = null; - - // for POI or street level - if ($this->iMaxRank >= 26) { - // starts if the search is on POI or street level, - // searches for the nearest POI or street, - // if a street is found and a POI is searched for, - // the nearest POI which the found street is a parent of is chosen. - $sSQL = 'select place_id,parent_place_id,rank_address,country_code,'; - $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance'; - $sSQL .= ' FROM '; - $sSQL .= ' placex'; - $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')'; - $sSQL .= ' AND'; - $sSQL .= ' rank_address between 26 and '.$this->iMaxRank; - $sSQL .= ' and (name is not null or housenumber is not null'; - $sSQL .= ' or rank_address between 26 and 27)'; - $sSQL .= ' and (rank_address between 26 and 27'; - $sSQL .= ' or ST_GeometryType(geometry) != \'ST_LineString\')'; - $sSQL .= ' and class not in (\'boundary\')'; - $sSQL .= ' and indexed_status = 0 and linked_place_id is null'; - $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') '; - $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))'; - $sSQL .= ' ORDER BY distance ASC limit 1'; - Debug::printSQL($sSQL); - - $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.'); - - Debug::printVar('POI/street level result', $aPlace); - if ($aPlace) { - $iPlaceID = $aPlace['place_id']; - $oResult = new Result($iPlaceID); - $iRankAddress = $aPlace['rank_address']; - } - - if ($aPlace) { - // if street and maxrank > streetlevel - if ($iRankAddress <= 27 && $this->iMaxRank > 27) { - // find the closest object (up to a certain radius) of which the street is a parent of - $sSQL = ' select place_id,'; - $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance'; - $sSQL .= ' FROM '; - $sSQL .= ' placex'; - // radius ? - $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)'; - $sSQL .= ' AND parent_place_id = '.$iPlaceID; - $sSQL .= ' and rank_address > 28'; - $sSQL .= ' and ST_GeometryType(geometry) != \'ST_LineString\''; - $sSQL .= ' and (name is not null or housenumber is not null)'; - $sSQL .= ' and class not in (\'boundary\')'; - $sSQL .= ' and indexed_status = 0 and linked_place_id is null'; - $sSQL .= ' ORDER BY distance ASC limit 1'; - Debug::printSQL($sSQL); - - $aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.'); - Debug::printVar('Closest POI result', $aStreet); - - if ($aStreet) { - $aPlace = $aStreet; - $oResult = new Result($aStreet['place_id']); - $iRankAddress = 30; - } - } - - // In the US we can check TIGER data for nearest housenumber - if (CONST_Use_US_Tiger_Data - && $iRankAddress <= 27 - && $aPlace['country_code'] == 'us' - && $this->iMaxRank >= 28 - ) { - $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search,'; - $sSQL .= ' (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fhnr,'; - $sSQL .= ' startnumber, endnumber, step,'; - $sSQL .= ' ST_Distance('.$sPointSQL.', linegeo) as distance'; - $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$oResult->iId; - $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)'; - $sSQL .= ' ORDER BY distance ASC limit 1'; - Debug::printSQL($sSQL); - - $aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.'); - Debug::printVar('Tiger house number result', $aPlaceTiger); - - if ($aPlaceTiger) { - $aPlace = $aPlaceTiger; - $oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER); - $iRndNum = max(0, round($aPlaceTiger['fhnr'] / $aPlaceTiger['step']) * $aPlaceTiger['step']); - $oResult->iHouseNumber = $aPlaceTiger['startnumber'] + $iRndNum; - if ($oResult->iHouseNumber > $aPlaceTiger['endnumber']) { - $oResult->iHouseNumber = $aPlaceTiger['endnumber']; - } - $iRankAddress = 30; - } - } - } - - if ($bDoInterpolation && $this->iMaxRank >= 30) { - $fDistance = $fSearchDiam; - if ($aPlace) { - // We can't reliably go from the closest street to an - // interpolation line because the closest interpolation - // may have a different street segments as a parent. - // Therefore allow an interpolation line to take precedence - // even when the street is closer. - $fDistance = $iRankAddress < 28 ? 0.001 : $aPlace['distance']; - } - - $aHouse = $this->lookupInterpolation($sPointSQL, $fDistance); - Debug::printVar('Interpolation result', $aPlace); - - if ($aHouse) { - $oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE); - $iRndNum = max(0, round($aHouse['fhnr'] / $aHouse['step']) * $aHouse['step']); - $oResult->iHouseNumber = $aHouse['startnumber'] + $iRndNum; - if ($oResult->iHouseNumber > $aHouse['endnumber']) { - $oResult->iHouseNumber = $aHouse['endnumber']; - } - $aPlace = $aHouse; - } - } - - if (!$aPlace) { - // if no POI or street is found ... - $oResult = $this->lookupLargeArea($sPointSQL, 25); - } - } else { - // lower than street level ($iMaxRank < 26 ) - $oResult = $this->lookupLargeArea($sPointSQL, $this->iMaxRank); - } - - Debug::printVar('Final result', $oResult); - return $oResult; - } -} diff --git a/lib-php/SearchContext.php b/lib-php/SearchContext.php deleted file mode 100644 index 3223b5c99e..0000000000 --- a/lib-php/SearchContext.php +++ /dev/null @@ -1,319 +0,0 @@ -aFullNameWords = $aWordList; - } - - public function getFullNameTerms() - { - return $this->aFullNameWords; - } - - /** - * Check if a reference point is defined. - * - * @return bool True if a reference point is defined. - */ - public function hasNearPoint() - { - return $this->fNearRadius !== false; - } - - /** - * Get radius around reference point. - * - * @return float Search radius around reference point. - */ - public function nearRadius() - { - return $this->fNearRadius; - } - - /** - * Set search reference point in WGS84. - * - * If set, then only places around this point will be taken into account. - * - * @param float $fLat Latitude of point. - * @param float $fLon Longitude of point. - * @param float $fRadius Search radius around point. - * - * @return void - */ - public function setNearPoint($fLat, $fLon, $fRadius = 0.1) - { - $this->fNearRadius = $fRadius; - $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)'; - } - - /** - * Check if the search is geographically restricted. - * - * Searches are restricted if a reference point is given or if - * a bounded viewbox is set. - * - * @return bool True, if the search is geographically bounded. - */ - public function isBoundedSearch() - { - return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded); - } - - /** - * Set rectangular viewbox. - * - * The viewbox may be bounded which means that no search results - * must be outside the viewbox. - * - * @param float[4] $aViewBox Coordinates of the viewbox. - * @param bool $bBounded True if the viewbox is bounded. - * - * @return void - */ - public function setViewboxFromBox(&$aViewBox, $bBounded) - { - $this->bViewboxBounded = $bBounded; - $this->sqlViewboxCentre = ''; - - $this->sqlViewboxSmall = sprintf( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)', - $aViewBox[0], - $aViewBox[1], - $aViewBox[2], - $aViewBox[3] - ); - - $fHeight = abs($aViewBox[0] - $aViewBox[2]); - $fWidth = abs($aViewBox[1] - $aViewBox[3]); - - $this->sqlViewboxLarge = sprintf( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)', - max($aViewBox[0], $aViewBox[2]) + $fHeight, - max($aViewBox[1], $aViewBox[3]) + $fWidth, - min($aViewBox[0], $aViewBox[2]) - $fHeight, - min($aViewBox[1], $aViewBox[3]) - $fWidth - ); - } - - /** - * Set viewbox along a route. - * - * The viewbox may be bounded which means that no search results - * must be outside the viewbox. - * - * @param object $oDB Nominatim::DB instance to use for computing the box. - * @param string[] $aRoutePoints List of x,y coordinates along a route. - * @param float $fRouteWidth Buffer around the route to use. - * @param bool $bBounded True if the viewbox bounded. - * - * @return void - */ - public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded) - { - $this->bViewboxBounded = $bBounded; - $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING("; - $sSep = ''; - foreach ($aRoutePoints as $aPoint) { - $fPoint = (float)$aPoint; - $this->sqlViewboxCentre .= $sSep.$fPoint; - $sSep = ($sSep == ' ') ? ',' : ' '; - } - $this->sqlViewboxCentre .= ")'::geometry,4326)"; - - $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')'; - $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox'); - $this->sqlViewboxSmall = "'".$sGeom."'::geometry"; - - $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')'; - $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox'); - $this->sqlViewboxLarge = "'".$sGeom."'::geometry"; - } - - /** - * Set list of excluded place IDs. - * - * @param integer[] $aExcluded List of IDs. - * - * @return void - */ - public function setExcludeList($aExcluded) - { - $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')'; - } - - /** - * Set list of countries to restrict search to. - * - * @param string[] $aCountries List of two-letter lower-case country codes. - * - * @return void - */ - public function setCountryList($aCountries) - { - $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')'; - $this->aCountryList = $aCountries; - } - - /** - * Extract a reference point from a query string. - * - * @param string $sQuery Query to scan. - * - * @return string The remaining query string. - */ - public function setNearPointFromQuery($sQuery) - { - $aResult = parseLatLon($sQuery); - - if ($aResult !== false - && $aResult[1] <= 90.1 - && $aResult[1] >= -90.1 - && $aResult[2] <= 180.1 - && $aResult[2] >= -180.1 - ) { - $this->setNearPoint($aResult[1], $aResult[2]); - $sQuery = trim(str_replace($aResult[0], ' ', $sQuery)); - } - - return $sQuery; - } - - /** - * Get an SQL snippet for computing the distance from the reference point. - * - * @param string $sObj SQL variable name to compute the distance from. - * - * @return string An SQL string. - */ - public function distanceSQL($sObj) - { - return 'ST_Distance('.$this->sqlNear.", $sObj)"; - } - - /** - * Get an SQL snippet for checking if something is within range of the - * reference point. - * - * @param string $sObj SQL variable name to compute if it is within range. - * - * @return string An SQL string. - */ - public function withinSQL($sObj) - { - return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius); - } - - /** - * Get an SQL snippet of the importance factor of the viewbox. - * - * The importance factor is computed by checking if an object is within - * the viewbox and/or the extended version of the viewbox. - * - * @param string $sObj SQL variable name of object to weight the importance - * - * @return string SQL snippet of the factor with a leading multiply sign. - */ - public function viewboxImportanceSQL($sObj) - { - $sSQL = ''; - - if ($this->sqlViewboxSmall) { - $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END"; - } - if ($this->sqlViewboxLarge) { - $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END"; - } - - return $sSQL; - } - - /** - * SQL snippet checking if a place ID should be excluded. - * - * @param string $sVariable SQL variable name of place ID to check, - * potentially prefixed with more SQL. - * - * @return string SQL snippet. - */ - public function excludeSQL($sVariable) - { - if ($this->sqlExcludeList) { - return $sVariable.$this->sqlExcludeList; - } - - return ''; - } - - /** - * Check if the given country is covered by the search context. - * - * @param string $sCountryCode Country code of the country to check. - * - * @return True, if no country code restrictions are set or the - * country is included in the country list. - */ - public function isCountryApplicable($sCountryCode) - { - return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList); - } - - public function debugInfo() - { - return array( - 'Near radius' => $this->fNearRadius, - 'Near point (SQL)' => $this->sqlNear, - 'Bounded viewbox' => $this->bViewboxBounded, - 'Viewbox (SQL, small)' => $this->sqlViewboxSmall, - 'Viewbox (SQL, large)' => $this->sqlViewboxLarge, - 'Viewbox (SQL, centre)' => $this->sqlViewboxCentre, - 'Countries (SQL)' => $this->sqlCountryList, - 'Excluded IDs (SQL)' => $this->sqlExcludeList - ); - } -} diff --git a/lib-php/SearchDescription.php b/lib-php/SearchDescription.php deleted file mode 100644 index 5d2caf0057..0000000000 --- a/lib-php/SearchDescription.php +++ /dev/null @@ -1,985 +0,0 @@ -oContext = $oContext; - } - - /** - * Get current search rank. - * - * The higher the search rank the lower the likelihood that the - * search is a correct interpretation of the search query. - * - * @return integer Search rank. - */ - public function getRank() - { - return $this->iSearchRank; - } - - /** - * Extract key/value pairs from a query. - * - * Key/value pairs are recognised if they are of the form [=]. - * If multiple terms of this kind are found then all terms are removed - * but only the first is used for search. - * - * @param string $sQuery Original query string. - * - * @return string The query string with the special search patterns removed. - */ - public function extractKeyValuePairs($sQuery) - { - // Search for terms of kind [=]. - preg_match_all( - '/\\[([\\w_]*)=([\\w_]*)\\]/', - $sQuery, - $aSpecialTermsRaw, - PREG_SET_ORDER - ); - - foreach ($aSpecialTermsRaw as $aTerm) { - $sQuery = str_replace($aTerm[0], ' ', $sQuery); - if (!$this->hasOperator()) { - $this->setPoiSearch(Operator::TYPE, $aTerm[1], $aTerm[2]); - } - } - - return $sQuery; - } - - /** - * Check if the combination of parameters is sensible. - * - * @return bool True, if the search looks valid. - */ - public function isValidSearch() - { - if (empty($this->aName)) { - if ($this->sHouseNumber) { - return false; - } - if (!$this->sClass && !$this->sCountryCode) { - return false; - } - } - if ($this->bNameNeedsAddress && empty($this->aAddress)) { - return false; - } - - return true; - } - - /////////// Search building functions - - /** - * Create a copy of this search description adding to search rank. - * - * @param integer $iTermCost Cost to add to the current search rank. - * - * @return object Cloned search description. - */ - public function clone($iTermCost) - { - $oSearch = clone $this; - $oSearch->iSearchRank += $iTermCost; - - return $oSearch; - } - - /** - * Check if the search currently includes a name. - * - * @param bool bIncludeNonNames If true stop-word tokens are taken into - * account, too. - * - * @return bool True, if search has a name. - */ - public function hasName($bIncludeNonNames = false) - { - return !empty($this->aName) - || (!empty($this->aNameNonSearch) && $bIncludeNonNames); - } - - /** - * Check if the search currently includes an address term. - * - * @return bool True, if any address term is included, including stop-word - * terms. - */ - public function hasAddress() - { - return !empty($this->aAddress) || !empty($this->aAddressNonSearch); - } - - /** - * Check if a country restriction is currently included in the search. - * - * @return bool True, if a country restriction is set. - */ - public function hasCountry() - { - return $this->sCountryCode !== ''; - } - - /** - * Check if a postcode is currently included in the search. - * - * @return bool True, if a postcode is set. - */ - public function hasPostcode() - { - return $this->sPostcode !== ''; - } - - /** - * Check if a house number is set for the search. - * - * @return bool True, if a house number is set. - */ - public function hasHousenumber() - { - return $this->sHouseNumber !== ''; - } - - /** - * Check if a special type of place is requested. - * - * param integer iOperator When set, check for the particular - * operator used for the special type. - * - * @return bool True, if speial type is requested or, if requested, - * a special type with the given operator. - */ - public function hasOperator($iOperator = null) - { - return $iOperator === null ? $this->iOperator != Operator::NONE : $this->iOperator == $iOperator; - } - - /** - * Add the given token to the list of terms to search for in the address. - * - * @param integer iID ID of term to add. - * @param bool bSearchable Term should be used to search for result - * (i.e. term is not a stop word). - */ - public function addAddressToken($iId, $bSearchable = true) - { - if ($bSearchable) { - $this->aAddress[$iId] = $iId; - } else { - $this->aAddressNonSearch[$iId] = $iId; - } - } - - /** - * Add the given full-word token to the list of terms to search for in the - * name. - * - * @param integer iId ID of term to add. - * @param bool bRareName True if the term is infrequent enough to not - * require other constraints for efficient search. - */ - public function addNameToken($iId, $bRareName) - { - $this->aName[$iId] = $iId; - $this->bRareName = $bRareName; - $this->bNameNeedsAddress = false; - } - - /** - * Add the given partial token to the list of terms to search for in - * the name. - * - * @param integer iID ID of term to add. - * @param bool bSearchable Term should be used to search for result - * (i.e. term is not a stop word). - * @param bool bNeedsAddress True if the term is too unspecific to be used - * in a stand-alone search without an address - * to narrow down the search. - * @param integer iPhraseNumber Index of phrase, where the partial term - * appears. - */ - public function addPartialNameToken($iId, $bSearchable, $bNeedsAddress, $iPhraseNumber) - { - if (empty($this->aName)) { - $this->bNameNeedsAddress = $bNeedsAddress; - } elseif ($bSearchable && count($this->aName) >= 2) { - $this->bNameNeedsAddress = false; - } else { - $this->bNameNeedsAddress &= $bNeedsAddress; - } - if ($bSearchable) { - $this->aName[$iId] = $iId; - } else { - $this->aNameNonSearch[$iId] = $iId; - } - $this->iNamePhrase = $iPhraseNumber; - } - - /** - * Set country restriction for the search. - * - * @param string sCountryCode Country code of country to restrict search to. - */ - public function setCountry($sCountryCode) - { - $this->sCountryCode = $sCountryCode; - $this->iNamePhrase = -1; - } - - /** - * Set postcode search constraint. - * - * @param string sPostcode Postcode the result should have. - */ - public function setPostcode($sPostcode) - { - $this->sPostcode = $sPostcode; - $this->iNamePhrase = -1; - } - - /** - * Make this search a search for a postcode object. - * - * @param integer iId Token Id for the postcode. - * @param string sPostcode Postcode to look for. - */ - public function setPostcodeAsName($iId, $sPostcode) - { - $this->iOperator = Operator::POSTCODE; - $this->aAddress = array_merge($this->aAddress, $this->aName); - $this->aName = array($iId => $sPostcode); - $this->bRareName = true; - $this->iNamePhrase = -1; - } - - /** - * Set house number search cnstraint. - * - * @param string sNumber House number the result should have. - */ - public function setHousenumber($sNumber) - { - $this->sHouseNumber = $sNumber; - $this->iNamePhrase = -1; - } - - /** - * Make this search a search for a house number. - * - * @param integer iId Token Id for the house number. - */ - public function setHousenumberAsName($iId) - { - $this->aAddress = array_merge($this->aAddress, $this->aName); - $this->bRareName = false; - $this->bNameNeedsAddress = true; - $this->aName = array($iId => $iId); - $this->iNamePhrase = -1; - } - - /** - * Make this search a POI search. - * - * In a POI search, objects are not (only) searched by their name - * but also by the primary OSM key/value pair (class and type in Nominatim). - * - * @param integer $iOperator Type of POI search - * @param string $sClass Class (or OSM tag key) of POI. - * @param string $sType Type (or OSM tag value) of POI. - * - * @return void - */ - public function setPoiSearch($iOperator, $sClass, $sType) - { - $this->iOperator = $iOperator; - $this->sClass = $sClass; - $this->sType = $sType; - $this->iNamePhrase = -1; - } - - public function getNamePhrase() - { - return $this->iNamePhrase; - } - - /** - * Get the global search context. - * - * @return object Objects of global search constraints. - */ - public function getContext() - { - return $this->oContext; - } - - /////////// Query functions - - - /** - * Query database for places that match this search. - * - * @param object $oDB Nominatim::DB instance to use. - * @param integer $iMinRank Minimum address rank to restrict search to. - * @param integer $iMaxRank Maximum address rank to restrict search to. - * @param integer $iLimit Maximum number of results. - * - * @return mixed[] An array with two fields: IDs contains the list of - * matching place IDs and houseNumber the houseNumber - * if applicable or -1 if not. - */ - public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit) - { - $aResults = array(); - - if ($this->sCountryCode - && empty($this->aName) - && !$this->iOperator - && !$this->sClass - && !$this->oContext->hasNearPoint() - ) { - // Just looking for a country - look it up - if (4 >= $iMinRank && 4 <= $iMaxRank) { - $aResults = $this->queryCountry($oDB); - } - } elseif (empty($this->aName) && empty($this->aAddress)) { - // Neither name nor address? Then we must be - // looking for a POI in a geographic area. - if ($this->oContext->isBoundedSearch()) { - $aResults = $this->queryNearbyPoi($oDB, $iLimit); - } - } elseif ($this->iOperator == Operator::POSTCODE) { - // looking for postcode - $aResults = $this->queryPostcode($oDB, $iLimit); - } else { - // Ordinary search: - // First search for places according to name and address. - $aResults = $this->queryNamedPlace( - $oDB, - $iMinRank, - $iMaxRank, - $iLimit - ); - - // finally get POIs if requested - if ($this->sClass && !empty($aResults)) { - $aResults = $this->queryPoiByOperator($oDB, $aResults, $iLimit); - } - } - - Debug::printDebugTable('Place IDs', $aResults); - - if (!empty($aResults) && $this->sPostcode) { - $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX); - if ($sPlaceIds) { - $sSQL = 'SELECT place_id FROM placex'; - $sSQL .= ' WHERE place_id in ('.$sPlaceIds.')'; - $sSQL .= " AND postcode != '".$this->sPostcode."'"; - Debug::printSQL($sSQL); - $aFilteredPlaceIDs = $oDB->getCol($sSQL); - if ($aFilteredPlaceIDs) { - foreach ($aFilteredPlaceIDs as $iPlaceId) { - $aResults[$iPlaceId]->iResultRank++; - } - } - } - } - - return $aResults; - } - - - private function queryCountry(&$oDB) - { - $sSQL = 'SELECT place_id FROM placex '; - $sSQL .= "WHERE country_code='".$this->sCountryCode."'"; - $sSQL .= ' AND rank_search = 4'; - if ($this->oContext->bViewboxBounded) { - $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)'; - } - $sSQL .= ' ORDER BY st_area(geometry) DESC LIMIT 1'; - - Debug::printSQL($sSQL); - - $iPlaceId = $oDB->getOne($sSQL); - - $aResults = array(); - if ($iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId); - } - - return $aResults; - } - - private function queryNearbyPoi(&$oDB, $iLimit) - { - if (!$this->sClass) { - return array(); - } - - $aDBResults = array(); - $sPoiTable = $this->poiTable(); - - if ($oDB->tableExists($sPoiTable)) { - $sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct'; - if ($this->oContext->sqlCountryList) { - $sSQL .= ' JOIN placex USING (place_id)'; - } - if ($this->oContext->hasNearPoint()) { - $sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid'); - } elseif ($this->oContext->bViewboxBounded) { - $sSQL .= ' WHERE ST_Contains('.$this->oContext->sqlViewboxSmall.', ct.centroid)'; - } - if ($this->oContext->sqlCountryList) { - $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList; - } - $sSQL .= $this->oContext->excludeSQL(' AND place_id'); - if ($this->oContext->sqlViewboxCentre) { - $sSQL .= ' ORDER BY ST_Distance('; - $sSQL .= $this->oContext->sqlViewboxCentre.', ct.centroid) ASC'; - } elseif ($this->oContext->hasNearPoint()) { - $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC'; - } - $sSQL .= " LIMIT $iLimit"; - Debug::printSQL($sSQL); - $aDBResults = $oDB->getCol($sSQL); - } - - if ($this->oContext->hasNearPoint()) { - $sSQL = 'SELECT place_id FROM placex WHERE '; - $sSQL .= 'class = :class and type = :type'; - $sSQL .= ' AND '.$this->oContext->withinSQL('geometry'); - $sSQL .= ' AND linked_place_id is null'; - if ($this->oContext->sqlCountryList) { - $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList; - } - $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC'; - $sSQL .= " LIMIT $iLimit"; - Debug::printSQL($sSQL); - $aDBResults = $oDB->getCol( - $sSQL, - array(':class' => $this->sClass, ':type' => $this->sType) - ); - } - - $aResults = array(); - foreach ($aDBResults as $iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId); - } - - return $aResults; - } - - private function queryPostcode(&$oDB, $iLimit) - { - $sSQL = 'SELECT p.place_id FROM location_postcode p '; - - if (!empty($this->aAddress)) { - $sSQL .= ', search_name s '; - $sSQL .= 'WHERE s.place_id = p.parent_place_id '; - $sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)'; - $sSQL .= ' @> '.$oDB->getArraySQL($this->aAddress).' AND '; - } else { - $sSQL .= 'WHERE '; - } - - $sSQL .= "p.postcode = '".reset($this->aName)."'"; - $sSQL .= $this->countryCodeSQL(' AND p.country_code'); - if ($this->oContext->bViewboxBounded) { - $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)'; - } - $sSQL .= $this->oContext->excludeSQL(' AND p.place_id'); - $sSQL .= " LIMIT $iLimit"; - - Debug::printSQL($sSQL); - - $aResults = array(); - foreach ($oDB->getCol($sSQL) as $iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE); - } - - return $aResults; - } - - private function queryNamedPlace(&$oDB, $iMinAddressRank, $iMaxAddressRank, $iLimit) - { - $aTerms = array(); - $aOrder = array(); - - if (!empty($this->aName)) { - $aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName); - } - if (!empty($this->aAddress)) { - // For infrequent name terms disable index usage for address - if ($this->bRareName) { - $aTerms[] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> '.$oDB->getArraySQL($this->aAddress); - } else { - $aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress); - } - } - - $sCountryTerm = $this->countryCodeSQL('country_code'); - if ($sCountryTerm) { - $aTerms[] = $sCountryTerm; - } - - if ($this->sHouseNumber) { - $aTerms[] = 'address_rank between 16 and 30'; - } elseif (!$this->sClass || $this->iOperator == Operator::NAME) { - if ($iMinAddressRank > 0) { - $aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))"; - } - } - - if ($this->oContext->hasNearPoint()) { - $aTerms[] = $this->oContext->withinSQL('centroid'); - $aOrder[] = $this->oContext->distanceSQL('centroid'); - } elseif ($this->sPostcode) { - if (empty($this->aAddress)) { - $aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."' AND ST_DWithin(search_name.centroid, p.geometry, 0.12))"; - } else { - $aOrder[] = "(SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."')"; - } - } - - $sExcludeSQL = $this->oContext->excludeSQL('place_id'); - if ($sExcludeSQL) { - $aTerms[] = $sExcludeSQL; - } - - if ($this->oContext->bViewboxBounded) { - $aTerms[] = 'centroid && '.$this->oContext->sqlViewboxSmall; - } - - if ($this->sHouseNumber) { - $sImportanceSQL = '- abs(26 - address_rank) + 3'; - } else { - $sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75001-(search_rank::float/40) ELSE importance END)'; - } - $sImportanceSQL .= $this->oContext->viewboxImportanceSQL('centroid'); - $aOrder[] = "$sImportanceSQL DESC"; - - $aFullNameAddress = $this->oContext->getFullNameTerms(); - if (!empty($aFullNameAddress)) { - $sExactMatchSQL = ' ( '; - $sExactMatchSQL .= ' SELECT count(*) FROM ( '; - $sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($aFullNameAddress).')'; - $sExactMatchSQL .= ' INTERSECT '; - $sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)'; - $sExactMatchSQL .= ' ) s'; - $sExactMatchSQL .= ') as exactmatch'; - $aOrder[] = 'exactmatch DESC'; - } else { - $sExactMatchSQL = '0::int as exactmatch'; - } - - if (empty($aTerms)) { - return array(); - } - - if ($this->hasHousenumber()) { - $sHouseNumberRegex = $oDB->getDBQuoted('\\\\m'.$this->sHouseNumber.'\\\\M'); - - // Housenumbers on streets and places. - $sPlacexSql = 'SELECT array_agg(place_id) FROM placex'; - $sPlacexSql .= ' WHERE parent_place_id = sin.place_id AND sin.address_rank < 30'; - $sPlacexSql .= $this->oContext->excludeSQL(' AND place_id'); - $sPlacexSql .= ' and housenumber ~* E'.$sHouseNumberRegex; - - // Interpolations on streets and places. - $sInterpolSql = 'null'; - $sTigerSql = 'null'; - if (preg_match('/^[0-9]+$/', $this->sHouseNumber)) { - $sIpolHnr = 'WHERE parent_place_id = sin.place_id '; - $sIpolHnr .= ' AND startnumber is not NULL AND sin.address_rank < 30'; - $sIpolHnr .= ' AND '.$this->sHouseNumber.' between startnumber and endnumber'; - $sIpolHnr .= ' AND ('.$this->sHouseNumber.' - startnumber) % step = 0'; - - $sInterpolSql = 'SELECT array_agg(place_id) FROM location_property_osmline '.$sIpolHnr; - if (CONST_Use_US_Tiger_Data) { - $sTigerSql = 'SELECT array_agg(place_id) FROM location_property_tiger '.$sIpolHnr; - $sTigerSql .= " and sin.country_code = 'us'"; - } - } - - if ($this->sClass) { - $iLimit = 40; - } - - $sSelfHnr = 'SELECT * FROM placex WHERE place_id = search_name.place_id'; - $sSelfHnr .= ' AND housenumber ~* E'.$sHouseNumberRegex; - - $aTerms[] = '(address_rank < 30 or exists('.$sSelfHnr.'))'; - - - $sSQL = 'SELECT sin.*, '; - $sSQL .= '('.$sPlacexSql.') as placex_hnr, '; - $sSQL .= '('.$sInterpolSql.') as interpol_hnr, '; - $sSQL .= '('.$sTigerSql.') as tiger_hnr '; - $sSQL .= ' FROM ('; - $sSQL .= ' SELECT place_id, address_rank, country_code,'.$sExactMatchSQL.','; - $sSQL .= ' CASE WHEN importance = 0 OR importance IS NULL'; - $sSQL .= ' THEN 0.75001-(search_rank::float/40) ELSE importance END as importance'; - $sSQL .= ' FROM search_name'; - $sSQL .= ' WHERE '.join(' and ', $aTerms); - $sSQL .= ' ORDER BY '.join(', ', $aOrder); - $sSQL .= ' LIMIT 40000'; - $sSQL .= ') as sin'; - $sSQL .= ' ORDER BY address_rank = 30 desc, placex_hnr, interpol_hnr, tiger_hnr,'; - $sSQL .= ' importance'; - $sSQL .= ' LIMIT '.$iLimit; - } else { - if ($this->sClass) { - $iLimit = 40; - } - - $sSQL = 'SELECT place_id, address_rank, '.$sExactMatchSQL; - $sSQL .= ' FROM search_name'; - $sSQL .= ' WHERE '.join(' and ', $aTerms); - $sSQL .= ' ORDER BY '.join(', ', $aOrder); - $sSQL .= ' LIMIT '.$iLimit; - } - - Debug::printSQL($sSQL); - - $aDBResults = $oDB->getAll($sSQL, null, 'Could not get places for search terms.'); - - $aResults = array(); - - foreach ($aDBResults as $aResult) { - $oResult = new Result($aResult['place_id']); - $oResult->iExactMatches = $aResult['exactmatch']; - $oResult->iAddressRank = $aResult['address_rank']; - - $bNeedResult = true; - if ($this->hasHousenumber() && $aResult['address_rank'] < 30) { - if ($aResult['placex_hnr']) { - foreach (explode(',', substr($aResult['placex_hnr'], 1, -1)) as $sPlaceID) { - $iPlaceID = intval($sPlaceID); - $oHnrResult = new Result($iPlaceID); - $oHnrResult->iExactMatches = $aResult['exactmatch']; - $oHnrResult->iAddressRank = 30; - $aResults[$iPlaceID] = $oHnrResult; - $bNeedResult = false; - } - } - if ($aResult['interpol_hnr']) { - foreach (explode(',', substr($aResult['interpol_hnr'], 1, -1)) as $sPlaceID) { - $iPlaceID = intval($sPlaceID); - $oHnrResult = new Result($iPlaceID, Result::TABLE_OSMLINE); - $oHnrResult->iExactMatches = $aResult['exactmatch']; - $oHnrResult->iAddressRank = 30; - $oHnrResult->iHouseNumber = intval($this->sHouseNumber); - $aResults[$iPlaceID] = $oHnrResult; - $bNeedResult = false; - } - } - if ($aResult['tiger_hnr']) { - foreach (explode(',', substr($aResult['tiger_hnr'], 1, -1)) as $sPlaceID) { - $iPlaceID = intval($sPlaceID); - $oHnrResult = new Result($iPlaceID, Result::TABLE_TIGER); - $oHnrResult->iExactMatches = $aResult['exactmatch']; - $oHnrResult->iAddressRank = 30; - $oHnrResult->iHouseNumber = intval($this->sHouseNumber); - $aResults[$iPlaceID] = $oHnrResult; - $bNeedResult = false; - } - } - - if ($aResult['address_rank'] < 26) { - $oResult->iResultRank += 2; - } else { - $oResult->iResultRank++; - } - } - - if ($bNeedResult) { - $aResults[$aResult['place_id']] = $oResult; - } - } - - return $aResults; - } - - - private function queryPoiByOperator(&$oDB, $aParentIDs, $iLimit) - { - $aResults = array(); - $sPlaceIDs = Result::joinIdsByTable($aParentIDs, Result::TABLE_PLACEX); - - if (!$sPlaceIDs) { - return $aResults; - } - - if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NAME) { - // If they were searching for a named class (i.e. 'Kings Head pub') - // then we might have an extra match - $sSQL = 'SELECT place_id FROM placex '; - $sSQL .= " WHERE place_id in ($sPlaceIDs)"; - $sSQL .= " AND class='".$this->sClass."' "; - $sSQL .= " AND type='".$this->sType."'"; - $sSQL .= ' AND linked_place_id is null'; - $sSQL .= $this->oContext->excludeSQL(' AND place_id'); - $sSQL .= ' ORDER BY rank_search ASC '; - $sSQL .= " LIMIT $iLimit"; - - Debug::printSQL($sSQL); - - foreach ($oDB->getCol($sSQL) as $iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId); - } - } - - // NEAR and IN are handled the same - if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) { - $sClassTable = $this->poiTable(); - $bCacheTable = $oDB->tableExists($sClassTable); - - $sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)"; - Debug::printSQL($sSQL); - $iMaxRank = (int) $oDB->getOne($sSQL); - - // For state / country level searches the normal radius search doesn't work very well - $sPlaceGeom = false; - if ($iMaxRank < 9 && $bCacheTable) { - // Try and get a polygon to search in instead - $sSQL = 'SELECT geometry FROM placex'; - $sSQL .= " WHERE place_id in ($sPlaceIDs)"; - $sSQL .= " AND rank_search < $iMaxRank + 5"; - $sSQL .= ' AND ST_Area(Box2d(geometry)) < 20'; - $sSQL .= " AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')"; - $sSQL .= ' ORDER BY rank_search ASC '; - $sSQL .= ' LIMIT 1'; - Debug::printSQL($sSQL); - $sPlaceGeom = $oDB->getOne($sSQL); - } - - if ($sPlaceGeom) { - $sPlaceIDs = false; - } else { - $iMaxRank += 5; - $sSQL = 'SELECT place_id FROM placex'; - $sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank"; - Debug::printSQL($sSQL); - $aPlaceIDs = $oDB->getCol($sSQL); - $sPlaceIDs = join(',', $aPlaceIDs); - } - - if ($sPlaceIDs || $sPlaceGeom) { - $fRange = 0.01; - if ($bCacheTable) { - // More efficient - can make the range bigger - $fRange = 0.05; - - $sOrderBySQL = ''; - if ($this->oContext->hasNearPoint()) { - $sOrderBySQL = $this->oContext->distanceSQL('l.centroid'); - } elseif ($sPlaceIDs) { - $sOrderBySQL = 'ST_Distance(l.centroid, f.geometry)'; - } elseif ($sPlaceGeom) { - $sOrderBySQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)"; - } - - $sSQL = 'SELECT distinct i.place_id'; - if ($sOrderBySQL) { - $sSQL .= ', i.order_term'; - } - $sSQL .= ' from (SELECT l.place_id'; - if ($sOrderBySQL) { - $sSQL .= ','.$sOrderBySQL.' as order_term'; - } - $sSQL .= ' from '.$sClassTable.' as l'; - - if ($sPlaceIDs) { - $sSQL .= ',placex as f WHERE '; - $sSQL .= "f.place_id in ($sPlaceIDs) "; - $sSQL .= " AND ST_DWithin(l.centroid, f.centroid, $fRange)"; - } elseif ($sPlaceGeom) { - $sSQL .= " WHERE ST_Contains('$sPlaceGeom', l.centroid)"; - } - - $sSQL .= $this->oContext->excludeSQL(' AND l.place_id'); - $sSQL .= 'limit 300) i '; - if ($sOrderBySQL) { - $sSQL .= 'order by order_term asc'; - } - $sSQL .= " limit $iLimit"; - - Debug::printSQL($sSQL); - - foreach ($oDB->getCol($sSQL) as $iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId); - } - } else { - if ($this->oContext->hasNearPoint()) { - $fRange = $this->oContext->nearRadius(); - } - - $sOrderBySQL = ''; - if ($this->oContext->hasNearPoint()) { - $sOrderBySQL = $this->oContext->distanceSQL('l.geometry'); - } else { - $sOrderBySQL = 'ST_Distance(l.geometry, f.geometry)'; - } - - $sSQL = 'SELECT distinct l.place_id'; - if ($sOrderBySQL) { - $sSQL .= ','.$sOrderBySQL.' as orderterm'; - } - $sSQL .= ' FROM placex as l, placex as f'; - $sSQL .= " WHERE f.place_id in ($sPlaceIDs)"; - $sSQL .= " AND ST_DWithin(l.geometry, f.centroid, $fRange)"; - $sSQL .= " AND l.class='".$this->sClass."'"; - $sSQL .= " AND l.type='".$this->sType."'"; - $sSQL .= $this->oContext->excludeSQL(' AND l.place_id'); - if ($sOrderBySQL) { - $sSQL .= 'ORDER BY orderterm ASC'; - } - $sSQL .= " limit $iLimit"; - - Debug::printSQL($sSQL); - - foreach ($oDB->getCol($sSQL) as $iPlaceId) { - $aResults[$iPlaceId] = new Result($iPlaceId); - } - } - } - } - - return $aResults; - } - - private function poiTable() - { - return 'place_classtype_'.$this->sClass.'_'.$this->sType; - } - - private function countryCodeSQL($sVar) - { - if ($this->sCountryCode) { - return $sVar.' = \''.$this->sCountryCode."'"; - } - if ($this->oContext->sqlCountryList) { - return $sVar.' in '.$this->oContext->sqlCountryList; - } - - return ''; - } - - /////////// Sort functions - - - public static function bySearchRank($a, $b) - { - if ($a->iSearchRank == $b->iSearchRank) { - return $a->iOperator + strlen($a->sHouseNumber) - - $b->iOperator - strlen($b->sHouseNumber); - } - - return $a->iSearchRank < $b->iSearchRank ? -1 : 1; - } - - //////////// Debugging functions - - - public function debugInfo() - { - return array( - 'Search rank' => $this->iSearchRank, - 'Country code' => $this->sCountryCode, - 'Name terms' => $this->aName, - 'Name terms (stop words)' => $this->aNameNonSearch, - 'Address terms' => $this->aAddress, - 'Address terms (stop words)' => $this->aAddressNonSearch, - 'Address terms (full words)' => $this->aFullNameAddress ?? '', - 'Special search' => $this->iOperator, - 'Class' => $this->sClass, - 'Type' => $this->sType, - 'House number' => $this->sHouseNumber, - 'Postcode' => $this->sPostcode - ); - } - - public function dumpAsHtmlTableRow(&$aWordIDs) - { - $kf = function ($k) use (&$aWordIDs) { - return $aWordIDs[$k] ?? '['.$k.']'; - }; - - echo ''; - echo "$this->iSearchRank"; - echo ''.join(', ', array_map($kf, $this->aName)).''; - echo ''.join(', ', array_map($kf, $this->aNameNonSearch)).''; - echo ''.join(', ', array_map($kf, $this->aAddress)).''; - echo ''.join(', ', array_map($kf, $this->aAddressNonSearch)).''; - echo ''.$this->sCountryCode.''; - echo ''.Operator::toString($this->iOperator).''; - echo ''.$this->sClass.''; - echo ''.$this->sType.''; - echo ''.$this->sPostcode.''; - echo ''.$this->sHouseNumber.''; - - echo ''; - } -} diff --git a/lib-php/SearchPosition.php b/lib-php/SearchPosition.php deleted file mode 100644 index aeeeb2c33f..0000000000 --- a/lib-php/SearchPosition.php +++ /dev/null @@ -1,95 +0,0 @@ -sPhraseType = $sPhraseType; - $this->iPhrase = $iPhrase; - $this->iNumPhrases = $iNumPhrases; - } - - public function setTokenPosition($iToken, $iNumTokens) - { - $this->iToken = $iToken; - $this->iNumTokens = $iNumTokens; - } - - /** - * Check if the phrase can be of the given type. - * - * @param string $sType Type of phrse requested. - * - * @return True if the phrase is untyped or of the given type. - */ - public function maybePhrase($sType) - { - return $this->sPhraseType == '' || $this->sPhraseType == $sType; - } - - /** - * Check if the phrase is exactly of the given type. - * - * @param string $sType Type of phrse requested. - * - * @return True if the phrase of the given type. - */ - public function isPhrase($sType) - { - return $this->sPhraseType == $sType; - } - - /** - * Return true if the token is the very first in the query. - */ - public function isFirstToken() - { - return $this->iPhrase == 0 && $this->iToken == 0; - } - - /** - * Check if the token is the final one in the query. - */ - public function isLastToken() - { - return $this->iToken + 1 == $this->iNumTokens && $this->iPhrase + 1 == $this->iNumPhrases; - } - - /** - * Check if the current token is part of the first phrase in the query. - */ - public function isFirstPhrase() - { - return $this->iPhrase == 0; - } - - /** - * Get the phrase position in the query. - */ - public function getPhrase() - { - return $this->iPhrase; - } -} diff --git a/lib-php/Shell.php b/lib-php/Shell.php deleted file mode 100644 index 4be13235eb..0000000000 --- a/lib-php/Shell.php +++ /dev/null @@ -1,92 +0,0 @@ -baseCmd = $sBaseCmd; - $this->aParams = array(); - $this->aEnv = null; // null = use the same environment as the current PHP process - - $this->stdoutString = null; - - foreach ($aParams as $sParam) { - $this->addParams($sParam); - } - } - - public function addParams(...$aParams) - { - foreach ($aParams as $sParam) { - if (isset($sParam) && $sParam !== null && $sParam !== '') { - array_push($this->aParams, $sParam); - } - } - return $this; - } - - public function addEnvPair($sKey, $sVal) - { - if (isset($sKey) && $sKey && isset($sVal)) { - if (!isset($this->aEnv)) { - $this->aEnv = $_ENV; - } - $this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV); - } - return $this; - } - - public function escapedCmd() - { - $aEscaped = array_map(function ($sParam) { - return $this->escapeParam($sParam); - }, array_merge(array($this->baseCmd), $this->aParams)); - - return join(' ', $aEscaped); - } - - public function run($bExitOnFail = false) - { - $sCmd = $this->escapedCmd(); - // $aEnv does not need escaping, proc_open seems to handle it fine - - $aFDs = array( - 0 => array('pipe', 'r'), - 1 => STDOUT, - 2 => STDERR - ); - $aPipes = null; - $hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv); - if (!is_resource($hProc)) { - throw new \Exception('Unable to run command: ' . $sCmd); - } - - fclose($aPipes[0]); // no stdin - - $iStat = proc_close($hProc); - - if ($iStat != 0 && $bExitOnFail) { - exit($iStat); - } - - return $iStat; - } - - private function escapeParam($sParam) - { - return (preg_match('/^-*\w+$/', $sParam)) ? $sParam : escapeshellarg($sParam); - } -} diff --git a/lib-php/SimpleWordList.php b/lib-php/SimpleWordList.php deleted file mode 100644 index 7009d370f9..0000000000 --- a/lib-php/SimpleWordList.php +++ /dev/null @@ -1,144 +0,0 @@ - 0) { - $this->aWords = explode(' ', $sPhrase); - } else { - $this->aWords = array(); - } - } - - /** - * Get all possible tokens that are present in this word list. - * - * @return array The list of string tokens in the word list. - */ - public function getTokens() - { - $aTokens = array(); - $iNumWords = count($this->aWords); - - for ($i = 0; $i < $iNumWords; $i++) { - $sPhrase = $this->aWords[$i]; - $aTokens[$sPhrase] = $sPhrase; - - for ($j = $i + 1; $j < $iNumWords; $j++) { - $sPhrase .= ' '.$this->aWords[$j]; - $aTokens[$sPhrase] = $sPhrase; - } - } - - return $aTokens; - } - - /** - * Compute all possible permutations of phrase splits that result in - * words which are in the token list. - */ - public function getWordSets($oTokens) - { - $iNumWords = count($this->aWords); - - if ($iNumWords == 0) { - return null; - } - - // Caches the word set for the partial phrase up to word i. - $aSetCache = array_fill(0, $iNumWords, array()); - - // Initialise first element of cache. There can only be the word. - if ($oTokens->containsAny($this->aWords[0])) { - $aSetCache[0][] = array($this->aWords[0]); - } - - // Now do the next elements using what we already have. - for ($i = 1; $i < $iNumWords; $i++) { - for ($j = $i; $j > 0; $j--) { - $sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial; - if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) { - $aPartial = array($sPartial); - foreach ($aSetCache[$j - 1] as $aSet) { - if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) { - $aSetCache[$i][] = array_merge($aSet, $aPartial); - } - } - if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) { - usort( - $aSetCache[$i], - array('\Nominatim\SimpleWordList', 'cmpByArraylen') - ); - $aSetCache[$i] = array_slice( - $aSetCache[$i], - 0, - SimpleWordList::MAX_WORDSETS - ); - } - } - } - - // finally the current full phrase - $sPartial = $this->aWords[0].' '.$sPartial; - if ($oTokens->containsAny($sPartial)) { - $aSetCache[$i][] = array($sPartial); - } - } - - $aWordSets = $aSetCache[$iNumWords - 1]; - usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen')); - return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS); - } - - /** - * Custom search routine which takes two arrays. The array with the fewest - * items wins. If same number of items then the one with the longest first - * element wins. - */ - public static function cmpByArraylen($aA, $aB) - { - $iALen = count($aA); - $iBLen = count($aB); - - if ($iALen == $iBLen) { - return strlen($aB[0]) <=> strlen($aA[0]); - } - - return ($iALen < $iBLen) ? -1 : 1; - } - - public function debugInfo() - { - return $this->aWords; - } -} diff --git a/lib-php/SpecialSearchOperator.php b/lib-php/SpecialSearchOperator.php deleted file mode 100644 index 94df59ea2b..0000000000 --- a/lib-php/SpecialSearchOperator.php +++ /dev/null @@ -1,52 +0,0 @@ -getConstants(); - - Operator::$aConstantNames = array(); - foreach ($aConstants as $sName => $iValue) { - Operator::$aConstantNames[$iValue] = $sName; - } - } - - return Operator::$aConstantNames[$iOperator]; - } -} diff --git a/lib-php/Status.php b/lib-php/Status.php deleted file mode 100644 index 4f1555cdb3..0000000000 --- a/lib-php/Status.php +++ /dev/null @@ -1,59 +0,0 @@ -oDB =& $oDB; - } - - public function status() - { - if (!$this->oDB) { - throw new Exception('No database', 700); - } - - try { - $this->oDB->connect(); - } catch (\Nominatim\DatabaseError $e) { - throw new Exception('Database connection failed', 700); - } - - $oTokenizer = new \Nominatim\Tokenizer($this->oDB); - $oTokenizer->checkStatus(); - } - - public function dataDate() - { - $sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1'; - $iDataDateEpoch = $this->oDB->getOne($sSQL); - - if ($iDataDateEpoch === false) { - throw new Exception('Import date is not available', 705); - } - - return $iDataDateEpoch; - } - - public function databaseVersion() - { - $sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\''; - return $this->oDB->getOne($sSQL); - } -} diff --git a/lib-php/TokenCountry.php b/lib-php/TokenCountry.php deleted file mode 100644 index 3f93f45e0f..0000000000 --- a/lib-php/TokenCountry.php +++ /dev/null @@ -1,82 +0,0 @@ -iId = $iId; - $this->sCountryCode = $sCountryCode; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oSearch->hasCountry() - && $oPosition->maybePhrase('country') - && $oSearch->getContext()->isCountryApplicable($this->sCountryCode); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - $oNewSearch = $oSearch->clone($oPosition->isLastToken() ? 1 : 6); - $oNewSearch->setCountry($this->sCountryCode); - - return array($oNewSearch); - } - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'country', - 'Info' => $this->sCountryCode - ); - } - - public function debugCode() - { - return 'C'; - } -} diff --git a/lib-php/TokenHousenumber.php b/lib-php/TokenHousenumber.php deleted file mode 100644 index 62c2a624a6..0000000000 --- a/lib-php/TokenHousenumber.php +++ /dev/null @@ -1,116 +0,0 @@ -iId = $iId; - $this->sToken = $sToken; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oSearch->hasHousenumber() - && !$oSearch->hasOperator(\Nominatim\Operator::POSTCODE) - && $oPosition->maybePhrase('street'); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - $aNewSearches = array(); - - // sanity check: if the housenumber is not mainly made - // up of numbers, add a penalty - $iSearchCost = 1; - if (preg_match('/\\d/', $this->sToken) === 0 - || preg_match_all('/[^0-9 ]/', $this->sToken, $aMatches) > 3) { - $iSearchCost += strlen($this->sToken) - 1; - } - if (!$oSearch->hasOperator(\Nominatim\Operator::NONE)) { - $iSearchCost++; - } - if (empty($this->iId)) { - $iSearchCost++; - } - // also must not appear in the middle of the address - if ($oSearch->hasAddress() || $oSearch->hasPostcode()) { - $iSearchCost++; - } - - $oNewSearch = $oSearch->clone($iSearchCost); - $oNewSearch->setHousenumber($this->sToken); - $aNewSearches[] = $oNewSearch; - - // Housenumbers may appear in the name when the place has its own - // address terms. - if ($this->iId !== null - && ($oSearch->getNamePhrase() >= 0 || !$oSearch->hasName()) - && !$oSearch->hasAddress() - ) { - $oNewSearch = $oSearch->clone($iSearchCost); - $oNewSearch->setHousenumberAsName($this->iId); - - $aNewSearches[] = $oNewSearch; - } - - return $aNewSearches; - } - - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'house number', - 'Info' => array('nr' => $this->sToken) - ); - } - - public function debugCode() - { - return 'H'; - } -} diff --git a/lib-php/TokenList.php b/lib-php/TokenList.php deleted file mode 100644 index 9a3950a162..0000000000 --- a/lib-php/TokenList.php +++ /dev/null @@ -1,134 +0,0 @@ -aTokens); - } - - /** - * Check if there are tokens for the given token word. - * - * @param string $sWord Token word to look for. - * - * @return bool True if there is one or more token for the token word. - */ - public function contains($sWord) - { - return isset($this->aTokens[$sWord]); - } - - /** - * Check if there are partial or full tokens for the given word. - * - * @param string $sWord Token word to look for. - * - * @return bool True if there is one or more token for the token word. - */ - public function containsAny($sWord) - { - return isset($this->aTokens[$sWord]); - } - - /** - * Get the list of tokens for the given token word. - * - * @param string $sWord Token word to look for. - * - * @return object[] Array of tokens for the given token word or an - * empty array if no tokens could be found. - */ - public function get($sWord) - { - return isset($this->aTokens[$sWord]) ? $this->aTokens[$sWord] : array(); - } - - public function getFullWordIDs() - { - $ids = array(); - - foreach ($this->aTokens as $aTokenList) { - foreach ($aTokenList as $oToken) { - if (is_a($oToken, '\Nominatim\Token\Word')) { - $ids[$oToken->getId()] = $oToken->getId(); - } - } - } - - return $ids; - } - - /** - * Add a new token for the given word. - * - * @param string $sWord Word the token describes. - * @param object $oToken Token object to add. - * - * @return void - */ - public function addToken($sWord, $oToken) - { - if (isset($this->aTokens[$sWord])) { - $this->aTokens[$sWord][] = $oToken; - } else { - $this->aTokens[$sWord] = array($oToken); - } - } - - public function debugTokenByWordIdList() - { - $aWordsIDs = array(); - foreach ($this->aTokens as $sToken => $aWords) { - foreach ($aWords as $aToken) { - $iId = $aToken->getId(); - if ($iId !== null) { - $aWordsIDs[$iId] = '#'.$sToken.'('.$aToken->debugCode().' '.$iId.')#'; - } - } - } - - return $aWordsIDs; - } - - public function debugInfo() - { - return $this->aTokens; - } -} diff --git a/lib-php/TokenPartial.php b/lib-php/TokenPartial.php deleted file mode 100644 index 3dc6f308a9..0000000000 --- a/lib-php/TokenPartial.php +++ /dev/null @@ -1,127 +0,0 @@ -iId = $iId; - $this->bNumberToken = (bool) preg_match('#^[0-9 ]+$#', $sToken); - $this->iSearchNameCount = $iSearchNameCount; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oPosition->isPhrase('country'); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - $aNewSearches = array(); - - // Partial token in Address. - if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase()) - && $oSearch->hasName() - ) { - $iSearchCost = $this->bNumberToken ? 2 : 1; - if ($this->iSearchNameCount >= CONST_Max_Word_Frequency) { - $iSearchCost += 1; - } - - $oNewSearch = $oSearch->clone($iSearchCost); - $oNewSearch->addAddressToken( - $this->iId, - $this->iSearchNameCount < CONST_Max_Word_Frequency - ); - - $aNewSearches[] = $oNewSearch; - } - - // Partial token in Name. - if ((!$oSearch->hasPostcode() && !$oSearch->hasAddress()) - && (!$oSearch->hasName(true) - || $oSearch->getNamePhrase() == $oPosition->getPhrase()) - ) { - $iSearchCost = 1; - if (!$oSearch->hasName(true)) { - $iSearchCost += 1; - } - if ($this->bNumberToken) { - $iSearchCost += 1; - } - - $oNewSearch = $oSearch->clone($iSearchCost); - $oNewSearch->addPartialNameToken( - $this->iId, - $this->iSearchNameCount < CONST_Max_Word_Frequency, - $this->iSearchNameCount > CONST_Search_NameOnlySearchFrequencyThreshold, - $oPosition->getPhrase() - ); - - $aNewSearches[] = $oNewSearch; - } - - return $aNewSearches; - } - - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'partial', - 'Info' => array( - 'count' => $this->iSearchNameCount - ) - ); - } - - public function debugCode() - { - return 'w'; - } -} diff --git a/lib-php/TokenPostcode.php b/lib-php/TokenPostcode.php deleted file mode 100644 index 0ff92929cb..0000000000 --- a/lib-php/TokenPostcode.php +++ /dev/null @@ -1,111 +0,0 @@ -iId = $iId; - $iSplitPos = strpos($sPostcode, '@'); - if ($iSplitPos === false) { - $this->sPostcode = $sPostcode; - } else { - $this->sPostcode = substr($sPostcode, 0, $iSplitPos); - } - $this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oSearch->hasPostcode() && $oPosition->maybePhrase('postalcode'); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - $aNewSearches = array(); - - // If we have structured search or this is the first term, - // make the postcode the primary search element. - if ($oSearch->hasOperator(\Nominatim\Operator::NONE) && $oPosition->isFirstToken()) { - $oNewSearch = $oSearch->clone(1); - $oNewSearch->setPostcodeAsName($this->iId, $this->sPostcode); - - $aNewSearches[] = $oNewSearch; - } - - // If we have a structured search or this is not the first term, - // add the postcode as an addendum. - if (!$oSearch->hasOperator(\Nominatim\Operator::POSTCODE) - && ($oPosition->isPhrase('postalcode') || $oSearch->hasName()) - ) { - $iPenalty = 1; - if (strlen($this->sPostcode) < 4) { - $iPenalty += 4 - strlen($this->sPostcode); - } - $oNewSearch = $oSearch->clone($iPenalty); - $oNewSearch->setPostcode($this->sPostcode); - - $aNewSearches[] = $oNewSearch; - } - - return $aNewSearches; - } - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'postcode', - 'Info' => $this->sPostcode.'('.$this->sCountryCode.')' - ); - } - - public function debugCode() - { - return 'P'; - } -} diff --git a/lib-php/TokenSpecialTerm.php b/lib-php/TokenSpecialTerm.php deleted file mode 100644 index 475ae71ba1..0000000000 --- a/lib-php/TokenSpecialTerm.php +++ /dev/null @@ -1,125 +0,0 @@ -iId = $iID; - $this->sClass = $sClass; - $this->sType = $sType; - $this->iOperator = $iOperator; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oSearch->hasOperator() - && $oPosition->isPhrase('') - && ($this->iOperator != \Nominatim\Operator::NONE - || (!$oSearch->hasAddress() && !$oSearch->hasHousenumber() && !$oSearch->hasCountry())); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - $iSearchCost = 0; - - $iOp = $this->iOperator; - if ($iOp == \Nominatim\Operator::NONE) { - if ($oPosition->isFirstToken() - || $oSearch->hasName() - || $oSearch->getContext()->isBoundedSearch() - ) { - $iOp = \Nominatim\Operator::NAME; - $iSearchCost += 3; - } else { - $iOp = \Nominatim\Operator::NEAR; - $iSearchCost += 4; - if (!$oPosition->isFirstToken()) { - $iSearchCost += 3; - } - } - } elseif ($oPosition->isFirstToken()) { - $iSearchCost += 2; - } elseif ($oPosition->isLastToken()) { - $iSearchCost += 4; - } else { - $iSearchCost += 6; - } - - if ($oSearch->hasHousenumber()) { - $iSearchCost ++; - } - - $oNewSearch = $oSearch->clone($iSearchCost); - $oNewSearch->setPoiSearch($iOp, $this->sClass, $this->sType); - - return array($oNewSearch); - } - - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'special term', - 'Info' => array( - 'class' => $this->sClass, - 'type' => $this->sType, - 'operator' => \Nominatim\Operator::toString($this->iOperator) - ) - ); - } - - public function debugCode() - { - return 'S'; - } -} diff --git a/lib-php/TokenWord.php b/lib-php/TokenWord.php deleted file mode 100644 index a7557d38b5..0000000000 --- a/lib-php/TokenWord.php +++ /dev/null @@ -1,110 +0,0 @@ -iId = $iId; - $this->iSearchNameCount = $iSearchNameCount; - $this->iTermCount = $iTermCount; - } - - public function getId() - { - return $this->iId; - } - - /** - * Check if the token can be added to the given search. - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return True if the token is compatible with the search configuration - * given the position. - */ - public function isExtendable($oSearch, $oPosition) - { - return !$oPosition->isPhrase('country'); - } - - /** - * Derive new searches by adding this token to an existing search. - * - * @param object $oSearch Partial search description derived so far. - * @param object $oPosition Description of the token position within - the query. - * - * @return SearchDescription[] List of derived search descriptions. - */ - public function extendSearch($oSearch, $oPosition) - { - // Full words can only be a name if they appear at the beginning - // of the phrase. In structured search the name must forcibly in - // the first phrase. In unstructured search it may be in a later - // phrase when the first phrase is a house number. - if ($oSearch->hasName() - || !($oPosition->isFirstPhrase() || $oPosition->isPhrase('')) - ) { - if ($this->iTermCount > 1 - && ($oPosition->isPhrase('') || !$oPosition->isFirstPhrase()) - ) { - $oNewSearch = $oSearch->clone(1); - $oNewSearch->addAddressToken($this->iId); - - return array($oNewSearch); - } - } elseif (!$oSearch->hasName(true)) { - $oNewSearch = $oSearch->clone(1); - $oNewSearch->addNameToken( - $this->iId, - CONST_Search_NameOnlySearchFrequencyThreshold - && $this->iSearchNameCount - < CONST_Search_NameOnlySearchFrequencyThreshold - ); - - return array($oNewSearch); - } - - return array(); - } - - public function debugInfo() - { - return array( - 'ID' => $this->iId, - 'Type' => 'word', - 'Info' => array( - 'count' => $this->iSearchNameCount, - 'terms' => $this->iTermCount - ) - ); - } - - public function debugCode() - { - return 'W'; - } -} diff --git a/lib-php/cmd.php b/lib-php/cmd.php deleted file mode 100644 index 6f1299dd16..0000000000 --- a/lib-php/cmd.php +++ /dev/null @@ -1,199 +0,0 @@ -= $iSize || $aArg[$i][0] == '-') { - showUsage($aSpec, $bExitOnError, 'Parameter of \''.$aLine[0].'\' is missing'); - } - - switch ($aLine[6]) { - case 'realpath': - $xVal[] = realpath($aArg[$i]); - break; - case 'realdir': - $sPath = realpath(dirname($aArg[$i])); - if ($sPath) { - $xVal[] = $sPath . '/' . basename($aArg[$i]); - } else { - $xVal[] = $sPath; - } - break; - case 'bool': - $xVal[] = (bool)$aArg[$i]; - break; - case 'int': - $xVal[] = (int)$aArg[$i]; - break; - case 'float': - $xVal[] = (float)$aArg[$i]; - break; - default: - $xVal[] = $aArg[$i]; - break; - } - } - if ($aLine[4] == 1) { - $xVal = $xVal[0]; - } - } else { - $xVal = true; - } - } else { - fail('Variable numbers of params not yet supported'); - } - - if ($aLine[3] > 1) { - if (!array_key_exists($aLine[0], $aResult)) { - $aResult[$aLine[0]] = array(); - } - $aResult[$aLine[0]][] = $xVal; - } else { - $aResult[$aLine[0]] = $xVal; - } - } else { - $bUnknown = $aArg[$i]; - } - } - - if (array_key_exists('help', $aResult)) { - showUsage($aSpec); - } - if ($bUnknown && $bExitOnUnknown) { - showUsage($aSpec, $bExitOnError, 'Unknown option \''.$bUnknown.'\''); - } - - foreach ($aSpec as $aLine) { - if (is_array($aLine)) { - if ($aCounts[$aLine[0]] < $aLine[2]) { - showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is missing'); - } - if ($aCounts[$aLine[0]] > $aLine[3]) { - showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is present too many times'); - } - if ($aLine[6] == 'bool' && !array_key_exists($aLine[0], $aResult)) { - $aResult[$aLine[0]] = false; - } - } - } - return $bUnknown; -} - -function showUsage($aSpec, $bExit = false, $sError = false) -{ - if ($sError) { - echo basename($_SERVER['argv'][0]).': '.$sError."\n"; - echo 'Try `'.basename($_SERVER['argv'][0]).' --help` for more information.'."\n"; - exit; - } - echo 'Usage: '.basename($_SERVER['argv'][0])."\n"; - $bFirst = true; - foreach ($aSpec as $aLine) { - if (is_array($aLine)) { - if ($bFirst) { - $bFirst = false; - echo "\n"; - } - $aNames = array(); - if ($aLine[1]) { - $aNames[] = '-'.$aLine[1]; - } - if ($aLine[0]) { - $aNames[] = '--'.$aLine[0]; - } - $sName = join(', ', $aNames); - echo ' '.$sName.str_repeat(' ', 30-strlen($sName)).$aLine[7]."\n"; - } else { - echo $aLine."\n"; - } - } - echo "\n"; - exit; -} - -function info($sMsg) -{ - echo date('Y-m-d H:i:s == ').$sMsg."\n"; -} - -$aWarnings = array(); - - -function warn($sMsg) -{ - $GLOBALS['aWarnings'][] = $sMsg; - echo date('Y-m-d H:i:s == ').'WARNING: '.$sMsg."\n"; -} - - -function repeatWarnings() -{ - foreach ($GLOBALS['aWarnings'] as $sMsg) { - echo ' * ',$sMsg."\n"; - } -} - - -function setupHTTPProxy() -{ - if (!getSettingBool('HTTP_PROXY')) { - return; - } - - $sProxy = 'tcp://'.getSetting('HTTP_PROXY_HOST').':'.getSetting('HTTP_PROXY_PROT'); - $aHeaders = array(); - - $sLogin = getSetting('HTTP_PROXY_LOGIN'); - $sPassword = getSetting('HTTP_PROXY_PASSWORD'); - - if ($sLogin && $sPassword) { - $sAuth = base64_encode($sLogin.':'.$sPassword); - $aHeaders = array('Proxy-Authorization: Basic '.$sAuth); - } - - $aProxyHeader = array( - 'proxy' => $sProxy, - 'request_fulluri' => true, - 'header' => $aHeaders - ); - - $aContext = array('http' => $aProxyHeader, 'https' => $aProxyHeader); - stream_context_set_default($aContext); -} diff --git a/lib-php/dotenv_loader.php b/lib-php/dotenv_loader.php deleted file mode 100644 index bcddf008b0..0000000000 --- a/lib-php/dotenv_loader.php +++ /dev/null @@ -1,21 +0,0 @@ -load(CONST_ConfigDir.'/env.defaults'); - - if (file_exists('.env')) { - $dotenv->load('.env'); - } -} diff --git a/lib-php/init-cmd.php b/lib-php/init-cmd.php deleted file mode 100644 index 44e7adb2ef..0000000000 --- a/lib-php/init-cmd.php +++ /dev/null @@ -1,13 +0,0 @@ -getCode() == 0 ? 500 : $exception->getCode()); - header('Content-type: application/json; charset=utf-8'); - include(CONST_LibDir.'/template/error-json.php'); - exit(); -} - -function exception_handler_xml($exception) -{ - http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode()); - header('Content-type: text/xml; charset=utf-8'); - echo ''."\n"; - include(CONST_LibDir.'/template/error-xml.php'); - exit(); -} - -function shutdown_exception_handler_xml() -{ - $error = error_get_last(); - if ($error !== null && $error['type'] === E_ERROR) { - exception_handler_xml(new \Exception($error['message'], 500)); - } -} - -function shutdown_exception_handler_json() -{ - $error = error_get_last(); - if ($error !== null && $error['type'] === E_ERROR) { - exception_handler_json(new \Exception($error['message'], 500)); - } -} - - -function set_exception_handler_by_format($sFormat = null) -{ - // Multiple calls to register_shutdown_function will cause multiple callbacks - // to be executed, we only want the last executed. Thus we don't want to register - // one by default without an explicit $sFormat set. - - if (!isset($sFormat)) { - set_exception_handler('exception_handler_json'); - } elseif ($sFormat == 'xml') { - set_exception_handler('exception_handler_xml'); - register_shutdown_function('shutdown_exception_handler_xml'); - } else { - set_exception_handler('exception_handler_json'); - register_shutdown_function('shutdown_exception_handler_json'); - } -} -// set a default -set_exception_handler_by_format(); - - -/*************************************************************************** - * HTTP Reply header setup - */ - -if (CONST_NoAccessControl) { - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Methods: OPTIONS,GET'); - if (!empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { - header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']); - } -} -if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') { - exit; -} - -if (CONST_Debug) { - header('Content-type: text/html; charset=utf-8'); -} diff --git a/lib-php/init.php b/lib-php/init.php deleted file mode 100644 index 9e71a761d6..0000000000 --- a/lib-php/init.php +++ /dev/null @@ -1,12 +0,0 @@ -getOne("select max(osm_id) from place where osm_type = 'N'"); - // Lookup the timestamp that node was created - $sLastNodeURL = 'https://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID.'/1'; - $sLastNodeXML = file_get_contents($sLastNodeURL); - - if ($sLastNodeXML === false) { - return false; - } - - preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate); - - return $aLastNodeDate[1]; -} - - -function byImportance($a, $b) -{ - if ($a['importance'] != $b['importance']) { - return ($a['importance'] > $b['importance']?-1:1); - } - - return $a['foundorder'] <=> $b['foundorder']; -} - - -function javascript_renderData($xVal, $iOptions = 0) -{ - $sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : ''; - if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) { - // Unset, we call javascript_renderData again during exception handling - unset($_GET['json_callback']); - throw new Exception('Invalid json_callback value', 400); - } - - $iOptions |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; - if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) { - $iOptions |= JSON_PRETTY_PRINT; - } - - $jsonout = json_encode($xVal, $iOptions); - - if ($sCallback) { - header('Content-Type: application/javascript; charset=UTF-8'); - echo $_GET['json_callback'].'('.$jsonout.')'; - } else { - header('Content-Type: application/json; charset=UTF-8'); - echo $jsonout; - } -} - -function addQuotes($s) -{ - return "'".$s."'"; -} - -function parseLatLon($sQuery) -{ - $sFound = null; - $fQueryLat = null; - $fQueryLon = null; - - if (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9.]*)[°\s]+([0-9.]+)?[′\']*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 - * degrees decimal minutes - * N 40 26.767, W 79 58.933 - * N 40°26.767′, W 79°58.933′ - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60); - $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60); - } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\']*[\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\'\s]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 - * degrees decimal minutes - * 40 26.767 N, 79 58.933 W - * 40° 26.767′ N 79° 58.933′ W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60); - $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60); - } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 7 8 - * degrees decimal seconds - * N 40 26 46 W 79 58 56 - * N 40° 26′ 46″, W 79° 58′ 56″ - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600); - $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600); - } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 5 6 7 8 - * degrees decimal seconds - * 40 26 46 N 79 58 56 W - * 40° 26′ 46″ N, 79° 58′ 56″ W - * 40° 26′ 46.78″ N, 79° 58′ 56.89″ W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600); - $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600); - } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*[,\s]+([EW])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * N 40.446° W 79.982° - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]); - $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]); - } elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[°\s]+([NS])[,\s]+([0-9]+[0-9]*\\.[0-9]+)[°\s]+([EW])\\s*/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * 40.446° N 79.982° W - */ - $sFound = $aData[0]; - $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]); - $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]); - } elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[,\s]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) { - /* 1 2 3 4 - * degrees decimal - * 12.34, 56.78 - * 12.34 56.78 - * [12.456,-78.90] - */ - $sFound = $aData[0]; - $fQueryLat = $aData[2]; - $fQueryLon = $aData[3]; - } else { - return false; - } - - return array($sFound, $fQueryLat, $fQueryLon); -} - -function addressRankToGeocodeJsonType($iAddressRank) -{ - if ($iAddressRank >= 29 && $iAddressRank <= 30) { - return 'house'; - } - if ($iAddressRank >= 26 && $iAddressRank < 28) { - return 'street'; - } - if ($iAddressRank >= 22 && $iAddressRank < 26) { - return 'locality'; - } - if ($iAddressRank >= 17 && $iAddressRank < 22) { - return 'district'; - } - if ($iAddressRank >= 13 && $iAddressRank < 17) { - return 'city'; - } - if ($iAddressRank >= 10 && $iAddressRank < 13) { - return 'county'; - } - if ($iAddressRank >= 5 && $iAddressRank < 10) { - return 'state'; - } - if ($iAddressRank >= 4 && $iAddressRank < 5) { - return 'country'; - } - - return 'locality'; -} - -if (!function_exists('array_key_last')) { - function array_key_last(array $array) - { - if (!empty($array)) { - return key(array_slice($array, -1, 1, true)); - } - } -} diff --git a/lib-php/log.php b/lib-php/log.php deleted file mode 100644 index 1d56773393..0000000000 --- a/lib-php/log.php +++ /dev/null @@ -1,104 +0,0 @@ -getDBQuotedList(array( - $sType, - $hLog[0], - $hLog[2], - $hLog[1], - $sUserAgent, - join(',', $aLanguageList), - $sOutputFormat, - $hLog[3] - ))); - $sSQL .= ')'; - $oDB->exec($sSQL); - } - - return $hLog; -} - -function logEnd(&$oDB, $hLog, $iNumResults) -{ - $fEndTime = microtime(true); - - if (CONST_Log_DB) { - $aEndTime = explode('.', $fEndTime); - if (!isset($aEndTime[1])) { - $aEndTime[1] = '0'; - } - $sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1]; - - $sSQL = 'update new_query_log set endtime = '.$oDB->getDBQuoted($sEndTime).', results = '.$iNumResults; - $sSQL .= ' where starttime = '.$oDB->getDBQuoted($hLog[0]); - $sSQL .= ' and ipaddress = '.$oDB->getDBQuoted($hLog[1]); - $sSQL .= ' and query = '.$oDB->getDBQuoted($hLog[2]); - $oDB->exec($sSQL); - } - - if (CONST_Log_File) { - $aOutdata = sprintf( - "[%s] %.4f %d %s \"%s\"\n", - $hLog[0], - $fEndTime-$hLog[5], - $iNumResults, - $hLog[4], - $hLog[2] - ); - file_put_contents(CONST_Log_File, $aOutdata, FILE_APPEND | LOCK_EX); - } -} diff --git a/lib-php/output.php b/lib-php/output.php deleted file mode 100644 index 44c4dde853..0000000000 --- a/lib-php/output.php +++ /dev/null @@ -1,38 +0,0 @@ - 'Feature', - 'properties' => array( - 'geocoding' => array() - ) - ); - - if (isset($aPlace['place_id'])) { - $aFilteredPlaces['properties']['geocoding']['place_id'] = $aPlace['place_id']; - } - $sOSMType = formatOSMType($aPlace['osm_type']); - if ($sOSMType) { - $aFilteredPlaces['properties']['geocoding']['osm_type'] = $sOSMType; - $aFilteredPlaces['properties']['geocoding']['osm_id'] = $aPlace['osm_id']; - } - - $aFilteredPlaces['properties']['geocoding']['osm_key'] = $aPlace['class']; - $aFilteredPlaces['properties']['geocoding']['osm_value'] = $aPlace['type']; - - $aFilteredPlaces['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPlace['rank_address']); - - $aFilteredPlaces['properties']['geocoding']['accuracy'] = (int) $fDistance; - - $aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress']; - - if ($aPlace['placename'] !== null) { - $aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename']; - } - - if (isset($aPlace['address'])) { - $aPlace['address']->addGeocodeJsonAddressParts( - $aFilteredPlaces['properties']['geocoding'] - ); - - $aFilteredPlaces['properties']['geocoding']['admin'] - = $aPlace['address']->getAdminLevels(); - } - - if (isset($aPlace['asgeojson'])) { - $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true); - } else { - $aFilteredPlaces['geometry'] = array( - 'type' => 'Point', - 'coordinates' => array( - (float) $aPlace['lon'], - (float) $aPlace['lat'] - ) - ); - } - - javascript_renderData(array( - 'type' => 'FeatureCollection', - 'geocoding' => array( - 'version' => '0.1.0', - 'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', - 'licence' => 'ODbL', - 'query' => $sQuery - ), - 'features' => array($aFilteredPlaces) - )); -} diff --git a/lib-php/template/address-geojson.php b/lib-php/template/address-geojson.php deleted file mode 100644 index dc3c3832af..0000000000 --- a/lib-php/template/address-geojson.php +++ /dev/null @@ -1,85 +0,0 @@ - 'Feature', - 'properties' => array() - ); - - if (isset($aPlace['place_id'])) { - $aFilteredPlaces['properties']['place_id'] = $aPlace['place_id']; - } - $sOSMType = formatOSMType($aPlace['osm_type']); - if ($sOSMType) { - $aFilteredPlaces['properties']['osm_type'] = $sOSMType; - $aFilteredPlaces['properties']['osm_id'] = $aPlace['osm_id']; - } - - $aFilteredPlaces['properties']['place_rank'] = $aPlace['rank_search']; - - $aFilteredPlaces['properties']['category'] = $aPlace['class']; - $aFilteredPlaces['properties']['type'] = $aPlace['type']; - - $aFilteredPlaces['properties']['importance'] = $aPlace['importance']; - - $aFilteredPlaces['properties']['addresstype'] = strtolower($aPlace['addresstype']); - - $aFilteredPlaces['properties']['name'] = $aPlace['placename']; - - $aFilteredPlaces['properties']['display_name'] = $aPlace['langaddress']; - - if (isset($aPlace['address'])) { - $aFilteredPlaces['properties']['address'] = $aPlace['address']->getAddressNames(); - } - if (isset($aPlace['sExtraTags'])) { - $aFilteredPlaces['properties']['extratags'] = $aPlace['sExtraTags']; - } - if (isset($aPlace['sNameDetails'])) { - $aFilteredPlaces['properties']['namedetails'] = $aPlace['sNameDetails']; - } - - if (isset($aPlace['aBoundingBox'])) { - $aFilteredPlaces['bbox'] = array( - (float) $aPlace['aBoundingBox'][2], // minlon - (float) $aPlace['aBoundingBox'][0], // minlat - (float) $aPlace['aBoundingBox'][3], // maxlon - (float) $aPlace['aBoundingBox'][1] // maxlat - ); - } - - if (isset($aPlace['asgeojson'])) { - $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true); - } else { - $aFilteredPlaces['geometry'] = array( - 'type' => 'Point', - 'coordinates' => array( - (float) $aPlace['lon'], - (float) $aPlace['lat'] - ) - ); - } - - - javascript_renderData(array( - 'type' => 'FeatureCollection', - 'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', - 'features' => array($aFilteredPlaces) - )); -} diff --git a/lib-php/template/address-json.php b/lib-php/template/address-json.php deleted file mode 100644 index 0766eaf4a9..0000000000 --- a/lib-php/template/address-json.php +++ /dev/null @@ -1,82 +0,0 @@ -getAddressNames(); - } - if (isset($aPlace['sExtraTags'])) { - $aFilteredPlaces['extratags'] = $aPlace['sExtraTags']; - } - if (isset($aPlace['sNameDetails'])) { - $aFilteredPlaces['namedetails'] = $aPlace['sNameDetails']; - } - - if (isset($aPlace['aBoundingBox'])) { - $aFilteredPlaces['boundingbox'] = $aPlace['aBoundingBox']; - } - - if (isset($aPlace['asgeojson'])) { - $aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson'], true); - } - - if (isset($aPlace['assvg'])) { - $aFilteredPlaces['svg'] = $aPlace['assvg']; - } - - if (isset($aPlace['astext'])) { - $aFilteredPlaces['geotext'] = $aPlace['astext']; - } - - if (isset($aPlace['askml'])) { - $aFilteredPlaces['geokml'] = $aPlace['askml']; - } -} - -javascript_renderData($aFilteredPlaces); diff --git a/lib-php/template/address-xml.php b/lib-php/template/address-xml.php deleted file mode 100644 index c418a4c496..0000000000 --- a/lib-php/template/address-xml.php +++ /dev/null @@ -1,110 +0,0 @@ -\n"; - -echo '\n"; - -if (empty($aPlace)) { - if (isset($sError)) { - echo "$sError"; - } else { - echo 'Unable to geocode'; - } -} else { - echo ''.htmlspecialchars($aPlace['langaddress']).''; - - if (isset($aPlace['address'])) { - echo ''; - foreach ($aPlace['address']->getAddressNames() as $sKey => $sValue) { - $sKey = str_replace(' ', '_', $sKey); - echo "<$sKey>"; - echo htmlspecialchars($sValue); - echo ""; - } - echo ''; - } - - if (isset($aPlace['sExtraTags'])) { - echo ''; - foreach ($aPlace['sExtraTags'] as $sKey => $sValue) { - echo ''; - } - echo ''; - } - - if (isset($aPlace['sNameDetails'])) { - echo ''; - foreach ($aPlace['sNameDetails'] as $sKey => $sValue) { - echo ''; - echo htmlspecialchars($sValue); - echo ''; - } - echo ''; - } - - if (isset($aPlace['askml'])) { - echo "\n"; - echo $aPlace['askml']; - echo ''; - } -} - -echo ''; diff --git a/lib-php/template/details-json.php b/lib-php/template/details-json.php deleted file mode 100644 index ae80a85b26..0000000000 --- a/lib-php/template/details-json.php +++ /dev/null @@ -1,120 +0,0 @@ -format(DateTime::RFC3339); -$aPlaceDetails['importance'] = (float) $aPointDetails['importance']; -$aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_importance']; - -$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags']; -$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia']; -$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails); -if (isset($sIcon)) { - $aPlaceDetails['icon'] = $sIcon; -} - -$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address']; -$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search']; - -$aPlaceDetails['isarea'] = $aPointDetails['isarea']; -$aPlaceDetails['centroid'] = array( - 'type' => 'Point', - 'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] ) - ); - -$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson'], true); - -$funcMapAddressLine = function ($aFull) { - return array( - 'localname' => $aFull['localname'], - 'place_id' => isset($aFull['place_id']) ? (int) $aFull['place_id'] : null, - 'osm_id' => isset($aFull['osm_id']) ? (int) $aFull['osm_id'] : null, - 'osm_type' => isset($aFull['osm_type']) ? $aFull['osm_type'] : null, - 'place_type' => isset($aFull['place_type']) ? $aFull['place_type'] : null, - 'class' => $aFull['class'], - 'type' => $aFull['type'], - 'admin_level' => isset($aFull['admin_level']) ? (int) $aFull['admin_level'] : null, - 'rank_address' => $aFull['rank_address'] ? (int) $aFull['rank_address'] : null, - 'distance' => (float) $aFull['distance'], - 'isaddress' => isset($aFull['isaddress']) ? (bool) $aFull['isaddress'] : null - ); -}; - -$funcMapKeyword = function ($aFull) { - return array( - 'id' => (int) $aFull['word_id'], - 'token' => $aFull['word_token'] - ); -}; - -if ($aAddressLines) { - $aPlaceDetails['address'] = array_map($funcMapAddressLine, $aAddressLines); -} - -if ($aLinkedLines) { - $aPlaceDetails['linked_places'] = array_map($funcMapAddressLine, $aLinkedLines); -} - -if ($bIncludeKeywords) { - $aPlaceDetails['keywords'] = array(); - - if ($aPlaceSearchNameKeywords) { - $aPlaceDetails['keywords']['name'] = array_map($funcMapKeyword, $aPlaceSearchNameKeywords); - } else { - $aPlaceDetails['keywords']['name'] = array(); - } - - if ($aPlaceSearchAddressKeywords) { - $aPlaceDetails['keywords']['address'] = array_map($funcMapKeyword, $aPlaceSearchAddressKeywords); - } else { - $aPlaceDetails['keywords']['address'] = array(); - } -} - -if ($bIncludeHierarchy) { - if ($bGroupHierarchy) { - $aPlaceDetails['hierarchy'] = array(); - foreach ($aHierarchyLines as $aAddressLine) { - if ($aAddressLine['type'] == 'yes') { - $sType = $aAddressLine['class']; - } else { - $sType = $aAddressLine['type']; - } - - if (!isset($aPlaceDetails['hierarchy'][$sType])) { - $aPlaceDetails['hierarchy'][$sType] = array(); - } - $aPlaceDetails['hierarchy'][$sType][] = $funcMapAddressLine($aAddressLine); - } - } else { - $aPlaceDetails['hierarchy'] = array_map($funcMapAddressLine, $aHierarchyLines); - } -} - -javascript_renderData($aPlaceDetails); diff --git a/lib-php/template/error-json.php b/lib-php/template/error-json.php deleted file mode 100644 index fea7d5c52b..0000000000 --- a/lib-php/template/error-json.php +++ /dev/null @@ -1,19 +0,0 @@ - $exception->getCode(), - 'message' => $exception->getMessage() - ); - - if (CONST_Debug) { - $error['details'] = $exception->getFile() . '('. $exception->getLine() . ')'; - } - - javascript_renderData(array('error' => $error)); diff --git a/lib-php/template/error-xml.php b/lib-php/template/error-xml.php deleted file mode 100644 index a21ac19853..0000000000 --- a/lib-php/template/error-xml.php +++ /dev/null @@ -1,7 +0,0 @@ - - getCode() ?> - getMessage() ?> - -
getFile() . '('. $exception->getLine() . ')' ?>
- -
\ No newline at end of file diff --git a/lib-php/template/search-batch-json.php b/lib-php/template/search-batch-json.php deleted file mode 100644 index 430237a294..0000000000 --- a/lib-php/template/search-batch-json.php +++ /dev/null @@ -1,83 +0,0 @@ - $aPointDetails) { - $aPlace = array( - 'place_id'=>$aPointDetails['place_id'], - ); - - $sOSMType = formatOSMType($aPointDetails['osm_type']); - if ($sOSMType) { - $aPlace['osm_type'] = $sOSMType; - $aPlace['osm_id'] = $aPointDetails['osm_id']; - } - - if (isset($aPointDetails['aBoundingBox'])) { - $aPlace['boundingbox'] = array( - $aPointDetails['aBoundingBox'][0], - $aPointDetails['aBoundingBox'][1], - $aPointDetails['aBoundingBox'][2], - $aPointDetails['aBoundingBox'][3] - ); - } - - if (isset($aPointDetails['zoom'])) { - $aPlace['zoom'] = $aPointDetails['zoom']; - } - - $aPlace['lat'] = $aPointDetails['lat']; - $aPlace['lon'] = $aPointDetails['lon']; - $aPlace['display_name'] = $aPointDetails['name']; - $aPlace['place_rank'] = $aPointDetails['rank_search']; - - $aPlace['category'] = $aPointDetails['class']; - $aPlace['type'] = $aPointDetails['type']; - - $aPlace['importance'] = $aPointDetails['importance']; - - if (isset($aPointDetails['icon'])) { - $aPlace['icon'] = $aPointDetails['icon']; - } - - if (isset($aPointDetails['address'])) { - $aPlace['address'] = $aPointDetails['address']->getAddressNames(); - } - - if (isset($aPointDetails['asgeojson'])) { - $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true); - } - - if (isset($aPointDetails['assvg'])) { - $aPlace['svg'] = $aPointDetails['assvg']; - } - - if (isset($aPointDetails['astext'])) { - $aPlace['geotext'] = $aPointDetails['astext']; - } - - if (isset($aPointDetails['askml'])) { - $aPlace['geokml'] = $aPointDetails['askml']; - } - - $aFilteredPlaces[] = $aPlace; - } - $aOutput['batch'][] = $aFilteredPlaces; -} - -javascript_renderData($aOutput, array('geojson')); diff --git a/lib-php/template/search-geocodejson.php b/lib-php/template/search-geocodejson.php deleted file mode 100644 index bba41a0d22..0000000000 --- a/lib-php/template/search-geocodejson.php +++ /dev/null @@ -1,72 +0,0 @@ - $aPointDetails) { - $aPlace = array( - 'type' => 'Feature', - 'properties' => array( - 'geocoding' => array() - ) - ); - - if (isset($aPointDetails['place_id'])) { - $aPlace['properties']['geocoding']['place_id'] = $aPointDetails['place_id']; - } - $sOSMType = formatOSMType($aPointDetails['osm_type']); - if ($sOSMType) { - $aPlace['properties']['geocoding']['osm_type'] = $sOSMType; - $aPlace['properties']['geocoding']['osm_id'] = $aPointDetails['osm_id']; - } - $aPlace['properties']['geocoding']['osm_key'] = $aPointDetails['class']; - $aPlace['properties']['geocoding']['osm_value'] = $aPointDetails['type']; - - $aPlace['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPointDetails['rank_address']); - - $aPlace['properties']['geocoding']['label'] = $aPointDetails['langaddress']; - - if ($aPointDetails['placename'] !== null) { - $aPlace['properties']['geocoding']['name'] = $aPointDetails['placename']; - } - - if (isset($aPointDetails['address'])) { - $aPointDetails['address']->addGeocodeJsonAddressParts( - $aPlace['properties']['geocoding'] - ); - - $aPlace['properties']['geocoding']['admin'] - = $aPointDetails['address']->getAdminLevels(); - } - - if (isset($aPointDetails['asgeojson'])) { - $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true); - } else { - $aPlace['geometry'] = array( - 'type' => 'Point', - 'coordinates' => array( - (float) $aPointDetails['lon'], - (float) $aPointDetails['lat'] - ) - ); - } - $aFilteredPlaces[] = $aPlace; -} - - -javascript_renderData(array( - 'type' => 'FeatureCollection', - 'geocoding' => array( - 'version' => '0.1.0', - 'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', - 'licence' => 'ODbL', - 'query' => $sQuery - ), - 'features' => $aFilteredPlaces - )); diff --git a/lib-php/template/search-geojson.php b/lib-php/template/search-geojson.php deleted file mode 100644 index 7665700dff..0000000000 --- a/lib-php/template/search-geojson.php +++ /dev/null @@ -1,83 +0,0 @@ - $aPointDetails) { - $aPlace = array( - 'type' => 'Feature', - 'properties' => array( - 'place_id'=>$aPointDetails['place_id'], - ) - ); - - $sOSMType = formatOSMType($aPointDetails['osm_type']); - if ($sOSMType) { - $aPlace['properties']['osm_type'] = $sOSMType; - $aPlace['properties']['osm_id'] = $aPointDetails['osm_id']; - } - - if (isset($aPointDetails['aBoundingBox'])) { - $aPlace['bbox'] = array( - (float) $aPointDetails['aBoundingBox'][2], // minlon - (float) $aPointDetails['aBoundingBox'][0], // minlat - (float) $aPointDetails['aBoundingBox'][3], // maxlon - (float) $aPointDetails['aBoundingBox'][1] // maxlat - ); - } - - if (isset($aPointDetails['zoom'])) { - $aPlace['properties']['zoom'] = $aPointDetails['zoom']; - } - - $aPlace['properties']['display_name'] = $aPointDetails['name']; - - $aPlace['properties']['place_rank'] = $aPointDetails['rank_search']; - $aPlace['properties']['category'] = $aPointDetails['class']; - - $aPlace['properties']['type'] = $aPointDetails['type']; - - $aPlace['properties']['importance'] = $aPointDetails['importance']; - - if (isset($aPointDetails['icon']) && $aPointDetails['icon']) { - $aPlace['properties']['icon'] = $aPointDetails['icon']; - } - - if (isset($aPointDetails['address'])) { - $aPlace['properties']['address'] = $aPointDetails['address']->getAddressNames(); - } - - if (isset($aPointDetails['asgeojson'])) { - $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true); - } else { - $aPlace['geometry'] = array( - 'type' => 'Point', - 'coordinates' => array( - (float) $aPointDetails['lon'], - (float) $aPointDetails['lat'] - ) - ); - } - - - if (isset($aPointDetails['sExtraTags'])) { - $aPlace['properties']['extratags'] = $aPointDetails['sExtraTags']; - } - if (isset($aPointDetails['sNameDetails'])) { - $aPlace['properties']['namedetails'] = $aPointDetails['sNameDetails']; - } - - $aFilteredPlaces[] = $aPlace; -} - -javascript_renderData(array( - 'type' => 'FeatureCollection', - 'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', - 'features' => $aFilteredPlaces - )); diff --git a/lib-php/template/search-json.php b/lib-php/template/search-json.php deleted file mode 100644 index 5fb1302080..0000000000 --- a/lib-php/template/search-json.php +++ /dev/null @@ -1,81 +0,0 @@ - $aPointDetails) { - $aPlace = array( - 'place_id'=>$aPointDetails['place_id'], - 'licence'=>'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', - ); - - $sOSMType = formatOSMType($aPointDetails['osm_type']); - if ($sOSMType) { - $aPlace['osm_type'] = $sOSMType; - $aPlace['osm_id'] = $aPointDetails['osm_id']; - } - - if (isset($aPointDetails['aBoundingBox'])) { - $aPlace['boundingbox'] = $aPointDetails['aBoundingBox']; - } - - if (isset($aPointDetails['zoom'])) { - $aPlace['zoom'] = $aPointDetails['zoom']; - } - - $aPlace['lat'] = $aPointDetails['lat']; - $aPlace['lon'] = $aPointDetails['lon']; - - $aPlace['display_name'] = $aPointDetails['name']; - - if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') { - $aPlace['place_rank'] = $aPointDetails['rank_search']; - $aPlace['category'] = $aPointDetails['class']; - } else { - $aPlace['class'] = $aPointDetails['class']; - } - $aPlace['type'] = $aPointDetails['type']; - - $aPlace['importance'] = $aPointDetails['importance']; - - if (isset($aPointDetails['icon']) && $aPointDetails['icon']) { - $aPlace['icon'] = $aPointDetails['icon']; - } - - if (isset($aPointDetails['address'])) { - $aPlace['address'] = $aPointDetails['address']->getAddressNames(); - } - - if (isset($aPointDetails['asgeojson'])) { - $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true); - } - - if (isset($aPointDetails['assvg'])) { - $aPlace['svg'] = $aPointDetails['assvg']; - } - - if (isset($aPointDetails['astext'])) { - $aPlace['geotext'] = $aPointDetails['astext']; - } - - if (isset($aPointDetails['askml'])) { - $aPlace['geokml'] = $aPointDetails['askml']; - } - - if (isset($aPointDetails['sExtraTags'])) { - $aPlace['extratags'] = $aPointDetails['sExtraTags']; - } - if (isset($aPointDetails['sNameDetails'])) { - $aPlace['namedetails'] = $aPointDetails['sNameDetails']; - } - - $aFilteredPlaces[] = $aPlace; -} - -javascript_renderData($aFilteredPlaces); diff --git a/lib-php/template/search-xml.php b/lib-php/template/search-xml.php deleted file mode 100644 index 8dda65e2a1..0000000000 --- a/lib-php/template/search-xml.php +++ /dev/null @@ -1,138 +0,0 @@ -\n"; - -echo '<'; -echo (isset($sXmlRootTag)?$sXmlRootTag:'searchresults'); -echo " timestamp='".date(DATE_RFC822)."'"; -echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'"; -echo " querystring='".htmlspecialchars($sQuery, ENT_QUOTES)."'"; -if (isset($aMoreParams['viewbox'])) { - echo " viewbox='".htmlspecialchars($aMoreParams['viewbox'], ENT_QUOTES)."'"; -} -if (isset($aMoreParams['exclude_place_ids'])) { - echo " exclude_place_ids='".htmlspecialchars($aMoreParams['exclude_place_ids'])."'"; -} -echo " more_url='".htmlspecialchars($sMoreURL)."'"; -echo ">\n"; - -foreach ($aSearchResults as $iResNum => $aResult) { - echo "'; - } - echo "\n"; - echo $aResult['askml']; - echo ''; - } - - if (isset($aResult['sExtraTags'])) { - if (!$bHasDelim) { - $bHasDelim = true; - echo '>'; - } - echo "\n"; - foreach ($aResult['sExtraTags'] as $sKey => $sValue) { - echo ''; - } - echo ''; - } - - if (isset($aResult['sNameDetails'])) { - if (!$bHasDelim) { - $bHasDelim = true; - echo '>'; - } - echo "\n"; - foreach ($aResult['sNameDetails'] as $sKey => $sValue) { - echo ''; - echo htmlspecialchars($sValue); - echo ''; - } - echo ''; - } - - if (isset($aResult['address'])) { - if (!$bHasDelim) { - $bHasDelim = true; - echo '>'; - } - echo "\n"; - foreach ($aResult['address']->getAddressNames() as $sKey => $sValue) { - $sKey = str_replace(' ', '_', $sKey); - echo "<$sKey>"; - echo htmlspecialchars($sValue); - echo ""; - } - } - - if ($bHasDelim) { - echo ''; - } else { - echo '/>'; - } -} - -echo ''; diff --git a/lib-php/tokenizer/icu_tokenizer.php b/lib-php/tokenizer/icu_tokenizer.php deleted file mode 100644 index e45d076548..0000000000 --- a/lib-php/tokenizer/icu_tokenizer.php +++ /dev/null @@ -1,235 +0,0 @@ -oDB =& $oDB; - $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules); - $this->oTransliterator = \Transliterator::createFromRules(CONST_Transliteration); - } - - public function checkStatus() - { - $sSQL = 'SELECT word_id FROM word WHERE word_id is not null limit 1'; - $iWordID = $this->oDB->getOne($sSQL); - if ($iWordID === false) { - throw new \Exception('Query failed', 703); - } - if (!$iWordID) { - throw new \Exception('No value', 704); - } - } - - - public function normalizeString($sTerm) - { - if ($this->oNormalizer === null) { - return $sTerm; - } - - return $this->oNormalizer->transliterate($sTerm); - } - - - public function mostFrequentWords($iNum) - { - $sSQL = "SELECT word FROM word WHERE type = 'W'"; - $sSQL .= "ORDER BY info->'count' DESC LIMIT ".$iNum; - return $this->oDB->getCol($sSQL); - } - - - private function makeStandardWord($sTerm) - { - return trim($this->oTransliterator->transliterate(' '.$sTerm.' ')); - } - - - public function tokensForSpecialTerm($sTerm) - { - $aResults = array(); - - $sSQL = "SELECT word_id, info->>'class' as class, info->>'type' as type "; - $sSQL .= ' FROM word WHERE word_token = :term and type = \'S\''; - - Debug::printVar('Term', $sTerm); - Debug::printSQL($sSQL); - $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $this->makeStandardWord($sTerm))); - - Debug::printVar('Results', $aSearchWords); - - foreach ($aSearchWords as $aSearchTerm) { - $aResults[] = new \Nominatim\Token\SpecialTerm( - $aSearchTerm['word_id'], - $aSearchTerm['class'], - $aSearchTerm['type'], - \Nominatim\Operator::TYPE - ); - } - - Debug::printVar('Special term tokens', $aResults); - - return $aResults; - } - - - public function extractTokensFromPhrases(&$aPhrases) - { - $sNormQuery = ''; - $aWordLists = array(); - $aTokens = array(); - foreach ($aPhrases as $iPhrase => $oPhrase) { - $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase()); - $sPhrase = $this->makeStandardWord($oPhrase->getPhrase()); - Debug::printVar('Phrase', $sPhrase); - - $oWordList = new SimpleWordList($sPhrase); - $aTokens = array_merge($aTokens, $oWordList->getTokens()); - $aWordLists[] = $oWordList; - } - - Debug::printVar('Tokens', $aTokens); - Debug::printVar('WordLists', $aWordLists); - - $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery); - - foreach ($aPhrases as $iPhrase => $oPhrase) { - $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens)); - } - - return $oValidTokens; - } - - - private function computeValidTokens($aTokens, $sNormQuery) - { - $oValidTokens = new TokenList(); - - if (!empty($aTokens)) { - $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery); - - // Try more interpretations for Tokens that could not be matched. - foreach ($aTokens as $sToken) { - if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) { - if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) { - // US ZIP+4 codes - merge in the 5-digit ZIP code - $oValidTokens->addToken( - $sToken, - new Token\Postcode(null, $aData[1], 'us') - ); - } elseif (preg_match('/^[0-9]+$/', $sToken)) { - // Unknown single word token with a number. - // Assume it is a house number. - $oValidTokens->addToken( - $sToken, - new Token\HouseNumber(null, trim($sToken)) - ); - } - } - } - } - - return $oValidTokens; - } - - - private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery) - { - // Check which tokens we have, get the ID numbers - $sSQL = 'SELECT word_id, word_token, type, word,'; - $sSQL .= " info->>'op' as operator,"; - $sSQL .= " info->>'class' as class, info->>'type' as ctype,"; - $sSQL .= " info->>'count' as count,"; - $sSQL .= " info->>'lookup' as lookup"; - $sSQL .= ' FROM word WHERE word_token in ('; - $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')'; - - Debug::printSQL($sSQL); - - $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.'); - - foreach ($aDBWords as $aWord) { - $iId = (int) $aWord['word_id']; - $sTok = $aWord['word_token']; - - switch ($aWord['type']) { - case 'C': // country name tokens - if ($aWord['word'] !== null) { - $oValidTokens->addToken( - $sTok, - new Token\Country($iId, $aWord['word']) - ); - } - break; - case 'H': // house number tokens - $sLookup = $aWord['lookup'] ?? $aWord['word_token']; - $oValidTokens->addToken($sTok, new Token\HouseNumber($iId, $sLookup)); - break; - case 'P': // postcode tokens - // Postcodes are not normalized, so they may have content - // that makes SQL injection possible. Reject postcodes - // that would need special escaping. - if ($aWord['word'] !== null - && pg_escape_string($aWord['word']) == $aWord['word'] - ) { - $iSplitPos = strpos($aWord['word'], '@'); - if ($iSplitPos === false) { - $sPostcode = $aWord['word']; - } else { - $sPostcode = substr($aWord['word'], 0, $iSplitPos); - } - - $oValidTokens->addToken( - $sTok, - new Token\Postcode($iId, $sPostcode, null) - ); - } - break; - case 'S': // tokens for classification terms (special phrases) - if ($aWord['class'] !== null && $aWord['ctype'] !== null) { - $oValidTokens->addToken($sTok, new Token\SpecialTerm( - $iId, - $aWord['class'], - $aWord['ctype'], - (isset($aWord['operator'])) ? Operator::NEAR : Operator::NONE - )); - } - break; - case 'W': // full-word tokens - $oValidTokens->addToken($sTok, new Token\Word( - $iId, - (int) $aWord['count'], - substr_count($aWord['word_token'], ' ') - )); - break; - case 'w': // partial word terms - $oValidTokens->addToken($sTok, new Token\Partial( - $iId, - $aWord['word_token'], - (int) $aWord['count'] - )); - break; - default: - break; - } - } - } -} diff --git a/lib-php/tokenizer/legacy_tokenizer.php b/lib-php/tokenizer/legacy_tokenizer.php deleted file mode 100644 index 6f3d23043f..0000000000 --- a/lib-php/tokenizer/legacy_tokenizer.php +++ /dev/null @@ -1,265 +0,0 @@ -oDB =& $oDB; - $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules); - } - - public function checkStatus() - { - $sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')"); - if ($sStandardWord === false) { - throw new \Exception('Module failed', 701); - } - - if ($sStandardWord != 'a') { - throw new \Exception('Module call failed', 702); - } - - $sSQL = "SELECT word_id FROM word WHERE word_token IN (' a')"; - $iWordID = $this->oDB->getOne($sSQL); - if ($iWordID === false) { - throw new \Exception('Query failed', 703); - } - if (!$iWordID) { - throw new \Exception('No value', 704); - } - } - - - public function normalizeString($sTerm) - { - if ($this->oNormalizer === null) { - return $sTerm; - } - - return $this->oNormalizer->transliterate($sTerm); - } - - - public function mostFrequentWords($iNum) - { - $sSQL = 'SELECT word FROM word WHERE word is not null '; - $sSQL .= 'ORDER BY search_name_count DESC LIMIT '.$iNum; - return $this->oDB->getCol($sSQL); - } - - - public function tokensForSpecialTerm($sTerm) - { - $aResults = array(); - - $sSQL = 'SELECT word_id, class, type FROM word '; - $sSQL .= ' WHERE word_token = \' \' || make_standard_name(:term)'; - $sSQL .= ' AND class is not null AND class not in (\'place\')'; - - Debug::printVar('Term', $sTerm); - Debug::printSQL($sSQL); - $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $sTerm)); - - Debug::printVar('Results', $aSearchWords); - - foreach ($aSearchWords as $aSearchTerm) { - $aResults[] = new \Nominatim\Token\SpecialTerm( - $aSearchTerm['word_id'], - $aSearchTerm['class'], - $aSearchTerm['type'], - \Nominatim\Operator::TYPE - ); - } - - Debug::printVar('Special term tokens', $aResults); - - return $aResults; - } - - - public function extractTokensFromPhrases(&$aPhrases) - { - // First get the normalized version of all phrases - $sNormQuery = ''; - $sSQL = 'SELECT '; - $aParams = array(); - foreach ($aPhrases as $iPhrase => $oPhrase) { - $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase()); - $sSQL .= 'make_standard_name(:' .$iPhrase.') as p'.$iPhrase.','; - $aParams[':'.$iPhrase] = $oPhrase->getPhrase(); - - // Conflicts between US state abbreviations and various words - // for 'the' in different languages - switch (strtolower($oPhrase->getPhrase())) { - case 'il': - $aParams[':'.$iPhrase] = 'illinois'; - break; - case 'al': - $aParams[':'.$iPhrase] = 'alabama'; - break; - case 'la': - $aParams[':'.$iPhrase] = 'louisiana'; - break; - default: - $aParams[':'.$iPhrase] = $oPhrase->getPhrase(); - break; - } - } - $sSQL = substr($sSQL, 0, -1); - - Debug::printSQL($sSQL); - Debug::printVar('SQL parameters', $aParams); - - $aNormPhrases = $this->oDB->getRow($sSQL, $aParams); - - Debug::printVar('SQL result', $aNormPhrases); - - // now compute all possible tokens - $aWordLists = array(); - $aTokens = array(); - foreach ($aNormPhrases as $sPhrase) { - $oWordList = new SimpleWordList($sPhrase); - - foreach ($oWordList->getTokens() as $sToken) { - $aTokens[' '.$sToken] = ' '.$sToken; - $aTokens[$sToken] = $sToken; - } - - $aWordLists[] = $oWordList; - } - - Debug::printVar('Tokens', $aTokens); - Debug::printVar('WordLists', $aWordLists); - - $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery); - - foreach ($aPhrases as $iPhrase => $oPhrase) { - $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens)); - } - - return $oValidTokens; - } - - - private function computeValidTokens($aTokens, $sNormQuery) - { - $oValidTokens = new TokenList(); - - if (!empty($aTokens)) { - $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery); - - // Try more interpretations for Tokens that could not be matched. - foreach ($aTokens as $sToken) { - if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) { - if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) { - // US ZIP+4 codes - merge in the 5-digit ZIP code - $oValidTokens->addToken( - $sToken, - new Token\Postcode(null, $aData[1], 'us') - ); - } elseif (preg_match('/^[0-9]+$/', $sToken)) { - // Unknown single word token with a number. - // Assume it is a house number. - $oValidTokens->addToken( - $sToken, - new Token\HouseNumber(null, trim($sToken)) - ); - } - } - } - } - - return $oValidTokens; - } - - - private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery) - { - // Check which tokens we have, get the ID numbers - $sSQL = 'SELECT word_id, word_token, word, class, type, country_code,'; - $sSQL .= ' operator, coalesce(search_name_count, 0) as count'; - $sSQL .= ' FROM word WHERE word_token in ('; - $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')'; - - Debug::printSQL($sSQL); - - $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.'); - - foreach ($aDBWords as $aWord) { - $oToken = null; - $iId = (int) $aWord['word_id']; - - if ($aWord['class']) { - // Special terms need to appear in their normalized form. - // (postcodes are not normalized in the word table) - $sNormWord = $this->normalizeString($aWord['word']); - if ($aWord['word'] && strpos($sNormQuery, $sNormWord) === false) { - continue; - } - - if ($aWord['class'] == 'place' && $aWord['type'] == 'house') { - $oToken = new Token\HouseNumber($iId, trim($aWord['word_token'])); - } elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') { - if ($aWord['word'] - && pg_escape_string($aWord['word']) == $aWord['word'] - ) { - $oToken = new Token\Postcode( - $iId, - $aWord['word'], - $aWord['country_code'] - ); - } - } else { - // near and in operator the same at the moment - $oToken = new Token\SpecialTerm( - $iId, - $aWord['class'], - $aWord['type'], - $aWord['operator'] ? Operator::NEAR : Operator::NONE - ); - } - } elseif ($aWord['country_code']) { - $oToken = new Token\Country($iId, $aWord['country_code']); - } elseif ($aWord['word_token'][0] == ' ') { - $oToken = new Token\Word( - $iId, - (int) $aWord['count'], - substr_count($aWord['word_token'], ' ') - ); - // For backward compatibility: ignore all partial tokens with more - // than one word. - } elseif (strpos($aWord['word_token'], ' ') === false) { - $oToken = new Token\Partial( - $iId, - $aWord['word_token'], - (int) $aWord['count'] - ); - } - - if ($oToken) { - // remove any leading spaces - if ($aWord['word_token'][0] == ' ') { - $oValidTokens->addToken(substr($aWord['word_token'], 1), $oToken); - } else { - $oValidTokens->addToken($aWord['word_token'], $oToken); - } - } - } - } -} diff --git a/lib-php/website/deletable.php b/lib-php/website/deletable.php deleted file mode 100644 index ffb202fd42..0000000000 --- a/lib-php/website/deletable.php +++ /dev/null @@ -1,36 +0,0 @@ -getSet('format', array('json'), 'json'); -set_exception_handler_by_format($sOutputFormat); - -$oDB = new Nominatim\DB(CONST_Database_DSN); -$oDB->connect(); - -$sSQL = 'select placex.place_id, country_code,'; -$sSQL .= " name->'name' as name, i.* from placex, import_polygon_delete i"; -$sSQL .= ' where placex.osm_id = i.osm_id and placex.osm_type = i.osm_type'; -$sSQL .= ' and placex.class = i.class and placex.type = i.type'; -$aPolygons = $oDB->getAll($sSQL, null, 'Could not get list of deleted OSM elements.'); - -if (CONST_Debug) { - var_dump($aPolygons); - exit; -} - -if ($sOutputFormat == 'json') { - javascript_renderData($aPolygons); -} diff --git a/lib-php/website/details.php b/lib-php/website/details.php deleted file mode 100644 index 98fb6ef75f..0000000000 --- a/lib-php/website/details.php +++ /dev/null @@ -1,263 +0,0 @@ -getSet('format', array('json'), 'json'); -set_exception_handler_by_format($sOutputFormat); - -$aLangPrefOrder = $oParams->getPreferredLanguages(); - -$sPlaceId = $oParams->getString('place_id'); -$sOsmType = $oParams->getSet('osmtype', array('N', 'W', 'R')); -$iOsmId = $oParams->getInt('osmid', 0); -$sClass = $oParams->getString('class'); - -$bIncludeKeywords = $oParams->getBool('keywords', false); -$bIncludeAddressDetails = $oParams->getBool('addressdetails', false); -$bIncludeLinkedPlaces = $oParams->getBool('linkedplaces', true); -$bIncludeHierarchy = $oParams->getBool('hierarchy', false); -$bGroupHierarchy = $oParams->getBool('group_hierarchy', false); -$bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson', false); - -$oDB = new Nominatim\DB(CONST_Database_DSN); -$oDB->connect(); - -$sLanguagePrefArraySQL = $oDB->getArraySQL($oDB->getDBQuotedList($aLangPrefOrder)); - -if ($sOsmType && $iOsmId !== 0) { - $sSQL = 'SELECT place_id FROM placex WHERE osm_type = :type AND osm_id = :id'; - $aSQLParams = array(':type' => $sOsmType, ':id' => $iOsmId); - // osm_type and osm_id are not unique enough - if ($sClass) { - $sSQL .= ' AND class= :class'; - $aSQLParams[':class'] = $sClass; - } - $sSQL .= ' ORDER BY class ASC'; - $sPlaceId = $oDB->getOne($sSQL, $aSQLParams); - - - // Nothing? Maybe it's an interpolation. - // XXX Simply returns the first parent street it finds. It should - // get a house number and get the right interpolation. - if (!$sPlaceId && $sOsmType == 'W' && (!$sClass || $sClass == 'place')) { - $sSQL = 'SELECT place_id FROM location_property_osmline' - .' WHERE osm_id = :id LIMIT 1'; - $sPlaceId = $oDB->getOne($sSQL, array(':id' => $iOsmId)); - } - - // Be nice about our error messages for broken geometry - - if (!$sPlaceId && $oDB->tableExists('import_polygon_error')) { - $sSQL = 'SELECT '; - $sSQL .= ' osm_type, '; - $sSQL .= ' osm_id, '; - $sSQL .= ' errormessage, '; - $sSQL .= ' class, '; - $sSQL .= ' type, '; - $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname,"; - $sSQL .= ' ST_AsText(prevgeometry) AS prevgeom, '; - $sSQL .= ' ST_AsText(newgeometry) AS newgeom'; - $sSQL .= ' FROM import_polygon_error '; - $sSQL .= ' WHERE osm_type = :type'; - $sSQL .= ' AND osm_id = :id'; - $sSQL .= ' ORDER BY updated DESC'; - $sSQL .= ' LIMIT 1'; - $aPointDetails = $oDB->getRow($sSQL, array(':type' => $sOsmType, ':id' => $iOsmId)); - if ($aPointDetails) { - if (preg_match('/\[(-?\d+\.\d+) (-?\d+\.\d+)\]/', $aPointDetails['errormessage'], $aMatches)) { - $aPointDetails['error_x'] = $aMatches[1]; - $aPointDetails['error_y'] = $aMatches[2]; - } else { - $aPointDetails['error_x'] = 0; - $aPointDetails['error_y'] = 0; - } - include(CONST_LibDir.'/template/details-error-'.$sOutputFormat.'.php'); - exit; - } - } - - if ($sPlaceId === false) { - throw new \Exception('No place with that OSM ID found.', 404); - } -} else { - if ($sPlaceId === false) { - userError('Required parameters missing. Need either osmtype/osmid or place_id.'); - } -} - -$iPlaceID = (int)$sPlaceId; - -if (CONST_Use_US_Tiger_Data) { - $iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_tiger WHERE place_id = '.$iPlaceID); - if ($iParentPlaceID) { - $iPlaceID = $iParentPlaceID; - } -} - -// interpolated house numbers -$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_osmline WHERE place_id = '.$iPlaceID); -if ($iParentPlaceID) { - $iPlaceID = $iParentPlaceID; -} - -// artificial postcodes -$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_postcode WHERE place_id = '.$iPlaceID); -if ($iParentPlaceID) { - $iPlaceID = $iParentPlaceID; -} - -$hLog = logStart($oDB, 'details', $_SERVER['QUERY_STRING'], $aLangPrefOrder); - -// Get the details for this point -$sSQL = 'SELECT place_id, osm_type, osm_id, class, type, name, admin_level,'; -$sSQL .= ' housenumber, postcode, country_code,'; -$sSQL .= ' importance, wikipedia,'; -$sSQL .= ' ROUND(EXTRACT(epoch FROM indexed_date)) AS indexed_epoch,'; -$sSQL .= ' parent_place_id, '; -$sSQL .= ' rank_address, '; -$sSQL .= ' rank_search, '; -$sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, "; -$sSQL .= " ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea, "; -$sSQL .= ' ST_y(centroid) AS lat, '; -$sSQL .= ' ST_x(centroid) AS lon, '; -$sSQL .= ' CASE '; -$sSQL .= ' WHEN importance = 0 OR importance IS NULL '; -$sSQL .= ' THEN 0.75-(rank_search::float/40) '; -$sSQL .= ' ELSE importance '; -$sSQL .= ' END as calculated_importance, '; -if ($bIncludePolygonAsGeoJSON) { - $sSQL .= ' ST_AsGeoJSON(CASE '; - $sSQL .= ' WHEN ST_NPoints(geometry) > 5000 '; - $sSQL .= ' THEN ST_SimplifyPreserveTopology(geometry, 0.0001) '; - $sSQL .= ' ELSE geometry '; - $sSQL .= ' END) as asgeojson'; -} else { - $sSQL .= ' ST_AsGeoJSON(centroid) as asgeojson'; -} -$sSQL .= ' FROM placex '; -$sSQL .= " WHERE place_id = $iPlaceID"; - -$aPointDetails = $oDB->getRow($sSQL, null, 'Could not get details of place object.'); - -if (!$aPointDetails) { - throw new \Exception('No place with that place ID found.', 404); -} - -$aPointDetails['localname'] = $aPointDetails['localname']?$aPointDetails['localname']:$aPointDetails['housenumber']; - -// Get all alternative names (languages, etc) -$sSQL = 'SELECT (each(name)).key,(each(name)).value FROM placex '; -$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(name)).key"; -$aPointDetails['aNames'] = $oDB->getAssoc($sSQL); - -// Address tags -$sSQL = 'SELECT (each(address)).key as key,(each(address)).value FROM placex '; -$sSQL .= "WHERE place_id = $iPlaceID ORDER BY key"; -$aPointDetails['aAddressTags'] = $oDB->getAssoc($sSQL); - -// Extra tags -$sSQL = 'SELECT (each(extratags)).key,(each(extratags)).value FROM placex '; -$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(extratags)).key"; -$aPointDetails['aExtraTags'] = $oDB->getAssoc($sSQL); - -// Address -$aAddressLines = false; -if ($bIncludeAddressDetails) { - $oDetails = new Nominatim\AddressDetails($oDB, $iPlaceID, -1, $sLanguagePrefArraySQL); - $aAddressLines = $oDetails->getAddressDetails(true); -} - -// Linked places -$aLinkedLines = false; -if ($bIncludeLinkedPlaces) { - $sSQL = 'SELECT placex.place_id, osm_type, osm_id, class, type, housenumber,'; - $sSQL .= ' admin_level, rank_address, '; - $sSQL .= " ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,"; - $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, "; - $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, "; - $sSQL .= ' length(name::text) AS namelength '; - $sSQL .= ' FROM '; - $sSQL .= ' placex, '; - $sSQL .= ' ( '; - $sSQL .= ' SELECT centroid AS placegeometry '; - $sSQL .= ' FROM placex '; - $sSQL .= " WHERE place_id = $iPlaceID "; - $sSQL .= ' ) AS x'; - $sSQL .= " WHERE linked_place_id = $iPlaceID"; - $sSQL .= ' ORDER BY '; - $sSQL .= ' rank_address ASC, '; - $sSQL .= ' rank_search ASC, '; - $sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL), "; - $sSQL .= ' housenumber'; - $aLinkedLines = $oDB->getAll($sSQL); -} - -// All places this is an immediate parent of -$aHierarchyLines = false; -if ($bIncludeHierarchy) { - $sSQL = 'SELECT obj.place_id, osm_type, osm_id, class, type, housenumber,'; - $sSQL .= " admin_level, rank_address, ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,"; - $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, "; - $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, "; - $sSQL .= ' length(name::text) AS namelength '; - $sSQL .= ' FROM '; - $sSQL .= ' ( '; - $sSQL .= ' SELECT placex.place_id, osm_type, osm_id, class, type, housenumber, admin_level, rank_address, rank_search, geometry, name '; - $sSQL .= ' FROM placex '; - $sSQL .= " WHERE parent_place_id = $iPlaceID "; - $sSQL .= ' ORDER BY '; - $sSQL .= ' rank_address ASC, '; - $sSQL .= ' rank_search ASC '; - $sSQL .= ' LIMIT 500 '; - $sSQL .= ' ) AS obj,'; - $sSQL .= ' ( '; - $sSQL .= ' SELECT centroid AS placegeometry '; - $sSQL .= ' FROM placex '; - $sSQL .= " WHERE place_id = $iPlaceID "; - $sSQL .= ' ) AS x'; - $sSQL .= ' ORDER BY '; - $sSQL .= ' rank_address ASC, '; - $sSQL .= ' rank_search ASC, '; - $sSQL .= ' localname, '; - $sSQL .= ' housenumber'; - $aHierarchyLines = $oDB->getAll($sSQL); -} - -$aPlaceSearchNameKeywords = false; -$aPlaceSearchAddressKeywords = false; -if ($bIncludeKeywords) { - $sSQL = "SELECT * FROM search_name WHERE place_id = $iPlaceID"; - $aPlaceSearchName = $oDB->getRow($sSQL); - - if (!empty($aPlaceSearchName)) { - $sWordIds = substr($aPlaceSearchName['name_vector'], 1, -1); - if (!empty($sWordIds)) { - $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')'; - $aPlaceSearchNameKeywords = $oDB->getAll($sSQL); - } - - $sWordIds = substr($aPlaceSearchName['nameaddress_vector'], 1, -1); - if (!empty($sWordIds)) { - $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')'; - $aPlaceSearchAddressKeywords = $oDB->getAll($sSQL); - } - } -} - -logEnd($oDB, $hLog, 1); - -include(CONST_LibDir.'/template/details-'.$sOutputFormat.'.php'); diff --git a/lib-php/website/lookup.php b/lib-php/website/lookup.php deleted file mode 100644 index 3a7ddb8537..0000000000 --- a/lib-php/website/lookup.php +++ /dev/null @@ -1,101 +0,0 @@ -getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml'); -set_exception_handler_by_format($sOutputFormat); - -// Preferred language -$aLangPrefOrder = $oParams->getPreferredLanguages(); - -$oDB = new Nominatim\DB(CONST_Database_DSN); -$oDB->connect(); - -$hLog = logStart($oDB, 'place', $_SERVER['QUERY_STRING'], $aLangPrefOrder); - -$aSearchResults = array(); -$aCleanedQueryParts = array(); - -$oPlaceLookup = new Nominatim\PlaceLookup($oDB); -$oPlaceLookup->loadParamArray($oParams); -$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true)); - -$aOsmIds = explode(',', $oParams->getString('osm_ids', '')); - -if (count($aOsmIds) > CONST_Places_Max_ID_count) { - userError('Bulk User: Only ' . CONST_Places_Max_ID_count . ' ids are allowed in one request.'); -} - -foreach ($aOsmIds as $sItem) { - // Skip empty sItem - if (empty($sItem)) { - continue; - } - - $sType = $sItem[0]; - $iId = (int) substr($sItem, 1); - if ($iId > 0 && ($sType == 'N' || $sType == 'W' || $sType == 'R')) { - $aCleanedQueryParts[] = $sType . $iId; - $oPlace = $oPlaceLookup->lookupOSMID($sType, $iId); - if ($oPlace) { - // we want to use the search-* output templates, so we need to fill - // $aSearchResults and slightly change the (reverse search) oPlace - // key names - $oResult = $oPlace; - unset($oResult['aAddress']); - if (isset($oPlace['aAddress'])) { - $oResult['address'] = $oPlace['aAddress']; - } - if ($sOutputFormat != 'geocodejson') { - unset($oResult['langaddress']); - $oResult['name'] = $oPlace['langaddress']; - } - - $aOutlineResult = $oPlaceLookup->getOutlines( - $oPlace['place_id'], - $oPlace['lon'], - $oPlace['lat'], - Nominatim\ClassTypes\getDefRadius($oPlace) - ); - - if ($aOutlineResult) { - $oResult = array_merge($oResult, $aOutlineResult); - } - - $aSearchResults[] = $oResult; - } - } -} - - -if (CONST_Debug) { - exit; -} - -$sXmlRootTag = 'lookupresults'; -$sQuery = join(',', $aCleanedQueryParts); -// we initialize these to avoid warnings in our logfile -$sViewBox = ''; -$bShowPolygons = ''; -$aExcludePlaceIDs = array(); -$sMoreURL = ''; - -logEnd($oDB, $hLog, 1); - -$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat; -include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php'); diff --git a/lib-php/website/polygons.php b/lib-php/website/polygons.php deleted file mode 100644 index 5a90abe599..0000000000 --- a/lib-php/website/polygons.php +++ /dev/null @@ -1,63 +0,0 @@ -getSet('format', array('json'), 'json'); -set_exception_handler_by_format($sOutputFormat); - -$iDays = $oParams->getInt('days', false); -$bReduced = $oParams->getBool('reduced', false); -$sClass = $oParams->getString('class', false); - -$oDB = new Nominatim\DB(CONST_Database_DSN); -$oDB->connect(); - -$iTotalBroken = (int) $oDB->getOne('SELECT count(*) FROM import_polygon_error'); - -$aPolygons = array(); -while ($iTotalBroken && empty($aPolygons)) { - $sSQL = 'SELECT osm_type, osm_id, class, type, name->\'name\' as "name",'; - $sSQL .= 'country_code, errormessage, updated'; - $sSQL .= ' FROM import_polygon_error'; - - $aWhere = array(); - if ($iDays) { - $aWhere[] = "updated > 'now'::timestamp - '".$iDays." day'::interval"; - $iDays++; - } - - if ($bReduced) { - $aWhere[] = "errormessage like 'Area reduced%'"; - } - if ($sClass) { - $sWhere[] = "class = '".pg_escape_string($sClass)."'"; - } - - if (!empty($aWhere)) { - $sSQL .= ' WHERE '.join(' and ', $aWhere); - } - - $sSQL .= ' ORDER BY updated desc LIMIT 1000'; - $aPolygons = $oDB->getAll($sSQL); -} - -if (CONST_Debug) { - var_dump($aPolygons); - exit; -} - -if ($sOutputFormat == 'json') { - javascript_renderData($aPolygons); -} diff --git a/lib-php/website/reverse-only-search.php b/lib-php/website/reverse-only-search.php deleted file mode 100644 index 43cbd265f8..0000000000 --- a/lib-php/website/reverse-only-search.php +++ /dev/null @@ -1,20 +0,0 @@ -getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2'); -set_exception_handler_by_format($sOutputFormat); - -throw new Exception('Reverse-only import does not support forward searching.', 404); diff --git a/lib-php/website/reverse.php b/lib-php/website/reverse.php deleted file mode 100644 index f24c655a40..0000000000 --- a/lib-php/website/reverse.php +++ /dev/null @@ -1,95 +0,0 @@ -getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml'); -set_exception_handler_by_format($sOutputFormat); - -// Preferred language -$aLangPrefOrder = $oParams->getPreferredLanguages(); - -$oDB = new Nominatim\DB(CONST_Database_DSN); -$oDB->connect(); - -$hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder); - -$oPlaceLookup = new Nominatim\PlaceLookup($oDB); -$oPlaceLookup->loadParamArray($oParams); -$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true)); - -$sOsmType = $oParams->getSet('osm_type', array('N', 'W', 'R')); -$iOsmId = $oParams->getInt('osm_id', -1); -$fLat = $oParams->getFloat('lat'); -$fLon = $oParams->getFloat('lon'); -$iZoom = $oParams->getInt('zoom', 18); - -if ($sOsmType && $iOsmId > 0) { - $aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId); -} elseif ($fLat !== false && $fLon !== false) { - $oReverseGeocode = new Nominatim\ReverseGeocode($oDB); - $oReverseGeocode->setZoom($iZoom); - - $oLookup = $oReverseGeocode->lookup($fLat, $fLon); - - if ($oLookup) { - $aPlaces = $oPlaceLookup->lookup(array($oLookup->iId => $oLookup)); - if (!empty($aPlaces)) { - $aPlace = reset($aPlaces); - } - } -} else { - userError('Need coordinates or OSM object to lookup.'); -} - -if (isset($aPlace)) { - $aOutlineResult = $oPlaceLookup->getOutlines( - $aPlace['place_id'], - $aPlace['lon'], - $aPlace['lat'], - Nominatim\ClassTypes\getDefRadius($aPlace), - $fLat, - $fLon - ); - - if ($aOutlineResult) { - $aPlace = array_merge($aPlace, $aOutlineResult); - } -} else { - $aPlace = array(); -} - -logEnd($oDB, $hLog, count($aPlace) ? 1 : 0); - -if (CONST_Debug) { - var_dump($aPlace); - exit; -} - -if ($sOutputFormat == 'geocodejson') { - $sQuery = $fLat.','.$fLon; - if (isset($aPlace['place_id'])) { - $fDistance = $oDB->getOne( - 'SELECT ST_Distance(ST_SetSRID(ST_Point(:lon,:lat),4326), centroid) FROM placex where place_id = :placeid', - array(':lon' => $fLon, ':lat' => $fLat, ':placeid' => $aPlace['place_id']) - ); - } -} - -$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat; -include(CONST_LibDir.'/template/address-'.$sOutputTemplate.'.php'); diff --git a/lib-php/website/search.php b/lib-php/website/search.php deleted file mode 100644 index e8f2398398..0000000000 --- a/lib-php/website/search.php +++ /dev/null @@ -1,93 +0,0 @@ -connect(); -$oParams = new Nominatim\ParameterParser(); - -$oGeocode = new Nominatim\Geocode($oDB); - -$aLangPrefOrder = $oParams->getPreferredLanguages(); -$oGeocode->setLanguagePreference($aLangPrefOrder); - -// Format for output -$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2'); -set_exception_handler_by_format($sOutputFormat); - -$oGeocode->loadParamArray($oParams, null); - -if (CONST_Search_BatchMode && isset($_GET['batch'])) { - $aBatch = json_decode($_GET['batch'], true); - $aBatchResults = array(); - foreach ($aBatch as $aBatchParams) { - $oBatchGeocode = clone $oGeocode; - $oBatchParams = new Nominatim\ParameterParser($aBatchParams); - $oBatchGeocode->loadParamArray($oBatchParams); - $oBatchGeocode->setQueryFromParams($oBatchParams); - $aSearchResults = $oBatchGeocode->lookup(); - $aBatchResults[] = $aSearchResults; - } - include(CONST_LibDir.'/template/search-batch-json.php'); - exit; -} - -$oGeocode->setQueryFromParams($oParams); - -if (!$oGeocode->getQueryString() - && isset($_SERVER['PATH_INFO']) - && strlen($_SERVER['PATH_INFO']) > 0 - && $_SERVER['PATH_INFO'][0] == '/' -) { - $sQuery = substr(rawurldecode($_SERVER['PATH_INFO']), 1); - - // reverse order of '/' separated string - $aPhrases = explode('/', $sQuery); - $aPhrases = array_reverse($aPhrases); - $sQuery = join(', ', $aPhrases); - $oGeocode->setQuery($sQuery); -} - -$hLog = logStart($oDB, 'search', $oGeocode->getQueryString(), $aLangPrefOrder); - -$aSearchResults = $oGeocode->lookup(); - -logEnd($oDB, $hLog, count($aSearchResults)); - -$sQuery = $oGeocode->getQueryString(); - -$aMoreParams = $oGeocode->getMoreUrlParams(); -$aMoreParams['format'] = $sOutputFormat; -if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $aMoreParams['accept-language'] = $_SERVER['HTTP_ACCEPT_LANGUAGE']; -} - -if (isset($_SERVER['REQUEST_SCHEME']) - && isset($_SERVER['HTTP_HOST']) - && isset($_SERVER['DOCUMENT_URI']) -) { - $sMoreURL = $_SERVER['REQUEST_SCHEME'].'://' - .$_SERVER['HTTP_HOST'].$_SERVER['DOCUMENT_URI'].'/?' - .http_build_query($aMoreParams); -} else { - $sMoreURL = '/search.php?'.http_build_query($aMoreParams); -} - -if (CONST_Debug) { - exit; -} - -$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat; -include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php'); diff --git a/lib-php/website/status.php b/lib-php/website/status.php deleted file mode 100644 index 2839f72a6c..0000000000 --- a/lib-php/website/status.php +++ /dev/null @@ -1,56 +0,0 @@ -getSet('format', array('text', 'json'), 'text'); - -$oDB = new Nominatim\DB(CONST_Database_DSN); - -if ($sOutputFormat == 'json') { - header('content-type: application/json; charset=UTF-8'); -} - - -try { - $oStatus = new Nominatim\Status($oDB); - $oStatus->status(); - - if ($sOutputFormat == 'json') { - $epoch = $oStatus->dataDate(); - $aResponse = array( - 'status' => 0, - 'message' => 'OK', - 'data_updated' => (new DateTime('@'.$epoch))->format(DateTime::RFC3339), - 'software_version' => CONST_NominatimVersion - ); - $sDatabaseVersion = $oStatus->databaseVersion(); - if ($sDatabaseVersion) { - $aResponse['database_version'] = $sDatabaseVersion; - } - javascript_renderData($aResponse); - } else { - echo 'OK'; - } -} catch (Exception $oErr) { - if ($sOutputFormat == 'json') { - $aResponse = array( - 'status' => $oErr->getCode(), - 'message' => $oErr->getMessage() - ); - javascript_renderData($aResponse); - } else { - header('HTTP/1.0 500 Internal Server Error'); - echo 'ERROR: '.$oErr->getMessage(); - } -} diff --git a/mkdocs.yml b/mkdocs.yml index b6fd0ec7f5..6a24e81680 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,8 +26,7 @@ nav: - 'Basic Installation': 'admin/Installation.md' - 'Import' : 'admin/Import.md' - 'Update' : 'admin/Update.md' - - 'Deploy (Python frontend)' : 'admin/Deployment-Python.md' - - 'Deploy (PHP frontend)' : 'admin/Deployment-PHP.md' + - 'Deploy' : 'admin/Deployment-Python.md' - 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md' - 'Advanced Installations' : 'admin/Advanced-Installations.md' - 'Maintenance' : 'admin/Maintenance.md' diff --git a/packaging/nominatim-api/extra_src/paths.py b/packaging/nominatim-api/extra_src/paths.py index 6131319ca8..797acbb55e 100644 --- a/packaging/nominatim-api/extra_src/paths.py +++ b/packaging/nominatim-api/extra_src/paths.py @@ -9,7 +9,6 @@ """ from pathlib import Path -PHPLIB_DIR = None DATA_DIR = None SQLLIB_DIR = None CONFIG_DIR = (Path(__file__) / '..' / 'resources' / 'settings').resolve() diff --git a/packaging/nominatim-db/extra_src/nominatim_db/paths.py b/packaging/nominatim-db/extra_src/nominatim_db/paths.py index 2294834fc1..796ff08b35 100644 --- a/packaging/nominatim-db/extra_src/nominatim_db/paths.py +++ b/packaging/nominatim-db/extra_src/nominatim_db/paths.py @@ -9,7 +9,6 @@ """ from pathlib import Path -PHPLIB_DIR = None DATA_DIR = (Path(__file__) / '..' / 'resources').resolve() SQLLIB_DIR = (DATA_DIR / 'lib-sql') CONFIG_DIR = (DATA_DIR / 'settings') diff --git a/settings/env.defaults b/settings/env.defaults index f4c33e7720..8aee4a0fe2 100644 --- a/settings/env.defaults +++ b/settings/env.defaults @@ -177,16 +177,6 @@ NOMINATIM_MAPICON_URL= # When unset, the local language (i.e. the name tag without suffix) will be used. NOMINATIM_DEFAULT_LANGUAGE= -# Enable a special batch query mode. -# This feature is currently undocumented and potentially broken. -NOMINATIM_SEARCH_BATCH_MODE=no - -# Threshold for searches by name only. -# Threshold where the lookup strategy in the database is switched. If there -# are less occurrences of a tem than given, the search does the lookup only -# against the name, otherwise it uses indexes for name and address. -NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD=500 - # Maximum number of OSM ids accepted by /lookup. NOMINATIM_LOOKUP_MAX_COUNT=50 diff --git a/src/nominatim_db/cli.py b/src/nominatim_db/cli.py index 9fd439f8d0..9a54f338e0 100644 --- a/src/nominatim_db/cli.py +++ b/src/nominatim_db/cli.py @@ -19,7 +19,6 @@ from .config import Configuration from .errors import UsageError -from .tools.exec_utils import run_php_server from . import clicmd from . import version from .clicmd.args import NominatimArgs, Subcommand @@ -154,10 +153,10 @@ class AdminServe: from the current project directory. This webserver is only suitable for testing and development. Do not use it in production setups! - There are different webservers available. The default 'php' engine - runs the classic PHP frontend. The other engines are Python servers - which run the new Python frontend code. This is highly experimental - at the moment and may not include the full API. + There are two different webserver implementations for Python available: + falcon (the default) and starlette. You need to make sure the + appropriate Python packages as well as the uvicorn package are + installed to use this function. By the default, the webserver can be accessed at: http://127.0.0.1:8088 """ @@ -167,19 +166,12 @@ def add_args(self, parser: argparse.ArgumentParser) -> None: group.add_argument('--server', default='127.0.0.1:8088', help='The address the server will listen to.') group.add_argument('--engine', default='falcon', - choices=('php', 'falcon', 'starlette'), + choices=('falcon', 'starlette'), help='Webserver framework to run. (default: falcon)') def run(self, args: NominatimArgs) -> int: - if args.engine == 'php': - if args.config.lib_dir.php is None: - raise UsageError("PHP frontend not configured.") - LOG.warning('\n\nWARNING: the PHP frontend is deprecated ' - 'and will be removed in Nominatim 5.0.\n\n') - run_php_server(args.server, args.project_dir / 'website') - else: - asyncio.run(self.run_uvicorn(args)) + asyncio.run(self.run_uvicorn(args)) return 0 diff --git a/src/nominatim_db/clicmd/refresh.py b/src/nominatim_db/clicmd/refresh.py index adc7ee656c..741411658e 100644 --- a/src/nominatim_db/clicmd/refresh.py +++ b/src/nominatim_db/clicmd/refresh.py @@ -69,7 +69,8 @@ def add_args(self, parser: argparse.ArgumentParser) -> None: group.add_argument('--importance', action='store_true', help='Recompute place importances (expensive!)') group.add_argument('--website', action='store_true', - help='Refresh the directory that serves the scripts for the web API') + help='DEPRECATED. This function has no function anymore' + ' and will be removed in a future version.') group.add_argument('--data-object', action='append', type=_parse_osm_object, metavar='OBJECT', help='Mark the given OSM object as requiring an update' @@ -159,14 +160,8 @@ def run(self, args: NominatimArgs) -> int: #pylint: disable=too-many-branches, t refresh.recompute_importance(conn) if args.website: - webdir = args.project_dir / 'website' - LOG.warning('Setting up website directory at %s', webdir) - # This is a little bit hacky: call the tokenizer setup, so that - # the tokenizer directory gets repopulated as well, in case it - # wasn't there yet. - self._get_tokenizer(args.config) - with connect(args.config.get_libpq_dsn()) as conn: - refresh.setup_website(webdir, args.config, conn) + LOG.error('WARNING: Website setup is no longer required. ' + 'This function will be removed in future version of Nominatim.') if args.data_object or args.data_area: with connect(args.config.get_libpq_dsn()) as conn: diff --git a/src/nominatim_db/clicmd/setup.py b/src/nominatim_db/clicmd/setup.py index 07a76f5903..a7066ff2c4 100644 --- a/src/nominatim_db/clicmd/setup.py +++ b/src/nominatim_db/clicmd/setup.py @@ -88,7 +88,7 @@ def run(self, args: NominatimArgs) -> int: # pylint: disable=too-many-statements async def async_run(self, args: NominatimArgs) -> int: from ..data import country_info - from ..tools import database_import, refresh, postcodes, freeze + from ..tools import database_import, postcodes, freeze from ..indexer.indexer import Indexer num_threads = args.threads or psutil.cpu_count() or 1 @@ -141,11 +141,6 @@ async def async_run(self, args: NominatimArgs) -> int: LOG.warning('Recompute word counts') tokenizer.update_statistics(args.config, threads=num_threads) - webdir = args.project_dir / 'website' - LOG.warning('Setup website at %s', webdir) - with connect(args.config.get_libpq_dsn()) as conn: - refresh.setup_website(webdir, args.config, conn) - self._finalize_database(args.config.get_libpq_dsn(), args.offline) return 0 diff --git a/src/nominatim_db/config.py b/src/nominatim_db/config.py index 357cc9d717..a34ec94403 100644 --- a/src/nominatim_db/config.py +++ b/src/nominatim_db/config.py @@ -74,7 +74,6 @@ def __init__(self, project_dir: Optional[Union[Path, str]], class _LibDirs: module: Path osm2pgsql: Path - php = paths.PHPLIB_DIR sql = paths.SQLLIB_DIR data = paths.DATA_DIR diff --git a/src/nominatim_db/tokenizer/factory.py b/src/nominatim_db/tokenizer/factory.py index b9022d8d01..5003c322e3 100644 --- a/src/nominatim_db/tokenizer/factory.py +++ b/src/nominatim_db/tokenizer/factory.py @@ -15,9 +15,6 @@ This module provides the functions to create and configure a new tokenizer as well as instantiating the appropriate tokenizer for updating an existing database. - -A tokenizer usually also includes PHP code for querying. The appropriate PHP -normalizer module is installed, when the tokenizer is created. """ from typing import Optional import logging diff --git a/src/nominatim_db/tokenizer/icu_tokenizer.py b/src/nominatim_db/tokenizer/icu_tokenizer.py index 7cd96d591f..1b95a90174 100644 --- a/src/nominatim_db/tokenizer/icu_tokenizer.py +++ b/src/nominatim_db/tokenizer/icu_tokenizer.py @@ -13,7 +13,6 @@ import itertools import logging from pathlib import Path -from textwrap import dedent from psycopg.types.json import Jsonb from psycopg import sql as pysql @@ -64,7 +63,6 @@ def init_new_db(self, config: Configuration, init_db: bool = True) -> None: """ self.loader = ICURuleLoader(config) - self._install_php(config.lib_dir.php, overwrite=True) self._save_config() if init_db: @@ -81,8 +79,6 @@ def init_from_project(self, config: Configuration) -> None: with connect(self.dsn) as conn: self.loader.load_config_from_db(conn) - self._install_php(config.lib_dir.php, overwrite=False) - def finalize_import(self, config: Configuration) -> None: """ Do any required postprocessing to make the tokenizer data ready @@ -281,22 +277,6 @@ def most_frequent_words(self, conn: Connection, num: int) -> List[str]: return list(s[0].split('@')[0] for s in cur) - def _install_php(self, phpdir: Optional[Path], overwrite: bool = True) -> None: - """ Install the php script for the tokenizer. - """ - if phpdir is not None: - assert self.loader is not None - php_file = self.data_dir / "tokenizer.php" - - if not php_file.exists() or overwrite: - php_file.write_text(dedent(f"""\ - None: """ Save the configuration that needs to remain stable for the given database as database properties. diff --git a/src/nominatim_db/tokenizer/legacy_tokenizer.py b/src/nominatim_db/tokenizer/legacy_tokenizer.py index 04b7b8814c..7880533227 100644 --- a/src/nominatim_db/tokenizer/legacy_tokenizer.py +++ b/src/nominatim_db/tokenizer/legacy_tokenizer.py @@ -14,7 +14,6 @@ from pathlib import Path import re import shutil -from textwrap import dedent from icu import Transliterator import psycopg @@ -120,8 +119,6 @@ def init_new_db(self, config: Configuration, init_db: bool = True) -> None: self.normalization = config.TERM_NORMALIZATION - self._install_php(config, overwrite=True) - with connect(self.dsn) as conn: _check_module(module_dir, conn) self._save_config(conn, config) @@ -145,8 +142,6 @@ def init_from_project(self, config: Configuration) -> None: config.lib_dir.module, config.project_dir / 'module') - self._install_php(config, overwrite=False) - def finalize_import(self, config: Configuration) -> None: """ Do any required postprocessing to make the tokenizer data ready for use. @@ -272,21 +267,6 @@ def most_frequent_words(self, conn: Connection, num: int) -> List[str]: return list(s[0] for s in cur) - def _install_php(self, config: Configuration, overwrite: bool = True) -> None: - """ Install the php script for the tokenizer. - """ - if config.lib_dir.php is not None: - php_file = self.data_dir / "tokenizer.php" - - if not php_file.exists() or overwrite: - php_file.write_text(dedent(f"""\ - None: """ Set up the word table and fill it with pre-computed word frequencies. diff --git a/src/nominatim_db/tools/exec_utils.py b/src/nominatim_db/tools/exec_utils.py index 1adcc777b0..4cbbf95d9b 100644 --- a/src/nominatim_db/tools/exec_utils.py +++ b/src/nominatim_db/tools/exec_utils.py @@ -14,19 +14,12 @@ import subprocess import shutil -from ..typing import StrPath from ..db.connection import get_pg_env from ..errors import UsageError from ..version import OSM2PGSQL_REQUIRED_VERSION LOG = logging.getLogger() -def run_php_server(server_address: str, base_dir: StrPath) -> None: - """ Run the built-in server from the given directory. - """ - subprocess.run(['/usr/bin/env', 'php', '-S', server_address], - cwd=str(base_dir), check=True) - def run_osm2pgsql(options: Mapping[str, Any]) -> None: """ Run osm2pgsql with the given options. diff --git a/src/nominatim_db/tools/refresh.py b/src/nominatim_db/tools/refresh.py index d48c4e45a0..557c43ae6f 100644 --- a/src/nominatim_db/tools/refresh.py +++ b/src/nominatim_db/tools/refresh.py @@ -11,17 +11,15 @@ import csv import gzip import logging -from textwrap import dedent from pathlib import Path from psycopg import sql as pysql from ..config import Configuration from ..db.connection import Connection, connect, postgis_version_tuple,\ - drop_tables, table_exists + drop_tables from ..db.utils import execute_file from ..db.sql_preprocessor import SQLPreprocessor -from ..version import NOMINATIM_VERSION LOG = logging.getLogger() @@ -99,34 +97,6 @@ def create_functions(conn: Connection, config: Configuration, debug=enable_debug) - -WEBSITE_SCRIPTS = ( - 'deletable.php', - 'details.php', - 'lookup.php', - 'polygons.php', - 'reverse.php', - 'search.php', - 'status.php' -) - -# constants needed by PHP scripts: PHP name, config name, type -PHP_CONST_DEFS = ( - ('Database_DSN', 'DATABASE_DSN', str), - ('Default_Language', 'DEFAULT_LANGUAGE', str), - ('Log_DB', 'LOG_DB', bool), - ('Log_File', 'LOG_FILE', Path), - ('NoAccessControl', 'CORS_NOACCESSCONTROL', bool), - ('Places_Max_ID_count', 'LOOKUP_MAX_COUNT', int), - ('PolygonOutput_MaximumTypes', 'POLYGON_OUTPUT_MAX_TYPES', int), - ('Search_BatchMode', 'SEARCH_BATCH_MODE', bool), - ('Search_NameOnlySearchFrequencyThreshold', 'SEARCH_NAME_ONLY_THRESHOLD', str), - ('Use_US_Tiger_Data', 'USE_US_TIGER_DATA', bool), - ('MapIcon_URL', 'MAPICON_URL', str), - ('Search_WithinCountries', 'SEARCH_WITHIN_COUNTRIES', bool), -) - - def import_wikipedia_articles(dsn: str, data_path: Path, ignore_errors: bool = False) -> int: """ Replaces the wikipedia importance tables with new data. The import is run in a single transaction so that the new data @@ -272,46 +242,6 @@ def _quote_php_variable(var_type: Type[Any], config: Configuration, return f"'{quoted}'" -def setup_website(basedir: Path, config: Configuration, conn: Connection) -> None: - """ Create the website script stubs. - """ - if config.lib_dir.php is None: - LOG.info("Python frontend does not require website setup. Skipping.") - return - - if not basedir.exists(): - LOG.info('Creating website directory.') - basedir.mkdir() - - assert config.project_dir is not None - basedata = dedent(f"""\ - None: """ Mark the given OSM object for reindexing. When 'recursive' is set diff --git a/test/Makefile b/test/Makefile index 5f78eeac87..9768ebd728 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,14 +1,10 @@ -all: bdd php python -no-test-db: bdd-no-test-db php +all: bdd python bdd: cd bdd && behave -DREMOVE_TEMPLATE=1 -php: - cd php && phpunit ./ - python: pytest python -.PHONY: bdd php no-test-db python +.PHONY: bdd python diff --git a/test/bdd/api/details/simple.feature b/test/bdd/api/details/simple.feature index 0e456aa5bb..5e0bacc5a9 100644 --- a/test/bdd/api/details/simple.feature +++ b/test/bdd/api/details/simple.feature @@ -42,16 +42,6 @@ Feature: Object details | N300209696:highway | - @v1-api-php-only - Scenario: Details for interpolation way just return the dependent street - When sending details query for W1 - Then the result is valid json - And results contain - | category | - | highway | - - - @v1-api-python-only Scenario: Details for interpolation way return the interpolation When sending details query for W1 Then the result is valid json @@ -60,17 +50,6 @@ Feature: Object details | place | houses | W | 1 | 15 | - @v1-api-php-only - @Fail - Scenario: Details for Tiger way just return the dependent street - When sending details query for 112871 - Then the result is valid json - And results contain - | category | - | highway | - - - @v1-api-python-only @Fail Scenario: Details for interpolation way return the interpolation When sending details query for 112871 @@ -81,17 +60,6 @@ Feature: Object details And result has not attributes osm_type,osm_id - @v1-api-php-only - @Fail - Scenario: Details for postcodes just return the dependent place - When sending details query for 112820 - Then the result is valid json - And results contain - | category | - | boundary | - - - @v1-api-python-only @Fail Scenario: Details for interpolation way return the interpolation When sending details query for 112820 @@ -102,7 +70,6 @@ Feature: Object details And result has not attributes osm_type,osm_id - @v1-api-python-only Scenario Outline: Details debug output returns no errors When sending debug details query for Then the result is valid html diff --git a/test/bdd/api/reverse/layers.feature b/test/bdd/api/reverse/layers.feature index ef02886478..f1885f0e46 100644 --- a/test/bdd/api/reverse/layers.feature +++ b/test/bdd/api/reverse/layers.feature @@ -3,7 +3,6 @@ Feature: Layer parameter in reverse geocoding Testing correct function of layer selection while reverse geocoding - @v1-api-python-only Scenario: POIs are selected by default When sending v1/reverse at 47.14077,9.52414 Then results contain @@ -11,7 +10,6 @@ Feature: Layer parameter in reverse geocoding | tourism | viewpoint | - @v1-api-python-only Scenario Outline: Same address level POI with different layers When sending v1/reverse at 47.14077,9.52414 | layer | @@ -31,7 +29,6 @@ Feature: Layer parameter in reverse geocoding | natural,poi | tourism | - @v1-api-python-only Scenario Outline: POIs are not selected without housenumber for address layer When sending v1/reverse at 47.13816,9.52168 | layer | @@ -46,7 +43,6 @@ Feature: Layer parameter in reverse geocoding | address | amenity | parking | - @v1-api-python-only Scenario: Between natural and low-zoom address prefer natural When sending v1/reverse at 47.13636,9.52094 | layer | zoom | @@ -56,7 +52,6 @@ Feature: Layer parameter in reverse geocoding | waterway | - @v1-api-python-only Scenario Outline: Search for mountain peaks begins at level 12 When sending v1/reverse at 47.08293,9.57109 | layer | zoom | @@ -71,7 +66,6 @@ Feature: Layer parameter in reverse geocoding | 13 | waterway | river | - @v1-api-python-only Scenario Outline: Reverse search with manmade layers When sending v1/reverse at 32.46904,-86.44439 | layer | diff --git a/test/bdd/api/reverse/v1_params.feature b/test/bdd/api/reverse/v1_params.feature index a1f08afd37..09a190ed9b 100644 --- a/test/bdd/api/reverse/v1_params.feature +++ b/test/bdd/api/reverse/v1_params.feature @@ -18,20 +18,6 @@ Feature: v1/reverse Parameter Tests When sending v1/reverse at ,52.52 Then a HTTP 400 is returned - @v1-api-php-only - Scenario: Missing osm_id parameter - When sending v1/reverse at , - | osm_type | - | N | - Then a HTTP 400 is returned - - @v1-api-php-only - Scenario: Missing osm_type parameter - When sending v1/reverse at , - | osm_id | - | 3498564 | - Then a HTTP 400 is returned - Scenario Outline: Bad format for lat or lon When sending v1/reverse at , @@ -151,7 +137,6 @@ Feature: v1/reverse Parameter Tests | foo; evil | - @v1-api-python-only Scenario Outline: Reverse debug mode produces valid HTML When sending v1/reverse at , with format debug | lat | lon | diff --git a/test/bdd/api/search/params.feature b/test/bdd/api/search/params.feature index e667b690b0..77e2ea20f8 100644 --- a/test/bdd/api/search/params.feature +++ b/test/bdd/api/search/params.feature @@ -353,19 +353,6 @@ Feature: Search queries | svg | | geokml | - @v1-api-php-only - Scenario: Search along a route - When sending json search query "rathaus" with address - Then result addresses contain - | ID | town | - | 0 | Schaan | - When sending json search query "rathaus" with address - | bounded | routewidth | route | - | 1 | 0.1 | 9.54353,47.11772,9.54314,47.11894 | - Then result addresses contain - | town | - | Triesenberg | - Scenario: Array parameters are ignored When sending json search query "Vaduz" with address diff --git a/test/bdd/api/search/postcode.feature b/test/bdd/api/search/postcode.feature index 827af1ea79..bb1b755bf4 100644 --- a/test/bdd/api/search/postcode.feature +++ b/test/bdd/api/search/postcode.feature @@ -3,7 +3,7 @@ Feature: Searches with postcodes Various searches involving postcodes - @v1-api-php-only + @Fail Scenario: US 5+4 ZIP codes are shortened to 5 ZIP codes if not found When sending json search query "36067 1111, us" with address Then result addresses contain diff --git a/test/bdd/api/search/queries.feature b/test/bdd/api/search/queries.feature index b2793faa3a..6e640accab 100644 --- a/test/bdd/api/search/queries.feature +++ b/test/bdd/api/search/queries.feature @@ -106,17 +106,6 @@ Feature: Search queries | class | type | | club | scout | - @v1-api-php-only - Scenario: With multiple amenity search only the first is used - When sending json search query "[club=scout] [church] vaduz" - Then results contain - | class | type | - | club | scout | - When sending json search query "[amenity=place_of_worship] [club=scout] vaduz" - Then results contain - | class | type | - | amenity | place_of_worship | - Scenario: POI search near given coordinate When sending json search query "restaurant near 47.16712,9.51100" Then results contain @@ -129,13 +118,6 @@ Feature: Search queries | class | type | | leisure | firepit | - @v1-api-php-only - Scenario: Arbitrary key/value search near given coordinate and named place - When sending json search query "[leisure=firepit] ebenholz 47° 9′ 26″ N 9° 36′ 45″ E" - Then results contain - | class | type | - | leisure | firepit | - Scenario: POI search in a bounded viewbox When sending json search query "restaurants" diff --git a/test/bdd/db/query/postcodes.feature b/test/bdd/db/query/postcodes.feature index fa4f6a0b48..1ee73e986d 100644 --- a/test/bdd/db/query/postcodes.feature +++ b/test/bdd/db/query/postcodes.feature @@ -98,7 +98,6 @@ Feature: Querying fo postcode variants @fail-legacy - @v1-api-python-only Scenario: Postcode areas are preferred over postcode points Given the grid with origin DE | 1 | 2 | diff --git a/test/bdd/db/query/reverse.feature b/test/bdd/db/query/reverse.feature index 1294110260..11ee868567 100644 --- a/test/bdd/db/query/reverse.feature +++ b/test/bdd/db/query/reverse.feature @@ -2,7 +2,6 @@ Feature: Reverse searches Test results of reverse queries - @v1-api-python-only Scenario: POI in POI area Given the 0.0001 grid with origin 1,1 | 1 | | | | | | | | 2 | diff --git a/test/bdd/environment.py b/test/bdd/environment.py index 155b8d90a0..e2e644034f 100644 --- a/test/bdd/environment.py +++ b/test/bdd/environment.py @@ -66,9 +66,3 @@ def before_tag(context, tag): if tag == 'fail-legacy': if context.config.userdata['TOKENIZER'] == 'legacy': context.scenario.skip("Not implemented in legacy tokenizer") - if tag == 'v1-api-php-only': - if context.config.userdata['API_ENGINE'] != 'php': - context.scenario.skip("Only valid with PHP version of v1 API.") - if tag == 'v1-api-python-only': - if context.config.userdata['API_ENGINE'] == 'php': - context.scenario.skip("Only valid with Python version of v1 API.") diff --git a/test/bdd/steps/nominatim_environment.py b/test/bdd/steps/nominatim_environment.py index c4b055885d..9836865c18 100644 --- a/test/bdd/steps/nominatim_environment.py +++ b/test/bdd/steps/nominatim_environment.py @@ -44,11 +44,9 @@ def __init__(self, config): self.api_db_done = False self.website_dir = None - self.api_engine = None - if config['API_ENGINE'] != 'php': - if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"): - raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'") - self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")() + if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"): + raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'") + self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")() if self.tokenizer == 'legacy' and self.server_module_path is None: raise RuntimeError("You must set -DSERVER_MODULE_PATH when testing the legacy tokenizer.") @@ -110,15 +108,6 @@ def write_nominatim_config(self, dbname): self.website_dir = tempfile.TemporaryDirectory() - try: - conn = self.connect_database(dbname) - except: - conn = False - refresh.setup_website(Path(self.website_dir.name) / 'website', - self.get_test_config(), conn) - if conn: - conn.close() - def get_test_config(self): cfg = Configuration(Path(self.website_dir.name), environ=self.test_env) diff --git a/test/bdd/steps/steps_api_queries.py b/test/bdd/steps/steps_api_queries.py index 93501e42c7..4d15381d41 100644 --- a/test/bdd/steps/steps_api_queries.py +++ b/test/bdd/steps/steps_api_queries.py @@ -1,13 +1,10 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-3.0-or-later # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2022 by the Nominatim developer community. +# Copyright (C) 2024 by the Nominatim developer community. # For a full list of authors see the git log. """ Steps that run queries against the API. - - Queries may either be run directly via PHP using the query script - or via the HTTP interface using php-cgi. """ from pathlib import Path import json @@ -25,29 +22,6 @@ LOG = logging.getLogger(__name__) -BASE_SERVER_ENV = { - 'HTTP_HOST' : 'localhost', - 'HTTP_USER_AGENT' : 'Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0', - 'HTTP_ACCEPT' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_ENCODING' : 'gzip, deflate', - 'HTTP_CONNECTION' : 'keep-alive', - 'SERVER_SIGNATURE' : '
Nominatim BDD Tests
', - 'SERVER_SOFTWARE' : 'Nominatim test', - 'SERVER_NAME' : 'localhost', - 'SERVER_ADDR' : '127.0.1.1', - 'SERVER_PORT' : '80', - 'REMOTE_ADDR' : '127.0.0.1', - 'DOCUMENT_ROOT' : '/var/www', - 'REQUEST_SCHEME' : 'http', - 'CONTEXT_PREFIX' : '/', - 'SERVER_ADMIN' : 'webmaster@localhost', - 'REMOTE_PORT' : '49319', - 'GATEWAY_INTERFACE' : 'CGI/1.1', - 'SERVER_PROTOCOL' : 'HTTP/1.1', - 'REQUEST_METHOD' : 'GET', - 'REDIRECT_STATUS' : 'CGI' -} - def make_todo_list(context, result_id): if result_id is None: @@ -88,50 +62,12 @@ def send_api_query(endpoint, params, fmt, context): for h in context.table.headings: params[h] = context.table[0][h] - if context.nominatim.api_engine is None: - return send_api_query_php(endpoint, params, context) - return asyncio.run(context.nominatim.api_engine(endpoint, params, Path(context.nominatim.website_dir.name), context.nominatim.test_env, getattr(context, 'http_headers', {}))) - -def send_api_query_php(endpoint, params, context): - env = dict(BASE_SERVER_ENV) - env['QUERY_STRING'] = urlencode(params) - - env['SCRIPT_NAME'] = f'/{endpoint}.php' - env['REQUEST_URI'] = f"{env['SCRIPT_NAME']}?{env['QUERY_STRING']}" - env['CONTEXT_DOCUMENT_ROOT'] = os.path.join(context.nominatim.website_dir.name, 'website') - env['SCRIPT_FILENAME'] = os.path.join(env['CONTEXT_DOCUMENT_ROOT'], - f'{endpoint}.php') - - LOG.debug("Environment:" + json.dumps(env, sort_keys=True, indent=2)) - - if hasattr(context, 'http_headers'): - for k, v in context.http_headers.items(): - env['HTTP_' + k.upper().replace('-', '_')] = v - - cmd = ['/usr/bin/env', 'php-cgi', '-f', env['SCRIPT_FILENAME']] - - for k,v in params.items(): - cmd.append(f"{k}={v}") - - outp, err = run_script(cmd, cwd=context.nominatim.website_dir.name, env=env) - - assert len(err) == 0, f"Unexpected PHP error: {err}" - - if outp.startswith('Status: '): - status = int(outp[8:11]) - else: - status = 200 - - content_start = outp.find('\r\n\r\n') - - return outp[content_start + 4:], status - @given(u'the HTTP header') def add_http_header(context): if not hasattr(context, 'http_headers'): diff --git a/test/php/Nominatim/AddressDetailsTest.php b/test/php/Nominatim/AddressDetailsTest.php deleted file mode 100644 index 2041dcb4bf..0000000000 --- a/test/php/Nominatim/AddressDetailsTest.php +++ /dev/null @@ -1,118 +0,0 @@ -oDbStub = $this->getMockBuilder(\DB::class) - ->setMethods(array('getAll')) - ->getMock(); - $this->oDbStub->method('getAll') - ->willReturn($data); - } - - public function testGetLocaleAddress() - { - $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en'); - $expected = join(', ', array( - '10 Downing Street', - '10', - 'Downing Street', - 'St. James\'s', - 'Covent Garden', - 'Westminster', - 'London', - 'Greater London', - 'England', - 'SW1A 2AA', - 'United Kingdom' - )); - $this->assertEquals($expected, $oAD->getLocaleAddress()); - } - - public function testGetAddressDetails() - { - $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en'); - $this->assertEquals(18, count($oAD->getAddressDetails(true))); - $this->assertEquals(12, count($oAD->getAddressDetails(false))); - } - - public function testGetAddressNames() - { - $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en'); - $expected = array( - 'tourism' => '10 Downing Street', - 'house_number' => '10', - 'road' => 'Downing Street', - 'neighbourhood' => 'St. James\'s', - 'suburb' => 'Covent Garden', - 'city' => 'London', - 'state_district' => 'Greater London', - 'state' => 'England', - 'ISO3166-2-lvl4' => 'GB-ENG', - 'ISO3166-2-lvl6' => 'GB-LND', - 'postcode' => 'SW1A 2AA', - 'country' => 'United Kingdom', - 'country_code' => 'gb' - ); - - $this->assertEquals($expected, $oAD->getAddressNames()); - } - - public function testGetAdminLevels() - { - $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en'); - $expected = array( - 'level8' => 'Westminster', - 'level6' => 'London', - 'level5' => 'Greater London', - 'level4' => 'England', - 'level2' => 'United Kingdom' - ); - $this->assertEquals($expected, $oAD->getAdminLevels()); - } - - public function testDebugInfo() - { - $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en'); - $this->assertTrue(is_array($oAD->debugInfo())); - $this->assertEquals(18, count($oAD->debugInfo())); - } -} diff --git a/test/php/Nominatim/ClassTypesTest.php b/test/php/Nominatim/ClassTypesTest.php deleted file mode 100644 index d2900d822b..0000000000 --- a/test/php/Nominatim/ClassTypesTest.php +++ /dev/null @@ -1,102 +0,0 @@ - 'boundary', 'type' => 'administrative', - 'rank_address' => '4', 'place_type' => 'city'); - $this->assertEquals('city', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'boundary', 'type' => 'administrative', - 'rank_address' => '10'); - $this->assertEquals('state_district', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'boundary', 'type' => 'administrative'); - $this->assertEquals('administrative', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'place', 'type' => 'hamlet', 'rank_address' => '20'); - $this->assertEquals('hamlet', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'highway', 'type' => 'residential', - 'rank_address' => '26'); - $this->assertEquals('road', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'place', 'type' => 'house_number', - 'rank_address' => '30'); - $this->assertEquals('house_number', ClassTypes\getLabelTag($aPlace)); - - $aPlace = array('class' => 'amenity', 'type' => 'prison', - 'rank_address' => '30'); - $this->assertEquals('amenity', ClassTypes\getLabelTag($aPlace)); - } - - public function testGetLabel() - { - $aPlace = array('class' => 'boundary', 'type' => 'administrative', - 'rank_address' => '4', 'place_type' => 'city'); - $this->assertEquals('City', ClassTypes\getLabel($aPlace)); - - $aPlace = array('class' => 'boundary', 'type' => 'administrative', - 'rank_address' => '10'); - $this->assertEquals('State District', ClassTypes\getLabel($aPlace)); - - $aPlace = array('class' => 'boundary', 'type' => 'administrative'); - $this->assertEquals('Administrative', ClassTypes\getLabel($aPlace)); - - $aPlace = array('class' => 'amenity', 'type' => 'prison'); - $this->assertEquals('Prison', ClassTypes\getLabel($aPlace)); - - $aPlace = array('class' => 'amenity', 'type' => 'foobar'); - $this->assertNull(ClassTypes\getLabel($aPlace)); - } - - public function testGetBoundaryLabel() - { - $this->assertEquals('City', ClassTypes\getBoundaryLabel(8, null)); - $this->assertEquals('Administrative', ClassTypes\getBoundaryLabel(18, null)); - $this->assertEquals('None', ClassTypes\getBoundaryLabel(18, null, 'None')); - $this->assertEquals('State', ClassTypes\getBoundaryLabel(4, 'de', 'None')); - $this->assertEquals('County', ClassTypes\getBoundaryLabel(4, 'se', 'None')); - $this->assertEquals('Municipality', ClassTypes\getBoundaryLabel(7, 'se', 'None')); - } - - public function testGetDefRadius() - { - $aResult = array('class' => '', 'type' => ''); - $this->assertEquals(0.00005, ClassTypes\getDefRadius($aResult)); - - $aResult = array('class' => 'place', 'type' => 'country'); - $this->assertEquals(7, ClassTypes\getDefRadius($aResult)); - } - - public function testGetIcon() - { - $aResult = array('class' => '', 'type' => ''); - $this->assertNull(ClassTypes\getIcon($aResult)); - - $aResult = array('class' => 'place', 'type' => 'airport'); - $this->assertEquals('transport_airport2', ClassTypes\getIcon($aResult)); - } - - public function testGetImportance() - { - $aResult = array('class' => '', 'type' => ''); - $this->assertNull(ClassTypes\getImportance($aResult)); - - $aResult = array('class' => 'place', 'type' => 'airport'); - $this->assertGreaterThan(0, ClassTypes\getImportance($aResult)); - } -} diff --git a/test/php/Nominatim/DBTest.php b/test/php/Nominatim/DBTest.php deleted file mode 100644 index 1c6f763742..0000000000 --- a/test/php/Nominatim/DBTest.php +++ /dev/null @@ -1,228 +0,0 @@ -connection = $oConnection; - } -} - -// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses -class DBTest extends \PHPUnit\Framework\TestCase -{ - public function testReusingConnection() - { - $oDB = new NominatimSubClassedDB(''); - $oDB->setConnection('anything'); - $this->assertTrue($oDB->connect()); - } - - public function testCheckConnection() - { - $oDB = new \Nominatim\DB(''); - $this->assertFalse($oDB->checkConnection()); - } - - public function testErrorHandling() - { - $this->expectException(DatabaseError::class); - $this->expectExceptionMessage('Failed to establish database connection'); - - $oDB = new \Nominatim\DB('pgsql:dbname=abc'); - $oDB->connect(); - } - - public function testErrorHandling2() - { - $this->expectException(DatabaseError::class); - $this->expectExceptionMessage('Database query failed'); - - $oPDOStub = $this->getMockBuilder(PDO::class) - ->setMethods(array('query', 'quote')) - ->getMock(); - - $oPDOStub->method('query') - ->will($this->returnCallback(function ($sVal) { - return "'$sVal'"; - })); - - $oPDOStub->method('query') - ->will($this->returnCallback(function () { - throw new \PDOException('ERROR: syntax error at or near "FROM"'); - })); - - $oDB = new NominatimSubClassedDB(''); - $oDB->setConnection($oPDOStub); - $oDB->getOne('SELECT name FROM'); - } - - public function testGetPostgresVersion() - { - $oDBStub = $this->getMockBuilder(\Nominatim\DB::class) - ->disableOriginalConstructor() - ->setMethods(array('getOne')) - ->getMock(); - - $oDBStub->method('getOne') - ->willReturn('100006'); - - $this->assertEquals(10, $oDBStub->getPostgresVersion()); - } - - public function testGetPostgisVersion() - { - $oDBStub = $this->getMockBuilder(\Nominatim\DB::class) - ->disableOriginalConstructor() - ->setMethods(array('getOne')) - ->getMock(); - - $oDBStub->method('getOne') - ->willReturn('2.4.4'); - - $this->assertEquals(2.4, $oDBStub->getPostgisVersion()); - } - - public function testParseDSN() - { - $this->assertEquals( - array(), - \Nominatim\DB::parseDSN('') - ); - $this->assertEquals( - array( - 'database' => 'db1', - 'hostspec' => 'machine1' - ), - \Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1') - ); - $this->assertEquals( - array( - 'database' => 'db1', - 'hostspec' => 'machine1', - 'port' => '1234', - 'username' => 'john', - 'password' => 'secret' - ), - \Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1;port=1234;user=john;password=secret') - ); - } - - public function testGenerateDSN() - { - $this->assertEquals( - 'pgsql:', - \Nominatim\DB::generateDSN(array()) - ); - $this->assertEquals( - 'pgsql:host=machine1;dbname=db1', - \Nominatim\DB::generateDSN(\Nominatim\DB::parseDSN('pgsql:host=machine1;dbname=db1')) - ); - } - - public function testAgainstDatabase() - { - $unit_test_dsn = getenv('UNIT_TEST_DSN') != false ? - getenv('UNIT_TEST_DSN') : - 'pgsql:dbname=nominatim_unit_tests'; - - ## Create the database. - { - $aDSNParsed = \Nominatim\DB::parseDSN($unit_test_dsn); - $sDbname = $aDSNParsed['database']; - $aDSNParsed['database'] = 'postgres'; - - $oDB = new \Nominatim\DB(\Nominatim\DB::generateDSN($aDSNParsed)); - $oDB->connect(); - $oDB->exec('DROP DATABASE IF EXISTS ' . $sDbname); - $oDB->exec('CREATE DATABASE ' . $sDbname); - } - - $oDB = new \Nominatim\DB($unit_test_dsn); - $oDB->connect(); - - $this->assertTrue( - $oDB->checkConnection($sDbname) - ); - - # Tables, Indices - { - $oDB->exec('CREATE TABLE table1 (id integer, city varchar, country varchar)'); - - $this->assertTrue($oDB->tableExists('table1')); - $this->assertFalse($oDB->tableExists('table99')); - $this->assertFalse($oDB->tableExists(null)); - } - - # select queries - { - $oDB->exec( - "INSERT INTO table1 VALUES (1, 'Berlin', 'Germany'), (2, 'Paris', 'France')" - ); - - $this->assertEquals( - array( - array('city' => 'Berlin'), - array('city' => 'Paris') - ), - $oDB->getAll('SELECT city FROM table1') - ); - $this->assertEquals( - array(), - $oDB->getAll('SELECT city FROM table1 WHERE id=999') - ); - - - $this->assertEquals( - array('id' => 1, 'city' => 'Berlin', 'country' => 'Germany'), - $oDB->getRow('SELECT * FROM table1 WHERE id=1') - ); - $this->assertEquals( - false, - $oDB->getRow('SELECT * FROM table1 WHERE id=999') - ); - - - $this->assertEquals( - array('Berlin', 'Paris'), - $oDB->getCol('SELECT city FROM table1') - ); - $this->assertEquals( - array(), - $oDB->getCol('SELECT city FROM table1 WHERE id=999') - ); - - $this->assertEquals( - 'Berlin', - $oDB->getOne('SELECT city FROM table1 WHERE id=1') - ); - $this->assertEquals( - null, - $oDB->getOne('SELECT city FROM table1 WHERE id=999') - ); - - $this->assertEquals( - array('Berlin' => 'Germany', 'Paris' => 'France'), - $oDB->getAssoc('SELECT city, country FROM table1') - ); - $this->assertEquals( - array(), - $oDB->getAssoc('SELECT city, country FROM table1 WHERE id=999') - ); - } - } -} diff --git a/test/php/Nominatim/DatabaseErrorTest.php b/test/php/Nominatim/DatabaseErrorTest.php deleted file mode 100644 index e24049ca62..0000000000 --- a/test/php/Nominatim/DatabaseErrorTest.php +++ /dev/null @@ -1,39 +0,0 @@ -getMockBuilder(PDOException::class) - ->setMethods(array('getMessage')) - ->getMock(); - - $oSqlStub->method('getMessage') - ->willReturn('Unknown table.'); - - $oErr = new DatabaseError('Sql error', 123, null, $oSqlStub); - $this->assertEquals('Sql error', $oErr->getMessage()); - $this->assertEquals(123, $oErr->getCode()); - $this->assertEquals('Unknown table.', $oErr->getSqlError()); - } - - public function testSqlObjectDump() - { - $oErr = new DatabaseError('Sql error', 123, null, array('one' => 'two')); - $this->assertStringContainsString('two', $oErr->getSqlDebugDump()); - } -} diff --git a/test/php/Nominatim/DebugTest.php b/test/php/Nominatim/DebugTest.php deleted file mode 100644 index 84e8f21558..0000000000 --- a/test/php/Nominatim/DebugTest.php +++ /dev/null @@ -1,209 +0,0 @@ -oWithDebuginfo = $this->getMockBuilder(\GeococdeMock::class) - ->setMethods(array('debugInfo')) - ->getMock(); - $this->oWithDebuginfo->method('debugInfo') - ->willReturn(array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3')); - - - $this->oWithToString = $this->getMockBuilder(\SomeMock::class) - ->setMethods(array('__toString')) - ->getMock(); - $this->oWithToString->method('__toString')->willReturn('me as string'); - } - - public function testPrintVar() - { - $this->expectOutputString(<<Var0: -
Var1:  True
-
Var2:  False
-
Var3:  0
-
Var4:  'String'
-
Var5:  0 => 'one'
-       1 => 'two'
-       2 => 'three'
-
Var6:  'key' => 'value'
-       'key2' => 'value2'
-
Var7:  me as string
-
Var8:  'value', 'value2'
- -EOT - ); - - Debug::printVar('Var0', null); - Debug::printVar('Var1', true); - Debug::printVar('Var2', false); - Debug::printVar('Var3', 0); - Debug::printVar('Var4', 'String'); - Debug::printVar('Var5', array('one', 'two', 'three')); - Debug::printVar('Var6', array('key' => 'value', 'key2' => 'value2')); - Debug::printVar('Var7', $this->oWithToString); - Debug::printVar('Var8', Debug::fmtArrayVals(array('key' => 'value', 'key2' => 'value2'))); - } - - - public function testDebugArray() - { - $this->expectOutputString(<<Arr0: 'null' -
Arr1:  'key1' => 'val1'
-       'key2' => 'val2'
-       'key3' => 'val3'
- -EOT - ); - - Debug::printDebugArray('Arr0', null); - Debug::printDebugArray('Arr1', $this->oWithDebuginfo); - } - - - public function testPrintDebugTable() - { - $this->expectOutputString(<<Table1: - -
-Table2: - -
-Table3: - - - - - - - - - - - - - -
01
'one'
'two'
'three'
'four'
-Table4: - - - - - - - - - - - -
key1key2key3
'val1'
'val2'
'val3'
- -EOT - ); - - Debug::printDebugTable('Table1', null); - - Debug::printDebugTable('Table2', array()); - - // Numeric headers - Debug::printDebugTable('Table3', array(array('one', 'two'), array('three', 'four'))); - - // Associate array - Debug::printDebugTable('Table4', array($this->oWithDebuginfo)); - } - - public function testPrintGroupTable() - { - $this->expectOutputString(<<Table1: - -
-Table2: - -
-Table3: - - - - - - - - - - - - - - - - - - - - - -
Groupkey1key2
group1
'val1'
'val2'
group1
'one'
'two'
group2
'val1'
'val2'
-Table4: - - - - - - - - - - - - - - - - - - - -
Groupkey1key2key3
group1
'val1'
'val2'
'val3'
group1
'val1'
'val2'
'val3'
- -EOT - ); - - Debug::printGroupTable('Table1', null); - Debug::printGroupTable('Table2', array()); - - // header are taken from first group item, thus no key3 gets printed - $aGroups = array( - 'group1' => array( - array('key1' => 'val1', 'key2' => 'val2'), - array('key1' => 'one', 'key2' => 'two', 'unknown' => 1), - ), - 'group2' => array( - array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3'), - ) - ); - Debug::printGroupTable('Table3', $aGroups); - - $aGroups = array( - 'group1' => array($this->oWithDebuginfo, $this->oWithDebuginfo), - ); - Debug::printGroupTable('Table4', $aGroups); - } -} diff --git a/test/php/Nominatim/LibTest.php b/test/php/Nominatim/LibTest.php deleted file mode 100644 index 5d711240d1..0000000000 --- a/test/php/Nominatim/LibTest.php +++ /dev/null @@ -1,94 +0,0 @@ -assertSame("'St. John's'", addQuotes("St. John's")); - $this->assertSame("''", addQuotes('')); - } - - public function testParseLatLon() - { - // no coordinates expected - $this->assertFalse(parseLatLon('')); - $this->assertFalse(parseLatLon('abc')); - $this->assertFalse(parseLatLon('12 34')); - - // coordinates expected - $this->assertNotNull(parseLatLon('0.0 -0.0')); - - $aRes = parseLatLon(' abc 12.456 -78.90 def '); - $this->assertEquals($aRes[1], 12.456); - $this->assertEquals($aRes[2], -78.90); - $this->assertEquals($aRes[0], ' 12.456 -78.90 '); - - $aRes = parseLatLon(' [12.456,-78.90] '); - $this->assertEquals($aRes[1], 12.456); - $this->assertEquals($aRes[2], -78.90); - $this->assertEquals($aRes[0], ' [12.456,-78.90] '); - - $aRes = parseLatLon(' -12.456,-78.90 '); - $this->assertEquals($aRes[1], -12.456); - $this->assertEquals($aRes[2], -78.90); - $this->assertEquals($aRes[0], ' -12.456,-78.90 '); - - // http://en.wikipedia.org/wiki/Geographic_coordinate_conversion - // these all represent the same location - $aQueries = array( - '40 26.767 N 79 58.933 W', - '40° 26.767′ N 79° 58.933′ W', - "40° 26.767' N 79° 58.933' W", - "40° 26.767' - N 79° 58.933' W", - 'N 40 26.767, W 79 58.933', - 'N 40°26.767′, W 79°58.933′', - ' N 40°26.767′, W 79°58.933′', - "N 40°26.767', W 79°58.933'", - - '40 26 46 N 79 58 56 W', - '40° 26′ 46″ N 79° 58′ 56″ W', - '40° 26′ 46.00″ N 79° 58′ 56.00″ W', - '40°26′46″N 79°58′56″W', - 'N 40 26 46 W 79 58 56', - 'N 40° 26′ 46″, W 79° 58′ 56″', - 'N 40° 26\' 46", W 79° 58\' 56"', - 'N 40° 26\' 46", W 79° 58\' 56"', - - '40.446 -79.982', - '40.446,-79.982', - '40.446° N 79.982° W', - 'N 40.446° W 79.982°', - - '[40.446 -79.982]', - '[40.446, -79.982]', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ', - ); - - - foreach ($aQueries as $sQuery) { - $aRes = parseLatLon($sQuery); - $this->assertEqualsWithDelta(40.446, $aRes[1], 0.01, 'degrees decimal ' . $sQuery); - $this->assertEqualsWithDelta(-79.982, $aRes[2], 0.01, 'degrees decimal ' . $sQuery); - $this->assertEquals($sQuery, $aRes[0]); - } - } -} diff --git a/test/php/Nominatim/ParameterParserTest.php b/test/php/Nominatim/ParameterParserTest.php deleted file mode 100644 index 82716d4de9..0000000000 --- a/test/php/Nominatim/ParameterParserTest.php +++ /dev/null @@ -1,248 +0,0 @@ - '1', - 'bool2' => '0', - 'bool3' => 'true', - 'bool4' => 'false', - 'bool5' => '' - )); - - $this->assertSame(false, $oParams->getBool('non-exists')); - $this->assertSame(true, $oParams->getBool('non-exists', true)); - $this->assertSame(true, $oParams->getBool('bool1')); - $this->assertSame(false, $oParams->getBool('bool2')); - $this->assertSame(true, $oParams->getBool('bool3')); - $this->assertSame(true, $oParams->getBool('bool4')); - $this->assertSame(false, $oParams->getBool('bool5')); - } - - - public function testGetInt() - { - $oParams = new ParameterParser(array( - 'int1' => '5', - 'int2' => '-1', - 'int3' => 0 - )); - - $this->assertSame(false, $oParams->getInt('non-exists')); - $this->assertSame(999, $oParams->getInt('non-exists', 999)); - $this->assertSame(5, $oParams->getInt('int1')); - - $this->assertSame(-1, $oParams->getInt('int2')); - $this->assertSame(0, $oParams->getInt('int3')); - } - - - public function testGetIntWithNonNumber() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Integer number expected for parameter 'int4'"); - - (new ParameterParser(array('int4' => 'a')))->getInt('int4'); - } - - - public function testGetIntWithEmpytString() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Integer number expected for parameter 'int5'"); - - (new ParameterParser(array('int5' => '')))->getInt('int5'); - } - - - public function testGetFloat() - { - - $oParams = new ParameterParser(array( - 'float1' => '1.0', - 'float2' => '-5', - 'float3' => 0 - )); - - $this->assertSame(false, $oParams->getFloat('non-exists')); - $this->assertSame(999, $oParams->getFloat('non-exists', 999)); - $this->assertSame(1.0, $oParams->getFloat('float1')); - $this->assertSame(-5.0, $oParams->getFloat('float2')); - $this->assertSame(0.0, $oParams->getFloat('float3')); - } - - public function testGetFloatWithEmptyString() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Floating-point number expected for parameter 'float4'"); - - (new ParameterParser(array('float4' => '')))->getFloat('float4'); - } - - public function testGetFloatWithTextString() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Floating-point number expected for parameter 'float5'"); - - (new ParameterParser(array('float5' => 'a')))->getFloat('float5'); - } - - - public function testGetFloatWithInvalidNumber() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Floating-point number expected for parameter 'float6'"); - - (new ParameterParser(array('float6' => '-55.')))->getFloat('float6'); - } - - - public function testGetString() - { - $oParams = new ParameterParser(array( - 'str1' => 'abc', - 'str2' => '', - 'str3' => '0' - )); - - $this->assertSame(false, $oParams->getString('non-exists')); - $this->assertSame('default', $oParams->getString('non-exists', 'default')); - $this->assertSame('abc', $oParams->getString('str1')); - $this->assertSame(false, $oParams->getStringList('str2')); - $this->assertSame(false, $oParams->getStringList('str3')); // sadly PHP magic treats 0 as false when returned - } - - - public function testGetSet() - { - $oParams = new ParameterParser(array( - 'val1' => 'foo', - 'val2' => '', - 'val3' => 0 - )); - - $this->assertSame(false, $oParams->getSet('non-exists', array('foo', 'bar'))); - $this->assertSame('default', $oParams->getSet('non-exists', array('foo', 'bar'), 'default')); - $this->assertSame('foo', $oParams->getSet('val1', array('foo', 'bar'))); - - $this->assertSame(false, $oParams->getSet('val2', array('foo', 'bar'))); - $this->assertSame(false, $oParams->getSet('val3', array('foo', 'bar'))); - } - - - public function testGetSetWithValueNotInSet() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Parameter 'val4' must be one of: foo, bar"); - - (new ParameterParser(array('val4' => 'faz')))->getSet('val4', array('foo', 'bar')); - } - - - public function testGetStringList() - { - $oParams = new ParameterParser(array( - 'list1' => ',a,b,c,,c,d', - 'list2' => 'a', - 'list3' => '', - 'list4' => '0' - )); - - $this->assertSame(false, $oParams->getStringList('non-exists')); - $this->assertSame(array('a', 'b'), $oParams->getStringList('non-exists', array('a', 'b'))); - $this->assertSame(array('a', 'b', 'c', 'c', 'd'), $oParams->getStringList('list1')); - $this->assertSame(array('a'), $oParams->getStringList('list2')); - $this->assertSame(false, $oParams->getStringList('list3')); - $this->assertSame(false, $oParams->getStringList('list4')); - } - - - public function testGetPreferredLanguages() - { - $oParams = new ParameterParser(array('accept-language' => '')); - $this->assertSame(array( - 'name:default' => 'name:default', - '_place_name:default' => '_place_name:default', - 'name' => 'name', - '_place_name' => '_place_name' - ), array_slice($oParams->getPreferredLanguages('default'), 0, 4)); - - $oParams = new ParameterParser(array('accept-language' => 'de,en')); - $this->assertSame(array( - 'name:de' => 'name:de', - '_place_name:de' => '_place_name:de', - 'name:en' => 'name:en', - '_place_name:en' => '_place_name:en', - 'name' => 'name', - '_place_name' => '_place_name' - ), array_slice($oParams->getPreferredLanguages('default'), 0, 6)); - - $oParams = new ParameterParser(array('accept-language' => 'fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3')); - $this->assertSame(array( - 'name:fr-ca' => 'name:fr-ca', - '_place_name:fr-ca' => '_place_name:fr-ca', - 'name:fr' => 'name:fr', - '_place_name:fr' => '_place_name:fr', - 'name:en-ca' => 'name:en-ca', - '_place_name:en-ca' => '_place_name:en-ca', - 'name:en' => 'name:en', - '_place_name:en' => '_place_name:en', - 'name' => 'name', - '_place_name' => '_place_name' - ), array_slice($oParams->getPreferredLanguages('default'), 0, 10)); - - $oParams = new ParameterParser(array('accept-language' => 'ja_rm,zh_pinyin')); - $this->assertSame(array( - 'name:ja_rm' => 'name:ja_rm', - '_place_name:ja_rm' => '_place_name:ja_rm', - 'name:zh_pinyin' => 'name:zh_pinyin', - '_place_name:zh_pinyin' => '_place_name:zh_pinyin', - 'name:ja' => 'name:ja', - '_place_name:ja' => '_place_name:ja', - 'name:zh' => 'name:zh', - '_place_name:zh' => '_place_name:zh', - 'name' => 'name', - '_place_name' => '_place_name' - ), array_slice($oParams->getPreferredLanguages('default'), 0, 10)); - } - - public function testHasSetAny() - { - $oParams = new ParameterParser(array( - 'one' => '', - 'two' => 0, - 'three' => '0', - 'four' => '1', - 'five' => 'anystring' - )); - $this->assertFalse($oParams->hasSetAny(array())); - $this->assertFalse($oParams->hasSetAny(array(''))); - $this->assertFalse($oParams->hasSetAny(array('unknown'))); - $this->assertFalse($oParams->hasSetAny(array('one', 'two', 'three'))); - $this->assertTrue($oParams->hasSetAny(array('one', 'four'))); - $this->assertTrue($oParams->hasSetAny(array('four'))); - $this->assertTrue($oParams->hasSetAny(array('five'))); - } -} diff --git a/test/php/Nominatim/ResultTest.php b/test/php/Nominatim/ResultTest.php deleted file mode 100644 index 8b95105e76..0000000000 --- a/test/php/Nominatim/ResultTest.php +++ /dev/null @@ -1,43 +0,0 @@ -iResultRank = $iResultRank; - - return $oResult; -} - - -class ResultTest extends \PHPUnit\Framework\TestCase -{ - public function testSplitResults() - { - $aSplitResults = Result::splitResults(array( - mkRankedResult(1, 2), - mkRankedResult(2, 0), - mkRankedResult(3, 0), - mkRankedResult(4, 2), - mkRankedResult(5, 1) - )); - - - $aHead = array_keys($aSplitResults['head']); - $aTail = array_keys($aSplitResults['tail']); - - $this->assertEquals($aHead, array(2, 3)); - $this->assertEquals($aTail, array(1, 4, 5)); - } -} diff --git a/test/php/Nominatim/SearchContextTest.php b/test/php/Nominatim/SearchContextTest.php deleted file mode 100644 index b5ef1a7a42..0000000000 --- a/test/php/Nominatim/SearchContextTest.php +++ /dev/null @@ -1,89 +0,0 @@ -oCtx = new SearchContext(); - } - - public function testHasNearPoint() - { - $this->assertFalse($this->oCtx->hasNearPoint()); - $this->oCtx->setNearPoint(0, 0); - $this->assertTrue($this->oCtx->hasNearPoint()); - } - - public function testNearRadius() - { - $this->oCtx->setNearPoint(1, 1); - $this->assertEquals(0.1, $this->oCtx->nearRadius()); - $this->oCtx->setNearPoint(1, 1, 0.338); - $this->assertEquals(0.338, $this->oCtx->nearRadius()); - } - - public function testWithinSQL() - { - $this->oCtx->setNearPoint(0.1, 23, 1); - - $this->assertEquals( - 'ST_DWithin(foo, ST_SetSRID(ST_Point(23,0.1),4326), 1.000000)', - $this->oCtx->withinSQL('foo') - ); - } - - public function testDistanceSQL() - { - $this->oCtx->setNearPoint(0.1, 23, 1); - - $this->assertEquals( - 'ST_Distance(ST_SetSRID(ST_Point(23,0.1),4326), foo)', - $this->oCtx->distanceSQL('foo') - ); - } - - public function testSetViewboxFromBox() - { - $viewbox = array(30, 20, 40, 50); - $this->oCtx->setViewboxFromBox($viewbox, true); - $this->assertEquals( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(30.000000,20.000000),ST_Point(40.000000,50.000000)),4326)', - $this->oCtx->sqlViewboxSmall - ); - // height: 10 - // width: 30 - $this->assertEquals( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(50.000000,80.000000),ST_Point(20.000000,-10.000000)),4326)', - $this->oCtx->sqlViewboxLarge - ); - - - $viewbox = array(-1.5, -2, 1.5, 2); - $this->oCtx->setViewboxFromBox($viewbox, true); - $this->assertEquals( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(-1.500000,-2.000000),ST_Point(1.500000,2.000000)),4326)', - $this->oCtx->sqlViewboxSmall - ); - // height: 3 - // width: 4 - $this->assertEquals( - 'ST_SetSRID(ST_MakeBox2D(ST_Point(4.500000,6.000000),ST_Point(-4.500000,-6.000000)),4326)', - $this->oCtx->sqlViewboxLarge - ); - } -} diff --git a/test/php/Nominatim/ShellTest.php b/test/php/Nominatim/ShellTest.php deleted file mode 100644 index 822194980b..0000000000 --- a/test/php/Nominatim/ShellTest.php +++ /dev/null @@ -1,128 +0,0 @@ -expectException('ArgumentCountError'); - $this->expectExceptionMessage('Too few arguments to function'); - $oCmd = new \Nominatim\Shell(); - - - $oCmd = new \Nominatim\Shell('wc', '-l', 'file.txt'); - $this->assertSame( - "wc -l 'file.txt'", - $oCmd->escapedCmd() - ); - } - - public function testaddParams() - { - $oCmd = new \Nominatim\Shell('grep'); - $oCmd->addParams('-a', 'abc') - ->addParams(10); - - $this->assertSame( - 'grep -a abc 10', - $oCmd->escapedCmd(), - 'no escaping needed, chained' - ); - - $oCmd = new \Nominatim\Shell('grep'); - $oCmd->addParams(); - $oCmd->addParams(null); - $oCmd->addParams(''); - - $this->assertEmpty($oCmd->aParams); - $this->assertSame('grep', $oCmd->escapedCmd(), 'empty params'); - - $oCmd = new \Nominatim\Shell('echo', '-n', 0); - $this->assertSame( - 'echo -n 0', - $oCmd->escapedCmd(), - 'zero param' - ); - - $oCmd = new \Nominatim\Shell('/path with space/do.php'); - $oCmd->addParams('-a', ' b '); - $oCmd->addParams('--flag'); - $oCmd->addParams('two words'); - $oCmd->addParams('v=1'); - - $this->assertSame( - "'/path with space/do.php' -a ' b ' --flag 'two words' 'v=1'", - $oCmd->escapedCmd(), - 'escape whitespace' - ); - - $oCmd = new \Nominatim\Shell('grep'); - $oCmd->addParams(';', '|more&', '2>&1'); - - $this->assertSame( - "grep ';' '|more&' '2>&1'", - $oCmd->escapedCmd(), - 'escape shell characters' - ); - } - - public function testaddEnvPair() - { - $oCmd = new \Nominatim\Shell('date'); - - $oCmd->addEnvPair('one', 'two words') - ->addEnvPair('null', null) - ->addEnvPair(null, 'null') - ->addEnvPair('empty', '') - ->addEnvPair('', 'empty'); - - $this->assertEquals( - array('one' => 'two words', 'empty' => ''), - $oCmd->aEnv - ); - - $oCmd->addEnvPair('one', 'overwrite'); - $this->assertEquals( - array('one' => 'overwrite', 'empty' => ''), - $oCmd->aEnv - ); - } - - public function testClone() - { - $oCmd = new \Nominatim\Shell('wc', '-l', 'file.txt'); - $oCmd2 = clone $oCmd; - $oCmd->addParams('--flag'); - $oCmd2->addParams('--flag2'); - - $this->assertSame( - "wc -l 'file.txt' --flag", - $oCmd->escapedCmd() - ); - - $this->assertSame( - "wc -l 'file.txt' --flag2", - $oCmd2->escapedCmd() - ); - } - - public function testRun() - { - $oCmd = new \Nominatim\Shell('echo'); - - $this->assertSame(0, $oCmd->run()); - - // var_dump($sStdout); - } -} diff --git a/test/php/Nominatim/SimpleWordListTest.php b/test/php/Nominatim/SimpleWordListTest.php deleted file mode 100644 index 69cb518091..0000000000 --- a/test/php/Nominatim/SimpleWordListTest.php +++ /dev/null @@ -1,136 +0,0 @@ -aTokens = array_flip($aTokens); - } - - public function containsAny($sTerm) - { - return isset($this->aTokens[$sTerm]); - } -} - -// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses -class SimpleWordListTest extends \PHPUnit\Framework\TestCase -{ - - - private function serializeSets($aSets) - { - $aParts = array(); - foreach ($aSets as $aSet) { - $aParts[] = '(' . join('|', $aSet) . ')'; - } - return join(',', $aParts); - } - - - public function testEmptyPhrase() - { - $oList = new SimpleWordList(''); - $this->assertNull($oList->getWordSets(new TokensFullSet())); - } - - - public function testSingleWordPhrase() - { - $oList = new SimpleWordList('a'); - - $this->assertEquals( - '(a)', - $this->serializeSets($oList->getWordSets(new TokensFullSet())) - ); - } - - - public function testMultiWordPhrase() - { - $oList = new SimpleWordList('a b'); - $this->assertEquals( - '(a b),(a|b)', - $this->serializeSets($oList->getWordSets(new TokensFullSet())) - ); - - $oList = new SimpleWordList('a b c'); - $this->assertEquals( - '(a b c),(a b|c),(a|b c),(a|b|c)', - $this->serializeSets($oList->getWordSets(new TokensFullSet())) - ); - - $oList = new SimpleWordList('a b c d'); - $this->assertEquals( - '(a b c d),(a b c|d),(a b|c d),(a|b c d),(a b|c|d),(a|b c|d),(a|b|c d),(a|b|c|d)', - $this->serializeSets($oList->getWordSets(new TokensFullSet())) - ); - } - - public function testCmpByArraylen() - { - // Array elements are phrases, we want to sort so longest phrases are first - $aList1 = array('hackney', 'bridge', 'london', 'england'); - $aList2 = array('hackney', 'london', 'bridge'); - $aList3 = array('bridge', 'hackney', 'london', 'england'); - - $this->assertEquals(0, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList1)); - - // list2 "wins". Less array elements - $this->assertEquals(1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList2)); - $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList2, $aList3)); - - // list1 "wins". Same number of array elements but longer first element - $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList3)); - } - - public function testMaxWordSets() - { - $aWords = array_fill(0, 4, 'a'); - $oList = new SimpleWordList(join(' ', $aWords)); - $this->assertEquals(8, count($oList->getWordSets(new TokensFullSet()))); - - $aWords = array_fill(0, 18, 'a'); - $oList = new SimpleWordList(join(' ', $aWords)); - $this->assertEquals(100, count($oList->getWordSets(new TokensFullSet()))); - } - - - public function testPartialTokensShortTerm() - { - $oList = new SimpleWordList('a b c d'); - $this->assertEquals( - '(a|b c d),(a|b c|d)', - $this->serializeSets($oList->getWordSets(new TokensPartialSet(array('a', 'b', 'd', 'b c', 'b c d')))) - ); - } - - - public function testPartialTokensLongTerm() - { - $aWords = array_fill(0, 18, 'a'); - $oList = new SimpleWordList(join(' ', $aWords)); - $this->assertEquals(80, count($oList->getWordSets(new TokensPartialSet(array('a', 'a a a a a'))))); - } -} diff --git a/test/php/Nominatim/StatusTest.php b/test/php/Nominatim/StatusTest.php deleted file mode 100644 index 5f8bac64ee..0000000000 --- a/test/php/Nominatim/StatusTest.php +++ /dev/null @@ -1,81 +0,0 @@ -expectException(\Exception::class); - $this->expectExceptionMessage('No database'); - $this->expectExceptionCode(700); - - $oDB = null; - $oStatus = new Status($oDB); - $this->assertEquals('No database', $oStatus->status()); - } - - public function testNoDatabaseConnectionFail() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Database connection failed'); - $this->expectExceptionCode(700); - - $oDbStub = $this->getMockBuilder(Nominatim\DB::class) - ->setMethods(array('connect')) - ->getMock(); - - $oDbStub->method('connect') - ->will($this->returnCallback(function () { - throw new \Nominatim\DatabaseError('psql connection problem', 500, null, 'unknown database'); - })); - - - $oStatus = new Status($oDbStub); - $this->assertEquals('No database', $oStatus->status()); - } - - public function testOK() - { - $oDbStub = $this->getMockBuilder(Nominatim\DB::class) - ->setMethods(array('connect', 'getOne')) - ->getMock(); - - $oDbStub->method('getOne') - ->will($this->returnCallback(function ($sql) { - if (preg_match("/make_standard_name\('(\w+)'\)/", $sql, $aMatch)) return $aMatch[1]; - if (preg_match('/SELECT word_id, word_token/', $sql)) return 1234; - })); - - $oStatus = new Status($oDbStub); - $this->assertNull($oStatus->status()); - } - - public function testDataDate() - { - $oDbStub = $this->getMockBuilder(Nominatim\DB::class) - ->setMethods(array('getOne')) - ->getMock(); - - $oDbStub->method('getOne') - ->willReturn(1519430221); - - $oStatus = new Status($oDbStub); - $this->assertEquals(1519430221, $oStatus->dataDate()); - } -} diff --git a/test/php/Nominatim/TokenListTest.php b/test/php/Nominatim/TokenListTest.php deleted file mode 100644 index 57e3c58fa0..0000000000 --- a/test/php/Nominatim/TokenListTest.php +++ /dev/null @@ -1,60 +0,0 @@ -oNormalizer = $this->getMockBuilder(\MockNormalizer::class) - ->setMethods(array('transliterate')) - ->getMock(); - $this->oNormalizer->method('transliterate') - ->will($this->returnCallback(function ($text) { - return strtolower($text); - })); - } - - private function wordResult($aFields) - { - $aRow = array( - 'word_id' => null, - 'word_token' => null, - 'word' => null, - 'class' => null, - 'type' => null, - 'country_code' => null, - 'count' => 0 - ); - return array_merge($aRow, $aFields); - } - - public function testList() - { - $TL = new TokenList; - - $this->assertEquals(0, $TL->count()); - - $TL->addToken('word1', 'token1'); - $TL->addToken('word1', 'token2'); - - $this->assertEquals(1, $TL->count()); - - $this->assertTrue($TL->contains('word1')); - $this->assertEquals(array('token1', 'token2'), $TL->get('word1')); - - $this->assertFalse($TL->contains('unknownword')); - $this->assertEquals(array(), $TL->get('unknownword')); - } -} diff --git a/test/php/Nominatim/tokenizer.php b/test/php/Nominatim/tokenizer.php deleted file mode 100644 index 923e0a2220..0000000000 --- a/test/php/Nominatim/tokenizer.php +++ /dev/null @@ -1,25 +0,0 @@ -oDB =& $oDB; - } - - public function checkStatus() - { - } -} diff --git a/test/php/bootstrap.php b/test/php/bootstrap.php deleted file mode 100644 index 7d25451109..0000000000 --- a/test/php/bootstrap.php +++ /dev/null @@ -1,14 +0,0 @@ - - - - - ../../lib-php/ - - - - - - - ./Nominatim - - - diff --git a/test/python/cli/test_cli.py b/test/python/cli/test_cli.py index 2831f84f1b..d42df50a76 100644 --- a/test/python/cli/test_cli.py +++ b/test/python/cli/test_cli.py @@ -37,15 +37,6 @@ def test_cli_version(cli_call, capsys): assert captured.out.startswith('Nominatim version') -def test_cli_serve_php(cli_call, mock_func_factory): - func = mock_func_factory(nominatim_db.cli, 'run_php_server') - - cli_call('serve', '--engine', 'php') == 0 - - assert func.called == 1 - - - class TestCliWithDb: @pytest.fixture(autouse=True) diff --git a/test/python/cli/test_cmd_import.py b/test/python/cli/test_cmd_import.py index e47d713c1f..f833dde347 100644 --- a/test/python/cli/test_cmd_import.py +++ b/test/python/cli/test_cmd_import.py @@ -52,7 +52,6 @@ def test_import_full(self, mock_func_factory, async_mock_func_factory, mock_func_factory(nominatim_db.tools.refresh, 'load_address_levels_from_config'), mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes'), async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'), - mock_func_factory(nominatim_db.tools.refresh, 'setup_website'), ] params = ['import', '--osm-file', __file__] @@ -81,7 +80,6 @@ def test_import_continue_load_data(self, mock_func_factory, async_mock_func_fact mock_func_factory(nominatim_db.data.country_info, 'create_country_names'), mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes'), async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'), - mock_func_factory(nominatim_db.tools.refresh, 'setup_website'), mock_func_factory(nominatim_db.db.properties, 'set_property') ] @@ -98,7 +96,6 @@ def test_import_continue_indexing(self, mock_func_factory, async_mock_func_facto async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'), mock_func_factory(nominatim_db.data.country_info, 'create_country_names'), async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'), - mock_func_factory(nominatim_db.tools.refresh, 'setup_website'), mock_func_factory(nominatim_db.db.properties, 'set_property') ] @@ -115,7 +112,6 @@ def test_import_continue_postprocess(self, mock_func_factory, async_mock_func_fa mocks = [ async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'), mock_func_factory(nominatim_db.data.country_info, 'create_country_names'), - mock_func_factory(nominatim_db.tools.refresh, 'setup_website'), mock_func_factory(nominatim_db.db.properties, 'set_property') ] diff --git a/test/python/cli/test_cmd_refresh.py b/test/python/cli/test_cmd_refresh.py index 9074b2cc3d..9f3d7bb241 100644 --- a/test/python/cli/test_cmd_refresh.py +++ b/test/python/cli/test_cmd_refresh.py @@ -25,7 +25,6 @@ def setup_cli_call(self, cli_call, temp_db, cli_tokenizer_mock): ('address-levels', 'load_address_levels_from_config'), ('wiki-data', 'import_wikipedia_articles'), ('importance', 'recompute_importance'), - ('website', 'setup_website'), ]) def test_refresh_command(self, mock_func_factory, command, func): mock_func_factory(nominatim_db.tools.refresh, 'create_functions') diff --git a/test/python/tools/test_refresh_setup_website.py b/test/python/tools/test_refresh_setup_website.py deleted file mode 100644 index fe29dd5243..0000000000 --- a/test/python/tools/test_refresh_setup_website.py +++ /dev/null @@ -1,104 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This file is part of Nominatim. (https://nominatim.org) -# -# Copyright (C) 2024 by the Nominatim developer community. -# For a full list of authors see the git log. -""" -Tests for setting up the website scripts. -""" -import subprocess - -import pytest - -from nominatim_db.tools import refresh - -@pytest.fixture -def test_script(tmp_path): - (tmp_path / 'php').mkdir() - - website_dir = (tmp_path / 'php' / 'website') - website_dir.mkdir() - - def _create_file(code): - outfile = website_dir / 'reverse-only-search.php' - outfile.write_text('