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
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)
#-----------------------------------------------------------------------------
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}")
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
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}
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
"""
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()
+++ /dev/null
-# 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()
correct location of the library **on the database server**. Add the following
line to your your `.env` file:
-```php
+```
NOMINATIM_DATABASE_MODULE_PATH="<directory on the database server where nominatim.so resides>"
```
+++ /dev/null
-# 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
-<Directory "/srv/nominatim-project/website">
- Options FollowSymLinks MultiViews
- AddType text/html .php
- DirectoryIndex search.php
- Require all granted
-</Directory>
-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/<php version>/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)
-
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
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
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
* [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).
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
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 |
|-----------| ----- | ------- |
| 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 |
|-----------| ----- | ------- |
##### 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
<searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
querystring="london" polygon="false" exclude_place_ids="100149"
- more_url="https://nominatim.openstreetmap.org/search.php?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
+ 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">
<place place_id="100149" osm_type="node" osm_id="107775" place_rank="15" address_rank="15"
boundingbox="51.3473219,51.6673219,-0.2876474,0.0323526" lat="51.5073219" lon="-0.1276474"
display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
-!!! Attention
- The current version of Nominatim implements two different search frontends:
- the old PHP frontend and the new Python frontend. They have a very similar
- API but differ in some implementation details. These are marked in the
- documentation as `[Python-only]` or `[PHP-only]`.
-
- `https://nominatim.openstreetmap.org` implements the **Python frontend**.
- So users should refer to the **`[Python-only]`** comments.
-
This section describes the API V1 of the Nominatim web service. The
service offers the following endpoints:
is in an area with no OSM data coverage.
-!!! danger "Deprecation warning"
- The reverse API used to allow address lookup for a single OSM object by
- its OSM id for `[PHP-only]`. The use is considered deprecated.
- Use the [Address Lookup API](Lookup.md) instead.
+!!! tip
+ The reverse API allows a lookup of object by coordinate. If you want
+ to look up an object by ID, use the [Address Lookup API](Lookup.md) instead.
!!! danger "Deprecation warning"
The API can also be used with the URL
|-----------| ----- | ------- |
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
-**`[Python-only]`**
-
The layer filter allows to select places by themes.
The `address` layer contains all places that make up an address:
|-----------| ----- | ------- |
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
-**`[Python-only]`**
-
The layer filter allows to select places by themes.
The `address` layer contains all places that make up an address:
| **After Changes:** | cannot be changed after import |
Defines the name of the database user that will run search queries. Usually
-this is the user under which the webserver is executed. When running Nominatim
-via php-fpm, you can also define a separate query user. The Postgres user
+this is the user under which the webserver is executed. The Postgres user
needs to be set up before starting the import.
Nominatim grants minimal rights to this user to all tables that are needed
used.
-#### NOMINATIM_SEARCH_BATCH_MODE
-
-| Summary | |
-| -------------- | --------------------------------------------------- |
-| **Description:** | Enable a special batch query mode |
-| **Format:** | boolean |
-| **Default:** | no |
-| **After Changes:** | run `nominatim refresh --website` |
-| **Comment:** | PHP frontend only |
-
-
-This feature is currently undocumented and potentially broken.
-
-
-#### NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD
-
-| Summary | |
-| -------------- | --------------------------------------------------- |
-| **Description:** | Threshold for switching the search index lookup strategy |
-| **Format:** | integer |
-| **Default:** | 500 |
-| **After Changes:** | run `nominatim refresh --website` |
-| **Comment:** | PHP frontend only |
-
-This setting defines the threshold over which a name is no longer considered
-as rare. When searching for places with rare names, only the name is used
-for place lookups. Otherwise the name and any address information is used.
-
-This setting only has an effect after `nominatim refresh --word-counts` has
-been called to compute the word frequencies.
-
-
#### NOMINATIM_LOOKUP_MAX_COUNT
| Summary | |
| **Format:** | boolean |
| **Default:** | no |
| **After Changes:** | run `nominatim refresh --website` |
-| **Comment:** | PHP frontend only |
Enable to search elements just within countries.
<request time> <execution time in s> <number of results> <type> "<query string>"
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.
## 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)
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:
. ~/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
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:
| +- 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
* 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`)
### Directory Structure
-Nominatim expects two files for a tokenizer:
-
-* `nominatim/tokenizer/<NAME>_tokenizer.py` containing the Python part of the
- implementation
-* `lib-php/tokenizer/<NAME>_tokenizer.php` with the PHP part of the
- implementation
-
-where `<NAME>` is a unique name for the tokenizer consisting of only lower-case
+Nominatim expects a single file `src/nominatim_db/tokenizer/<NAME>_tokenizer.py`
+containing the Python part of the implementation.
+`<NAME>` 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`.
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.
-
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`.
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-/**
- * Detailed list of address parts for a single result
- */
-class AddressDetails
-{
- private $iPlaceID;
- private $aAddressLines;
-
- public function __construct(&$oDB, $iPlaceID, $sHousenumber, $mLangPref)
- {
- $this->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'];
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\ClassTypes;
-
-/**
- * Create a label tag for the given place that can be used as an XML name.
- *
- * @param array[] $aPlace Information about the place to label.
- *
- * A label tag groups various object types together under a common
- * label. The returned value is lower case and has no spaces
- */
-function getLabelTag($aPlace, $sCountry = null)
-{
- $iRank = (int) ($aPlace['rank_address'] ?? 30);
- $sLabel;
- if (isset($aPlace['place_type'])) {
- $sLabel = $aPlace['place_type'];
- } elseif ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
- $sLabel = getBoundaryLabel($iRank/2, $sCountry);
- } elseif ($aPlace['type'] == 'postal_code') {
- $sLabel = 'postcode';
- } elseif ($iRank < 26) {
- $sLabel = $aPlace['type'];
- } elseif ($iRank < 28) {
- $sLabel = 'road';
- } elseif ($aPlace['class'] == 'place'
- && ($aPlace['type'] == 'house_number' ||
- $aPlace['type'] == 'house_name' ||
- $aPlace['type'] == 'country_code')
- ) {
- $sLabel = $aPlace['type'];
- } else {
- $sLabel = $aPlace['class'];
- }
-
- return strtolower(str_replace(' ', '_', $sLabel));
-}
-
-/**
- * Create a label for the given place.
- *
- * @param array[] $aPlace Information about the place to label.
- */
-function getLabel($aPlace, $sCountry = null)
-{
- if (isset($aPlace['place_type'])) {
- return ucwords(str_replace('_', ' ', $aPlace['place_type']));
- }
-
- if ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
- return getBoundaryLabel(($aPlace['rank_address'] ?? 30)/2, $sCountry ?? null);
- }
-
- // Return a label only for 'important' class/type combinations
- if (getImportance($aPlace) !== null) {
- return ucwords(str_replace('_', ' ', $aPlace['type']));
- }
-
- return null;
-}
-
-
-/**
- * Return a simple label for an administrative boundary for the given country.
- *
- * @param int $iAdminLevel Content of admin_level tag.
- * @param string $sCountry Country code of the country where the object is
- * in. May be null, in which case a world-wide
- * fallback is used.
- * @param string $sFallback String to return if no explicit string is listed.
- *
- * @return string
- */
-function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative')
-{
- static $aBoundaryList = array (
- 'default' => 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;
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/DatabaseError.php');
-
-/**
- * Uses PDO to access the database specified in the CONST_Database_DSN
- * setting.
- */
-class DB
-{
- protected $connection;
-
- public function __construct($sDSN = null)
- {
- $this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class DatabaseError extends \Exception
-{
-
- public function __construct($message, $code, $previous, $oPDOErr, $sSql = null)
- {
- parent::__construct($message, $code, $previous);
- // https://secure.php.net/manual/en/class.pdoexception.php
- $this->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;
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class Debug
-{
- public static function newFunction($sHeading)
- {
- echo "<pre><h2>Debug output for $sHeading</h2></pre>\n";
- }
-
- public static function newSection($sHeading)
- {
- echo "<hr><pre><h3>$sHeading</h3></pre>\n";
- }
-
- public static function printVar($sHeading, $mVar)
- {
- echo '<pre><b>'.$sHeading. ':</b> ';
- Debug::outputVar($mVar, str_repeat(' ', strlen($sHeading) + 3));
- echo "</pre>\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 '<b>'.$sHeading.":</b>\n";
- echo "<table border='1'>\n";
- if (!empty($aVar)) {
- echo " <tr>\n";
- $aKeys = array();
- $aInfo = reset($aVar);
- if (!is_array($aInfo)) {
- $aInfo = $aInfo->debugInfo();
- }
- foreach ($aInfo as $sKey => $mVal) {
- echo ' <th><small>'.$sKey.'</small></th>'."\n";
- $aKeys[] = $sKey;
- }
- echo " </tr>\n";
- foreach ($aVar as $oRow) {
- $aInfo = $oRow;
- if (!is_array($oRow)) {
- $aInfo = $oRow->debugInfo();
- }
- echo " <tr>\n";
- foreach ($aKeys as $sKey) {
- echo ' <td><pre>';
- if (isset($aInfo[$sKey])) {
- Debug::outputVar($aInfo[$sKey], '');
- }
- echo '</pre></td>'."\n";
- }
- echo " </tr>\n";
- }
- }
- echo "</table>\n";
- }
-
- public static function printGroupedSearch($aSearches, $aWordsIDs)
- {
- echo '<table border="1">';
- echo '<tr><th>rank</th><th>Name Tokens</th><th>Name Not</th>';
- echo '<th>Address Tokens</th><th>Address Not</th>';
- echo '<th>country</th><th>operator</th>';
- echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
- foreach ($aSearches as $aRankedSet) {
- foreach ($aRankedSet as $aRow) {
- $aRow->dumpAsHtmlTableRow($aWordsIDs);
- }
- }
- echo '</table>';
- }
-
- public static function printGroupTable($sHeading, $aVar)
- {
- echo '<b>'.$sHeading.":</b>\n";
- echo "<table border='1'>\n";
- if (!empty($aVar)) {
- echo " <tr>\n";
- echo ' <th><small>Group</small></th>'."\n";
- $aKeys = array();
- $aInfo = reset($aVar)[0];
- if (!is_array($aInfo)) {
- $aInfo = $aInfo->debugInfo();
- }
- foreach ($aInfo as $sKey => $mVal) {
- echo ' <th><small>'.$sKey.'</small></th>'."\n";
- $aKeys[] = $sKey;
- }
- echo " </tr>\n";
- foreach ($aVar as $sGrpKey => $aGroup) {
- foreach ($aGroup as $oRow) {
- $aInfo = $oRow;
- if (!is_array($oRow)) {
- $aInfo = $oRow->debugInfo();
- }
- echo " <tr>\n";
- echo ' <td><pre>'.$sGrpKey.'</pre></td>'."\n";
- foreach ($aKeys as $sKey) {
- echo ' <td><pre>';
- if (!empty($aInfo[$sKey])) {
- Debug::outputVar($aInfo[$sKey], '');
- }
- echo '</pre></td>'."\n";
- }
- echo " </tr>\n";
- }
- }
- }
- echo "</table>\n";
- }
-
- public static function printSQL($sSQL)
- {
- echo '<p><tt><b>'.date('c').'</b> <font color="#aaa">'.htmlspecialchars($sSQL, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401).'</font></tt></p>'."\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 '<i>'.($mVar ? 'True' : 'False').'</i>';
- 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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class Debug
-{
- public static function __callStatic($name, $arguments)
- {
- // nothing
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/Phrase.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/SearchDescription.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/SearchPosition.php');
-require_once(CONST_LibDir.'/TokenList.php');
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-class Geocode
-{
- protected $oDB;
-
- protected $oPlaceLookup;
- protected $oTokenizer;
-
- protected $aLangPrefOrder = array();
-
- protected $aExcludePlaceIDs = array();
-
- protected $iLimit = 20;
- protected $iFinalLimit = 10;
- protected $iOffset = 0;
- protected $bFallback = false;
-
- protected $aCountryCodes = false;
-
- protected $bBoundedSearch = false;
- protected $aViewBox = false;
- protected $aRoutePoints = false;
- protected $aRouteWidth = false;
-
- protected $iMaxRank = 20;
- protected $iMinAddressRank = 0;
- protected $iMaxAddressRank = 30;
- protected $aAddressRankList = array();
-
- protected $sAllowedTypesSQLList = false;
-
- protected $sQuery = false;
- protected $aStructuredQuery = false;
-
-
- public function __construct(&$oDB)
- {
- $this->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
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class ParameterParser
-{
- private $aParams;
-
-
- public function __construct($aParams = null)
- {
- $this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-/**
- * Segment of a query string.
- *
- * The parts of a query strings are usually separated by commas.
- */
-class Phrase
-{
- // Complete phrase as a string (guaranteed to have no leading or trailing
- // spaces).
- private $sPhrase;
- // Element type for structured searches.
- private $sPhraseType;
- // Possible segmentations of the phrase.
- private $aWordSets;
-
- public function __construct($sPhrase, $sPhraseType)
- {
- $this->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
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/AddressDetails.php');
-require_once(CONST_LibDir.'/Result.php');
-
-class PlaceLookup
-{
- protected $oDB;
-
- protected $aLangPrefOrderSql = "''";
-
- protected $bAddressDetails = false;
- protected $bExtraTags = false;
- protected $bNameDetails = false;
-
- protected $bIncludePolygonAsText = false;
- protected $bIncludePolygonAsGeoJSON = false;
- protected $bIncludePolygonAsKML = false;
- protected $bIncludePolygonAsSVG = false;
- protected $fPolygonSimplificationThreshold = 0.0;
-
- protected $sAnchorSql = null;
- protected $sAddressRankListSql = null;
- protected $sAllowedTypesSQLList = null;
- protected $bDeDupe = true;
-
-
- public function __construct(&$oDB)
- {
- $this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-/**
- * A single result of a search operation or a reverse lookup.
- *
- * This object only contains the id of the result. It does not yet
- * have any details needed to format the output document.
- */
-class Result
-{
- const TABLE_PLACEX = 0;
- const TABLE_POSTCODE = 1;
- const TABLE_OSMLINE = 2;
- const TABLE_TIGER = 3;
-
- /// Database table that contains the result.
- public $iTable;
- /// Id of the result.
- public $iId;
- /// House number (only for interpolation results).
- public $iHouseNumber = -1;
- /// Number of exact matches in address (address searches only).
- public $iExactMatches = 0;
- /// Subranking within the results (the higher the worse).
- public $iResultRank = 0;
- /// Address rank of the result.
- public $iAddressRank;
-
- public function debugInfo()
- {
- return array(
- 'Table' => $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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Result.php');
-
-class ReverseGeocode
-{
- protected $oDB;
- protected $iMaxRank = 28;
-
-
- public function __construct(&$oDB)
- {
- $this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-
-
-/**
- * Collection of search constraints that are independent of the
- * actual interpretation of the search query.
- *
- * The search context is shared between all SearchDescriptions. This
- * object mainly serves as context provider for the database queries.
- * Therefore most data is directly cached as SQL statements.
- */
-class SearchContext
-{
- /// Search radius around a given Near reference point.
- private $fNearRadius = false;
- /// True if search must be restricted to viewbox only.
- public $bViewboxBounded = false;
-
- /// Reference point for search (as SQL).
- public $sqlNear = '';
- /// Viewbox selected for search (as SQL).
- public $sqlViewboxSmall = '';
- /// Viewbox with a larger buffer around (as SQL).
- public $sqlViewboxLarge = '';
- /// Reference along a route (as SQL).
- public $sqlViewboxCentre = '';
- /// List of countries to restrict search to (as array).
- public $aCountryList = null;
- /// List of countries to restrict search to (as SQL).
- public $sqlCountryList = '';
- /// List of place IDs to exclude (as SQL).
- private $sqlExcludeList = '';
- /// Subset of word ids of full words in the query.
- private $aFullNameWords = array();
-
- public function setFullNameWords($aWordList)
- {
- $this->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
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/Result.php');
-
-/**
- * Description of a single interpretation of a search query.
- */
-class SearchDescription
-{
- /// Ranking how well the description fits the query.
- private $iSearchRank = 0;
- /// Country code of country the result must belong to.
- private $sCountryCode = '';
- /// List of word ids making up the name of the object.
- private $aName = array();
- /// True if the name is rare enough to force index use on name.
- private $bRareName = false;
- /// True if the name requires to be accompanied by address terms.
- private $bNameNeedsAddress = false;
- /// List of word ids making up the address of the object.
- private $aAddress = array();
- /// List of word ids that appear in the name but should be ignored.
- private $aNameNonSearch = array();
- /// List of word ids that appear in the address but should be ignored.
- private $aAddressNonSearch = array();
- /// Kind of search for special searches, see Nominatim::Operator.
- private $iOperator = Operator::NONE;
- /// Class of special feature to search for.
- private $sClass = '';
- /// Type of special feature to search for.
- private $sType = '';
- /// Housenumber of the object.
- private $sHouseNumber = '';
- /// Postcode for the object.
- private $sPostcode = '';
- /// Global search constraints.
- private $oContext;
-
- // Temporary values used while creating the search description.
-
- /// Index of phrase currently processed.
- private $iNamePhrase = -1;
-
- /**
- * Create an empty search description.
- *
- * @param object $oContext Global context to use. Will be inherited by
- * all derived search objects.
- */
- public function __construct($oContext)
- {
- $this->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 [<key>=<value>].
- * 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 [<key>=<value>].
- 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 '<tr>';
- echo "<td>$this->iSearchRank</td>";
- echo '<td>'.join(', ', array_map($kf, $this->aName)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aNameNonSearch)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aAddress)).'</td>';
- echo '<td>'.join(', ', array_map($kf, $this->aAddressNonSearch)).'</td>';
- echo '<td>'.$this->sCountryCode.'</td>';
- echo '<td>'.Operator::toString($this->iOperator).'</td>';
- echo '<td>'.$this->sClass.'</td>';
- echo '<td>'.$this->sType.'</td>';
- echo '<td>'.$this->sPostcode.'</td>';
- echo '<td>'.$this->sHouseNumber.'</td>';
-
- echo '</tr>';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-/**
- * Description of the position of a token within a query.
- */
-class SearchPosition
-{
- private $sPhraseType;
-
- private $iPhrase;
- private $iNumPhrases;
-
- private $iToken;
- private $iNumTokens;
-
-
- public function __construct($sPhraseType, $iPhrase, $iNumPhrases)
- {
- $this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class Shell
-{
- public function __construct($sBaseCmd, ...$aParams)
- {
- if (!$sBaseCmd) {
- throw new \Exception('Command missing in new() call');
- }
- $this->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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-/**
- * A word list creator based on simple splitting by space.
- *
- * Creates possible permutations of split phrases by finding all combination
- * of splitting the phrase on space boundaries.
- */
-class SimpleWordList
-{
- const MAX_WORDSET_LEN = 20;
- const MAX_WORDSETS = 100;
-
- // The phrase as a list of simple terms (without spaces).
- private $aWords;
-
- /**
- * Create a new word list
- *
- * @param string sPhrase Phrase to create the word list from. The phrase is
- * expected to be normalised, so that there are no
- * subsequent spaces.
- */
- public function __construct($sPhrase)
- {
- if (strlen($sPhrase) > 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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-/**
- * Operators describing special searches.
- */
-abstract class Operator
-{
- /// No operator selected.
- const NONE = 0;
- /// Search for POI of the given type.
- const TYPE = 1;
- /// Search for POIs near the given place.
- const NEAR = 2;
- /// Search for POIS in the given place.
- const IN = 3;
- /// Search for POIS named as given.
- const NAME = 4;
- /// Search for postcodes.
- const POSTCODE = 5;
-
- private static $aConstantNames = null;
-
-
- public static function toString($iOperator)
- {
- if ($iOperator == Operator::NONE) {
- return '';
- }
-
- if (Operator::$aConstantNames === null) {
- $oReflector = new \ReflectionClass('Nominatim\Operator');
- $aConstants = $oReflector->getConstants();
-
- Operator::$aConstantNames = array();
- foreach ($aConstants as $sName => $iValue) {
- Operator::$aConstantNames[$iValue] = $sName;
- }
- }
-
- return Operator::$aConstantNames[$iOperator];
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-use Exception;
-
-class Status
-{
- protected $oDB;
-
- public function __construct(&$oDB)
- {
- $this->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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A country token.
- */
-class Country
-{
- /// Database word id, if available.
- private $iId;
- /// Two-letter country code (lower-cased).
- private $sCountryCode;
-
- public function __construct($iId, $sCountryCode)
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A house number token.
- */
-class HouseNumber
-{
- /// Database word id, if available.
- private $iId;
- /// Normalized house number.
- private $sToken;
-
- public function __construct($iId, $sToken)
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/TokenCountry.php');
-require_once(CONST_LibDir.'/TokenHousenumber.php');
-require_once(CONST_LibDir.'/TokenPostcode.php');
-require_once(CONST_LibDir.'/TokenSpecialTerm.php');
-require_once(CONST_LibDir.'/TokenWord.php');
-require_once(CONST_LibDir.'/TokenPartial.php');
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * Saves information about the tokens that appear in a search query.
- *
- * Tokens are sorted by their normalized form, the token word. There are different
- * kinds of tokens, represented by different Token* classes. Note that
- * tokens do not have a common base class. All tokens need to have a field
- * with the word id that points to an entry in the `word` database table
- * but otherwise the information saved about a token can be very different.
- */
-class TokenList
-{
- // List of list of tokens indexed by their word_token.
- private $aTokens = array();
-
-
- /**
- * Return total number of tokens.
- *
- * @return Integer
- */
- public function count()
- {
- return count($this->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;
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Partial
-{
- /// Database word id, if applicable.
- private $iId;
- /// Number of appearances in the database.
- private $iSearchNameCount;
- /// True, if the token consists exclusively of digits and spaces.
- private $bNumberToken;
-
- public function __construct($iId, $sToken, $iSearchNameCount)
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A postcode token.
- */
-class Postcode
-{
- /// Database word id, if available.
- private $iId;
- /// Full normalized postcode (upper cased).
- private $sPostcode;
- // Optional country code the postcode belongs to (currently unused).
- private $sCountryCode;
-
- public function __construct($iId, $sPostcode, $sCountryCode = '')
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * A word token describing a place type.
- */
-class SpecialTerm
-{
- /// Database word id, if applicable.
- private $iId;
- /// Class (or OSM tag key) of the place to look for.
- private $sClass;
- /// Type (or OSM tag value) of the place to look for.
- private $sType;
- /// Relationship of the operator to the object (see Operator class).
- private $iOperator;
-
- public function __construct($iID, $sClass, $sType, $iOperator)
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Word
-{
- /// Database word id, if applicable.
- private $iId;
- /// Number of appearances in the database.
- private $iSearchNameCount;
- /// Number of terms in the word.
- private $iTermCount;
-
- public function __construct($iId, $iSearchNameCount, $iTermCount)
- {
- $this->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';
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/Shell.php');
-
-function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
-{
- $aQuick = array();
- $aCounts = array();
-
- foreach ($aSpec as $aLine) {
- if (is_array($aLine)) {
- if ($aLine[0]) {
- $aQuick['--'.$aLine[0]] = $aLine;
- }
- if ($aLine[1]) {
- $aQuick['-'.$aLine[1]] = $aLine;
- }
- $aCounts[$aLine[0]] = 0;
- }
- }
-
- $aResult = array();
- $bUnknown = false;
- $iSize = count($aArg);
- for ($i = 1; $i < $iSize; $i++) {
- if (isset($aQuick[$aArg[$i]])) {
- $aLine = $aQuick[$aArg[$i]];
- $aCounts[$aLine[0]]++;
- $xVal = null;
- if ($aLine[4] == $aLine[5]) {
- if ($aLine[4]) {
- $xVal = array();
- for ($n = $aLine[4]; $i < $iSize && $n; $n--) {
- $i++;
- if ($i >= $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);
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require('Symfony/Component/Dotenv/autoload.php');
-
-function loadDotEnv()
-{
- $dotenv = new \Symfony\Component\Dotenv\Dotenv();
- $dotenv->load(CONST_ConfigDir.'/env.defaults');
-
- if (file_exists('.env')) {
- $dotenv->load('.env');
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once('init.php');
-require_once('cmd.php');
-require_once('DebugNone.php');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once('init.php');
-require_once('ParameterParser.php');
-require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
-
-/***************************************************************************
- *
- * Error handling functions
- *
- */
-
-function userError($sMsg)
-{
- throw new \Exception($sMsg, 400);
-}
-
-
-function exception_handler_json($exception)
-{
- http_response_code($exception->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 '<?xml version="1.0" encoding="UTF-8" ?>'."\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');
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/DB.php');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-function loadSettings($sProjectDir)
-{
- @define('CONST_InstallDir', $sProjectDir);
- // Temporary hack to set the directory via environment instead of
- // the installed scripts. Neither setting is part of the official
- // set of settings.
- defined('CONST_ConfigDir') or define('CONST_ConfigDir', $_SERVER['NOMINATIM_CONFIGDIR']);
-}
-
-function getSetting($sConfName, $sDefault = null)
-{
- $sValue = $_SERVER['NOMINATIM_'.$sConfName];
-
- if ($sDefault !== null && !$sValue) {
- return $sDefault;
- }
-
- return $sValue;
-}
-
-function getSettingBool($sConfName)
-{
- $sVal = strtolower(getSetting($sConfName));
-
- return strcmp($sVal, 'yes') == 0
- || strcmp($sVal, 'true') == 0
- || strcmp($sVal, '1') == 0;
-}
-
-function fail($sError, $sUserError = false)
-{
- if (!$sUserError) {
- $sUserError = $sError;
- }
- error_log('ERROR: '.$sError);
- var_dump($sUserError);
- echo "\n";
- exit(-1);
-}
-
-
-function getProcessorCount()
-{
- $sCPU = file_get_contents('/proc/cpuinfo');
- preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
- return count($aMatches[0]);
-}
-
-
-function getTotalMemoryMB()
-{
- $sCPU = file_get_contents('/proc/meminfo');
- preg_match('#MemTotal: +([0-9]+) kB#', $sCPU, $aMatches);
- return (int)($aMatches[1]/1024);
-}
-
-
-function getCacheMemoryMB()
-{
- $sCPU = file_get_contents('/proc/meminfo');
- preg_match('#Cached: +([0-9]+) kB#', $sCPU, $aMatches);
- return (int)($aMatches[1]/1024);
-}
-
-function getDatabaseDate(&$oDB)
-{
- // Find the newest node in the DB
- $iLastOSMID = $oDB->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));
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-
-function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
-{
- $fStartTime = microtime(true);
- $aStartTime = explode('.', $fStartTime);
- if (!isset($aStartTime[1])) {
- $aStartTime[1] = '0';
- }
-
- $sOutputFormat = '';
- if (isset($_GET['format'])) {
- $sOutputFormat = $_GET['format'];
- }
-
- if ($sType == 'reverse') {
- $sOutQuery = (isset($_GET['lat'])?$_GET['lat']:'').'/';
- if (isset($_GET['lon'])) {
- $sOutQuery .= $_GET['lon'];
- }
- if (isset($_GET['zoom'])) {
- $sOutQuery .= '/'.$_GET['zoom'];
- }
- } else {
- $sOutQuery = $sQuery;
- }
-
- $hLog = array(
- date('Y-m-d H:i:s', $aStartTime[0]).'.'.$aStartTime[1],
- $_SERVER['REMOTE_ADDR'],
- $_SERVER['QUERY_STRING'],
- $sOutQuery,
- $sType,
- $fStartTime
- );
-
- if (CONST_Log_DB) {
- if (isset($_GET['email'])) {
- $sUserAgent = $_GET['email'];
- } elseif (isset($_SERVER['HTTP_REFERER'])) {
- $sUserAgent = $_SERVER['HTTP_REFERER'];
- } elseif (isset($_SERVER['HTTP_USER_AGENT'])) {
- $sUserAgent = $_SERVER['HTTP_USER_AGENT'];
- } else {
- $sUserAgent = '';
- }
- $sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
- $sSQL .= ' values (';
- $sSQL .= join(',', $oDB->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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-
-function formatOSMType($sType, $bIncludeExternal = true)
-{
- if ($sType == 'N') {
- return 'node';
- }
- if ($sType == 'W') {
- return 'way';
- }
- if ($sType == 'R') {
- return 'relation';
- }
-
- if (!$bIncludeExternal) {
- return '';
- }
-
- if ($sType == 'T') {
- return 'way';
- }
- if ($sType == 'I') {
- return 'way';
- }
-
- // not handled: P, L
-
- return '';
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-function getOsm2pgsqlBinary()
-{
- $sBinary = getSetting('OSM2PGSQL_BINARY');
-
- return $sBinary ? $sBinary : CONST_Default_Osm2pgsql;
-}
-
-function getImportStyle()
-{
- $sStyle = getSetting('IMPORT_STYLE');
-
- if (in_array($sStyle, array('admin', 'street', 'address', 'full', 'extratags'))) {
- return CONST_ConfigDir.'/import-'.$sStyle.'.style';
- }
-
- return $sStyle;
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-// https://github.com/geocoders/geocodejson-spec/
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
- javascript_renderData($aFilteredPlaces);
-} else {
- $aFilteredPlaces = array(
- 'type' => '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)
- ));
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
- javascript_renderData($aFilteredPlaces);
-} else {
- $aFilteredPlaces = array(
- 'type' => '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)
- ));
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- $aFilteredPlaces['error'] = $sError;
- } else {
- $aFilteredPlaces['error'] = 'Unable to geocode';
- }
-} else {
- if (isset($aPlace['place_id'])) {
- $aFilteredPlaces['place_id'] = $aPlace['place_id'];
- }
- $aFilteredPlaces['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- $aFilteredPlaces['osm_type'] = $sOSMType;
- $aFilteredPlaces['osm_id'] = $aPlace['osm_id'];
- }
- if (isset($aPlace['lat'])) {
- $aFilteredPlaces['lat'] = $aPlace['lat'];
- }
- if (isset($aPlace['lon'])) {
- $aFilteredPlaces['lon'] = $aPlace['lon'];
- }
-
- if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
- $aFilteredPlaces['place_rank'] = $aPlace['rank_search'];
-
- $aFilteredPlaces['category'] = $aPlace['class'];
- $aFilteredPlaces['type'] = $aPlace['type'];
-
- $aFilteredPlaces['importance'] = $aPlace['importance'];
-
- $aFilteredPlaces['addresstype'] = strtolower($aPlace['addresstype']);
-
- $aFilteredPlaces['name'] = $aPlace['placename'];
- }
-
- $aFilteredPlaces['display_name'] = $aPlace['langaddress'];
-
- if (isset($aPlace['address'])) {
- $aFilteredPlaces['address'] = $aPlace['address']->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);
+++ /dev/null
-<?php
-/**
- * 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.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\n";
-
-echo '<reversegeocode';
-echo " timestamp='".date(DATE_RFC822)."'";
-echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
-echo " querystring='".htmlspecialchars($_SERVER['QUERY_STRING'], ENT_QUOTES)."'";
-echo ">\n";
-
-if (empty($aPlace)) {
- if (isset($sError)) {
- echo "<error>$sError</error>";
- } else {
- echo '<error>Unable to geocode</error>';
- }
-} else {
- echo '<result';
- if ($aPlace['place_id']) {
- echo ' place_id="'.$aPlace['place_id'].'"';
- }
- $sOSMType = formatOSMType($aPlace['osm_type']);
- if ($sOSMType) {
- echo ' osm_type="'.$sOSMType.'"'.' osm_id="'.$aPlace['osm_id'].'"';
- }
- if ($aPlace['ref']) {
- echo ' ref="'.htmlspecialchars($aPlace['ref']).'"';
- }
- if (isset($aPlace['lat'])) {
- echo ' lat="'.htmlspecialchars($aPlace['lat']).'"';
- }
- if (isset($aPlace['lon'])) {
- echo ' lon="'.htmlspecialchars($aPlace['lon']).'"';
- }
- if (isset($aPlace['aBoundingBox'])) {
- echo ' boundingbox="';
- echo join(',', $aPlace['aBoundingBox']);
- echo '"';
- }
- echo " place_rank='".$aPlace['rank_search']."'";
- echo " address_rank='".$aPlace['rank_address']."'";
-
-
- if (isset($aPlace['asgeojson'])) {
- echo ' geojson=\'';
- echo $aPlace['asgeojson'];
- echo '\'';
- }
-
- if (isset($aPlace['assvg'])) {
- echo ' geosvg=\'';
- echo $aPlace['assvg'];
- echo '\'';
- }
-
- if (isset($aPlace['astext'])) {
- echo ' geotext=\'';
- echo $aPlace['astext'];
- echo '\'';
- }
- echo '>'.htmlspecialchars($aPlace['langaddress']).'</result>';
-
- if (isset($aPlace['address'])) {
- echo '<addressparts>';
- foreach ($aPlace['address']->getAddressNames() as $sKey => $sValue) {
- $sKey = str_replace(' ', '_', $sKey);
- echo "<$sKey>";
- echo htmlspecialchars($sValue);
- echo "</$sKey>";
- }
- echo '</addressparts>';
- }
-
- if (isset($aPlace['sExtraTags'])) {
- echo '<extratags>';
- foreach ($aPlace['sExtraTags'] as $sKey => $sValue) {
- echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
- }
- echo '</extratags>';
- }
-
- if (isset($aPlace['sNameDetails'])) {
- echo '<namedetails>';
- foreach ($aPlace['sNameDetails'] as $sKey => $sValue) {
- echo '<name desc="'.htmlspecialchars($sKey).'">';
- echo htmlspecialchars($sValue);
- echo '</name>';
- }
- echo '</namedetails>';
- }
-
- if (isset($aPlace['askml'])) {
- echo "\n<geokml>";
- echo $aPlace['askml'];
- echo '</geokml>';
- }
-}
-
-echo '</reversegeocode>';
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aPlaceDetails = array();
-
-$aPlaceDetails['place_id'] = (int) $aPointDetails['place_id'];
-$aPlaceDetails['parent_place_id'] = (int) $aPointDetails['parent_place_id'];
-
-$aPlaceDetails['osm_type'] = $aPointDetails['osm_type'];
-$aPlaceDetails['osm_id'] = (int) $aPointDetails['osm_id'];
-
-$aPlaceDetails['category'] = $aPointDetails['class'];
-$aPlaceDetails['type'] = $aPointDetails['type'];
-$aPlaceDetails['admin_level'] = $aPointDetails['admin_level'];
-
-$aPlaceDetails['localname'] = $aPointDetails['localname'];
-$aPlaceDetails['names'] = $aPointDetails['aNames'];
-
-$aPlaceDetails['addresstags'] = $aPointDetails['aAddressTags'];
-$aPlaceDetails['housenumber'] = $aPointDetails['housenumber'];
-$aPlaceDetails['calculated_postcode'] = $aPointDetails['postcode'];
-$aPlaceDetails['country_code'] = $aPointDetails['country_code'];
-
-$aPlaceDetails['indexed_date'] = (new DateTime('@'.$aPointDetails['indexed_epoch']))->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);
+++ /dev/null
-<?php
-/**
- * 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.
- */
- $error = array(
- 'code' => $exception->getCode(),
- 'message' => $exception->getMessage()
- );
-
- if (CONST_Debug) {
- $error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
- }
-
- javascript_renderData(array('error' => $error));
+++ /dev/null
-<error>
- <code><?php echo $exception->getCode() ?></code>
- <message><?php echo $exception->getMessage() ?></message>
- <?php if (CONST_Debug) { ?>
- <details><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></details>
- <?php } ?>
-</error>
\ No newline at end of file
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aOutput = array();
-$aOutput['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
-$aOutput['batch'] = array();
-
-foreach ($aBatchResults as $aSearchResults) {
- if (!$aSearchResults) {
- $aSearchResults = array();
- }
- $aFilteredPlaces = array();
- foreach ($aSearchResults as $iResNum => $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'));
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $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
- ));
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $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
- ));
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $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);
+++ /dev/null
-<?php
-/**
- * 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.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\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 "<place place_id='".$aResult['place_id']."'";
- $sOSMType = formatOSMType($aResult['osm_type']);
- if ($sOSMType) {
- echo " osm_type='$sOSMType'";
- echo " osm_id='".$aResult['osm_id']."'";
- }
- echo " place_rank='".$aResult['rank_search']."'";
- echo " address_rank='".$aResult['rank_address']."'";
-
- if (isset($aResult['aBoundingBox'])) {
- echo ' boundingbox="';
- echo join(',', $aResult['aBoundingBox']);
- echo '"';
- }
-
- if (isset($aResult['asgeojson'])) {
- echo ' geojson=\'';
- echo $aResult['asgeojson'];
- echo '\'';
- }
-
- if (isset($aResult['assvg'])) {
- echo ' geosvg=\'';
- echo $aResult['assvg'];
- echo '\'';
- }
-
- if (isset($aResult['astext'])) {
- echo ' geotext=\'';
- echo $aResult['astext'];
- echo '\'';
- }
-
- if (isset($aResult['zoom'])) {
- echo " zoom='".$aResult['zoom']."'";
- }
-
- echo " lat='".$aResult['lat']."'";
- echo " lon='".$aResult['lon']."'";
- echo " display_name='".htmlspecialchars($aResult['name'], ENT_QUOTES)."'";
-
- echo " class='".htmlspecialchars($aResult['class'])."'";
- echo " type='".htmlspecialchars($aResult['type'], ENT_QUOTES)."'";
- echo " importance='".htmlspecialchars($aResult['importance'])."'";
- if (isset($aResult['icon']) && $aResult['icon']) {
- echo " icon='".htmlspecialchars($aResult['icon'], ENT_QUOTES)."'";
- }
-
- $bHasDelim = false;
-
- if (isset($aResult['askml'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<geokml>";
- echo $aResult['askml'];
- echo '</geokml>';
- }
-
- if (isset($aResult['sExtraTags'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<extratags>";
- foreach ($aResult['sExtraTags'] as $sKey => $sValue) {
- echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
- }
- echo '</extratags>';
- }
-
- if (isset($aResult['sNameDetails'])) {
- if (!$bHasDelim) {
- $bHasDelim = true;
- echo '>';
- }
- echo "\n<namedetails>";
- foreach ($aResult['sNameDetails'] as $sKey => $sValue) {
- echo '<name desc="'.htmlspecialchars($sKey).'">';
- echo htmlspecialchars($sValue);
- echo '</name>';
- }
- echo '</namedetails>';
- }
-
- 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 "</$sKey>";
- }
- }
-
- if ($bHasDelim) {
- echo '</place>';
- } else {
- echo '/>';
- }
-}
-
-echo '</' . (isset($sXmlRootTag)?$sXmlRootTag:'searchresults') . '>';
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
- private $oDB;
-
- private $oNormalizer;
- private $oTransliterator;
-
- public function __construct(&$oDB)
- {
- $this->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;
- }
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
- private $oDB;
-
- private $oNormalizer = null;
-
- public function __construct(&$oDB)
- {
- $this->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);
- }
- }
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->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);
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-require_once(CONST_LibDir.'/AddressDetails.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-$sOutputFormat = $oParams->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');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->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');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->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);
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->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);
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->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');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/Geocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->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');
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-require_once(CONST_LibDir.'/Status.php');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->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();
- }
-}
- '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'
"""
from pathlib import Path
-PHPLIB_DIR = None
DATA_DIR = None
SQLLIB_DIR = None
CONFIG_DIR = (Path(__file__) / '..' / 'resources' / 'settings').resolve()
"""
from pathlib import Path
-PHPLIB_DIR = None
DATA_DIR = (Path(__file__) / '..' / 'resources').resolve()
SQLLIB_DIR = (DATA_DIR / 'lib-sql')
CONFIG_DIR = (DATA_DIR / 'settings')
# 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
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
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
"""
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
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'
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:
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
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
class _LibDirs:
module: Path
osm2pgsql: Path
- php = paths.PHPLIB_DIR
sql = paths.SQLLIB_DIR
data = paths.DATA_DIR
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
import itertools
import logging
from pathlib import Path
-from textwrap import dedent
from psycopg.types.json import Jsonb
from psycopg import sql as pysql
"""
self.loader = ICURuleLoader(config)
- self._install_php(config.lib_dir.php, overwrite=True)
self._save_config()
if init_db:
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
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"""\
- <?php
- @define('CONST_Max_Word_Frequency', 10000000);
- @define('CONST_Term_Normalization_Rules', "{self.loader.normalization_rules}");
- @define('CONST_Transliteration', "{self.loader.get_search_rules()}");
- require_once('{phpdir}/tokenizer/icu_tokenizer.php');"""), encoding='utf-8')
-
-
def _save_config(self) -> None:
""" Save the configuration that needs to remain stable for the given
database as database properties.
from pathlib import Path
import re
import shutil
-from textwrap import dedent
from icu import Transliterator
import psycopg
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)
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.
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"""\
- <?php
- @define('CONST_Max_Word_Frequency', {config.MAX_WORD_FREQUENCY});
- @define('CONST_Term_Normalization_Rules', "{config.TERM_NORMALIZATION}");
- require_once('{config.lib_dir.php}/tokenizer/legacy_tokenizer.php');
- """), encoding='utf-8')
-
-
def _init_db_tables(self, config: Configuration) -> None:
""" Set up the word table and fill it with pre-computed word
frequencies.
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.
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()
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
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"""\
- <?php
-
- @define('CONST_Debug', $_GET['debug'] ?? false);
- @define('CONST_LibDir', '{config.lib_dir.php}');
- @define('CONST_TokenizerDir', '{config.project_dir / 'tokenizer'}');
- @define('CONST_NominatimVersion', '{NOMINATIM_VERSION!s}');
-
- """)
-
- for php_name, conf_name, var_type in PHP_CONST_DEFS:
- varout = _quote_php_variable(var_type, config, conf_name)
-
- basedata += f"@define('CONST_{php_name}', {varout});\n"
-
- template = "\nrequire_once(CONST_LibDir.'/website/{}');\n"
-
- search_name_table_exists = bool(conn and table_exists(conn, 'search_name'))
-
- for script in WEBSITE_SCRIPTS:
- if not search_name_table_exists and script == 'search.php':
- out = template.format('reverse-only-search.php')
- else:
- out = template.format(script)
-
- (basedir / script).write_text(basedata + out, 'utf-8')
-
-
def invalidate_osm_object(osm_type: str, osm_id: int, conn: Connection,
recursive: bool = True) -> None:
""" Mark the given OSM object for reindexing. When 'recursive' is set
-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
| 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
| 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
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
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 <feature>
Then the result is valid html
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
| 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 |
| 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 |
| 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 |
| 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 |
| 13 | waterway | river |
- @v1-api-python-only
Scenario Outline: Reverse search with manmade layers
When sending v1/reverse at 32.46904,-86.44439
| layer |
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 ,
| foo; evil |
- @v1-api-python-only
Scenario Outline: Reverse debug mode produces valid HTML
When sending v1/reverse at , with format debug
| lat | lon |
| 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
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
| 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
| 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"
@fail-legacy
- @v1-api-python-only
Scenario: Postcode areas are preferred over postcode points
Given the grid with origin DE
| 1 | 2 |
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 |
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.")
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.")
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)
-# 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
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' : '<address>Nominatim BDD Tests</address>',
- '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:
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'):
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/AddressDetails.php');
-
-
-class AddressDetailsTest extends \PHPUnit\Framework\TestCase
-{
-
- protected function setUp(): void
- {
- // How the fixture got created
- //
- // 1) search for '10 downing street'
- // https://nominatim.openstreetmap.org/details.php?osmtype=R&osmid=1879842
- //
- // 2) find place_id in the local database
- // SELECT place_id, name FROM placex WHERE osm_type='R' AND osm_id=1879842;
- //
- // 3) set postgresql to non-align output, e.g. psql -A or \a in the CLI
- //
- // 4) query
- // SELECT row_to_json(row,true) FROM (
- // SELECT *, get_name_by_language(name, ARRAY['name:en']) as localname
- // FROM get_addressdata(194663412,10)
- // ORDER BY rank_address DESC, isaddress DESC
- // ) AS row;
- //
- // 5) copy&paste into file. Add commas between records
- //
- $json = file_get_contents(CONST_DataDir.'/test/php/fixtures/address_details_10_downing_street.json');
- $data = json_decode($json, true);
-
- $this->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()));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-class ClassTypesTest extends \PHPUnit\Framework\TestCase
-{
- public function testGetLabelTag()
- {
- $aPlace = array('class' => '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));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/DB.php');
-
-// subclassing so we can set the protected connection variable
-class NominatimSubClassedDB extends \Nominatim\DB
-{
- public function setConnection($oConnection)
- {
- $this->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')
- );
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/DatabaseError.php');
-
-class DatabaseErrorTest extends \PHPUnit\Framework\TestCase
-{
-
- public function testSqlMessage()
- {
- $oSqlStub = $this->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());
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/DebugHtml.php');
-
-class DebugTest extends \PHPUnit\Framework\TestCase
-{
-
- protected function setUp(): void
- {
- $this->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(<<<EOT
-<pre><b>Var0:</b> </pre>
-<pre><b>Var1:</b> <i>True</i></pre>
-<pre><b>Var2:</b> <i>False</i></pre>
-<pre><b>Var3:</b> 0</pre>
-<pre><b>Var4:</b> 'String'</pre>
-<pre><b>Var5:</b> 0 => 'one'
- 1 => 'two'
- 2 => 'three'</pre>
-<pre><b>Var6:</b> 'key' => 'value'
- 'key2' => 'value2'</pre>
-<pre><b>Var7:</b> me as string</pre>
-<pre><b>Var8:</b> 'value', 'value2'</pre>
-
-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(<<<EOT
-<pre><b>Arr0:</b> 'null'</pre>
-<pre><b>Arr1:</b> 'key1' => 'val1'
- 'key2' => 'val2'
- 'key3' => 'val3'</pre>
-
-EOT
- );
-
- Debug::printDebugArray('Arr0', null);
- Debug::printDebugArray('Arr1', $this->oWithDebuginfo);
- }
-
-
- public function testPrintDebugTable()
- {
- $this->expectOutputString(<<<EOT
-<b>Table1:</b>
-<table border='1'>
-</table>
-<b>Table2:</b>
-<table border='1'>
-</table>
-<b>Table3:</b>
-<table border='1'>
- <tr>
- <th><small>0</small></th>
- <th><small>1</small></th>
- </tr>
- <tr>
- <td><pre>'one'</pre></td>
- <td><pre>'two'</pre></td>
- </tr>
- <tr>
- <td><pre>'three'</pre></td>
- <td><pre>'four'</pre></td>
- </tr>
-</table>
-<b>Table4:</b>
-<table border='1'>
- <tr>
- <th><small>key1</small></th>
- <th><small>key2</small></th>
- <th><small>key3</small></th>
- </tr>
- <tr>
- <td><pre>'val1'</pre></td>
- <td><pre>'val2'</pre></td>
- <td><pre>'val3'</pre></td>
- </tr>
-</table>
-
-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(<<<EOT
-<b>Table1:</b>
-<table border='1'>
-</table>
-<b>Table2:</b>
-<table border='1'>
-</table>
-<b>Table3:</b>
-<table border='1'>
- <tr>
- <th><small>Group</small></th>
- <th><small>key1</small></th>
- <th><small>key2</small></th>
- </tr>
- <tr>
- <td><pre>group1</pre></td>
- <td><pre>'val1'</pre></td>
- <td><pre>'val2'</pre></td>
- </tr>
- <tr>
- <td><pre>group1</pre></td>
- <td><pre>'one'</pre></td>
- <td><pre>'two'</pre></td>
- </tr>
- <tr>
- <td><pre>group2</pre></td>
- <td><pre>'val1'</pre></td>
- <td><pre>'val2'</pre></td>
- </tr>
-</table>
-<b>Table4:</b>
-<table border='1'>
- <tr>
- <th><small>Group</small></th>
- <th><small>key1</small></th>
- <th><small>key2</small></th>
- <th><small>key3</small></th>
- </tr>
- <tr>
- <td><pre>group1</pre></td>
- <td><pre>'val1'</pre></td>
- <td><pre>'val2'</pre></td>
- <td><pre>'val3'</pre></td>
- </tr>
- <tr>
- <td><pre>group1</pre></td>
- <td><pre>'val1'</pre></td>
- <td><pre>'val2'</pre></td>
- <td><pre>'val3'</pre></td>
- </tr>
-</table>
-
-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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-class LibTest extends \PHPUnit\Framework\TestCase
-{
-
- public function testAddQuotes()
- {
- // FIXME: not quoting existing quote signs is probably a bug
- $this->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,\v-79.982]',
- ' 40.446 , -79.982 ',
- ' 40.446 , -79.982 ',
- ' 40.446 , -79.982 ',
- ' 40.446\v, -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]);
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ParameterParser.php');
-
-
-function userError($sError)
-{
- throw new \Exception($sError);
-}
-
-class ParameterParserTest extends \PHPUnit\Framework\TestCase
-{
-
-
- public function testGetBool()
- {
- $oParams = new ParameterParser(array(
- 'bool1' => '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')));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Result.php');
-
-function mkRankedResult($iId, $iResultRank)
-{
- $oResult = new Result($iId);
- $oResult->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));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SearchContext.php');
-
-class SearchContextTest extends \PHPUnit\Framework\TestCase
-{
- private $oCtx;
-
-
- protected function setUp(): void
- {
- $this->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
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Shell.php');
-
-class ShellTest extends \PHPUnit\Framework\TestCase
-{
- public function testNew()
- {
- $this->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);
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class TokensFullSet
-{
- public function containsAny($sTerm)
- {
- return true;
- }
-}
-
-// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
-class TokensPartialSet
-{
- public function __construct($aTokens)
- {
- $this->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')))));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-@define('CONST_TokenizerDir', dirname(__FILE__));
-
-require_once(CONST_LibDir.'/DB.php');
-require_once(CONST_LibDir.'/Status.php');
-
-
-class StatusTest extends \PHPUnit\Framework\TestCase
-{
-
- public function testNoDatabaseGiven()
- {
- $this->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());
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/TokenList.php');
-
-
-class TokenListTest extends \PHPUnit\Framework\TestCase
-{
- protected function setUp(): void
- {
- $this->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'));
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
-
-namespace Nominatim;
-
-class Tokenizer
-{
- private $oDB;
-
- public function __construct(&$oDB)
- {
- $this->oDB =& $oDB;
- }
-
- public function checkStatus()
- {
- }
-}
+++ /dev/null
-<?php
-/**
- * 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.
- */
- @define('CONST_LibDir', '../../lib-php');
- @define('CONST_DataDir', '../..');
-
- @define('CONST_Debug', true);
- @define('CONST_NoAccessControl', false);
+++ /dev/null
-[{"place_id":194663412,
- "osm_type":null,
- "osm_id":null,
- "name":{"name": "10 Downing Street", "name:en": "10 Downing Street", "name:es": "10 de Downing Street", "name:he": "דאונינג 10", "name:ko": "다우닝 가 10번지", "name:zh": "唐寧街10號"},
- "class":"tourism",
- "type":"attraction",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":29,
- "distance":0,
- "localname":"10 Downing Street"},
-{"place_id":194663412,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "10"},
- "class":"place",
- "type":"house_number",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":28,
- "distance":0,
- "localname":"10"},
-{"place_id":68310941,
- "osm_type":"W",
- "osm_id":4244999,
- "name":{"name": "Downing Street"},
- "class":"highway",
- "type":"residential",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":26,
- "distance":0,
- "localname":"Downing Street"},
-{"place_id":16037318,
- "osm_type":"N",
- "osm_id":1653239257,
- "name":{"name": "St. James's"},
- "class":"place",
- "type":"neighbourhood",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":22,
- "distance":0.00982435489434447,
- "localname":"St. James's"},
-{"place_id":51691981,
- "osm_type":"N",
- "osm_id":3937587633,
- "name":{"name": "St Clement Danes"},
- "class":"place",
- "type":"neighbourhood",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":22,
- "distance":0.0128768181947227,
- "localname":"St Clement Danes"},
-{"place_id":22208313,
- "osm_type":"N",
- "osm_id":2290086954,
- "name":{"name": "Covent Garden"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":20,
- "distance":0.00935748249317067,
- "localname":"Covent Garden"},
-{"place_id":21742712,
- "osm_type":"N",
- "osm_id":2288030397,
- "name":{"name": "Millbank"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0106525181285902,
- "localname":"Millbank"},
-{"place_id":122775,
- "osm_type":"N",
- "osm_id":26745371,
- "name":{"name": "St Giles"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0136188357358441,
- "localname":"St Giles"},
-{"place_id":134882,
- "osm_type":"N",
- "osm_id":27553719,
- "name":{"name": "Lambeth"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0093308163978298,
- "localname":"Lambeth"},
-{"place_id":194276676,
- "osm_type":"R",
- "osm_id":51781,
- "name":{"name": "City of Westminster", "name:be": "Вэстмінстэр", "name:cy": "San Steffan", "name:en": "Westminster", "name:he": "וסטמינסטר", "name:ru": "Вестминстер"},
- "class":"place",
- "type":"city",
- "admin_level":8,
- "fromarea":true,
- "isaddress":true,
- "rank_address":16,
- "distance":0.0340909562148044,
- "localname":"Westminster"},
-{"place_id":195398522,
- "osm_type":"N",
- "osm_id":107775,
- "name":{"name": "London", "name:ab": "Лондан", "name:af": "Londen", "name:am": "ለንደን", "name:an": "Londres", "name:ar": "لندن", "name:ba": "Лондон", "name:be": "Лондан", "name:bg": "Лондон", "name:bn": "লন্ডন", "name:bo": "ལོན་ཊོན།", "name:br": "Londrez", "name:ca": "Londres", "name:co": "Londra", "name:cs": "Londýn", "name:cu": "Лондонъ", "name:cv": "Лондон", "name:cy": "Llundain", "name:de": "London", "name:el": "Λονδίνο", "name:en": "London", "name:eo": "Londono", "name:es": "Londres", "name:eu": "Londres", "name:fa": "لندن", "name:fi": "Lontoo", "name:fr": "Londres", "name:fy": "Londen", "name:ga": "Londain", "name:gd": "Lunnainn", "name:gl": "Londres - London", "name:gn": "Londye", "name:gu": "લંડન", "name:gv": "Lunnin", "name:he": "לונדון", "name:hi": "लंदन", "name:ht": "Lonn", "name:hu": "London", "name:hy": "Լոնդոն", "name:is": "Lundúnir", "name:it": "Londra", "name:ja": "ロンドン", "name:ka": "ლონდონი", "name:kk": "Лондон", "name:kn": "ಲಂಡನ್", "name:ko": "런던", "name:ku": "London", "name:kv": "Лондон", "name:kw": "Loundres", "name:ky": "Лондон", "name:la": "Londinium", "name:li": "Londe", "name:ln": "Londoni", "name:lo": "ລອນດອນ", "name:lt": "Londonas", "name:lv": "Londona", "name:mi": "Rānana", "name:mk": "Лондон", "name:ml": "ലണ്ടൻ", "name:mn": "Лондон", "name:mr": "लंडन", "name:mt": "Londra", "name:my": "လန်ဒန်မြို့", "name:ne": "लण्डन", "name:nl": "Londen", "name:no": "London", "name:oc": "Londres", "name:or": "ଲଣ୍ଡନ", "name:os": "Лондон", "name:pl": "Londyn", "name:ps": "لندن", "name:pt": "Londres", "name:rm": "Londra", "name:ro": "Londra", "name:ru": "Лондон", "name:sa": "लन्डन्", "name:sc": "Londra", "name:si": "ලන්ඩන්", "name:sk": "Londýn", "name:sq": "Londra", "name:sr": "Лондон", "name:sv": "London", "name:ta": "இலண்டன்", "name:te": "లండన్", "name:tg": "Лондон", "name:th": "ลอนดอน", "name:tl": "Londres", "name:tr": "Londra", "name:tt": "Лондон", "name:uk": "Лондон", "name:ur": "لندن", "name:vi": "Luân Đôn", "name:wo": "Londar", "name:yi": "לאנדאן", "name:yo": "Lọndọnu", "name:zh": "倫敦", "name:zu": "ILondon", "name:ang": "Lunden", "name:arc": "ܠܘܢܕܘܢ", "name:arz": "لندن", "name:ast": "Londres", "name:bcl": "Londres", "name:cdo": "Lùng-dŭng", "name:ckb": "لەندەن", "name:diq": "Londra", "name:eml": "Lòndra", "name:ext": "Londri", "name:frp": "Londres", "name:gan": "倫敦", "name:haw": "Lākana", "name:ilo": "Londres", "name:jbo": "london", "name:koi": "Лондон", "name:krc": "Лондон", "name:lad": "Londra", "name:lbe": "Лондон", "name:lez": "Лондон", "name:lij": "Londra", "name:lmo": "Lundra", "name:mhr": "Лондон", "name:mrj": "Лондон", "name:mwl": "Londres", "name:mzn": "لندن", "name:nah": "Londres", "name:nap": "Londra", "name:new": "लण्डन", "name:nrm": "Londres", "name:pcd": "Londe", "name:pms": "Londra", "name:pnb": "لندن", "name:pnt": "Λονδίνο", "name:rue": "Лондон", "name:sah": "Лондон", "name:scn": "Londra", "name:sco": "Lunnon", "name:szl": "Lůndůn", "name:tet": "Londres", "name:tpi": "Landen", "name:tzl": "Londra", "name:udm": "Лондон", "name:vec": "Łondra", "name:vls": "Londn", "name:wuu": "伦敦", "name:xmf": "ლონდონი", "name:yue": "倫敦", "name:zea": "Londen", "name:nds-nl": "Londen", "name:bat-smg": "Londons", "name:roa-rup": "Londra", "name:roa-tara": "Londre", "name:be-tarask": "Лёндан", "name:zh_pinyin": "Lúndūn", "name:zh-classical": "倫敦", "name:zh-simplified": "伦敦", "name:zh-traditional": "倫敦"},
- "class":"place",
- "type":"city",
- "admin_level":2,
- "fromarea":true,
- "isaddress":false,
- "rank_address":16,
- "distance":0.00412384196971048,
- "localname":"London"},
-{"place_id":193774423,
- "osm_type":"R",
- "osm_id":65606,
- "name":{"name": "London", "ISO3166-2": "GB-LND", "name:be": "Лондан", "name:ca": "Londres", "name:el": "Λονδίνο", "name:en": "London", "name:eo": "Londono", "name:es": "Londres", "name:fa": "لندن", "name:fi": "Lontoo", "name:fr": "Londres", "name:fy": "Londen", "name:gl": "Londres", "name:hi": "लंदन", "name:lt": "Londonas", "name:nl": "Londen", "name:pl": "Londyn", "name:pt": "Londres", "name:ru": "Лондон", "name:uk": "Лондон", "name:vi": "Luân Đôn", "name:zh": "伦敦", "int_name": "London", "name:szl": "Lůndůn", "name:tzl": "Londra", "name:be-tarask": "Лёндан"},
- "class":"place",
- "type":"city",
- "admin_level":6,
- "fromarea":true,
- "isaddress":true,
- "rank_address":12,
- "distance":0.0172243361058611,
- "localname":"London"},
-{"place_id":194000080,
- "osm_type":"R",
- "osm_id":175342,
- "name":{"name": "Greater London", "name:be": "Вялікі Лондан", "name:de": "Groß-London", "name:en": "Greater London", "name:fr": "Grand Londres", "name:lt": "Didysis Londonas", "name:ru": "Большой Лондон", "name:uk": "Великий Лондон", "official_name": "Greater London (incl. City of London)", "name:be-tarask": "Вялікі Лёндан"},
- "class":"boundary",
- "type":"administrative",
- "admin_level":5,
- "fromarea":true,
- "isaddress":true,
- "rank_address":10,
- "distance":0.0172532381571105,
- "localname":"Greater London"},
-{"place_id":194325361,
- "osm_type":"R",
- "osm_id":58447,
- "name":{"ref": "ENG", "ISO3166-2": "GB-ENG", "name": "England", "name:be": "Англія", "name:br": "Bro-Saoz", "name:ca": "Anglaterra", "name:cs": "Anglie", "name:cy": "Lloegr", "name:de": "England", "name:el": "Αγγλία", "name:en": "England", "name:eo": "Anglujo", "name:es": "Inglaterra", "name:fi": "Englanti", "name:fr": "Angleterre", "name:fy": "Ingelân", "name:ga": "Sasana", "name:gd": "Sasainn", "name:gv": "Sostyn", "name:he": "אנגליה", "name:hu": "Anglia", "name:ia": "Anglaterra", "name:io": "Anglia", "name:it": "Inghilterra", "name:la": "Anglia", "name:lt": "Anglija", "name:nl": "Engeland", "name:pl": "Anglia", "name:pt": "Inglaterra", "name:ru": "Англия", "name:sk": "Anglicko", "name:sv": "England", "name:tr": "İngiltere", "name:uk": "Англія", "name:vi": "Anh", "name:vo": "Linglän", "name:zh": "英格蘭", "name:hsb": "Jendźelska", "name:nds": "England", "name:tok": "ma Inli", "name:tzl": "Anglatzara", "alt_name:eo": "Anglio", "alt_name:ia": "Anglia", "old_name:vi": "Anh Quốc", "alt_name:nds": "Ingland", "name:be-tarask": "Ангельшчына", "name:zh-classical": "英格蘭", "name:zh-simplified": "英格兰", "name:zh-traditional": "英格蘭"},
- "class":"boundary",
- "type":"administrative",
- "admin_level":4,
- "fromarea":true,
- "isaddress":true,
- "rank_address":8,
- "distance":1.75192967136328,
- "localname":"England"},
-{"place_id":null,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "SW1A 2AA"},
- "class":"place",
- "type":"postcode",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":5,
- "distance":0,
- "localname":"SW1A 2AA"},
-{"place_id":40715006,
- "osm_type":"N",
- "osm_id":3055075992,
- "name":{"ref": "SW1A 2AQ"},
- "class":"place",
- "type":"postcode",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":5,
- "distance":0.00172905579146705,
- "localname":"SW1A 2AQ"},
-{"place_id":194354400,
- "osm_type":"R",
- "osm_id":62149,
- "name":{"name": "United Kingdom", "name:ab": "Британиа Ду", "name:af": "Verenigde Koninkryk", "name:ak": "United Kingdom", "name:am": "ዩናይትድ ኪንግደም", "name:an": "Reino Unito", "name:ar": "المملكة المتحدة", "name:az": "Böyük Britaniya", "name:ba": "Бөйөк Британия", "name:be": "Вялікабрытанія", "name:bg": "Обединено кралство Великобритания и Северна Ирландия", "name:bi": "Unaeted Kingdom", "name:bm": "Angilɛtɛri", "name:bn": "যুক্তরাজ্য", "name:bo": "དབྱིན་ཇི་མཉམ་འབྲེལ།", "name:br": "Rouantelezh-Unanet", "name:bs": "Ujedinjeno Kraljevstvo Velike Britanije i Sjeverne Irske", "name:ca": "Regne Unit", "name:ce": "Йоккха Британи", "name:co": "Regnu Unitu", "name:cs": "Spojené království", "name:cu": "Вєлика Британїꙗ", "name:cv": "Аслă Британи", "name:cy": "Deyrnas Unedig", "name:da": "Storbritannien", "name:de": "Vereinigtes Königreich", "name:dv": "ޔުނައިޓެޑް ކިންގްޑަމް", "name:dz": "ཡུ་ནའི་ཊེཊ་ཀིང་ཌམ", "name:ee": "United Kingdom", "name:el": "Ηνωμένο Βασίλειο", "name:en": "United Kingdom", "name:eo": "Britujo", "name:es": "Reino Unido", "name:et": "Suurbritannia", "name:eu": "Erresuma Batua", "name:fa": "بریتانیا", "name:ff": "Laamateeri Rentundi", "name:fi": "Yhdistynyt kuningaskunta", "name:fo": "Stóra Bretland", "name:fr": "Royaume-Uni", "name:fy": "Feriene Keninkryk", "name:ga": "An Ríocht Aontaithe", "name:gd": "An Rìoghachd Aonaichte", "name:gl": "Reino Unido", "name:gn": "Tavetã Joaju", "name:gu": "યુનાઇટેડ કિંગડમ", "name:gv": "Reeriaght Unnaneysit", "name:ha": "Birtaniya", "name:he": "הממלכה המאוחדת", "name:hi": "यूनाइटेड किंगडम", "name:hr": "Ujedinjeno Kraljevstvo", "name:ht": "Wayòm Ini", "name:hu": "Egyesült Királyság", "name:hy": "Միացյալ Թագավորություն", "name:ia": "Regno Unite", "name:id": "Britania Raya", "name:ie": "Reyatu Unit", "name:ig": "Obodoézè Nà Ofú", "name:ii": "ꑱꇩ", "name:io": "Unionita Rejio", "name:is": "Bretland", "name:it": "Regno Unito", "name:ja": "イギリス", "name:jv": "Britania Raya", "name:ka": "გაერთიანებული სამეფო", "name:kg": "Royaume-Uni", "name:ki": "Ngeretha", "name:kk": "Ұлыбритания", "name:kl": "Tuluit Nunaat", "name:km": "រាជាណាចក្ររួម", "name:kn": "ಯುನೈಟೆಡ್ ಕಿಂಗ್ಡಂ", "name:ko": "영국", "name:ks": "یُنایٹِڑ کِنٛگڈَم", "name:ku": "Keyaniya Yekbûyî", "name:kv": "Ыджыд Британия", "name:kw": "Ruwvaneth Unys", "name:ky": "Улуу Британия жана Түндүк Ирландия", "name:la": "Britanniarum Regnum", "name:lb": "Groussbritannien an Nordirland", "name:lg": "Bungereza", "name:li": "Vereineg Keuninkriek", "name:ln": "Ingɛlɛ́tɛlɛ", "name:lo": "ສະຫະລາດຊະອານາຈັກ", "name:lt": "Jungtinė Karalystė", "name:lv": "Apvienotā Karaliste", "name:mg": "Fanjakana Mitambatra", "name:mi": "Kīngitanga Kotahi", "name:mk": "Обединето Кралство", "name:ml": "യുണൈറ്റഡ് കിങ്ഡം", "name:mn": "Их Британи", "name:mr": "युनायटेड किंग्डम", "name:ms": "United Kingdom", "name:mt": "Renju Unit", "name:my": "ယူနိုက်တက်ကင်းဒမ်းနိုင်ငံ", "name:na": "Ingerand", "name:ne": "संयुक्त अधिराज्य", "name:nl": "Verenigd Koninkrijk", "name:nn": "Storbritannia", "name:no": "Storbritannia", "name:nv": "Tótaʼ Dinéʼiʼ Bikéyah", "name:oc": "Reialme Unit", "name:or": "ଯୁକ୍ତରାଜ୍ୟ", "name:os": "Стыр Британи", "name:pa": "ਸੰਯੁਕਤ ਬਾਦਸ਼ਾਹੀ", "name:pl": "Wielka Brytania", "name:ps": "بريتانيا", "name:pt": "Reino Unido", "name:qu": "Hukllachasqa Qhapaq Suyu", "name:rm": "Reginavel Unì", "name:rn": "Ubwongereza", "name:ro": "Regatul Unit al Marii Britanii și al Irlandei de Nord", "name:ru": "Великобритания", "name:rw": "Ubwongereza", "name:sa": "संयुक्त अधिराज्य", "name:sc": "Rennu Auniadu", "name:se": "Ovttastuvvan gonagasriika", "name:sg": "Ködörögbïä--Ôko", "name:sh": "Ujedinjeno Kraljevstvo", "name:si": "එක්සත් රාජධානිය", "name:sk": "Spojené kráľovstvo", "name:sl": "Združeno kraljestvo Velike Britanije in Severne Irske", "name:sn": "United Kingdom", "name:so": "Midowga boqortooyada Britan", "name:sq": "Mbretëria e Bashkuar e Britanisë dhe Irlandës së Veriut", "name:sr": "Уједињено Краљевство", "name:ss": "United Kingdom", "name:su": "Britania", "name:sv": "Storbritannien", "name:sw": "Ufalme wa Muungano", "name:ta": "ஐக்கிய இராச்சியம்", "name:te": "యునైటెడ్ కింగ్డమ్", "name:tg": "Подшоҳии Муттаҳида", "name:th": "สหราชอาณาจักร", "name:ti": "እንግሊዝ", "name:tl": "Nagkakaisang Kaharian", "name:to": "Pilitānia", "name:tr": "Birleşik Krallık", "name:tt": "Бөекбритания", "name:tw": "United Kingdom", "name:ty": "Paratāne", "name:ug": "بۈيۈك بېرىتانىيە", "name:uk": "Велика Британія", "name:ur": "برطانیہ", "name:uz": "Birlashgan Qirollik", "name:vi": "Vương quốc Anh", "name:vo": "Regän Pebalöl", "name:wo": "Nguur-Yu-Bennoo", "name:yi": "פאראייניגטע קעניגרייך", "name:yo": "Ilẹ̀ọba Aṣọ̀kan", "name:za": "Yinghgoz", "name:zh": "英國", "name:zu": "Umbuso Ohlangeneyo", "alt_name": "United Kingdom; UK; Britain; Great Britain", "int_name": "United Kingdom", "name:als": "Vereinigtes Königreich", "name:ang": "Geāned Cynerīce", "name:arc": "ܡܠܟܘܬܐ ܡܚܝܕܬܐ", "name:arz": "المملكه المتحده", "name:ast": "Reinu Xuníu", "name:bar": "Vaeinigts Kinireich", "name:bcl": "Reyno Unido", "name:bjn": "Britania Raya", "name:bpy": "তিলপারাজ্য", "name:bug": "United Kingdom", "name:bxr": "Нэгдсэн Вант Улс", "name:cdo": "Ĭng-guók", "name:ceb": "Hiniusang Gingharian", "name:chr": "ᎡᎵᏏᎯ", "name:chy": "United Kingdom", "name:ckb": "شانشینی یەکگرتوو", "name:crh": "Büyük Britaniya", "name:csb": "Wiôlgô Britanijô", "name:diq": "Qraliya Yewbiyayiye", "name:dsb": "Wjelika Britaniska", "name:eml": "Régn Unî", "name:ext": "Réinu Uniu", "name:frp": "Royômo-Uni", "name:frr": "Feriind Kiningrik", "name:fur": "Ream Unît", "name:gag": "Büük Britaniya", "name:gan": "英國", "name:hak": "Yîn-koet", "name:haw": "Aupuni Mōʻī Hui Pū ʻia", "name:hif": "United Kingdom", "name:hsb": "Zjednoćene kralestwo", "name:ilo": "Nagkaykaysa a Pagarian", "name:jbo": "ritygu'e", "name:kab": "Legliz", "name:kbd": "Британиэшхуэ", "name:koi": "Ыджыт Бритму", "name:krc": "Уллу Британия", "name:ksh": "Jrußbritannie", "name:lad": "Reyno Unido", "name:lez": "ЧIехибритания", "name:lfn": "Rena Unida", "name:lij": "Regno Unïo", "name:lmo": "Regn Ünì", "name:ltg": "Lelbrytaneja", "name:mhr": "Ушымо Королевстве", "name:mrj": "Кого Британи", "name:mwl": "Reino Ounido", "name:mzn": "بریتانیا", "name:nah": "Tlacetilīlli Huēyitlahtohcāyōtl", "name:nap": "Gran Vretagna", "name:nds": "Vereenigt Königriek vun Grootbritannien un Noordirland", "name:nov": "Unionati Regia", "name:nrm": "Rouoyaume Unni", "name:pag": "Reino Unido", "name:pam": "Pisanmetung a Ka-arian", "name:pap": "Reino Uni", "name:pcd": "Roéyôme-Uni", "name:pih": "Yunitid Kingdum", "name:pms": "Regn Unì", "name:pnb": "برطانیہ", "name:pnt": "Ηνωμένο Βασίλειο", "name:rmy": "Phandlo Thagaripen la Bare Britaniyako thai le Nordutne Irlandesko", "name:rue": "Велика Брітанія", "name:sah": "Холбоhуктаах Хоруоллук", "name:scn": "Regnu Unitu", "name:sco": "Unitit Kinrick", "name:srn": "Ingriskondre", "name:stq": "Fereeniged Köönichriek fon Groot-Britannien un Noudirlound", "name:szl": "Wjelgo Brytańijo", "name:tet": "Reinu Naklibur", "name:tok": "ma Juke", "name:tpi": "Yunaitet Kingdom", "name:tzl": "Regipäts Viensiçat", "name:udm": "Великобритания", "name:vec": "Regno Unìo", "name:vep": "Sur' Britanii", "name:vls": "Verênigd Keunienkryk", "name:war": "Reino Unido", "name:wuu": "英国", "name:xal": "Ик Бритишин болн Ар Гәәлгүдин Ниицәтә Нутг", "name:xmf": "გოართოიანაფილი ომაფე", "name:yue": "英國", "name:zea": "Vereênigd Konienkriek", "name:zh_py": "Yingguo", "short_name": "UK", "alt_name:eo": "Britio", "alt_name:sr": "УК;У.К.", "alt_name:vi": "Vương quốc Liên hiệp Anh", "name:nds-nl": "Verienigd Keuninkriek", "name:zh_pyt": "Yīngguó", "name:bat-smg": "Jongtėnė Karalīstė", "name:cbk-zam": "Reinos Unidos de Gran Britania y Norte Irelandia", "name:fiu-vro": "Ütiskuningriik", "name:roa-rup": "Britania Mari", "name:roa-tara": "Regne Aunìte", "official_name": "United Kingdom of Great Britain and Northern Ireland", "short_name:el": "ΗΒ", "short_name:vo": "Britän", "name:be-tarask": "Вялікабрытанія", "name:zh-min-nan": "Liân-ha̍p Ông-kok", "official_name:be": "Злучанае Каралеўства Вялікабрытаніі і Паўночнай Ірландыі", "official_name:br": "Rouantelezh Unanet Breizh-Veur ha Norzhiwerzhon", "official_name:ca": "Regne Unit de Gran Bretanya i Irlanda del Nord", "official_name:cs": "Spojené království Velké Británie a Severního Irska", "official_name:de": "Vereinigtes Königreich Großbritannien und Nordirland", "official_name:el": "Ηνωμένο Βασίλειο της Μεγάλης Βρετανίας και της Βόρειας Ιρλανδίας", "official_name:en": "United Kingdom of Great Britain and Northern Ireland", "official_name:eo": "Unuiĝinta Reĝlando de Granda Britujo kaj Nord-Irlando", "official_name:es": "Reino Unido de Gran Bretaña", "official_name:et": "Suurbritannia ja Põhja-Iiri Ühendkuningriik", "official_name:fr": "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord", "official_name:hr": "Ujedinjeno Kraljevstvo Velike Britanije i Sjeverne Irske", "official_name:id": "Perserikatan Kerajaan Britania Raya dan Irlandia Utara", "official_name:it": "Regno Unito di Gran Bretagna e Irlanda del Nord", "official_name:ja": "グレートブリテン及び北アイルランド連合王国", "official_name:ku": "Keyaniya Yekbûyî ya Brîtaniya Mezin û Bakurê Îrlandê", "official_name:lb": "Vereenegt Kinnekräich vu Groussbritannien an Nordirland", "official_name:no": "Det forente kongeriket Storbritannia og Nord-Irland", "official_name:pl": "Zjednoczone Królestwo Wielkiej Brytanii i Irlandii Północnej", "official_name:pt": "Reino Unido da Grã-Bretanha e Irlanda do Norte", "official_name:ru": "Соединённое королевство Великобритании и Северной Ирландии", "official_name:sk": "Spojené kráľovstvo Veľkej Británie a Severného Írska", "official_name:sl": "Združeno kraljestvo Velike Britanije in Severne Irske", "official_name:sr": "Уједињено Краљевство Велике Британије и Северне Ирске", "official_name:sv": "Förenade konungariket Storbritannien och Nordirland", "official_name:vi": "Vương quốc Liên hiệp Anh và Bắc Ireland", "name:abbreviation": "UK", "name:zh-classical": "英國", "official_name:scn": "Regnu Unitu di Gran Britagna e Irlanna dû Nord", "name:zh-simplified": "英国", "name:zh-traditional": "英國"},
- "class":"place",
- "type":"country",
- "admin_level":2,
- "fromarea":true,
- "isaddress":true,
- "rank_address":4,
- "distance":4.56060933645498,
- "localname":"United Kingdom"},
-{"place_id":null,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "gb"},
- "class":"place",
- "type":"country_code",
- "admin_level":null,
- "fromarea":true,
- "isaddress":false,
- "rank_address":4,
- "distance":0,
- "localname":"gb"}
-]
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- backupGlobals="false"
- backupStaticAttributes="false"
- colors="true"
- convertErrorsToExceptions="true"
- convertNoticesToExceptions="true"
- convertWarningsToExceptions="true"
- processIsolation="false"
- stopOnFailure="false"
- bootstrap="./bootstrap.php"
- beStrictAboutTestsThatDoNotTestAnything="true"
- xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
- <coverage>
- <include>
- <directory>../../lib-php/</directory>
- </include>
- </coverage>
- <php>
- </php>
- <testsuites>
- <testsuite name="Nominatim PHP Test Suite">
- <directory>./Nominatim</directory>
- </testsuite>
- </testsuites>
-</phpunit>
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)
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__]
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')
]
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')
]
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')
]
('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')
+++ /dev/null
-# 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('<?php\n{}\n'.format(code), 'utf-8')
-
- return _create_file
-
-
-@pytest.fixture
-def run_website_script(tmp_path, project_env, temp_db_conn):
- project_env.lib_dir.php = tmp_path / 'php'
-
- def _runner():
- refresh.setup_website(tmp_path, project_env, temp_db_conn)
-
- proc = subprocess.run(['/usr/bin/env', 'php', '-Cq',
- tmp_path / 'search.php'], check=False)
-
- return proc.returncode
-
- return _runner
-
-
-def test_basedir_created(tmp_path, project_env, temp_db_conn):
- webdir = tmp_path / 'website'
-
- assert not webdir.exists()
-
- refresh.setup_website(webdir, project_env, temp_db_conn)
-
- assert webdir.exists()
-
-
-@pytest.mark.parametrize("setting,retval", (('yes', 10), ('no', 20)))
-def test_setup_website_check_bool(monkeypatch, test_script, run_website_script,
- setting, retval):
- monkeypatch.setenv('NOMINATIM_CORS_NOACCESSCONTROL', setting)
-
- test_script('exit(CONST_NoAccessControl ? 10 : 20);')
-
- assert run_website_script() == retval
-
-
-@pytest.mark.parametrize("setting", (0, 10, 99067))
-def test_setup_website_check_int(monkeypatch, test_script, run_website_script, setting):
- monkeypatch.setenv('NOMINATIM_LOOKUP_MAX_COUNT', str(setting))
-
- test_script('exit(CONST_Places_Max_ID_count == {} ? 10 : 20);'.format(setting))
-
- assert run_website_script() == 10
-
-
-def test_setup_website_check_empty_str(monkeypatch, test_script, run_website_script):
- monkeypatch.setenv('NOMINATIM_DEFAULT_LANGUAGE', '')
-
- test_script('exit(CONST_Default_Language === false ? 10 : 20);')
-
- assert run_website_script() == 10
-
-
-def test_setup_website_check_str(monkeypatch, test_script, run_website_script):
- monkeypatch.setenv('NOMINATIM_DEFAULT_LANGUAGE', 'ffde 2')
-
- test_script('exit(CONST_Default_Language === "ffde 2" ? 10 : 20);')
-
- assert run_website_script() == 10
-
-
-def test_relative_log_file(project_env, monkeypatch, test_script, run_website_script):
- monkeypatch.setenv('NOMINATIM_LOG_FILE', 'access.log')
-
- expected_file = str(project_env.project_dir / 'access.log')
- test_script(f'exit(CONST_Log_File === "{expected_file}" ? 10 : 20);')
-
- assert run_website_script() == 10
-
-def test_variable_with_bracket(project_env, monkeypatch, test_script, run_website_script):
- monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'pgsql:dbname=nominatim;user=foo;password=4{5')
-
- test_script('exit(CONST_Database_DSN === "pgsql:dbname=nominatim;user=foo;password=4{5" ? 10 : 20);')
-
- assert run_website_script() == 10
-