Merge pull request #3536 from lonvia/remove-php

Remove PHP frontend
This commit is contained in:
Sarah Hoffmann 2024-09-16 21:49:01 +02:00 committed by GitHub
commit e92e03e2e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
122 changed files with 61 additions and 12065 deletions

View File

@ -131,99 +131,6 @@ jobs:
working-directory: Nominatim working-directory: Nominatim
if: matrix.flavour != 'oldstuff' 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: install:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: create-archive needs: create-archive

View File

@ -74,25 +74,6 @@ if (BUILD_IMPORTER OR BUILD_API)
find_package(PythonInterp 3.7 REQUIRED) find_package(PythonInterp 3.7 REQUIRED)
endif() 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) # import scripts and utilities (importer only)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
@ -125,8 +106,6 @@ if (BUILD_TESTS)
find_program(PYTHON_BEHAVE behave) find_program(PYTHON_BEHAVE behave)
find_program(PYLINT NAMES pylint3 pylint) find_program(PYLINT NAMES pylint3 pylint)
find_program(PYTEST NAMES pytest py.test-3 py.test) find_program(PYTEST NAMES pytest py.test-3 py.test)
find_program(PHPCS phpcs)
find_program(PHPUNIT phpunit)
if (PYTHON_BEHAVE) if (PYTHON_BEHAVE)
message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}") message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}")
@ -141,24 +120,6 @@ if (BUILD_TESTS)
message(WARNING "behave not found. BDD tests disabled." ) message(WARNING "behave not found. BDD tests disabled." )
endif() 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) if (PYLINT)
message(STATUS "Using pylint binary ${PYLINT}") message(STATUS "Using pylint binary ${PYLINT}")
add_test(NAME pylint add_test(NAME pylint
@ -203,11 +164,7 @@ if (BUILD_IMPORTER)
DESTINATION ${CMAKE_INSTALL_BINDIR} DESTINATION ${CMAKE_INSTALL_BINDIR}
RENAME nominatim) RENAME nominatim)
if (EXISTS ${PHP_BIN}) configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
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()
foreach (submodule nominatim_db nominatim_api) foreach (submodule nominatim_db nominatim_api)
install(DIRECTORY src/${submodule} install(DIRECTORY src/${submodule}
@ -243,10 +200,6 @@ if (BUILD_MODULE)
DESTINATION ${NOMINATIM_LIBDIR}/module) DESTINATION ${NOMINATIM_LIBDIR}/module)
endif() endif()
if (BUILD_API AND EXISTS ${PHP_BIN})
install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
endif()
install(FILES settings/env.defaults install(FILES settings/env.defaults
settings/address-levels.json settings/address-levels.json
settings/phrase-settings.json settings/phrase-settings.json

View File

@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim (installed version).
""" """
from pathlib import Path from pathlib import Path
PHPLIB_DIR = None
SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve() SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve() DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve() CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()

View File

@ -1,15 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2022 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Path settings for extra data used by Nominatim (installed version).
"""
from pathlib import Path
PHPLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-php').resolve()
SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()

View File

@ -194,7 +194,7 @@ On the client side you now need to configure the import to point to the
correct location of the library **on the database server**. Add the following correct location of the library **on the database server**. Add the following
line to your your `.env` file: line to your your `.env` file:
```php ```
NOMINATIM_DATABASE_MODULE_PATH="<directory on the database server where nominatim.so resides>" NOMINATIM_DATABASE_MODULE_PATH="<directory on the database server where nominatim.so resides>"
``` ```

View File

@ -1,151 +0,0 @@
# Deploying Nominatim using the PHP frontend
!!! danger
The PHP frontend is deprecated and will be removed in Nominatim 5.0.
The Nominatim API is implemented as a PHP application. The `website/` directory
in the project directory contains the configured website. You can serve this
in a production environment with any web server that is capable to run
PHP scripts.
This section gives a quick overview on how to configure Apache and Nginx to
serve Nominatim. It is not meant as a full system administration guide on how
to run a web service. Please refer to the documentation of
[Apache](https://httpd.apache.org/docs/current/) and
[Nginx](https://nginx.org/en/docs/)
for background information on configuring the services.
!!! Note
Throughout this page, we assume your Nominatim project directory is
located in `/srv/nominatim-project` and you have installed Nominatim
using the default installation prefix `/usr/local`. If you have put it
somewhere else, you need to adjust the commands and configuration
accordingly.
We further assume that your web server runs as user `www-data`. Older
versions of CentOS may still use the user name `apache`. You also need
to adapt the instructions in this case.
## Making the website directory accessible
You need to make sure that the `website` directory is accessible for the
web server user. You can check that the permissions are correct by accessing
on of the php files as the web server user:
``` sh
sudo -u www-data head -n 1 /srv/nominatim-project/website/search.php
```
If this shows a permission error, then you need to adapt the permissions of
each directory in the path so that it is executable for `www-data`.
If you have SELinux enabled, further adjustments may be necessary to give the
web server access. At a minimum the following SELinux labelling should be done
for Nominatim:
``` sh
sudo semanage fcontext -a -t httpd_sys_content_t "/usr/local/nominatim/lib/lib-php(/.*)?"
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim-project/website(/.*)?"
sudo semanage fcontext -a -t lib_t "/srv/nominatim-project/module/nominatim.so"
sudo restorecon -R -v /usr/local/lib/nominatim
sudo restorecon -R -v /srv/nominatim-project
```
## Nominatim with Apache
### Installing the required packages
With Apache you can use the PHP module to run Nominatim.
Under Ubuntu/Debian install them with:
``` sh
sudo apt install apache2 libapache2-mod-php
```
### Configuring Apache
Make sure your Apache configuration contains the required permissions for the
directory and create an alias:
``` apache
<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)

View File

@ -257,8 +257,8 @@ successfully.
nominatim admin --check-database nominatim admin --check-database
``` ```
Now you can try out your installation by executing a simple query on the If you have installed the `nominatim-api` package, then you can try out
command line: your installation by executing a simple query on the command line:
``` sh ``` sh
nominatim search --query Berlin nominatim search --query Berlin
@ -270,10 +270,8 @@ or, when you have a reverse-only installation:
nominatim reverse --lat 51 --lon 45 nominatim reverse --lat 51 --lon 45
``` ```
If you want to run Nominatim as a service, you need to make a choice between If you want to run Nominatim as a service, make sure you have installed
running the modern Python frontend and the legacy PHP frontend. the right packages as per [Installation](Installation.md#software).
Make sure you have installed the right packages as per
[Installation](Installation.md#software).
#### Testing the Python frontend #### Testing the Python frontend
@ -291,36 +289,15 @@ or, if you prefer to use Starlette instead of Falcon as webserver,
nominatim serve --engine starlette nominatim serve --engine starlette
``` ```
Go to `http://localhost:8088/status.php` and you should see the message `OK`. 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.php?q=Berlin` You can also run a search query, e.g. `http://localhost:8088/search?q=Berlin`
or, for reverse-only installations a reverse query, 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. Do not use this test server in production.
To run Nominatim via webservers like Apache or nginx, please continue reading To run Nominatim via webservers like Apache or nginx, please continue reading
[Deploy the Python frontend](Deployment-Python.md). [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 ## Enabling search by category phrases

View File

@ -72,13 +72,6 @@ For running the Python frontend:
* [starlette](https://www.starlette.io/) * [starlette](https://www.starlette.io/)
* [uvicorn](https://www.uvicorn.org/) * [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 For dependencies for running tests and building documentation, see
the [Development section](../develop/Development-Environment.md). the [Development section](../develop/Development-Environment.md).

View File

@ -20,6 +20,18 @@ breaking changes. **Please read them before running the migration.**
If you are migrating from a version <3.6, then you still have to follow If you are migrating from a version <3.6, then you still have to follow
the manual migration steps up to 3.6. 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 ## 4.4.0 -> 4.5.0
### New structure for Python packages ### New structure for Python packages

View File

@ -59,13 +59,6 @@ When set, then JSON output will be wrapped in a callback function with
the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more
information. information.
| Parameter | Value | Default |
|-----------| ----- | ------- |
| pretty | 0 or 1 | 0 |
`[PHP-only]` Add indentation to the output to make it more human-readable.
### Output details ### Output details
| Parameter | Value | Default | | Parameter | Value | Default |
@ -95,10 +88,8 @@ members.
|-----------| ----- | ------- | |-----------| ----- | ------- |
| hierarchy | 0 or 1 | 0 | | hierarchy | 0 or 1 | 0 |
Include details of places lower in the address hierarchy. Include details of POIs and address that depend on the place. Only POIs
that use this place to determine their address will be returned.
`[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.
| Parameter | Value | Default | | Parameter | Value | Default |
|-----------| ----- | ------- | |-----------| ----- | ------- |
@ -129,7 +120,7 @@ as the ["Accept-Language" HTTP header](https://developer.mozilla.org/en-US/docs/
##### JSON ##### 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 ```json

View File

@ -168,7 +168,7 @@ Additional information requested with `addressdetails=1`, `extratags=1` and
<searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000" <searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright" attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
querystring="london" polygon="false" exclude_place_ids="100149" 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" <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" 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" display_name="London, Greater London, England, SW1A 2DU, United Kingdom"

View File

@ -1,12 +1,3 @@
!!! 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 This section describes the API V1 of the Nominatim web service. The
service offers the following endpoints: service offers the following endpoints:

View File

@ -32,10 +32,9 @@ projection. The API returns exactly one result or an error when the coordinate
is in an area with no OSM data coverage. is in an area with no OSM data coverage.
!!! danger "Deprecation warning" !!! tip
The reverse API used to allow address lookup for a single OSM object by The reverse API allows a lookup of object by coordinate. If you want
its OSM id for `[PHP-only]`. The use is considered deprecated. to look up an object by ID, use the [Address Lookup API](Lookup.md) instead.
Use the [Address Lookup API](Lookup.md) instead.
!!! danger "Deprecation warning" !!! danger "Deprecation warning"
The API can also be used with the URL The API can also be used with the URL
@ -149,8 +148,6 @@ In terms of address details the zoom levels are as follows:
|-----------| ----- | ------- | |-----------| ----- | ------- |
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) | | 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 layer filter allows to select places by themes.
The `address` layer contains all places that make up an address: The `address` layer contains all places that make up an address:

View File

@ -187,8 +187,6 @@ also excluded when the filter is set.
|-----------| ----- | ------- | |-----------| ----- | ------- |
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) | | 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 layer filter allows to select places by themes.
The `address` layer contains all places that make up an address: The `address` layer contains all places that make up an address:

View File

@ -57,8 +57,7 @@ parameter that is understood by libpq. See the [Postgres documentation](https://
| **After Changes:** | cannot be changed after import | | **After Changes:** | cannot be changed after import |
Defines the name of the database user that will run search queries. Usually 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 this is the user under which the webserver is executed. The Postgres user
via php-fpm, you can also define a separate query user. The Postgres user
needs to be set up before starting the import. needs to be set up before starting the import.
Nominatim grants minimal rights to this user to all tables that are needed Nominatim grants minimal rights to this user to all tables that are needed
@ -544,38 +543,6 @@ the local languages (in OSM: the name tag without any language suffix) is
used. 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 #### NOMINATIM_LOOKUP_MAX_COUNT
| Summary | | | Summary | |
@ -616,7 +583,6 @@ Setting this parameter to 0 disables polygon output completely.
| **Format:** | boolean | | **Format:** | boolean |
| **Default:** | no | | **Default:** | no |
| **After Changes:** | run `nominatim refresh --website` | | **After Changes:** | run `nominatim refresh --website` |
| **Comment:** | PHP frontend only |
Enable to search elements just within countries. Enable to search elements just within countries.
@ -728,7 +694,8 @@ The entries in the log file have the following format:
<request time> <execution time in s> <number of results> <type> "<query string>" <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 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. type contains the name of the endpoint used.
Can be used as the same time as NOMINATIM_LOG_DB. Can be used as the same time as NOMINATIM_LOG_DB.

View File

@ -26,12 +26,9 @@ following packages should get you started:
## Prerequisites for testing and documentation ## Prerequisites for testing and documentation
The Nominatim test suite consists of behavioural tests (using behave) and The Nominatim test suite consists of behavioural tests (using behave) and
unit tests (using PHPUnit for PHP code and pytest for Python code). unit tests (using pytest). It has the following additional requirements:
It has the following additional requirements:
* [behave test framework](https://behave.readthedocs.io) >= 1.2.6 * [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) * [Pylint](https://pylint.org/) (CI always runs the latest version from pip)
* [mypy](http://mypy-lang.org/) (plus typing information for external libs) * [mypy](http://mypy-lang.org/) (plus typing information for external libs)
* [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9) * [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9)
@ -63,7 +60,7 @@ The easiest way, to handle these Python dependencies is to run your
development from within a virtual environment. development from within a virtual environment.
```sh ```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: To set up the virtual environment with all necessary packages run:
@ -86,28 +83,6 @@ Now enter the virtual environment whenever you want to develop:
. ~/nominatim-dev-venv/bin/activate . ~/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 ### Running Nominatim during development
The source code for Nominatim can be found in the `src` directory and can The source code for Nominatim can be found in the `src` directory and can

View File

@ -8,7 +8,7 @@ the tests, see the [Development setup chapter](Development-Environment.md).
There are two kind of tests in this test suite. There are functional tests 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 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: This test directory is structured as follows:
@ -20,28 +20,11 @@ This test directory is structured as follows:
| +- db Tests for internal data processing on import and update | +- db Tests for internal data processing on import and update
| +- api Tests for API endpoints (search, reverse, etc.) | +- api Tests for API endpoints (search, reverse, etc.)
| |
+- php PHP unit tests
+- python Python unit tests +- python Python unit tests
+- testdb Base data for generating API test database +- testdb Base data for generating API test database
+- testdata Additional test data used by unit tests +- 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`) ## Python Unit Tests (`test/python`)
Unit tests for Python code can be found in the `python/` directory. The goal is Unit tests for Python code can be found in the `python/` directory. The goal is
@ -118,7 +101,7 @@ and compromises the following data:
* extract of Autauga country, Alabama, US (for tests against Tiger data) * extract of Autauga country, Alabama, US (for tests against Tiger data)
* additional data from `test/testdb/additional_api_test.data.osm` * 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. Most tests should be formulated as BDD DB creation tests (see below) instead.
### DB Creation Tests (`test/bdd/db`) ### DB Creation Tests (`test/bdd/db`)

View File

@ -91,14 +91,9 @@ for a custom tokenizer implementation.
### Directory Structure ### Directory Structure
Nominatim expects two files for a tokenizer: Nominatim expects a single file `src/nominatim_db/tokenizer/<NAME>_tokenizer.py`
containing the Python part of the implementation.
* `nominatim/tokenizer/<NAME>_tokenizer.py` containing the Python part of the `<NAME>` is a unique name for the tokenizer consisting of only lower-case
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
letters, digits and underscore. A tokenizer also needs to install some SQL letters, digits and underscore. A tokenizer also needs to install some SQL
functions. By convention, these should be placed in `lib-sql/tokenizer`. functions. By convention, these should be placed in `lib-sql/tokenizer`.
@ -282,73 +277,3 @@ permanently. The indexer calls this function when all processing is done and
replaces the content of the `token_info` column with the returned value before 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 the trigger stores the information in the database. May return NULL if no
information should be stored permanently. 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.

View File

@ -20,5 +20,5 @@ and can be found in the files in the `sql/functions/` directory.
The __search frontend__ implements the actual API. It takes search The __search frontend__ implements the actual API. It takes search
and reverse geocoding queries from the user, looks up the data and 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 returns the results in the requested format. This part is located in the
and can be found in the `lib/` and `website/` directories. `nominatim-api` package. The source code can be found in `src/nominatim_api`.

View File

@ -1,191 +0,0 @@
<?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'];
}
}
}

View File

@ -1,576 +0,0 @@
<?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;
}

View File

@ -1,362 +0,0 @@
<?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;
}
}

View File

@ -1,42 +0,0 @@
<?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;
}
}
}

View File

@ -1,189 +0,0 @@
<?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);
}
}

View File

@ -1,19 +0,0 @@
<?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
}
}

View File

@ -1,938 +0,0 @@
<?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

View File

@ -1,157 +0,0 @@
<?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;
}
}

View File

@ -1,89 +0,0 @@
<?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
);
}
}

View File

@ -1,615 +0,0 @@
<?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;
}
}

View File

@ -1,129 +0,0 @@
<?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);
}
}

View File

@ -1,401 +0,0 @@
<?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;
}
}

View File

@ -1,319 +0,0 @@
<?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
);
}
}

View File

@ -1,985 +0,0 @@
<?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>';
}
}

View File

@ -1,95 +0,0 @@
<?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;
}
}

View File

@ -1,92 +0,0 @@
<?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);
}
}

View File

@ -1,144 +0,0 @@
<?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;
}
}

View File

@ -1,52 +0,0 @@
<?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];
}
}

View File

@ -1,59 +0,0 @@
<?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);
}
}

View File

@ -1,82 +0,0 @@
<?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';
}
}

View File

@ -1,116 +0,0 @@
<?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';
}
}

View File

@ -1,134 +0,0 @@
<?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;
}
}

View File

@ -1,127 +0,0 @@
<?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';
}
}

View File

@ -1,111 +0,0 @@
<?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';
}
}

View File

@ -1,125 +0,0 @@
<?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';
}
}

View File

@ -1,110 +0,0 @@
<?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';
}
}

View File

@ -1,199 +0,0 @@
<?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);
}

View File

@ -1,21 +0,0 @@
<?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');
}
}

View File

@ -1,13 +0,0 @@
<?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');

View File

@ -1,98 +0,0 @@
<?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');
}

View File

@ -1,12 +0,0 @@
<?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');

View File

@ -1,246 +0,0 @@
<?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));
}
}
}

View File

@ -1,104 +0,0 @@
<?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);
}
}

View File

@ -1,38 +0,0 @@
<?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 '';
}

View File

@ -1,27 +0,0 @@
<?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;
}

View File

@ -1,83 +0,0 @@
<?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)
));
}

View File

@ -1,85 +0,0 @@
<?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)
));
}

View File

@ -1,82 +0,0 @@
<?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);

View File

@ -1,110 +0,0 @@
<?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>';

View File

@ -1,120 +0,0 @@
<?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);

View File

@ -1,19 +0,0 @@
<?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));

View File

@ -1,7 +0,0 @@
<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>

View File

@ -1,83 +0,0 @@
<?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'));

View File

@ -1,72 +0,0 @@
<?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
));

View File

@ -1,83 +0,0 @@
<?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
));

View File

@ -1,81 +0,0 @@
<?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);

View File

@ -1,138 +0,0 @@
<?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') . '>';

View File

@ -1,235 +0,0 @@
<?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;
}
}
}
}

View File

@ -1,265 +0,0 @@
<?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);
}
}
}
}
}

View File

@ -1,36 +0,0 @@
<?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);
}

View File

@ -1,263 +0,0 @@
<?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');

View File

@ -1,101 +0,0 @@
<?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');

View File

@ -1,63 +0,0 @@
<?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);
}

View File

@ -1,20 +0,0 @@
<?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);

View File

@ -1,95 +0,0 @@
<?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');

View File

@ -1,93 +0,0 @@
<?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');

View File

@ -1,56 +0,0 @@
<?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();
}
}

View File

@ -26,8 +26,7 @@ nav:
- 'Basic Installation': 'admin/Installation.md' - 'Basic Installation': 'admin/Installation.md'
- 'Import' : 'admin/Import.md' - 'Import' : 'admin/Import.md'
- 'Update' : 'admin/Update.md' - 'Update' : 'admin/Update.md'
- 'Deploy (Python frontend)' : 'admin/Deployment-Python.md' - 'Deploy' : 'admin/Deployment-Python.md'
- 'Deploy (PHP frontend)' : 'admin/Deployment-PHP.md'
- 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md' - 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md'
- 'Advanced Installations' : 'admin/Advanced-Installations.md' - 'Advanced Installations' : 'admin/Advanced-Installations.md'
- 'Maintenance' : 'admin/Maintenance.md' - 'Maintenance' : 'admin/Maintenance.md'

View File

@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim.
""" """
from pathlib import Path from pathlib import Path
PHPLIB_DIR = None
DATA_DIR = None DATA_DIR = None
SQLLIB_DIR = None SQLLIB_DIR = None
CONFIG_DIR = (Path(__file__) / '..' / 'resources' / 'settings').resolve() CONFIG_DIR = (Path(__file__) / '..' / 'resources' / 'settings').resolve()

View File

@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim.
""" """
from pathlib import Path from pathlib import Path
PHPLIB_DIR = None
DATA_DIR = (Path(__file__) / '..' / 'resources').resolve() DATA_DIR = (Path(__file__) / '..' / 'resources').resolve()
SQLLIB_DIR = (DATA_DIR / 'lib-sql') SQLLIB_DIR = (DATA_DIR / 'lib-sql')
CONFIG_DIR = (DATA_DIR / 'settings') CONFIG_DIR = (DATA_DIR / 'settings')

View File

@ -177,16 +177,6 @@ NOMINATIM_MAPICON_URL=
# When unset, the local language (i.e. the name tag without suffix) will be used. # When unset, the local language (i.e. the name tag without suffix) will be used.
NOMINATIM_DEFAULT_LANGUAGE= 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. # Maximum number of OSM ids accepted by /lookup.
NOMINATIM_LOOKUP_MAX_COUNT=50 NOMINATIM_LOOKUP_MAX_COUNT=50

View File

@ -19,7 +19,6 @@ from pathlib import Path
from .config import Configuration from .config import Configuration
from .errors import UsageError from .errors import UsageError
from .tools.exec_utils import run_php_server
from . import clicmd from . import clicmd
from . import version from . import version
from .clicmd.args import NominatimArgs, Subcommand from .clicmd.args import NominatimArgs, Subcommand
@ -154,10 +153,10 @@ class AdminServe:
from the current project directory. This webserver is only suitable from the current project directory. This webserver is only suitable
for testing and development. Do not use it in production setups! for testing and development. Do not use it in production setups!
There are different webservers available. The default 'php' engine There are two different webserver implementations for Python available:
runs the classic PHP frontend. The other engines are Python servers falcon (the default) and starlette. You need to make sure the
which run the new Python frontend code. This is highly experimental appropriate Python packages as well as the uvicorn package are
at the moment and may not include the full API. installed to use this function.
By the default, the webserver can be accessed at: http://127.0.0.1:8088 By the default, the webserver can be accessed at: http://127.0.0.1:8088
""" """
@ -167,19 +166,12 @@ class AdminServe:
group.add_argument('--server', default='127.0.0.1:8088', group.add_argument('--server', default='127.0.0.1:8088',
help='The address the server will listen to.') help='The address the server will listen to.')
group.add_argument('--engine', default='falcon', group.add_argument('--engine', default='falcon',
choices=('php', 'falcon', 'starlette'), choices=('falcon', 'starlette'),
help='Webserver framework to run. (default: falcon)') help='Webserver framework to run. (default: falcon)')
def run(self, args: NominatimArgs) -> int: def run(self, args: NominatimArgs) -> int:
if args.engine == 'php': asyncio.run(self.run_uvicorn(args))
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))
return 0 return 0

View File

@ -69,7 +69,8 @@ class UpdateRefresh:
group.add_argument('--importance', action='store_true', group.add_argument('--importance', action='store_true',
help='Recompute place importances (expensive!)') help='Recompute place importances (expensive!)')
group.add_argument('--website', action='store_true', 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', group.add_argument('--data-object', action='append',
type=_parse_osm_object, metavar='OBJECT', type=_parse_osm_object, metavar='OBJECT',
help='Mark the given OSM object as requiring an update' help='Mark the given OSM object as requiring an update'
@ -159,14 +160,8 @@ class UpdateRefresh:
refresh.recompute_importance(conn) refresh.recompute_importance(conn)
if args.website: if args.website:
webdir = args.project_dir / 'website' LOG.error('WARNING: Website setup is no longer required. '
LOG.warning('Setting up website directory at %s', webdir) 'This function will be removed in future version of Nominatim.')
# 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)
if args.data_object or args.data_area: if args.data_object or args.data_area:
with connect(args.config.get_libpq_dsn()) as conn: with connect(args.config.get_libpq_dsn()) as conn:

View File

@ -88,7 +88,7 @@ class SetupAll:
async def async_run(self, args: NominatimArgs) -> int: async def async_run(self, args: NominatimArgs) -> int:
from ..data import country_info 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 from ..indexer.indexer import Indexer
num_threads = args.threads or psutil.cpu_count() or 1 num_threads = args.threads or psutil.cpu_count() or 1
@ -141,11 +141,6 @@ class SetupAll:
LOG.warning('Recompute word counts') LOG.warning('Recompute word counts')
tokenizer.update_statistics(args.config, threads=num_threads) 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) self._finalize_database(args.config.get_libpq_dsn(), args.offline)
return 0 return 0

View File

@ -74,7 +74,6 @@ class Configuration:
class _LibDirs: class _LibDirs:
module: Path module: Path
osm2pgsql: Path osm2pgsql: Path
php = paths.PHPLIB_DIR
sql = paths.SQLLIB_DIR sql = paths.SQLLIB_DIR
data = paths.DATA_DIR data = paths.DATA_DIR

View File

@ -15,9 +15,6 @@ be used consistently when querying and updating the database.
This module provides the functions to create and configure a new tokenizer This module provides the functions to create and configure a new tokenizer
as well as instantiating the appropriate tokenizer for updating an existing as well as instantiating the appropriate tokenizer for updating an existing
database. 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 from typing import Optional
import logging import logging

View File

@ -13,7 +13,6 @@ from typing import Optional, Sequence, List, Tuple, Mapping, Any, cast, \
import itertools import itertools
import logging import logging
from pathlib import Path from pathlib import Path
from textwrap import dedent
from psycopg.types.json import Jsonb from psycopg.types.json import Jsonb
from psycopg import sql as pysql from psycopg import sql as pysql
@ -64,7 +63,6 @@ class ICUTokenizer(AbstractTokenizer):
""" """
self.loader = ICURuleLoader(config) self.loader = ICURuleLoader(config)
self._install_php(config.lib_dir.php, overwrite=True)
self._save_config() self._save_config()
if init_db: if init_db:
@ -81,8 +79,6 @@ class ICUTokenizer(AbstractTokenizer):
with connect(self.dsn) as conn: with connect(self.dsn) as conn:
self.loader.load_config_from_db(conn) self.loader.load_config_from_db(conn)
self._install_php(config.lib_dir.php, overwrite=False)
def finalize_import(self, config: Configuration) -> None: def finalize_import(self, config: Configuration) -> None:
""" Do any required postprocessing to make the tokenizer data ready """ Do any required postprocessing to make the tokenizer data ready
@ -281,22 +277,6 @@ class ICUTokenizer(AbstractTokenizer):
return list(s[0].split('@')[0] for s in cur) 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: def _save_config(self) -> None:
""" Save the configuration that needs to remain stable for the given """ Save the configuration that needs to remain stable for the given
database as database properties. database as database properties.

View File

@ -14,7 +14,6 @@ import logging
from pathlib import Path from pathlib import Path
import re import re
import shutil import shutil
from textwrap import dedent
from icu import Transliterator from icu import Transliterator
import psycopg import psycopg
@ -120,8 +119,6 @@ class LegacyTokenizer(AbstractTokenizer):
self.normalization = config.TERM_NORMALIZATION self.normalization = config.TERM_NORMALIZATION
self._install_php(config, overwrite=True)
with connect(self.dsn) as conn: with connect(self.dsn) as conn:
_check_module(module_dir, conn) _check_module(module_dir, conn)
self._save_config(conn, config) self._save_config(conn, config)
@ -145,8 +142,6 @@ class LegacyTokenizer(AbstractTokenizer):
config.lib_dir.module, config.lib_dir.module,
config.project_dir / 'module') config.project_dir / 'module')
self._install_php(config, overwrite=False)
def finalize_import(self, config: Configuration) -> None: def finalize_import(self, config: Configuration) -> None:
""" Do any required postprocessing to make the tokenizer data ready """ Do any required postprocessing to make the tokenizer data ready
for use. for use.
@ -272,21 +267,6 @@ class LegacyTokenizer(AbstractTokenizer):
return list(s[0] for s in cur) 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: def _init_db_tables(self, config: Configuration) -> None:
""" Set up the word table and fill it with pre-computed word """ Set up the word table and fill it with pre-computed word
frequencies. frequencies.

View File

@ -14,19 +14,12 @@ import re
import subprocess import subprocess
import shutil import shutil
from ..typing import StrPath
from ..db.connection import get_pg_env from ..db.connection import get_pg_env
from ..errors import UsageError from ..errors import UsageError
from ..version import OSM2PGSQL_REQUIRED_VERSION from ..version import OSM2PGSQL_REQUIRED_VERSION
LOG = logging.getLogger() 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: def run_osm2pgsql(options: Mapping[str, Any]) -> None:
""" Run osm2pgsql with the given options. """ Run osm2pgsql with the given options.

View File

@ -11,17 +11,15 @@ from typing import MutableSequence, Tuple, Any, Type, Mapping, Sequence, List, c
import csv import csv
import gzip import gzip
import logging import logging
from textwrap import dedent
from pathlib import Path from pathlib import Path
from psycopg import sql as pysql from psycopg import sql as pysql
from ..config import Configuration from ..config import Configuration
from ..db.connection import Connection, connect, postgis_version_tuple,\ from ..db.connection import Connection, connect, postgis_version_tuple,\
drop_tables, table_exists drop_tables
from ..db.utils import execute_file from ..db.utils import execute_file
from ..db.sql_preprocessor import SQLPreprocessor from ..db.sql_preprocessor import SQLPreprocessor
from ..version import NOMINATIM_VERSION
LOG = logging.getLogger() LOG = logging.getLogger()
@ -99,34 +97,6 @@ def create_functions(conn: Connection, config: Configuration,
debug=enable_debug) 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: def import_wikipedia_articles(dsn: str, data_path: Path, ignore_errors: bool = False) -> int:
""" Replaces the wikipedia importance tables with new data. """ Replaces the wikipedia importance tables with new data.
The import is run in a single transaction so that the new data The import is run in a single transaction so that the new data
@ -272,46 +242,6 @@ def _quote_php_variable(var_type: Type[Any], config: Configuration,
return f"'{quoted}'" 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, def invalidate_osm_object(osm_type: str, osm_id: int, conn: Connection,
recursive: bool = True) -> None: recursive: bool = True) -> None:
""" Mark the given OSM object for reindexing. When 'recursive' is set """ Mark the given OSM object for reindexing. When 'recursive' is set

View File

@ -1,14 +1,10 @@
all: bdd php python all: bdd python
no-test-db: bdd-no-test-db php
bdd: bdd:
cd bdd && behave -DREMOVE_TEMPLATE=1 cd bdd && behave -DREMOVE_TEMPLATE=1
php:
cd php && phpunit ./
python: python:
pytest python pytest python
.PHONY: bdd php no-test-db python .PHONY: bdd python

View File

@ -42,16 +42,6 @@ Feature: Object details
| N300209696:highway | | 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 Scenario: Details for interpolation way return the interpolation
When sending details query for W1 When sending details query for W1
Then the result is valid json Then the result is valid json
@ -60,17 +50,6 @@ Feature: Object details
| place | houses | W | 1 | 15 | | 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 @Fail
Scenario: Details for interpolation way return the interpolation Scenario: Details for interpolation way return the interpolation
When sending details query for 112871 When sending details query for 112871
@ -81,17 +60,6 @@ Feature: Object details
And result has not attributes osm_type,osm_id 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 @Fail
Scenario: Details for interpolation way return the interpolation Scenario: Details for interpolation way return the interpolation
When sending details query for 112820 When sending details query for 112820
@ -102,7 +70,6 @@ Feature: Object details
And result has not attributes osm_type,osm_id And result has not attributes osm_type,osm_id
@v1-api-python-only
Scenario Outline: Details debug output returns no errors Scenario Outline: Details debug output returns no errors
When sending debug details query for <feature> When sending debug details query for <feature>
Then the result is valid html Then the result is valid html

View File

@ -3,7 +3,6 @@
Feature: Layer parameter in reverse geocoding Feature: Layer parameter in reverse geocoding
Testing correct function of layer selection while reverse geocoding Testing correct function of layer selection while reverse geocoding
@v1-api-python-only
Scenario: POIs are selected by default Scenario: POIs are selected by default
When sending v1/reverse at 47.14077,9.52414 When sending v1/reverse at 47.14077,9.52414
Then results contain Then results contain
@ -11,7 +10,6 @@ Feature: Layer parameter in reverse geocoding
| tourism | viewpoint | | tourism | viewpoint |
@v1-api-python-only
Scenario Outline: Same address level POI with different layers Scenario Outline: Same address level POI with different layers
When sending v1/reverse at 47.14077,9.52414 When sending v1/reverse at 47.14077,9.52414
| layer | | layer |
@ -31,7 +29,6 @@ Feature: Layer parameter in reverse geocoding
| natural,poi | tourism | | natural,poi | tourism |
@v1-api-python-only
Scenario Outline: POIs are not selected without housenumber for address layer Scenario Outline: POIs are not selected without housenumber for address layer
When sending v1/reverse at 47.13816,9.52168 When sending v1/reverse at 47.13816,9.52168
| layer | | layer |
@ -46,7 +43,6 @@ Feature: Layer parameter in reverse geocoding
| address | amenity | parking | | address | amenity | parking |
@v1-api-python-only
Scenario: Between natural and low-zoom address prefer natural Scenario: Between natural and low-zoom address prefer natural
When sending v1/reverse at 47.13636,9.52094 When sending v1/reverse at 47.13636,9.52094
| layer | zoom | | layer | zoom |
@ -56,7 +52,6 @@ Feature: Layer parameter in reverse geocoding
| waterway | | waterway |
@v1-api-python-only
Scenario Outline: Search for mountain peaks begins at level 12 Scenario Outline: Search for mountain peaks begins at level 12
When sending v1/reverse at 47.08293,9.57109 When sending v1/reverse at 47.08293,9.57109
| layer | zoom | | layer | zoom |
@ -71,7 +66,6 @@ Feature: Layer parameter in reverse geocoding
| 13 | waterway | river | | 13 | waterway | river |
@v1-api-python-only
Scenario Outline: Reverse search with manmade layers Scenario Outline: Reverse search with manmade layers
When sending v1/reverse at 32.46904,-86.44439 When sending v1/reverse at 32.46904,-86.44439
| layer | | layer |

View File

@ -18,20 +18,6 @@ Feature: v1/reverse Parameter Tests
When sending v1/reverse at ,52.52 When sending v1/reverse at ,52.52
Then a HTTP 400 is returned 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 Scenario Outline: Bad format for lat or lon
When sending v1/reverse at , When sending v1/reverse at ,
@ -151,7 +137,6 @@ Feature: v1/reverse Parameter Tests
| foo; evil | | foo; evil |
@v1-api-python-only
Scenario Outline: Reverse debug mode produces valid HTML Scenario Outline: Reverse debug mode produces valid HTML
When sending v1/reverse at , with format debug When sending v1/reverse at , with format debug
| lat | lon | | lat | lon |

View File

@ -353,19 +353,6 @@ Feature: Search queries
| svg | | svg |
| geokml | | 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 Scenario: Array parameters are ignored
When sending json search query "Vaduz" with address When sending json search query "Vaduz" with address

View File

@ -3,7 +3,7 @@
Feature: Searches with postcodes Feature: Searches with postcodes
Various searches involving 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 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 When sending json search query "36067 1111, us" with address
Then result addresses contain Then result addresses contain

View File

@ -106,17 +106,6 @@ Feature: Search queries
| class | type | | class | type |
| club | scout | | 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 Scenario: POI search near given coordinate
When sending json search query "restaurant near 47.16712,9.51100" When sending json search query "restaurant near 47.16712,9.51100"
Then results contain Then results contain
@ -129,13 +118,6 @@ Feature: Search queries
| class | type | | class | type |
| leisure | firepit | | 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 Scenario: POI search in a bounded viewbox
When sending json search query "restaurants" When sending json search query "restaurants"

View File

@ -98,7 +98,6 @@ Feature: Querying fo postcode variants
@fail-legacy @fail-legacy
@v1-api-python-only
Scenario: Postcode areas are preferred over postcode points Scenario: Postcode areas are preferred over postcode points
Given the grid with origin DE Given the grid with origin DE
| 1 | 2 | | 1 | 2 |

View File

@ -2,7 +2,6 @@
Feature: Reverse searches Feature: Reverse searches
Test results of reverse queries Test results of reverse queries
@v1-api-python-only
Scenario: POI in POI area Scenario: POI in POI area
Given the 0.0001 grid with origin 1,1 Given the 0.0001 grid with origin 1,1
| 1 | | | | | | | | 2 | | 1 | | | | | | | | 2 |

View File

@ -66,9 +66,3 @@ def before_tag(context, tag):
if tag == 'fail-legacy': if tag == 'fail-legacy':
if context.config.userdata['TOKENIZER'] == 'legacy': if context.config.userdata['TOKENIZER'] == 'legacy':
context.scenario.skip("Not implemented in legacy tokenizer") 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.")

View File

@ -44,11 +44,9 @@ class NominatimEnvironment:
self.api_db_done = False self.api_db_done = False
self.website_dir = None self.website_dir = None
self.api_engine = None if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"):
if config['API_ENGINE'] != 'php': raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'")
if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"): self.api_engine = getattr(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: if self.tokenizer == 'legacy' and self.server_module_path is None:
raise RuntimeError("You must set -DSERVER_MODULE_PATH when testing the legacy tokenizer.") raise RuntimeError("You must set -DSERVER_MODULE_PATH when testing the legacy tokenizer.")
@ -110,15 +108,6 @@ class NominatimEnvironment:
self.website_dir = tempfile.TemporaryDirectory() 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): def get_test_config(self):
cfg = Configuration(Path(self.website_dir.name), environ=self.test_env) cfg = Configuration(Path(self.website_dir.name), environ=self.test_env)

Some files were not shown because too many files have changed in this diff Show More