]> git.openstreetmap.org Git - nominatim.git/commitdiff
Merge remote-tracking branch 'upstream/master'
authorSarah Hoffmann <lonvia@denofr.de>
Mon, 4 Mar 2024 08:36:25 +0000 (09:36 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Mon, 4 Mar 2024 08:36:25 +0000 (09:36 +0100)
76 files changed:
.github/workflows/ci-tests.yml
CMakeLists.txt
CONTRIBUTING.md
ChangeLog
Vagrantfile
cmake/paths-py-no-php.tmpl [new file with mode: 0644]
docs/admin/Advanced-Installations.md
docs/admin/Deployment-Python.md
docs/admin/Faq.md
docs/admin/Import.md
docs/admin/Installation.md
docs/api/Reverse.md
docs/api/Search.md
docs/customize/Import-Styles.md
docs/customize/Tokenizers.md
docs/develop/ICU-Tokenizer-Modules.md
docs/develop/Testing.md
docs/develop/parenting-flow.plantuml
docs/develop/parenting-flow.svg
docs/mkdocs.yml
lib-sql/functions/importance.sql
lib-sql/functions/place_triggers.sql
lib-sql/functions/placex_triggers.sql
lib-sql/functions/utils.sql
lib-sql/indices.sql
lib-sql/tables.sql
lib-sql/tokenizer/legacy_tokenizer.sql
nominatim/api/core.py
nominatim/api/logging.py
nominatim/api/result_formatting.py
nominatim/api/results.py
nominatim/api/reverse.py
nominatim/api/search/db_search_fields.py
nominatim/api/search/db_searches.py
nominatim/api/search/legacy_tokenizer.py
nominatim/api/search/query.py
nominatim/api/search/query_analyzer_factory.py
nominatim/api/search/token_assignment.py
nominatim/api/status.py
nominatim/api/types.py
nominatim/api/v1/classtypes.py
nominatim/api/v1/helpers.py
nominatim/api/v1/server_glue.py
nominatim/cli.py
nominatim/clicmd/setup.py
nominatim/db/async_core_library.py
nominatim/db/sql_preprocessor.py
nominatim/db/sqlite_functions.py
nominatim/server/falcon/server.py
nominatim/tokenizer/icu_tokenizer.py
nominatim/tokenizer/legacy_tokenizer.py
nominatim/tokenizer/sanitizers/base.py
nominatim/tools/check_database.py
nominatim/tools/collect_os_info.py
nominatim/tools/exec_utils.py
nominatim/tools/refresh.py
osm2pgsql
settings/flex-base.lua
settings/import-extratags.lua
settings/import-full.lua
test/bdd/api/search/simple.feature
test/bdd/db/import/addressing.feature
test/bdd/db/update/linked_places.feature
test/bdd/environment.py
test/bdd/steps/geometry_factory.py
test/bdd/steps/nominatim_environment.py
test/bdd/steps/steps_api_queries.py
test/bdd/steps/steps_db_ops.py
test/bdd/steps/steps_osm_data.py
test/python/api/test_api_search.py
test/python/api/test_result_formatting_v1.py
test/python/api/test_results.py
test/python/cli/test_cli.py
test/python/cli/test_cmd_admin.py
vagrant/Install-on-Ubuntu-20.sh
vagrant/Install-on-Ubuntu-22.sh

index 42c03edc17d9e76fd20463b6294c25ce1fa730bc..910114d7e5cf5e9e0a16d46949b14bbc2f8b5bd9 100644 (file)
@@ -11,7 +11,7 @@ jobs:
               with:
                 submodules: true
 
-            - uses: actions/cache@v3
+            - uses: actions/cache@v4
               with:
                   path: |
                      data/country_osm_grid.sql.gz
@@ -27,7 +27,7 @@ jobs:
                   mv nominatim-src.tar.bz2 Nominatim
 
             - name: 'Upload Artifact'
-              uses: actions/upload-artifact@v3
+              uses: actions/upload-artifact@v4
               with:
                   name: full-source
                   path: nominatim-src.tar.bz2
@@ -43,40 +43,28 @@ jobs:
                       ubuntu: 20
                       postgresql: '9.6'
                       postgis: '2.5'
-                      php: '7.3'
                       lua: '5.1'
                     - flavour: ubuntu-20
                       ubuntu: 20
                       postgresql: 13
                       postgis: 3
-                      php: '7.4'
                       lua: '5.3'
                     - flavour: ubuntu-22
                       ubuntu: 22
                       postgresql: 15
                       postgis: 3
-                      php: '8.1'
                       lua: '5.3'
 
         runs-on: ubuntu-${{ matrix.ubuntu }}.04
 
         steps:
-            - uses: actions/download-artifact@v3
+            - 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: ${{ matrix.php }}
-                  tools: phpunit:9, phpcs, composer
-                  ini-values: opcache.jit=disable
-              env:
-                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
             - uses: actions/setup-python@v4
               with:
                 python-version: 3.7
@@ -119,20 +107,11 @@ jobs:
               run: pip3 install -U pylint
               if: matrix.flavour != 'oldstuff'
 
-            - name: PHP linting
-              run: phpcs --report-width=120 .
-              working-directory: Nominatim
-              if: matrix.flavour != 'oldstuff'
-
             - name: Python linting
               run: python3 -m pylint nominatim
               working-directory: Nominatim
               if: matrix.flavour != 'oldstuff'
 
-            - name: PHP unit tests
-              run: phpunit ./
-              working-directory: Nominatim/test/php
-
             - name: Python unit tests
               run: python3 -m pytest test/python
               working-directory: Nominatim
@@ -156,7 +135,7 @@ jobs:
         runs-on: ubuntu-20.04
 
         steps:
-            - uses: actions/download-artifact@v3
+            - uses: actions/download-artifact@v4
               with:
                   name: full-source
 
@@ -185,16 +164,16 @@ jobs:
 
             - name: BDD tests (legacy tokenizer)
               run: |
-                  python3 -m behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build -DTOKENIZER=legacy --format=progress3
+                  python3 -m behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build -DAPI_ENGINE=php -DTOKENIZER=legacy --format=progress3
               working-directory: Nominatim/test/bdd
 
 
-    python-api-test:
+    php-test:
         needs: create-archive
         runs-on: ubuntu-22.04
 
         steps:
-            - uses: actions/download-artifact@v3
+            - uses: actions/download-artifact@v4
               with:
                   name: full-source
 
@@ -206,6 +185,23 @@ jobs:
                   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'
@@ -213,12 +209,9 @@ jobs:
             - name: Install test prerequsites
               run: sudo apt-get install -y -qq python3-behave
 
-            - name: Install Python webservers
-              run: pip3 install starlette asgi_lifespan httpx
-
-            - name: BDD tests (starlette)
+            - name: BDD tests (php)
               run: |
-                  python3 -m behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build -DAPI_ENGINE=starlette --format=progress3
+                  python3 -m behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build -DAPI_ENGINE=php --format=progress3
               working-directory: Nominatim/test/bdd
 
 
@@ -268,7 +261,7 @@ jobs:
                 OS: ${{ matrix.name }}
                 INSTALL_MODE: ${{ matrix.install_mode }}
 
-            - uses: actions/download-artifact@v3
+            - uses: actions/download-artifact@v4
               with:
                   name: full-source
                   path: /home/nominatim
@@ -354,90 +347,42 @@ jobs:
       runs-on: ubuntu-latest
       needs: create-archive
 
-      strategy:
-          matrix:
-              name: [Ubuntu-22]
-              include:
-                  - name: Ubuntu-22
-                    image: "ubuntu:22.04"
-                    ubuntu: 22
-                    install_mode: install-apache
-
-      container:
-          image: ${{ matrix.image }}
-          env:
-              LANG: en_US.UTF-8
-
-      defaults:
-          run:
-              shell: sudo -Hu nominatim bash --noprofile --norc -eo pipefail {0}
-
       steps:
-          - name: Prepare container (Ubuntu)
-            run: |
-                export APT_LISTCHANGES_FRONTEND=none
-                export DEBIAN_FRONTEND=noninteractive
-                apt-get update -qq
-                apt-get install -y git sudo wget
-                ln -snf /usr/share/zoneinfo/$CONTAINER_TIMEZONE /etc/localtime && echo $CONTAINER_TIMEZONE > /etc/timezone
-            shell: bash
-
-          - name: Setup import user
-            run: |
-                useradd -m nominatim
-                echo 'nominatim   ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/nominiatim
-                echo "/home/nominatim/Nominatim/vagrant/Install-on-${OS}.sh no $INSTALL_MODE" > /home/nominatim/vagrant.sh
-            shell: bash
-            env:
-              OS: ${{ matrix.name }}
-              INSTALL_MODE: ${{ matrix.install_mode }}
-
-          - uses: actions/download-artifact@v3
+          - uses: actions/download-artifact@v4
             with:
                 name: full-source
-                path: /home/nominatim
 
-          - name: Install Nominatim
-            run: |
-              export USERNAME=nominatim
-              export USERHOME=/home/nominatim
-              export NOSYSTEMD=yes
-              export HAVE_SELINUX=no
-              tar xf nominatim-src.tar.bz2
-              . vagrant.sh
-            working-directory: /home/nominatim
+          - name: Unpack Nominatim
+            run: tar xf nominatim-src.tar.bz2
+
+          - uses: ./Nominatim/.github/actions/setup-postgresql
+            with:
+                postgresql-version: 16
+                postgis-version: 3
+
+          - uses: ./Nominatim/.github/actions/build-nominatim
+            with:
+                flavour: ubuntu-22
+                lua: 5.3
 
           - name: Prepare import environment
             run: |
                 mv Nominatim/test/testdb/apidb-test-data.pbf test.pbf
-                mv Nominatim/settings/flex-base.lua flex-base.lua
-                mv Nominatim/settings/import-extratags.lua import-extratags.lua
-                mv Nominatim/settings/taginfo.lua taginfo.lua
                 rm -rf Nominatim
-                mkdir data-env-reverse
-            working-directory: /home/nominatim
 
           - name: Prepare Database
             run: |
                 nominatim import --prepare-database
-            working-directory: /home/nominatim/nominatim-project
 
           - name: Create import user
             run: |
-                sudo -u postgres createuser -S osm-import
-                sudo -u postgres psql -c "ALTER USER \"osm-import\" WITH PASSWORD 'osm-import';"
-            working-directory: /home/nominatim/nominatim-project
-
-          - name: Grant import user rights
-            run: |
-                sudo -u postgres psql -c "GRANT INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"osm-import\";"
-            working-directory: /home/nominatim/nominatim-project
+                sudo -u postgres createuser osm-import
+                psql -d nominatim -c "ALTER USER \"osm-import\" WITH PASSWORD 'osm-import'"
+                psql -d nominatim -c 'GRANT CREATE ON SCHEMA public TO "osm-import"'
 
           - name: Run import
             run: |
-                NOMINATIM_DATABASE_DSN="pgsql:host=127.0.0.1;dbname=nominatim;user=osm-import;password=osm-import" nominatim import --continue import-from-file --osm-file ../test.pbf
-            working-directory: /home/nominatim/nominatim-project
+                NOMINATIM_DATABASE_DSN="pgsql:host=127.0.0.1;dbname=nominatim;user=osm-import;password=osm-import" nominatim import --continue import-from-file --osm-file test.pbf
 
           - name: Check full import
             run: nominatim admin --check-database
-            working-directory: /home/nominatim/nominatim-project
\ No newline at end of file
index fc342e74c94023fe7c6b60a1faa401fee85538eb..4e29a75e50d2498eebeeb40ec50a68085bad1c76 100644 (file)
@@ -82,13 +82,14 @@ endif()
 
 # Setting PHP binary variable as to command line (prevailing) or auto detect
 
-if (BUILD_API OR BUILD_IMPORTER)
+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(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
+        message(WARNING "PHP binary not found. Only Python frontend can be used.")
+        set(PHP_BIN "")
     else()
         message (STATUS "Using PHP binary " ${PHP_BIN})
     endif()
@@ -247,7 +248,11 @@ if (BUILD_IMPORTER)
             PATTERN "paths.py" EXCLUDE
             PATTERN __pycache__ EXCLUDE)
 
-    configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed)
+    if (EXISTS ${PHP_BIN})
+        configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed)
+    else()
+        configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
+    endif()
     install(FILES ${PROJECT_BINARY_DIR}/paths-py.installed
             DESTINATION ${NOMINATIM_LIBDIR}/lib-python/nominatim
             RENAME paths.py)
@@ -275,7 +280,7 @@ if (BUILD_MODULE)
             DESTINATION ${NOMINATIM_LIBDIR}/module)
 endif()
 
-if (BUILD_API)
+if (BUILD_API AND EXISTS ${PHP_BIN})
     install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
 endif()
 
index 8baadb28e4d63bf31ef3230e4ec25a0686026f68..1df644e750e3c66fbee24102010e014e1faaffa3 100644 (file)
@@ -69,7 +69,7 @@ Before submitting a pull request make sure that the tests pass:
 
 Nominatim follows semantic versioning. Major releases are done for large changes
 that require (or at least strongly recommend) a reimport of the databases.
-Minor releases can usually be applied to exisiting databases. Patch releases
+Minor releases can usually be applied to existing databases. Patch releases
 contain bug fixes only and are released from a separate branch where the
 relevant changes are cherry-picked from the master branch.
 
index fae0d68f3897ede3bc93cd08fe32f435640d0e68..49fed459050e7a9330cbf1cdbf4408b3ab7ec823 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -23,7 +23,7 @@
  * new documentation section for library
  * various smaller fixes to existing documentation
    (thanks @woodpeck, @bloom256, @biswajit-k)
- * updates to vagrant install scripts, drop support for Ubunut 18
+ * updates to vagrant install scripts, drop support for Ubuntu 18
    (thanks @n-timofeev)
  * removed obsolete configuration variables from env.defaults
  * add script for generating a taginfo description (thanks @biswajit-k)
  * increase splitting for large geometries to improve indexing speed
  * remove deprecated get_magic_quotes_gpc() function
  * make sure that all postcodes have an entry in word and are thus searchable
- * remove use of ST_Covers in conjunction woth ST_Intersects,
+ * remove use of ST_Covers in conjunction with ST_Intersects,
    causes bad query planning and slow updates in Postgis3
  * update osm2pgsql
 
  * exclude postcode ranges separated by colon from centre point calculation
  * update osm2pgsql, better handling of imports without flatnode file
  * switch to more efficient algorithm for word set computation
- * use only boundries for country and state parts of addresses
+ * use only boundaries for country and state parts of addresses
  * improve updates of addresses with housenumbers and interpolations
  * remove country from place_addressline table and use country_code instead
  * optimise indexes on search_name partition tables
 
  * complete rewrite of reverse search algorithm
  * add new geojson and geocodejson output formats
- * add simple export script to exprot addresses to CSV
+ * add simple export script to export addresses to CSV
  * remove is_in terms from address computation
  * remove unused search_name_country tables
  * various smaller fixes to query parsing
  * move installation documentation into this repo
  * add self-documenting vagrant scripts
  * remove --create-website, recommend to use website directory in build
- * add accessor functions for URL parameters and improve erro checking
+ * add accessor functions for URL parameters and improve error checking
  * remove IP blocking and rate-limiting code
  * enable CI via travis
  * reformatting for more consistent coding style
  * update to refactored osm2pgsql which use libosmium based types
  * switch from osmosis to pyosmium for updates
  * be more strict when matching against special search terms
- * handle postcode entries with mutliple values correctly
+ * handle postcode entries with multiple values correctly
 
 2.5
 
index 57e64c2c090bcb3addc53e5ffac801f214ddf7d7..7f5f245931269c618d30c6706740b327ec39e66d 100644 (file)
@@ -38,7 +38,7 @@ Vagrant.configure("2") do |config|
     lv.memory = 2048
     lv.nested = true
     if ENV['CHECKOUT'] != 'y' then
-      override.vm.synced_folder ".", "/home/vagrant/Nominatim", type: 'nfs'
+      override.vm.synced_folder ".", "/home/vagrant/Nominatim", type: 'nfs', nfs_udp: false
     end
   end
 
diff --git a/cmake/paths-py-no-php.tmpl b/cmake/paths-py-no-php.tmpl
new file mode 100644 (file)
index 0000000..36856bf
--- /dev/null
@@ -0,0 +1,15 @@
+# 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 = None
+SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
+DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
+CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
index 3b98fec39579a5b286542349525bcd1bd63bcc5f..8bca2783e34feb5bd621771e2e083f452f9cc3a3 100644 (file)
@@ -5,6 +5,35 @@ your Nominatim database. It is assumed that you have already successfully
 installed the Nominatim software itself, if not return to the 
 [installation page](Installation.md).
 
+## Importing with a database user without superuser rights
+
+Nominatim usually creates its own PostgreSQL database at the beginning of the
+import process. This makes usage easier for the user but means that the
+database user doing the import needs the appropriate rights.
+
+If you prefer to run the import with a database user with limited rights,
+you can do so by changing the import process as follows:
+
+1. Run the command for database preparation with a database user with
+   superuser rights. For example, to use a db user 'dbadmin' for a
+   database 'nominatim', execute:
+
+   ```
+   NOMINATIM_DATABASE_DSN="pgsql:dbname=nominatim;user=dbadmin" nominatim import --prepare-database
+   ```
+
+2. Grant the import user the right to create tables. For example, foe user 'import-user':
+
+   ```
+   psql -d nominatim -c 'GRANT CREATE ON SCHEMA public TO "import-user"'
+   ```
+
+3. Now run the reminder of the import with the import user:
+
+   ```
+   NOMINATIM_DATABASE_DSN="pgsql:dbname=nominatim;user=import-user" nominatim import --continue import-from-file --osm-file file.pbf
+   ```
+
 ## Importing multiple regions (without updates)
 
 To import multiple regions in your database you can simply give multiple
index 6fd24167d24433d0509bec5c35f7deb8825d3f9a..e95df00dbe235341a3e2c5434880f67426b73360 100644 (file)
@@ -44,7 +44,7 @@ Next you need to set up the service that runs the Nominatim frontend. This is
 easiest done with a systemd job.
 
 First you need to tell systemd to create a socket file to be used by
-hunicorn. Crate the following file `/etc/systemd/system/nominatim.socket`:
+hunicorn. Create the following file `/etc/systemd/system/nominatim.socket`:
 
 ``` systemd
 [Unit]
index d17a53edf006d770061d8d37fe8c56dd5a25e8d0..7730c6c5593b557291b0166746254f81549bfe07 100644 (file)
@@ -37,40 +37,6 @@ nominatim import --continue indexing
 Otherwise it's best to start the full setup from the beginning.
 
 
-### PHP "open_basedir restriction in effect" warnings
-
-    PHP Warning:  file_get_contents(): open_basedir restriction in effect.
-
-You need to adjust the
-[open_basedir](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)
-setting in your PHP configuration (`php.ini` file). By default this setting may
-look like this:
-
-    open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/
-
-Either add reported directories to the list or disable this setting temporarily
-by adding ";" at the beginning of the line. Don't forget to enable this setting
-again once you are done with the PHP command line operations.
-
-
-### PHP timezeone warnings
-
-The Apache log may contain lots of PHP warnings like this:
-    `PHP Warning:  date_default_timezone_set() function.`
-
-You should set the default time zone as instructed in the warning in
-your `php.ini` file. Find the entry about timezone and set it to
-something like this:
-
-    ; Defines the default timezone used by the date functions
-    ; https://php.net/date.timezone
-    date.timezone = 'America/Denver'
-
-Or
-
-```
-echo "date.timezone = 'America/Denver'" > /etc/php.d/timezone.ini
-```
 
 ### nominatim.so version mismatch
 
@@ -170,7 +136,7 @@ recreate `nominatim.so`. Try
    cmake $main_Nominatim_path && make
 ```
 
-### Setup.php fails with "DB Error: extension not found"
+### Setup fails with "DB Error: extension not found"
 
 Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
 See the installation instructions for a full list of required packages.
index 0fd5ec29b4256a357fee4b11bb036ec47aa8a0d3..b31066d34799f5b3f355fe57e7561aa6aa9bac92 100644 (file)
@@ -268,18 +268,26 @@ nominatim reverse --lat 51 --lon 45
 ```
 
 If you want to run Nominatim as a service, you need to make a choice between
-running the traditional PHP frontend or the new experimental Python frontend.
+running the modern Python frontend and the legacy PHP frontend.
 Make sure you have installed the right packages as per
 [Installation](Installation.md#software).
 
-#### Testing the PHP frontend
+#### Testing the Python frontend
 
-You can run a small test server with the PHP frontend like this:
+To run the test server against the Python frontend, you must choose a
+web framework to use, either starlette or falcon. Make sure the appropriate
+packages are installed. Then run
 
-```sh
+``` sh
 nominatim serve
 ```
 
+or, if you prefer to use Starlette instead of Falcon as webserver,
+
+``` sh
+nominatim serve --engine starlette
+```
+
 Go to `http://localhost:8088/status.php` and you should see the message `OK`.
 You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`
 or, for reverse-only installations a reverse query,
@@ -287,22 +295,14 @@ 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).
-
-#### Testing the Python frontend
-
-To run the test server against the Python frontend, you must choose a
-web framework to use, either starlette or falcon. Make sure the appropriate
-packages are installed. Then run
+[Deploy the Python frontend](Deployment-Python.md).
 
-``` sh
-nominatim serve --engine falcon
-```
+#### Testing the PHP frontend
 
-or
+You can run a small test server with the PHP frontend like this:
 
-``` sh
-nominatim serve --engine starlette
+```sh
+nominatim serve --engine php
 ```
 
 Go to `http://localhost:8088/status.php` and you should see the message `OK`.
@@ -312,7 +312,8 @@ 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 Python frontend](Deployment-Python.md).
+[Deploy the PHP frontend](Deployment-PHP.md).
+
 
 
 ## Enabling search by category phrases
index 89e56c6e8e165621504e85c308fbfede5057c39b..ef6bd08112532a07c00e1c1577f6a1fc850d4873 100644 (file)
@@ -55,23 +55,24 @@ For running Nominatim:
   * [PyYaml](https://pyyaml.org/) (5.1+)
   * [datrie](https://github.com/pytries/datrie)
 
-When running the PHP frontend:
-
-  * [PHP](https://php.net) (7.3+)
-  * PHP-pgsql
-  * PHP-intl (bundled with PHP)
-
 For running continuous updates:
 
   * [pyosmium](https://osmcode.org/pyosmium/)
 
-For running the experimental Python frontend:
+For running the Python frontend:
 
   * one of the following web frameworks:
     * [falcon](https://falconframework.org/) (3.0+)
     * [starlette](https://www.starlette.io/)
   * [uvicorn](https://www.uvicorn.org/)
 
+For running the legacy PHP frontend:
+
+  * [PHP](https://php.net) (7.3+)
+  * PHP-pgsql
+  * PHP-intl (bundled with PHP)
+
+
 For dependencies for running tests and building documentation, see
 the [Development section](../develop/Development-Environment.md).
 
index 6d281da8aa9850debd2cd0b3539a81d636bcba1c..216ce6e613907b5a96d035d4d0a06eedd53197b9 100644 (file)
@@ -165,7 +165,7 @@ The `railway` layer includes railway infrastructure like tracks.
 Note that in Nominatim's standard configuration, only very few railway
 features are imported into the database.
 
-The `natural` layer collects feautures like rivers, lakes and mountains while
+The `natural` layer collects features like rivers, lakes and mountains while
 the `manmade` layer functions as a catch-all for features not covered by the
 other layers.
 
index fe848a177d29ca6e26e71ff3f4b8e0c5f8aef0e1..da2a96b0725e5f5b692dcda91ade48b88c8e0239 100644 (file)
@@ -179,7 +179,7 @@ also excluded when the filter is set.
     This parameter should not be confused with the 'country' parameter of
     the structured query. The 'country' parameter contains a search term
     and will be handled with some fuzziness. The `countrycodes` parameter
-    is a hard filter and as such should be prefered. Having both parameters
+    is a hard filter and as such should be preferred. Having both parameters
     in the same query will work. If the parameters contradict each other,
     the search will come up empty.
 
@@ -203,7 +203,7 @@ The `railway` layer includes railway infrastructure like tracks.
 Note that in Nominatim's standard configuration, only very few railway
 features are imported into the database.
 
-The `natural` layer collects feautures like rivers, lakes and mountains while
+The `natural` layer collects features like rivers, lakes and mountains while
 the `manmade` layer functions as a catch-all for features not covered by the
 other layers.
 
@@ -217,7 +217,7 @@ the 'state', 'country' or 'city' part of an address. A featureType of
 settlement selects any human inhabited feature from 'state' down to
 'neighbourhood'.
 
-When featureType ist set, then results are automatically restricted
+When featureType is set, then results are automatically restricted
 to the address layer (see above).
 
 !!! tip
@@ -227,7 +227,7 @@ to the address layer (see above).
 
 | Parameter | Value | Default |
 |-----------| ----- | ------- |
-| exclude_place_ids | comma-separeted list of place ids |
+| exclude_place_ids | comma-separated list of place ids |
 
 If you do not want certain OSM objects to appear in the search
 result, give a comma separated list of the `place_id`s you want to skip.
@@ -248,7 +248,7 @@ box. `x` is longitude, `y` is latitude.
 | bounded   | 0 or 1 | 0       |
 
 When set to 1, then it turns the 'viewbox' parameter (see above) into
-a filter paramter, excluding any results outside the viewbox.
+a filter parameter, excluding any results outside the viewbox.
 
 When `bounded=1` is given and the viewbox is small enough, then an amenity-only
 search is allowed. Give the special keyword for the amenity in square
index e96f96e039baaacbf99c7316f698a2a05708430a..eb548e10c460560ea3d9c29ef4870f625a9dc2e4 100644 (file)
@@ -280,7 +280,7 @@ kinds of geometries can be used:
 * __relation_as_multipolygon__ creates a (Multi)Polygon from the ways in
   the relation. If the ways do not form a valid area, then the object is
   silently discarded.
-* __relation_as_multiline__ creates a (Mutli)LineString from the ways in
+* __relation_as_multiline__ creates a (Multi)LineString from the ways in
   the relations. Ways are combined as much as possible without any regards
   to their order in the relation.
 
index 2c7b687834ba7cd53308833d6179586813ba29aa..df336a71198b08e1d8276fcdb2c080f7bd4cb7ef 100644 (file)
@@ -394,7 +394,7 @@ The analyzer cannot be customized.
 ##### Postcode token analyzer
 
 The analyzer `postcodes` is pupose-made to analyze postcodes. It supports
-a 'lookup' varaint of the token, which produces variants with optional
+a 'lookup' variant of the token, which produces variants with optional
 spaces. Use together with the clean-postcodes sanitizer.
 
 The analyzer cannot be customized.
index 63b1c3c1db5833f1f53225252f424d426e0941b8..daadf8998ac7a40cddf4a008ebb499bbb0f34883 100644 (file)
@@ -129,7 +129,7 @@ sanitizers:
 !!! warning
     This example is just a simplified show case on how to create a sanitizer.
     It is not really read for real-world use: while the sanitizer would
-    correcly transform `West 5th Street` into `5th Street`. it would also
+    correctly transform `West 5th Street` into `5th Street`. it would also
     shorten a simple `North Street` to `Street`.
 
 For more sanitizer examples, have a look at the sanitizers provided by Nominatim.
index be13d94983c795865334d63e13e72eb785f281f1..97d40ab7375a2d6a426e74896c886608c5ab4ca2 100644 (file)
@@ -10,7 +10,7 @@ There are two kind of tests in this test suite. There are functional tests
 which test the API interface using a BDD test framework and there are unit
 tests for specific PHP functions.
 
-This test directory is sturctured as follows:
+This test directory is structured as follows:
 
 ```
  -+-   bdd         Functional API tests
index ade927c64130e1024c45f7c0f5899b7ff4d777ed..dbfcc8bed38ef64ac5eedcc615fdb7ca7a0ea66e 100644 (file)
@@ -18,7 +18,7 @@ elseif (has 'addr:place'?) then (yes)
      **with same name**;
      kill
   else (no)
-    :add addr:place to adress;
+    :add addr:place to address;
     :**Use closest place**\n**rank 16 to 25**;
      kill
   endif
index 7e8271a9c37f43d81dc9aa7bff675c0f33513a2c..dc201d349cc98fcc4c6ba21feac841ac3790d292 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="275px" preserveAspectRatio="none" style="width:785px;height:275px;background:#FFFFFF;" version="1.1" viewBox="0 0 785 275" width="785px" zoomAndPan="magnify"><defs><filter height="300%" id="f1b513ppngo123" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><ellipse cx="379.5" cy="20" fill="#000000" filter="url(#f1b513ppngo123)" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="118,50,218,50,230,62,218,74,118,74,106,62,118,50" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="172" y="84.2104">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="100" x="118" y="65.8081">has 'addr:street'?</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="108,105.7104,228,105.7104,240,118.5151,228,131.3198,108,131.3198,96,118.5151,108,105.7104" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="120" x="108" y="115.9209">street with that name</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="45" x="111" y="128.7256">nearby?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="76" y="115.9209">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="14" x="240" y="115.9209">no</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="150" x="11" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="130" x="21" y="162.4585">Use closest street</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="0" x="25" y="176.4272"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="116" x="25" y="176.4272">with same name</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="106" x="197" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="82" x="211" y="162.4585">Use closest</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="44" x="207" y="176.4272">street</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="427.75,50,523.75,50,535.75,62,523.75,74,427.75,74,415.75,62,427.75,50" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="479.75" y="84.2104">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="96" x="427.75" y="65.8081">has 'addr:place'?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="56" x="535.75" y="59.4058">otherwise</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="417.75,105.7104,533.75,105.7104,545.75,118.5151,533.75,131.3198,417.75,131.3198,405.75,118.5151,417.75,105.7104" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="116" x="417.75" y="115.9209">place with that name</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="45" x="420.75" y="128.7256">nearby?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="385.75" y="115.9209">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="14" x="545.75" y="115.9209">no</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="144" x="313" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="124" x="323" y="162.4585">Use closest place</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="0" x="327" y="176.4272"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="116" x="327" y="176.4272">with same name</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="33.9688" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="179" x="477" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="159" x="487" y="162.4585">add addr:place to adress</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="144" x="494.5" y="210.2886"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="124" x="504.5" y="231.4272">Use closest place</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="91" x="504.5" y="245.396">rank 16 to 25</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="102" x="666" y="157.5972"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="82" x="676" y="178.7358">Use closest</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="44" x="676" y="192.7046">street</text><line style="stroke:#383838;stroke-width:1.5;" x1="96" x2="86" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="86" x2="86" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="82,131.3198,86,141.3198,90,131.3198,86,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="240" x2="250" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="250" x2="250" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="246,131.3198,250,141.3198,254,131.3198,250,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="566.5" x2="566.5" y1="175.2886" y2="210.2886"/><polygon fill="#383838" points="562.5,200.2886,566.5,210.2886,570.5,200.2886,566.5,204.2886" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="405.75" x2="385" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="385" x2="385" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="381,131.3198,385,141.3198,389,131.3198,385,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="545.75" x2="566.5" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="566.5" x2="566.5" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="562.5,131.3198,566.5,141.3198,570.5,131.3198,566.5,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="168" x2="168" y1="74" y2="105.7104"/><polygon fill="#383838" points="164,95.7104,168,105.7104,172,95.7104,168,99.7104" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="475.75" x2="475.75" y1="74" y2="105.7104"/><polygon fill="#383838" points="471.75,95.7104,475.75,105.7104,479.75,95.7104,475.75,99.7104" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="230" x2="415.75" y1="62" y2="62"/><polygon fill="#383838" points="405.75,58,415.75,62,405.75,66,409.75,62" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="379.5" x2="379.5" y1="30" y2="35"/><line style="stroke:#383838;stroke-width:1.5;" x1="379.5" x2="168" y1="35" y2="35"/><line style="stroke:#383838;stroke-width:1.5;" x1="168" x2="168" y1="35" y2="50"/><polygon fill="#383838" points="164,40,168,50,172,40,168,44" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="535.75" x2="717" y1="62" y2="62"/><line style="stroke:#383838;stroke-width:1.5;" x1="717" x2="717" y1="62" y2="157.5972"/><polygon fill="#383838" points="713,147.5972,717,157.5972,721,147.5972,717,151.5972" style="stroke:#383838;stroke-width:1.0;"/><!--MD5=[e03d31a5684b671bb715075c57004ccb]
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="275px" preserveAspectRatio="none" style="width:785px;height:275px;background:#FFFFFF;" version="1.1" viewBox="0 0 785 275" width="785px" zoomAndPan="magnify"><defs><filter height="300%" id="f1b513ppngo123" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><ellipse cx="379.5" cy="20" fill="#000000" filter="url(#f1b513ppngo123)" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="118,50,218,50,230,62,218,74,118,74,106,62,118,50" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="172" y="84.2104">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="100" x="118" y="65.8081">has 'addr:street'?</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="108,105.7104,228,105.7104,240,118.5151,228,131.3198,108,131.3198,96,118.5151,108,105.7104" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="120" x="108" y="115.9209">street with that name</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="45" x="111" y="128.7256">nearby?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="76" y="115.9209">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="14" x="240" y="115.9209">no</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="150" x="11" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="130" x="21" y="162.4585">Use closest street</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="0" x="25" y="176.4272"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="116" x="25" y="176.4272">with same name</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="106" x="197" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="82" x="211" y="162.4585">Use closest</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="44" x="207" y="176.4272">street</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="427.75,50,523.75,50,535.75,62,523.75,74,427.75,74,415.75,62,427.75,50" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="479.75" y="84.2104">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="96" x="427.75" y="65.8081">has 'addr:place'?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="56" x="535.75" y="59.4058">otherwise</text><polygon fill="#F8F8F8" filter="url(#f1b513ppngo123)" points="417.75,105.7104,533.75,105.7104,545.75,118.5151,533.75,131.3198,417.75,131.3198,405.75,118.5151,417.75,105.7104" style="stroke:#383838;stroke-width:1.5;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="116" x="417.75" y="115.9209">place with that name</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="45" x="420.75" y="128.7256">nearby?</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="20" x="385.75" y="115.9209">yes</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="14" x="545.75" y="115.9209">no</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="144" x="313" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="124" x="323" y="162.4585">Use closest place</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="0" x="327" y="176.4272"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="116" x="327" y="176.4272">with same name</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="33.9688" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="179" x="477" y="141.3198"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="159" x="487" y="162.4585">add addr:place to address</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="144" x="494.5" y="210.2886"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="124" x="504.5" y="231.4272">Use closest place</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="91" x="504.5" y="245.396">rank 16 to 25</text><rect fill="#F8F8F8" filter="url(#f1b513ppngo123)" height="47.9375" rx="12.5" ry="12.5" style="stroke:#383838;stroke-width:1.5;" width="102" x="666" y="157.5972"/><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="82" x="676" y="178.7358">Use closest</text><text fill="#000000" font-family="sans-serif" font-size="12" font-weight="bold" lengthAdjust="spacing" textLength="44" x="676" y="192.7046">street</text><line style="stroke:#383838;stroke-width:1.5;" x1="96" x2="86" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="86" x2="86" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="82,131.3198,86,141.3198,90,131.3198,86,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="240" x2="250" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="250" x2="250" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="246,131.3198,250,141.3198,254,131.3198,250,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="566.5" x2="566.5" y1="175.2886" y2="210.2886"/><polygon fill="#383838" points="562.5,200.2886,566.5,210.2886,570.5,200.2886,566.5,204.2886" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="405.75" x2="385" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="385" x2="385" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="381,131.3198,385,141.3198,389,131.3198,385,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="545.75" x2="566.5" y1="118.5151" y2="118.5151"/><line style="stroke:#383838;stroke-width:1.5;" x1="566.5" x2="566.5" y1="118.5151" y2="141.3198"/><polygon fill="#383838" points="562.5,131.3198,566.5,141.3198,570.5,131.3198,566.5,135.3198" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="168" x2="168" y1="74" y2="105.7104"/><polygon fill="#383838" points="164,95.7104,168,105.7104,172,95.7104,168,99.7104" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="475.75" x2="475.75" y1="74" y2="105.7104"/><polygon fill="#383838" points="471.75,95.7104,475.75,105.7104,479.75,95.7104,475.75,99.7104" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="230" x2="415.75" y1="62" y2="62"/><polygon fill="#383838" points="405.75,58,415.75,62,405.75,66,409.75,62" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="379.5" x2="379.5" y1="30" y2="35"/><line style="stroke:#383838;stroke-width:1.5;" x1="379.5" x2="168" y1="35" y2="35"/><line style="stroke:#383838;stroke-width:1.5;" x1="168" x2="168" y1="35" y2="50"/><polygon fill="#383838" points="164,40,168,50,172,40,168,44" style="stroke:#383838;stroke-width:1.0;"/><line style="stroke:#383838;stroke-width:1.5;" x1="535.75" x2="717" y1="62" y2="62"/><line style="stroke:#383838;stroke-width:1.5;" x1="717" x2="717" y1="62" y2="157.5972"/><polygon fill="#383838" points="713,147.5972,717,157.5972,721,147.5972,717,151.5972" style="stroke:#383838;stroke-width:1.0;"/><!--MD5=[e03d31a5684b671bb715075c57004ccb]\r
 @startuml\r
 skinparam monochrome true\r
 \r
@@ -19,7 +19,7 @@ elseif (has 'addr:place'?) then (yes)
      **with same name**;\r
      kill\r
   else (no)\r
-    :add addr:place to adress;\r
+    :add addr:place to address;\r
     :**Use closest place**\n**rank 16 to 25**;\r
      kill\r
   endif\r
@@ -30,12 +30,12 @@ endif
 \r
 \r
 @enduml\r
-
-PlantUML version 1.2021.12(Tue Oct 05 18:01:58 CEST 2021)
-(GPL source distribution)
-Java Runtime: OpenJDK Runtime Environment
-JVM: OpenJDK 64-Bit Server VM
-Default Encoding: UTF-8
-Language: en
-Country: US
+\r
+PlantUML version 1.2021.12(Tue Oct 05 18:01:58 CEST 2021)\r
+(GPL source distribution)\r
+Java Runtime: OpenJDK Runtime Environment\r
+JVM: OpenJDK 64-Bit Server VM\r
+Default Encoding: UTF-8\r
+Language: en\r
+Country: US\r
 --></g></svg>
\ No newline at end of file
index f332640ff98f404080df41124fd0bf8b444d5c59..74465d1a45d7b72d8014896f660010ffeb4a1d4f 100644 (file)
@@ -22,8 +22,8 @@ nav:
         - 'Basic Installation': 'admin/Installation.md'
         - 'Import' : 'admin/Import.md'
         - 'Update' : 'admin/Update.md'
-        - 'Deploy (PHP frontend)' : 'admin/Deployment-PHP.md'
         - 'Deploy (Python frontend)' : 'admin/Deployment-Python.md'
+        - 'Deploy (PHP frontend)' : 'admin/Deployment-PHP.md'
         - 'Nominatim UI'  : 'admin/Setup-Nominatim-UI.md'
         - 'Advanced Installations' : 'admin/Advanced-Installations.md'
         - 'Maintenance' : 'admin/Maintenance.md'
index 6c089d824b99565a72ad9de6202c5fd1f6ae5b2b..9b2fb7737ff20f8724f5003aba13da14ab42cab8 100644 (file)
@@ -130,7 +130,7 @@ BEGIN
 
   -- Still nothing? Fall back to a default.
   IF result.importance is null THEN
-    result.importance := 0.75001 - (rank_search::float / 40);
+    result.importance := 0.40001 - (rank_search::float / 75);
   END IF;
 
 {% if 'secondary_importance' in db.tables %}
index f3b6ab2b4e7f5eff2482eb45792a2f032f62b363..5b4a844138138fe2cbd72115b92d7a84b43a6199 100644 (file)
@@ -296,7 +296,9 @@ BEGIN
           extratags = NEW.extratags,
           admin_level = NEW.admin_level,
           indexed_status = 2,
-          geometry = NEW.geometry
+          geometry = CASE WHEN existingplacex.rank_address = 0
+                          THEN simplify_large_polygons(NEW.geometry)
+                          ELSE NEW.geometry END
       WHERE place_id = existingplacex.place_id;
 
     -- Invalidate linked places: they potentially get a new name and addresses.
index 841b7fd627196718de1c9e8067afb99e868c64ed..0f74336fbc7d2312140219fb30ad547b7c9267cf 100644 (file)
@@ -2,7 +2,7 @@
 --
 -- This file is part of Nominatim. (https://nominatim.org)
 --
--- Copyright (C) 2022 by the Nominatim developer community.
+-- Copyright (C) 2024 by the Nominatim developer community.
 -- For a full list of authors see the git log.
 
 -- Trigger functions for the placex table.
@@ -119,12 +119,14 @@ CREATE OR REPLACE FUNCTION find_associated_street(poi_osm_type CHAR(1),
   AS $$
 DECLARE
   location RECORD;
+  member JSONB;
   parent RECORD;
   result BIGINT;
   distance FLOAT;
   new_distance FLOAT;
   waygeom GEOMETRY;
 BEGIN
+{% if db.middle_db_format == '1' %}
   FOR location IN
     SELECT members FROM planet_osm_rels
     WHERE parts @> ARRAY[poi_osm_id]
@@ -161,6 +163,40 @@ BEGIN
     END LOOP;
   END LOOP;
 
+{% else %}
+  FOR member IN
+    SELECT value FROM planet_osm_rels r, LATERAL jsonb_array_elements(members)
+    WHERE planet_osm_member_ids(members, poi_osm_type::char(1)) && ARRAY[poi_osm_id]
+          and tags->>'type' = 'associatedStreet'
+          and value->>'role' = 'street'
+  LOOP
+    FOR parent IN
+      SELECT place_id, geometry
+       FROM placex
+       WHERE osm_type = (member->>'type')::char(1)
+             and osm_id = (member->>'ref')::bigint
+             and name is not null
+             and rank_search between 26 and 27
+    LOOP
+      -- Find the closest 'street' member.
+      -- Avoid distance computation for the frequent case where there is
+      -- only one street member.
+      IF waygeom is null THEN
+        result := parent.place_id;
+        waygeom := parent.geometry;
+      ELSE
+        distance := coalesce(distance, ST_Distance(waygeom, bbox));
+        new_distance := ST_Distance(parent.geometry, bbox);
+        IF new_distance < distance THEN
+          distance := new_distance;
+          result := parent.place_id;
+          waygeom := parent.geometry;
+        END IF;
+      END IF;
+    END LOOP;
+  END LOOP;
+{% endif %}
+
   RETURN result;
 END;
 $$
@@ -257,7 +293,11 @@ CREATE OR REPLACE FUNCTION find_linked_place(bnd placex)
   RETURNS placex
   AS $$
 DECLARE
+{% if db.middle_db_format == '1' %}
   relation_members TEXT[];
+{% else %}
+  relation_members JSONB;
+{% endif %}
   rel_member RECORD;
   linked_placex placex%ROWTYPE;
   bnd_name TEXT;
@@ -678,6 +718,12 @@ BEGIN
       NEW.country_code := NULL;
     END IF;
 
+    -- Simplify polygons with a very large memory footprint when they
+    -- do not take part in address computation.
+    IF NEW.rank_address = 0 THEN
+      NEW.geometry := simplify_large_polygons(NEW.geometry);
+    END IF;
+
   END IF;
 
   {% if debug %}RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;{% endif %}
@@ -749,7 +795,11 @@ CREATE OR REPLACE FUNCTION placex_update()
 DECLARE
   i INTEGER;
   location RECORD;
+{% if db.middle_db_format == '1' %}
   relation_members TEXT[];
+{% else %}
+  relation_member JSONB;
+{% endif %}
 
   geom GEOMETRY;
   parent_address_level SMALLINT;
@@ -794,6 +844,9 @@ BEGIN
   result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
 
   NEW.extratags := NEW.extratags - 'linked_place'::TEXT;
+  IF NEW.extratags = ''::hstore THEN
+    NEW.extratags := NULL;
+  END IF;
 
   -- NEW.linked_place_id contains the precomputed linkee. Save this and restore
   -- the previous link status.
@@ -968,6 +1021,7 @@ BEGIN
 
   -- waterway ways are linked when they are part of a relation and have the same class/type
   IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
+{% if db.middle_db_format == '1' %}
       FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
       LOOP
           FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
@@ -986,6 +1040,29 @@ BEGIN
               END IF;
           END LOOP;
       END LOOP;
+{% else %}
+    FOR relation_member IN
+      SELECT value FROM planet_osm_rels r, LATERAL jsonb_array_elements(r.members)
+      WHERE r.id = NEW.osm_id
+    LOOP
+      IF relation_member->>'role' IN ('', 'main_stream', 'side_stream')
+         and relation_member->>'type' = 'W'
+      THEN
+        {% if debug %}RAISE WARNING 'waterway parent %, child %', NEW.osm_id, relation_member;{% endif %}
+        FOR linked_node_id IN
+          SELECT place_id FROM placex
+          WHERE osm_type = 'W' and osm_id = (relation_member->>'ref')::bigint
+                and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
+                and (relation_member->>'role' != 'side_stream' or NEW.name->'name' = name->'name')
+        LOOP
+          UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
+          {% if 'search_name' in db.tables %}
+            DELETE FROM search_name WHERE place_id = linked_node_id;
+          {% endif %}
+        END LOOP;
+      END IF;
+    END LOOP;
+{% endif %}
       {% if debug %}RAISE WARNING 'Waterway processed';{% endif %}
   END IF;
 
@@ -1188,6 +1265,8 @@ BEGIN
     END IF;
   ELSEIF NEW.rank_address > 25 THEN
     max_rank := 25;
+  ELSEIF NEW.class in ('place','boundary') and NEW.type in ('postcode','postal_code') THEN
+    max_rank := NEW.rank_search;
   ELSE
     max_rank := NEW.rank_address;
   END IF;
index ff2f037d01dabdb86b40212fff372738727bfb0e..f8b365c582cce8cae569564bc9655335b8b69e1d 100644 (file)
@@ -73,6 +73,26 @@ END;
 $$
 LANGUAGE plpgsql IMMUTABLE;
 
+
+CREATE OR REPLACE FUNCTION get_rel_node_members(members JSONB, memberLabels TEXT[])
+  RETURNS SETOF BIGINT
+  AS $$
+DECLARE
+  member JSONB;
+BEGIN
+  FOR member IN SELECT * FROM jsonb_array_elements(members)
+  LOOP
+    IF member->>'type' = 'N' and member->>'role' = ANY(memberLabels) THEN
+        RETURN NEXT (member->>'ref')::bigint;
+    END IF;
+  END LOOP;
+
+  RETURN;
+END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+
 -- Copy 'name' to or from the default language.
 --
 -- \param country_code     Country code of the object being named.
@@ -416,6 +436,20 @@ END;
 $$
 LANGUAGE plpgsql IMMUTABLE;
 
+CREATE OR REPLACE FUNCTION simplify_large_polygons(geometry GEOMETRY)
+  RETURNS GEOMETRY
+  AS $$
+BEGIN
+  IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
+     and ST_MemSize(geometry) > 3000000
+  THEN
+    geometry := ST_SimplifyPreserveTopology(geometry, 0.0001);
+  END IF;
+  RETURN geometry;
+END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
 
 CREATE OR REPLACE FUNCTION place_force_delete(placeid BIGINT)
   RETURNS BOOLEAN
index ed078895ee8901473ac0f613b97e6d9cabe8c88e..b802a660e7c31446c5a62483707de1e4f771228f 100644 (file)
@@ -23,6 +23,10 @@ CREATE INDEX IF NOT EXISTS idx_placex_parent_place_id
 ---
 CREATE INDEX IF NOT EXISTS idx_placex_geometry ON placex
   USING GIST (geometry) {{db.tablespace.search_index}};
+-- Index is needed during import but can be dropped as soon as a full
+-- geometry index is in place. The partial index is almost as big as the full
+-- index.
+DROP INDEX IF EXISTS idx_placex_geometry_lower_rank_ways;
 ---
 CREATE INDEX IF NOT EXISTS idx_placex_geometry_reverse_lookupPolygon
   ON placex USING gist (geometry) {{db.tablespace.search_index}}
index 17216b50990dfd2b24e3b47b8969dac5904109c2..eafed6d8d2268e04c415d79b8d77847873161827 100644 (file)
@@ -298,7 +298,15 @@ CREATE TABLE IF NOT EXISTS wikipedia_redirect (
 
 -- osm2pgsql does not create indexes on the middle tables for Nominatim
 -- Add one for lookup of associated street relations.
-CREATE INDEX planet_osm_rels_parts_associated_idx ON planet_osm_rels USING gin(parts) WHERE tags @> ARRAY['associatedStreet'];
+{% if db.middle_db_format == '1' %}
+CREATE INDEX planet_osm_rels_parts_associated_idx ON planet_osm_rels USING gin(parts)
+  {{db.tablespace.address_index}}
+  WHERE tags @> ARRAY['associatedStreet'];
+{% else %}
+CREATE INDEX planet_osm_rels_relation_members_idx ON planet_osm_rels USING gin(planet_osm_member_ids(members, 'R'::character(1)))
+  WITH (fastupdate=off)
+  {{db.tablespace.address_index}};
+{% endif %}
 
 -- Needed for lookups if a node is part of an interpolation.
 CREATE INDEX IF NOT EXISTS idx_place_interpolations
index 8c8f56e1c264e937bf8617ab56b5e6e9d83325e2..c21d0510429910a94403203bfd2acf77df52fce1 100644 (file)
@@ -347,7 +347,7 @@ BEGIN
       END LOOP;
     END IF;
 
-    -- consider parts before an opening braket a full word as well
+    -- consider parts before an opening bracket a full word as well
     words := regexp_split_to_array(value, E'[(]');
     IF array_upper(words, 1) > 1 THEN
       s := make_standard_name(words[1]);
index 3481e647399b31cdd6c72201d34970b7a558fe17..333833b030f2d4b2d3775a91f64599768d0d7073 100644 (file)
@@ -374,7 +374,7 @@ class NominatimAPI:
         """ Close all active connections to the database.
 
             This function also closes the asynchronous worker loop making
-            the NominatimAPI object unusuable.
+            the NominatimAPI object unusable.
         """
         self._loop.run_until_complete(self._async_api.close())
         self._loop.close()
@@ -447,7 +447,7 @@ class NominatimAPI:
                   place. Only meaning full for POI-like objects (places with a
                   rank_address of 30).
               linked_place_id (Optional[int]): Internal ID of the place this object
-                  linkes to. When this ID is set then there is no guarantee that
+                  links to. When this ID is set then there is no guarantee that
                   the rest of the result information is complete.
               admin_level (int): Value of the `admin_level` OSM tag. Only meaningful
                   for administrative boundary objects.
index e16e0bd2d3bdbcab64b7f8c074ddbbe72cc4843e..30999a3f31282a085520baabf1a6b308f9d6ed7b 100644 (file)
@@ -13,6 +13,7 @@ import datetime as dt
 import textwrap
 import io
 import re
+import html
 
 import sqlalchemy as sa
 from sqlalchemy.ext.asyncio import AsyncConnection
@@ -83,7 +84,7 @@ class BaseLogger:
     def format_sql(self, conn: AsyncConnection, statement: 'sa.Executable',
                    extra_params: Union[Mapping[str, Any],
                                  Sequence[Mapping[str, Any]], None]) -> str:
-        """ Return the comiled version of the statement.
+        """ Return the compiled version of the statement.
         """
         compiled = cast('sa.ClauseElement', statement).compile(conn.sync_engine)
 
@@ -227,7 +228,7 @@ class HTMLLogger(BaseLogger):
                                HtmlFormatter(nowrap=True, lineseparator='<br />'))
             self._write(f'<div class="highlight"><code class="lang-sql">{sqlstr}</code></div>')
         else:
-            self._write(f'<code class="lang-sql">{sqlstr}</code>')
+            self._write(f'<code class="lang-sql">{html.escape(sqlstr)}</code>')
 
 
     def _python_var(self, var: Any) -> str:
@@ -235,7 +236,7 @@ class HTMLLogger(BaseLogger):
             fmt = highlight(str(var), PythonLexer(), HtmlFormatter(nowrap=True))
             return f'<div class="highlight"><code class="lang-python">{fmt}</code></div>'
 
-        return f'<code class="lang-python">{str(var)}</code>'
+        return f'<code class="lang-python">{html.escape(str(var))}</code>'
 
 
     def _write(self, text: str) -> None:
index a6bfa91c64fb5befb8fdb4dcb4b8acd0cbbb7eb3..4670a1d6d16a2766d8a589a60d25169cdf9b4beb 100644 (file)
@@ -5,7 +5,7 @@
 # Copyright (C) 2023 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """
-Helper classes and functions for formating results into API responses.
+Helper classes and functions for formatting results into API responses.
 """
 from typing import Type, TypeVar, Dict, List, Callable, Any, Mapping
 from collections import defaultdict
index 9b67c51aa580ef38befd5e9efb80ed7b7885b825..47fb85114634de804f36fa569dc79986894dccc4 100644 (file)
@@ -11,7 +11,7 @@ Data classes are part of the public API while the functions are for
 internal use only. That's why they are implemented as free-standing functions
 instead of member functions.
 """
-from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List, cast
+from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List, cast, Callable
 import enum
 import dataclasses
 import datetime as dt
@@ -233,7 +233,7 @@ class BaseResult:
             of the value or an artificial value computed from the place's
             search rank.
         """
-        return self.importance or (0.7500001 - (self.rank_search/40.0))
+        return self.importance or (0.40001 - (self.rank_search/75.0))
 
 
     def localize(self, locales: Locales) -> None:
@@ -466,7 +466,7 @@ async def add_result_details(conn: SearchConnection, results: List[BaseResultT],
 
 
 def _result_row_to_address_row(row: SaRow, isaddress: Optional[bool] = None) -> AddressLine:
-    """ Create a new AddressLine from the results of a datbase query.
+    """ Create a new AddressLine from the results of a database query.
     """
     extratags: Dict[str, str] = getattr(row, 'extratags', {}) or {}
     if 'linked_place' in extratags:
@@ -501,15 +501,17 @@ def _get_address_lookup_id(result: BaseResultT) -> int:
 
 async def _finalize_entry(conn: SearchConnection, result: BaseResultT) -> None:
     assert result.address_rows is not None
-    postcode = result.postcode
-    if not postcode and result.address:
-        postcode = result.address.get('postcode')
-    if postcode and ',' not in postcode and ';' not in postcode:
-        result.address_rows.append(AddressLine(
-            category=('place', 'postcode'),
-            names={'ref': postcode},
-            fromarea=False, isaddress=True, rank_address=5,
-            distance=0.0))
+    if result.category[0] not in ('boundary', 'place')\
+       or result.category[1] not in ('postal_code', 'postcode'):
+        postcode = result.postcode
+        if not postcode and result.address:
+            postcode = result.address.get('postcode')
+        if postcode and ',' not in postcode and ';' not in postcode:
+            result.address_rows.append(AddressLine(
+                category=('place', 'postcode'),
+                names={'ref': postcode},
+                fromarea=False, isaddress=True, rank_address=5,
+                distance=0.0))
     if result.country_code:
         async def _get_country_names() -> Optional[Dict[str, str]]:
             t = conn.t.country_name
@@ -551,7 +553,7 @@ def _setup_address_details(result: BaseResultT) -> None:
             extratags=result.extratags or {},
             admin_level=result.admin_level,
             fromarea=True, isaddress=True,
-            rank_address=result.rank_address or 100, distance=0.0))
+            rank_address=result.rank_address, distance=0.0))
     if result.source_table == SourceTable.PLACEX and result.address:
         housenumber = result.address.get('housenumber')\
                       or result.address.get('streetnumber')\
@@ -676,9 +678,12 @@ async def complete_address_details(conn: SearchConnection, results: List[BaseRes
                     rank_address=row.rank_address, distance=0.0))
 
     ### Now sort everything
+    def mk_sort_key(place_id: Optional[int]) -> Callable[[AddressLine], Tuple[bool, int, bool]]:
+        return lambda a: (a.place_id != place_id, -a.rank_address, a.isaddress)
+
     for result in results:
         assert result.address_rows is not None
-        result.address_rows.sort(key=lambda a: (-a.rank_address, a.isaddress))
+        result.address_rows.sort(key=mk_sort_key(result.place_id))
 
 
 def _placex_select_address_row(conn: SearchConnection,
index a2daee157eb032c943869f4c0cc0da7bf2883dae..e16742cfa34170f6a8afbd0da75b6c945d7635d7 100644 (file)
@@ -175,7 +175,7 @@ class ReverseGeocoder:
         t = self.conn.t.placex
 
         # PostgreSQL must not get the distance as a parameter because
-        # there is a danger it won't be able to proberly estimate index use
+        # there is a danger it won't be able to properly estimate index use
         # when used with prepared statements
         diststr = sa.text(f"{distance}")
 
index 6947a565f80dad421dcd9398975284988121a254..cd5717753ba722616084dc06bbfb76dda901c0fb 100644 (file)
@@ -94,7 +94,7 @@ class RankedTokens:
 
     def with_token(self, t: Token, transition_penalty: float) -> 'RankedTokens':
         """ Create a new RankedTokens list with the given token appended.
-            The tokens penalty as well as the given transision penalty
+            The tokens penalty as well as the given transition penalty
             are added to the overall penalty.
         """
         return RankedTokens(self.penalty + t.penalty + transition_penalty,
@@ -199,8 +199,7 @@ class SearchData:
             categories: Dict[Tuple[str, str], float] = {}
             min_penalty = 1000.0
             for t in tokens:
-                if t.penalty < min_penalty:
-                    min_penalty = t.penalty
+                min_penalty = min(min_penalty, t.penalty)
                 cat = t.get_category()
                 if t.penalty < categories.get(cat, 1000.0):
                     categories[cat] = t.penalty
index 555819e7451dac76042482cd101ffbdfd067c882..be883953276ccf74eed7497d0ece6caf9a275498 100644 (file)
@@ -5,7 +5,7 @@
 # Copyright (C) 2023 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """
-Implementation of the acutal database accesses for forward search.
+Implementation of the actual database accesses for forward search.
 """
 from typing import List, Tuple, AsyncIterator, Dict, Any, Callable, cast
 import abc
@@ -700,7 +700,7 @@ class PlaceSearch(AbstractSearch):
                or (details.viewbox is not None and details.viewbox.area < 0.5):
                 sql = sql.order_by(
                         penalty - sa.case((tsearch.c.importance > 0, tsearch.c.importance),
-                                    else_=0.75001-(sa.cast(tsearch.c.search_rank, sa.Float())/40)))
+                                    else_=0.40001-(sa.cast(tsearch.c.search_rank, sa.Float())/75)))
             sql = sql.add_columns(t.c.importance)
 
 
index e7984ee41832909fe608edd69dfc2dc6ec635a50..86d42a543d20ce8429770d16451e61f8b7ea1e4b 100644 (file)
@@ -44,7 +44,7 @@ class LegacyToken(qmod.Token):
 
     @property
     def info(self) -> Dict[str, Any]:
-        """ Dictionary of additional propoerties of the token.
+        """ Dictionary of additional properties of the token.
             Should only be used for debugging purposes.
         """
         return {'category': self.category,
index ad1b69ef521dfd303ae1d5b95aa8629859feb10b..333722fe44ffa94ed3816d8832b04fbc0b552e10 100644 (file)
@@ -169,7 +169,7 @@ class TokenList:
 
 @dataclasses.dataclass
 class QueryNode:
-    """ A node of the querry representing a break between terms.
+    """ A node of the query representing a break between terms.
     """
     btype: BreakType
     ptype: PhraseType
index bbc1eb6b1d787c483fc8086912279afda0a53b1a..3666b7fcf5c33cf33cd5ab416ede48a7d22f4d34 100644 (file)
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
     from nominatim.api.search.query import Phrase, QueryStruct
 
 class AbstractQueryAnalyzer(ABC):
-    """ Class for analysing incomming queries.
+    """ Class for analysing incoming queries.
 
         Query analyzers are tied to the tokenizer used on import.
     """
index 7a53a20efe35bd80c3f43cc68aeab5e27894983b..ca907b797dfe53b24e48d1602cc19ca97ff0fc2c 100644 (file)
@@ -72,7 +72,7 @@ class TokenAssignment: # pylint: disable=too-many-instance-attributes
 
 
 class _TokenSequence:
-    """ Working state used to put together the token assignements.
+    """ Working state used to put together the token assignments.
 
         Represents an intermediate state while traversing the tokenized
         query.
@@ -132,7 +132,7 @@ class _TokenSequence:
 
         # Name tokens are always acceptable and don't change direction
         if ttype == qmod.TokenType.PARTIAL:
-            # qualifiers cannot appear in the middle of the qeury. They need
+            # qualifiers cannot appear in the middle of the query. They need
             # to be near the next phrase.
             if self.direction == -1 \
                and any(t.ttype == qmod.TokenType.QUALIFIER for t in self.seq[:-1]):
@@ -238,10 +238,10 @@ class _TokenSequence:
 
     def recheck_sequence(self) -> bool:
         """ Check that the sequence is a fully valid token assignment
-            and addapt direction and penalties further if necessary.
+            and adapt direction and penalties further if necessary.
 
             This function catches some impossible assignments that need
-            forward context and can therefore not be exluded when building
+            forward context and can therefore not be excluded when building
             the assignment.
         """
         # housenumbers may not be further than 2 words from the beginning.
@@ -277,10 +277,10 @@ class _TokenSequence:
             # <address>,<postcode> should give preference to address search
             if base.postcode.start == 0:
                 penalty = self.penalty
-                self.direction = -1 # name searches are only possbile backwards
+                self.direction = -1 # name searches are only possible backwards
             else:
                 penalty = self.penalty + 0.1
-                self.direction = 1 # name searches are only possbile forwards
+                self.direction = 1 # name searches are only possible forwards
             yield dataclasses.replace(base, penalty=penalty)
 
 
index 8ac92f35835d0914e1d069b4cfef276ff95ac8e6..1069184c88e8bc9655111b737b3a060731d931e6 100644 (file)
@@ -5,7 +5,7 @@
 # Copyright (C) 2023 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """
-Classes and function releated to status call.
+Classes and function related to status call.
 """
 from typing import Optional
 import datetime as dt
index 5767fe1604a7d31e5b1adffdf3699adf8420a439..e93015fcd0a8ce9cbfa67b2cb602674695b4bb11 100644 (file)
@@ -316,7 +316,7 @@ class DataLayer(enum.Flag):
         for reverse and forward search.
     """
     ADDRESS = enum.auto()
-    """ The address layer contains all places relavant for addresses:
+    """ The address layer contains all places relevant for addresses:
         fully qualified addresses with a house number (or a house name equivalent,
         for some addresses) and places that can be part of an address like
         roads, cities, states.
@@ -415,7 +415,7 @@ class LookupDetails:
         more the geometry gets simplified.
     """
     locales: Locales = Locales()
-    """ Prefered languages for localization of results.
+    """ Preferred languages for localization of results.
     """
 
     @classmethod
@@ -544,7 +544,7 @@ class SearchDetails(LookupDetails):
 
 
     def layer_enabled(self, layer: DataLayer) -> bool:
-        """ Check if the given layer has been choosen. Also returns
+        """ Check if the given layer has been chosen. Also returns
             true when layer restriction has been disabled completely.
         """
         return self.layers is None or bool(self.layers & layer)
index 273fe2f5bf97ec39fb8bfa8dd5861779a8e429cd..b85d54011f5fc6c4eab0dd0a2681a5215dbaa52f 100644 (file)
@@ -5,7 +5,7 @@
 # Copyright (C) 2023 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """
-Hard-coded information about tag catagories.
+Hard-coded information about tag categories.
 
 These tables have been copied verbatim from the old PHP code. For future
 version a more flexible formatting is required.
@@ -44,7 +44,7 @@ def get_label_tag(category: Tuple[str, str], extratags: Optional[Mapping[str, st
 def bbox_from_result(result: Union[napi.ReverseResult, napi.SearchResult]) -> napi.Bbox:
     """ Compute a bounding box for the result. For ways and relations
         a given boundingbox is used. For all other object, a box is computed
-        around the centroid according to dimensions dereived from the
+        around the centroid according to dimensions derived from the
         search rank.
     """
     if (result.osm_object and result.osm_object[0] == 'N') or result.bbox is None:
index 896a131cc8c98da4eb982e69068e4f3df6cee2a8..ffd06a6a35e3439d4b47ae92a4bcd4d321506521 100644 (file)
@@ -155,7 +155,7 @@ COORD_REGEX = [re.compile(r'(?:(?P<pre>.*?)\s+)??' + r + r'(?:\s+(?P<post>.*))?'
 )]
 
 def extract_coords_from_query(query: str) -> Tuple[str, Optional[float], Optional[float]]:
-    """ Look for something that is formated like a coordinate at the
+    """ Look for something that is formatted like a coordinate at the
         beginning or end of the query. If found, extract the coordinate and
         return the remaining query (or the empty string if the query
         consisted of nothing but a coordinate).
index 70f7dc40611f9ba552d8e8397922b1c0fc78e61a..f08e804292a4e674a0266e9c4454b00dbd8d8feb 100644 (file)
@@ -240,7 +240,7 @@ class ASGIAdaptor(abc.ABC):
 
 
     def parse_geometry_details(self, fmt: str) -> Dict[str, Any]:
-        """ Create details strucutre from the supplied geometry parameters.
+        """ Create details structure from the supplied geometry parameters.
         """
         numgeoms = 0
         output = napi.GeometryFormat.NONE
@@ -531,7 +531,7 @@ async def deletable_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -
 async def polygons_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) -> Any:
     """ Server glue for /polygons endpoint.
         This is a special endpoint that shows polygons that have changed
-        thier size but are kept in the Nominatim database with their
+        their size but are kept in the Nominatim database with their
         old area to minimize disruption.
     """
     fmt = params.parse_format(RawDataList, 'json')
index 88a6078284424b4dc3beacf7d45757eddc1af3bb..720a8ece33e8e126422490735cdd7c055653ea4c 100644 (file)
@@ -159,13 +159,15 @@ class AdminServe:
         group = parser.add_argument_group('Server arguments')
         group.add_argument('--server', default='127.0.0.1:8088',
                            help='The address the server will listen to.')
-        group.add_argument('--engine', default='php',
+        group.add_argument('--engine', default='falcon',
                            choices=('php', 'falcon', 'starlette'),
-                           help='Webserver framework to run. (default: php)')
+                           help='Webserver framework to run. (default: falcon)')
 
 
     def run(self, args: NominatimArgs) -> int:
         if args.engine == 'php':
+            if args.config.lib_dir.php is None:
+                raise UsageError("PHP frontend not configured.")
             run_php_server(args.server, args.project_dir / 'website')
         else:
             import uvicorn # pylint: disable=import-outside-toplevel
index 67ca5bb10200aa1d095212898122569009e23d49..38a5a5b520470b0deff8a69365073f1830f93eb0 100644 (file)
@@ -39,8 +39,7 @@ class SetupAll:
     """
 
     def add_args(self, parser: argparse.ArgumentParser) -> None:
-        group_name = parser.add_argument_group('Required arguments')
-        group1 = group_name.add_argument_group()
+        group1 = parser.add_argument_group('Required arguments')
         group1.add_argument('--osm-file', metavar='FILE', action='append',
                            help='OSM file to be imported'
                                 ' (repeat for importing multiple files)',
index d86ce5ec15a92631430ecaacd637590fdd1aede5..4a4b9f290fc323dee3ce37e3509f1b48d49ba448 100644 (file)
@@ -5,7 +5,7 @@
 # Copyright (C) 2023 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """
-Import the base libary to use with asynchronous SQLAlchemy.
+Import the base library to use with asynchronous SQLAlchemy.
 """
 # pylint: disable=invalid-name
 
index 3762d82eff1a8a0b0a8e17223eb3f9f9c154e329..af5bc3357959abf52b9518e83144403e2150564b 100644 (file)
@@ -7,7 +7,7 @@
 """
 Preprocessing of SQL files.
 """
-from typing import Set, Dict, Any
+from typing import Set, Dict, Any, cast
 import jinja2
 
 from nominatim.db.connection import Connection
@@ -28,13 +28,24 @@ def _get_partitions(conn: Connection) -> Set[int]:
 
 def _get_tables(conn: Connection) -> Set[str]:
     """ Return the set of tables currently in use.
-        Only includes non-partitioned
     """
     with conn.cursor() as cur:
         cur.execute("SELECT tablename FROM pg_tables WHERE schemaname = 'public'")
 
         return set((row[0] for row in list(cur)))
 
+def _get_middle_db_format(conn: Connection, tables: Set[str]) -> str:
+    """ Returns the version of the slim middle tables.
+    """
+    if 'osm2pgsql_properties' not in tables:
+        return '1'
+
+    with conn.cursor() as cur:
+        cur.execute("SELECT value FROM osm2pgsql_properties WHERE property = 'db_format'")
+        row = cur.fetchone()
+
+        return cast(str, row[0]) if row is not None else '1'
+
 
 def _setup_tablespace_sql(config: Configuration) -> Dict[str, str]:
     """ Returns a dict with tablespace expressions for the different tablespace
@@ -84,6 +95,7 @@ class SQLPreprocessor:
         db_info['tables'] = _get_tables(conn)
         db_info['reverse_only'] = 'search_name' not in db_info['tables']
         db_info['tablespace'] = _setup_tablespace_sql(config)
+        db_info['middle_db_format'] = _get_middle_db_format(conn, db_info['tables'])
 
         self.env.globals['config'] = config
         self.env.globals['db'] = db_info
@@ -115,7 +127,7 @@ class SQLPreprocessor:
 
     def run_parallel_sql_file(self, dsn: str, name: str, num_threads: int = 1,
                               **kwargs: Any) -> None:
-        """ Execure the given SQL files using parallel asynchronous connections.
+        """ Execute the given SQL files using parallel asynchronous connections.
             The keyword arguments may supply additional parameters for
             preprocessing.
 
index 2134ae457b06ac263236f63927475a9fb33a7684..a56c04edc8f6a1eb2a1d3933cc4b5924a26cb38c 100644 (file)
@@ -26,7 +26,7 @@ def weigh_search(search_vector: Optional[str], rankings: str, default: float) ->
 
 class ArrayIntersectFuzzy:
     """ Compute the array of common elements of all input integer arrays.
-        Very large input paramenters may be ignored to speed up
+        Very large input parameters may be ignored to speed up
         computation. Therefore, the result is a superset of common elements.
 
         Input and output arrays are given as comma-separated lists.
index 1551c06257ff405aeca53a1b822ad2577c67ce7e..bdae350744e6774ff927f8bec597da8b05ee90e6 100644 (file)
@@ -128,7 +128,7 @@ class FileLoggingMiddleware:
                                resource: Optional[EndpointWrapper],
                                req_succeeded: bool) -> None:
         """ Callback after requests writes to the logfile. It only
-            writes logs for sucessful requests for search, reverse and lookup.
+            writes logs for successful requests for search, reverse and lookup.
         """
         if not req_succeeded or resource is None or resp.status != 200\
             or resource.name not in ('reverse', 'search', 'lookup', 'details'):
index 5a90edf58d849a9b0ec68d528cf42f6ad93d56b6..c1821d7edc7b88b2aa1f95797be2ddfce0ee0c85 100644 (file)
@@ -214,19 +214,20 @@ class ICUTokenizer(AbstractTokenizer):
             return list(s[0].split('@')[0] for s in cur)
 
 
-    def _install_php(self, phpdir: Path, overwrite: bool = True) -> None:
+    def _install_php(self, phpdir: Optional[Path], overwrite: bool = True) -> None:
         """ Install the php script for the tokenizer.
         """
-        assert self.loader is not None
-        php_file = self.data_dir / "tokenizer.php"
+        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')
+            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:
@@ -285,7 +286,7 @@ class ICUTokenizer(AbstractTokenizer):
 
 
     def _create_lookup_indices(self, config: Configuration, table_name: str) -> None:
-        """ Create addtional indexes used when running the API.
+        """ Create additional indexes used when running the API.
         """
         with connect(self.dsn) as conn:
             sqlp = SQLPreprocessor(conn, config)
index 2d28a8b29891623e72e7f8c2f0f7b7c9cafbd160..f3a00839aa2f0302ba611f0f0454f7fdf818c02e 100644 (file)
@@ -269,15 +269,16 @@ class LegacyTokenizer(AbstractTokenizer):
     def _install_php(self, config: Configuration, overwrite: bool = True) -> None:
         """ Install the php script for the tokenizer.
         """
-        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')
+        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:
index 2de868c787cb6f47d75039a1b239ab385f9853af..ac8d90e4677e726449f3a1889cdcae10ae12a1b7 100644 (file)
@@ -60,5 +60,5 @@ class SanitizerHandler(Protocol):
 
         Return:
             The result must be a callable that takes a place description
-            and transforms name and address as reuqired.
+            and transforms name and address as required.
         """
index 721f2ceea0630b61ea18b9cb117e34f7577a9421..8ffd93fe4c3f09932509ab24a92a13b0e41792a8 100644 (file)
@@ -127,7 +127,7 @@ def _get_indexes(conn: Connection) -> List[str]:
 
 # CHECK FUNCTIONS
 #
-# Functions are exectured in the order they appear here.
+# Functions are executed in the order they appear here.
 
 @_check(hint="""\
              {error}
index c8fda908c731324e28eb074e9fc0a31bd99f574e..779c55c7b00d835fd26eb6287674e8d1115e89c5 100644 (file)
@@ -78,7 +78,7 @@ def from_file_find_line_portion(
     filename: str, start: str, sep: str, fieldnum: int = 1
 ) -> Optional[str]:
     """open filename, finds the line starting with the 'start' string.
-    Splits the line using seperator and returns a "fieldnum" from the split."""
+    Splits the line using separator and returns a "fieldnum" from the split."""
     with open(filename, encoding='utf8') as file:
         result = ""
         for line in file:
index c742e3e0061d1d8778d55c8ab975b52873f3fc91..db89c38947d35a17f6e75a0964ea3b48a5aaa4ce 100644 (file)
@@ -31,7 +31,7 @@ def run_osm2pgsql(options: Mapping[str, Any]) -> None:
     """
     env = get_pg_env(options['dsn'])
     cmd = [str(options['osm2pgsql']),
-           '--hstore', '--latlon', '--slim',
+           '--slim',
            '--log-progress', 'true',
            '--number-processes', '1' if options['append'] else str(options['threads']),
            '--cache', str(options['osm2pgsql_cache']),
@@ -43,7 +43,7 @@ def run_osm2pgsql(options: Mapping[str, Any]) -> None:
                                     os.environ.get('LUAPATH', ';')))
         cmd.extend(('--output', 'flex'))
     else:
-        cmd.extend(('--output', 'gazetteer'))
+        cmd.extend(('--output', 'gazetteer', '--hstore', '--latlon'))
 
     cmd.append('--append' if options['append'] else '--create')
 
index 43e5b1eb387a4641ef49274bc63eaa485c171eda..008fc7143313469a7962934df9ded6c8415b3e7b 100644 (file)
@@ -213,6 +213,10 @@ def _quote_php_variable(var_type: Type[Any], config: Configuration,
 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()
index 415de9abdf2d003a5c0a0abe8e8fc139acacc2b5..cf66989fa2a47aa406f25c0be79e3b4e146284ee 160000 (submodule)
--- a/osm2pgsql
+++ b/osm2pgsql
@@ -1 +1 @@
-Subproject commit 415de9abdf2d003a5c0a0abe8e8fc139acacc2b5
+Subproject commit cf66989fa2a47aa406f25c0be79e3b4e146284ee
index dc2c12eef1f2d4bb4f2c67b075376ebcd2f4323c..4d960d7267a3cc502c57938a2ebb12057ca95b31 100644 (file)
@@ -5,6 +5,7 @@ local module = {}
 
 local PRE_DELETE = nil
 local PRE_EXTRAS = nil
+local POST_DELETE = nil
 local MAIN_KEYS = nil
 local NAMES = nil
 local ADDRESS_TAGS = nil
@@ -249,9 +250,9 @@ function Place:write_row(k, v, save_extra_mains)
     }
 
     if save_extra_mains then
-        for k, v in pairs(self.object.tags) do
-            if save_extra_mains(k, v) then
-                self.extratags[k] = nil
+        for tk, tv in pairs(self.object.tags) do
+            if save_extra_mains(tk, tv) then
+                self.extratags[tk] = nil
             end
         end
     end
@@ -539,7 +540,7 @@ function module.set_unused_handling(data)
     end
 end
 
-function set_relation_types(data)
+function module.set_relation_types(data)
     module.RELATION_TYPES = {}
     for k, v in data do
         if v == 'multipolygon' then
index 204bd1c8f56be7632195b92179ad408b4881ddef..830345c64e7d0749594a26a3db6fec22697f70de 100644 (file)
@@ -33,7 +33,8 @@ flex.set_main_tags{
     craft = 'always',
     junction = 'fallback',
     landuse = 'fallback',
-    leisure = 'always',
+    leisure = {'always',
+               nature_reserve = 'fallback'},
     office = 'always',
     mountain_pass = 'always',
     shop = 'always',
@@ -56,11 +57,12 @@ flex.set_prefilters{delete_keys = {'note', 'note:*', 'source', '*source', 'attri
                                    natural = {'yes', 'no', 'coastline'},
                                    highway = {'no', 'turning_circle', 'mini_roundabout',
                                               'noexit', 'crossing', 'give_way', 'stop'},
-                                   railway = {'level_crossing', 'no', 'rail'},
+                                   railway = {'level_crossing', 'no', 'rail', 'switch',
+                                              'abandoned', 'signal', 'buffer_stop', 'razed'},
                                    man_made = {'survey_point', 'cutline'},
                                    aerialway = {'pylon', 'no'},
                                    aeroway = {'no'},
-                                   amenity = {'no'},
+                                   amenity = {'no', 'parking_space', 'parking_entrance'},
                                    club = {'no'},
                                    craft = {'no'},
                                    leisure = {'no'},
@@ -72,7 +74,7 @@ flex.set_prefilters{delete_keys = {'note', 'note:*', 'source', '*source', 'attri
                                    tunnel = {'no'},
                                    waterway = {'riverbank'},
                                    building = {'no'},
-                                   boundary = {'place'}},
+                                   boundary = {'place', 'land_area'}},
                     extra_keys = {'*:prefix', '*:suffix', 'name:prefix:*', 'name:suffix:*',
                                'name:etymology', 'name:signed', 'name:botanical',
                                'wikidata', '*:wikidata',
index 1dc317a9bc0f5e1b4c9bcbd3d1ee4e1a6996f449..5b1ab060f1cbb437631472d92e278daddbc4ac80 100644 (file)
@@ -33,7 +33,8 @@ flex.set_main_tags{
     craft = 'always',
     junction = 'fallback',
     landuse = 'fallback',
-    leisure = 'always',
+    leisure = {'always',
+               nature_reserve = 'fallback'},
     office = 'always',
     mountain_pass = 'always',
     shop = 'always',
@@ -60,11 +61,12 @@ flex.set_prefilters{delete_keys = {'note', 'note:*', 'source', '*source', 'attri
                                    natural = {'yes', 'no', 'coastline'},
                                    highway = {'no', 'turning_circle', 'mini_roundabout',
                                               'noexit', 'crossing', 'give_way', 'stop'},
-                                   railway = {'level_crossing', 'no', 'rail'},
+                                   railway = {'level_crossing', 'no', 'rail', 'switch',
+                                              'abandoned', 'signal', 'buffer_stop', 'razed'},
                                    man_made = {'survey_point', 'cutline'},
                                    aerialway = {'pylon', 'no'},
                                    aeroway = {'no'},
-                                   amenity = {'no'},
+                                   amenity = {'no', 'parking_space', 'parking_entrance'},
                                    club = {'no'},
                                    craft = {'no'},
                                    leisure = {'no'},
@@ -76,7 +78,7 @@ flex.set_prefilters{delete_keys = {'note', 'note:*', 'source', '*source', 'attri
                                    tunnel = {'no'},
                                    waterway = {'riverbank'},
                                    building = {'no'},
-                                   boundary = {'place'}},
+                                   boundary = {'place', 'land_area'}},
                     extra_keys = {'wikidata', 'wikipedia', 'wikipedia:*'}
                    }
 
index 121271cdf1abd3eb04be9b9715c70de7715a15bb..655c639b5d244e8ae5b84a78dbfc68a8171968e2 100644 (file)
@@ -120,7 +120,7 @@ Feature: Simple Tests
           | querystring | pub |
           | viewbox     | 12,33,77,45.13 |
 
-    Scenario: Empty XML search with exluded place ids
+    Scenario: Empty XML search with excluded place ids
         When sending xml search query "jghrleoxsbwjer"
           | exclude_place_ids |
           | 123,76,342565 |
@@ -128,7 +128,7 @@ Feature: Simple Tests
           | attr              | value |
           | exclude_place_ids | 123,76,342565 |
 
-    Scenario: Empty XML search with bad exluded place ids
+    Scenario: Empty XML search with bad excluded place ids
         When sending xml search query "jghrleoxsbwjer"
           | exclude_place_ids |
           | , |
index e72ff448a87893ea1352e661ac7ad1aa26fdb557..8cc74eadd9f2f721b52a01ebd512ced82fb30a60 100644 (file)
@@ -56,7 +56,7 @@ Feature: Address computation
             | N1     | R1      | True |
             | N1     | R2      | True |
 
-    Scenario: with boundaries of same rank the one with the closer centroid is prefered
+    Scenario: with boundaries of same rank the one with the closer centroid is preferred
         Given the grid
             | 1 |   |   | 3 |  | 5 |
             |   | 9 |   |   |  |   |
index 539d928543e5942f3d3647ded9a3be010b3df24d..9204b3bb04d08870a47c9da17fac946926adcbc2 100644 (file)
@@ -258,7 +258,7 @@ Feature: Updates of linked places
         When marking for delete N1
         Then placex contains
             | object | extratags |
-            | R1     |  |
+            | R1     | - |
 
     Scenario: Update linked_place info when linkee type changes
         Given the 0.1 grid
index 664b5ac79e7d2013182ebff5036f04889870f586..460f3569d3f1a75a91b0e61ba986693f211cbc69 100644 (file)
@@ -28,7 +28,7 @@ userconfig = {
     'SERVER_MODULE_PATH' : None,
     'TOKENIZER' : None, # Test with a custom tokenizer
     'STYLE' : 'extratags',
-    'API_ENGINE': 'php',
+    'API_ENGINE': 'falcon',
     'PHPCOV' : False, # set to output directory to enable code coverage
 }
 
index a8fda5ffff11bc1567cce52fca55388266f959df..19c0406c61ec085c21558a0ea7f1a730a7cccee5 100644 (file)
@@ -21,7 +21,7 @@ class GeometryFactory:
             The function understands the following formats:
 
               country:<country code>
-                 Point geoemtry guaranteed to be in the given country
+                 Point geometry guaranteed to be in the given country
               <P>
                  Point geometry
               <P>,...,<P>
@@ -50,7 +50,7 @@ class GeometryFactory:
 
     def mk_wkt_point(self, point):
         """ Parse a point description.
-            The point may either consist of 'x y' cooordinates or a number
+            The point may either consist of 'x y' coordinates or a number
             that refers to a grid setup.
         """
         geom = point.strip()
index 11dede3049854a323388fceae13fa61b428fb689..1fc6f887ee0c11d66a6b2b9fa06ee29ada36608b 100644 (file)
@@ -270,8 +270,8 @@ class NominatimEnvironment:
             self.db_drop_database(self.test_db)
 
     def _reuse_or_drop_db(self, name):
-        """ Check for the existance of the given DB. If reuse is enabled,
-            then the function checks for existance and returns True if the
+        """ Check for the existence of the given DB. If reuse is enabled,
+            then the function checks for existnce and returns True if the
             database is already there. Otherwise an existing database is
             dropped and always false returned.
         """
index 3d3b16c76d4a365787413753bb719d96cbf44bf5..aa1b43b8493d387a7323c1fc5ff6b67eb5e7e53e 100644 (file)
@@ -243,7 +243,7 @@ def step_impl(context, fmt):
         try:
             tree = ET.fromstring(context.response.page)
         except Exception as ex:
-            assert False, f"Could not parse page:\n{context.response.page}"
+            assert False, f"Could not parse page: {ex}\n{context.response.page}"
 
         assert tree.tag == 'html'
         body = tree.find('./body')
index 14ae5d520684eb060ac9a9f6d5632de1399fde09..c30ee894280d4eb912a325d6669b0148e2c35d7c 100644 (file)
@@ -52,33 +52,52 @@ def add_data_to_planet_relations(context):
         for tests on data that looks up members.
     """
     with context.db.cursor() as cur:
-        for r in context.table:
-            last_node = 0
-            last_way = 0
-            parts = []
-            if r['members']:
-                members = []
-                for m in r['members'].split(','):
-                    mid = NominatimID(m)
-                    if mid.typ == 'N':
-                        parts.insert(last_node, int(mid.oid))
-                        last_node += 1
-                        last_way += 1
-                    elif mid.typ == 'W':
-                        parts.insert(last_way, int(mid.oid))
-                        last_way += 1
-                    else:
-                        parts.append(int(mid.oid))
-
-                    members.extend((mid.typ.lower() + mid.oid, mid.cls or ''))
-            else:
-                members = None
-
-            tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
-
-            cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off, parts, members, tags)
-                           VALUES (%s, %s, %s, %s, %s, %s)""",
-                        (r['id'], last_node, last_way, parts, members, list(tags)))
+        cur.execute("SELECT value FROM osm2pgsql_properties WHERE property = 'db_format'")
+        row = cur.fetchone()
+        if row is None or row[0] == '1':
+            for r in context.table:
+                last_node = 0
+                last_way = 0
+                parts = []
+                if r['members']:
+                    members = []
+                    for m in r['members'].split(','):
+                        mid = NominatimID(m)
+                        if mid.typ == 'N':
+                            parts.insert(last_node, int(mid.oid))
+                            last_node += 1
+                            last_way += 1
+                        elif mid.typ == 'W':
+                            parts.insert(last_way, int(mid.oid))
+                            last_way += 1
+                        else:
+                            parts.append(int(mid.oid))
+
+                        members.extend((mid.typ.lower() + mid.oid, mid.cls or ''))
+                else:
+                    members = None
+
+                tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
+
+                cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off, parts, members, tags)
+                               VALUES (%s, %s, %s, %s, %s, %s)""",
+                            (r['id'], last_node, last_way, parts, members, list(tags)))
+        else:
+            for r in context.table:
+                if r['members']:
+                    members = []
+                    for m in r['members'].split(','):
+                        mid = NominatimID(m)
+                        members.append({'ref': mid.oid, 'role': mid.cls or '', 'type': mid.typ})
+                else:
+                    members = []
+
+                tags = {h[5:]: r[h] for h in r.headings if h.startswith("tags+")}
+
+                cur.execute("""INSERT INTO planet_osm_rels (id, tags, members)
+                               VALUES (%s, %s, %s)""",
+                            (r['id'], psycopg2.extras.Json(tags),
+                             psycopg2.extras.Json(members)))
 
 @given("the ways")
 def add_data_to_planet_ways(context):
@@ -86,12 +105,19 @@ def add_data_to_planet_ways(context):
         tests on that that looks up node ids in this table.
     """
     with context.db.cursor() as cur:
+        cur.execute("SELECT value FROM osm2pgsql_properties WHERE property = 'db_format'")
+        row = cur.fetchone()
+        json_tags = row is not None and row[0] != '1'
         for r in context.table:
-            tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
+            if json_tags:
+                tags = psycopg2.extras.Json({h[5:]: r[h] for h in r.headings if h.startswith("tags+")})
+            else:
+                tags = list(chain.from_iterable([(h[5:], r[h])
+                                                 for h in r.headings if h.startswith("tags+")]))
             nodes = [ int(x.strip()) for x in r['nodes'].split(',') ]
 
             cur.execute("INSERT INTO planet_osm_ways (id, nodes, tags) VALUES (%s, %s, %s)",
-                        (r['id'], nodes, list(tags)))
+                        (r['id'], nodes, tags))
 
 ################################ WHEN ##################################
 
@@ -164,7 +190,7 @@ def delete_places(context, oids):
 def check_place_contents(context, table, exact):
     """ Check contents of place/placex tables. Each row represents a table row
         and all data must match. Data not present in the expected table, may
-        be arbitry. The rows are identified via the 'object' column which must
+        be arbitrary. The rows are identified via the 'object' column which must
         have an identifier of the form '<NRW><osm id>[:<class>]'. When multiple
         rows match (for example because 'class' was left out and there are
         multiple entries for the given OSM object) then all must match. All
@@ -211,7 +237,7 @@ def check_place_has_entry(context, table, oid):
 def check_search_name_contents(context, exclude):
     """ Check contents of place/placex tables. Each row represents a table row
         and all data must match. Data not present in the expected table, may
-        be arbitry. The rows are identified via the 'object' column which must
+        be arbitrary. The rows are identified via the 'object' column which must
         have an identifier of the form '<NRW><osm id>[:<class>]'. All
         expected rows are expected to be present with at least one database row.
     """
@@ -260,7 +286,7 @@ def check_search_name_has_entry(context, oid):
 def check_location_postcode(context):
     """ Check full contents for location_postcode table. Each row represents a table row
         and all data must match. Data not present in the expected table, may
-        be arbitry. The rows are identified via 'country' and 'postcode' columns.
+        be arbitrary. The rows are identified via 'country' and 'postcode' columns.
         All rows must be present as excepted and there must not be additional
         rows.
     """
@@ -317,7 +343,7 @@ def check_word_table_for_postcodes(context, exclude, postcodes):
 def check_place_addressline(context):
     """ Check the contents of the place_addressline table. Each row represents
         a table row and all data must match. Data not present in the expected
-        table, may be arbitry. The rows are identified via the 'object' column,
+        table, may be arbitrary. The rows are identified via the 'object' column,
         representing the addressee and the 'address' column, representing the
         address item.
     """
@@ -384,7 +410,7 @@ def check_location_property_osmline(context, oid, neg):
 def check_place_contents(context, exact):
     """ Check contents of the interpolation table. Each row represents a table row
         and all data must match. Data not present in the expected table, may
-        be arbitry. The rows are identified via the 'object' column which must
+        be arbitrary. The rows are identified via the 'object' column which must
         have an identifier of the form '<osm id>[:<startnumber>]'. When multiple
         rows match (for example because 'startnumber' was left out and there are
         multiple entries for the given OSM object) then all must match. All
index 336fb707038bc02d775f84c85ba3165f3b583221..2d17c1a0e1f2eacd68ad24a41037f23611859665 100644 (file)
@@ -75,7 +75,7 @@ def define_node_grid(context, grid_step, origin):
             # TODO coordinate
             coords = origin.split(',')
             if len(coords) != 2:
-                raise RuntimeError('Grid origin expects orgin with x,y coordinates.')
+                raise RuntimeError('Grid origin expects origin with x,y coordinates.')
             origin = (float(coords[0]), float(coords[1]))
         elif origin in ALIASES:
             origin = ALIASES[origin]
index 22dbaa2642d63a99ad227246c2c135139c087bcc..25f63bb8490a6964fc71ac94efa6aec96fcb28ff 100644 (file)
@@ -23,7 +23,7 @@ API_OPTIONS = {'search'}
 
 @pytest.fixture(autouse=True)
 def setup_icu_tokenizer(apiobj):
-    """ Setup the propoerties needed for using the ICU tokenizer.
+    """ Setup the properties needed for using the ICU tokenizer.
     """
     apiobj.add_data('properties',
                     [{'property': 'tokenizer', 'value': 'icu'},
index 0c54667ead80b86180ff8e1d654fec0ae17bc42e..0ff834a4549332ad589f39d958af17df00970649 100644 (file)
@@ -77,7 +77,7 @@ def test_search_details_minimal():
             'admin_level': 15,
             'names': {},
             'localname': '',
-            'calculated_importance': pytest.approx(0.0000001),
+            'calculated_importance': pytest.approx(0.00001),
             'rank_address': 30,
             'rank_search': 30,
             'isarea': False,
index 2a279028b370cb4815684609b3b4dc4365ad5c3b..54a5454945860618a61ebf01317871b9db90cc4d 100644 (file)
@@ -37,7 +37,7 @@ def test_minimal_detailed_result():
 
     assert res.lon == 23.1
     assert res.lat == 0.5
-    assert res.calculated_importance() == pytest.approx(0.0000001)
+    assert res.calculated_importance() == pytest.approx(0.00001)
 
 def test_detailed_result_custom_importance():
     res = DetailedResult(SourceTable.PLACEX,
index 93e8610887a94dd501d1b4fd3a203ed417267851..998bccb434c418eeaa076995f851a0cbaeab8ba9 100644 (file)
@@ -8,7 +8,7 @@
 Tests for command line interface wrapper.
 
 These tests just check that the various command line parameters route to the
-correct functionionality. They use a lot of monkeypatching to avoid executing
+correct functionality. They use a lot of monkeypatching to avoid executing
 the actual functions.
 """
 import importlib
@@ -63,7 +63,7 @@ def test_cli_add_data_tiger_data(cli_call, cli_tokenizer_mock, mock_func_factory
 def test_cli_serve_php(cli_call, mock_func_factory):
     func = mock_func_factory(nominatim.cli, 'run_php_server')
 
-    cli_call('serve') == 0
+    cli_call('serve', '--engine', 'php') == 0
 
     assert func.called == 1
 
index 45104ea6850fd75ea596bc7818774a715a17063f..cc80c19aac3735f5250224a87ef4b64c926fceef 100644 (file)
@@ -8,7 +8,7 @@
 Test for the command line interface wrapper admin subcommand.
 
 These tests just check that the various command line parameters route to the
-correct functionionality. They use a lot of monkeypatching to avoid executing
+correct functionality. They use a lot of monkeypatching to avoid executing
 the actual functions.
 """
 import pytest
index 720e80c890161296e520841004bffb58db564f61..57361ec7755cf1c2047120d6c3d1d768b680e1b6 100755 (executable)
@@ -16,16 +16,16 @@ export DEBIAN_FRONTEND=noninteractive #DOCS:
 # Make sure all packages are up-to-date by running:
 #
 
-    sudo apt update -qq
+    sudo apt-get update -qq
 
 # Now you can install all packages needed for Nominatim:
 
-    sudo apt install -y build-essential cmake g++ libboost-dev libboost-system-dev \
+    sudo apt-get install -y build-essential cmake g++ libboost-dev libboost-system-dev \
                         libboost-filesystem-dev libexpat1-dev zlib1g-dev \
                         libbz2-dev libpq-dev liblua5.3-dev lua5.3 lua-dkjson \
                         nlohmann-json3-dev postgresql-12-postgis-3 \
                         postgresql-contrib-12 postgresql-12-postgis-3-scripts \
-                        php-cli php-pgsql php-intl libicu-dev python3-dotenv \
+                        libicu-dev python3-dotenv \
                         python3-psycopg2 python3-psutil python3-jinja2 python3-pip \
                         python3-icu python3-datrie python3-yaml git
 
@@ -133,45 +133,107 @@ fi                                 #DOCS:
 
 # Nominatim is now ready to use. You can continue with
 # [importing a database from OSM data](../admin/Import.md). If you want to set up
-# a webserver first, continue reading.
+# the API frontend first, continue reading.
 #
+# Setting up the Python frontend
+# ==============================
+#
+# Some of the Python packages in Ubuntu are too old. Therefore run the
+# frontend from a Python virtualenv with current packages.
+#
+# To set up the virtualenv, run:
+
+#DOCS:```sh
+sudo apt-get install -y virtualenv
+virtualenv $USERHOME/nominatim-venv
+$USERHOME/nominatim-venv/bin/pip install SQLAlchemy PyICU psycopg[binary] \
+              psycopg2-binary python-dotenv PyYAML falcon uvicorn gunicorn
+#DOCS:```
+
+# Next you need to create a systemd job that runs Nominatim on gunicorn.
+# First create a systemd job that manages the socket file:
+
+#DOCS:```sh
+sudo tee /etc/systemd/system/nominatim.socket << EOFSOCKETSYSTEMD
+[Unit]
+Description=Gunicorn socket for Nominatim
+
+[Socket]
+ListenStream=/run/nominatim.sock
+SocketUser=www-data
+
+[Install]
+WantedBy=multi-user.target
+EOFSOCKETSYSTEMD
+#DOCS:```
+
+# Then create the service for Nominatim itself.
+
+#DOCS:```sh
+sudo tee /etc/systemd/system/nominatim.service << EOFNOMINATIMSYSTEMD
+[Unit]
+Description=Nominatim running as a gunicorn application
+After=network.target
+Requires=nominatim.socket
+
+[Service]
+Type=simple
+Environment="PYTHONPATH=/usr/local/lib/nominatim/lib-python/"
+User=www-data
+Group=www-data
+WorkingDirectory=$USERHOME/nominatim-project
+ExecStart=$USERHOME/nominatim-venv/bin/gunicorn -b unix:/run/nominatim.sock -w 4 -k uvicorn.workers.UvicornWorker nominatim.server.falcon.server:run_wsgi
+ExecReload=/bin/kill -s HUP \$MAINPID
+StandardOutput=append:/var/log/gunicorn-nominatim.log
+StandardError=inherit
+PrivateTmp=true
+TimeoutStopSec=5
+KillMode=mixed
+
+[Install]
+WantedBy=multi-user.target
+EOFNOMINATIMSYSTEMD
+#DOCS:```
+
+# Activate the services:
+
+if [ "x$NOSYSTEMD" != "xyes" ]; then  #DOCS:
+    sudo systemctl daemon-reload
+    sudo systemctl enable nominatim.socket
+    sudo systemctl start nominatim.socket
+    sudo systemctl enable nominatim.service
+fi                                    #DOCS:
+
+
 # Setting up a webserver
 # ======================
 #
-# The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/Import.md#creating-the-project-directory).
-# This directory needs to exist when being configured.
-# Therefore set up a project directory and create a website directory:
+# The webserver is only needed as a proxy between the public interface
+# and the gunicorn service.
+#
+# The frontend will need configuration information from the project
+# directory, which will be populated later
+# [during the import process](../admin/Import.md#creating-the-project-directory)
+# Already create the project directory itself now:
+
 
     mkdir $USERHOME/nominatim-project
-    mkdir $USERHOME/nominatim-project/website
 
-# The import process will populate the directory later.
 
-#
 # Option 1: Using Apache
 # ----------------------
 #
 if [ "x$2" == "xinstall-apache" ]; then #DOCS:
-#
-# Apache has a PHP module that can be used to serve Nominatim. To install them
-# run:
+# First install apache itself and enable the proxy module:
 
-    sudo apt install -y apache2 libapache2-mod-php
+    sudo apt-get install -y apache2
+    sudo a2enmod proxy_http
 
-# You need to create an alias to the website directory in your apache
-# configuration. Add a separate nominatim configuration to your webserver:
+# To set up proxying for Apache add the following configuration:
 
 #DOCS:```sh
 sudo tee /etc/apache2/conf-available/nominatim.conf << EOFAPACHECONF
-<Directory "$USERHOME/nominatim-project/website">
-  Options FollowSymLinks MultiViews
-  AddType text/html   .php
-  DirectoryIndex search.php
-  Require all granted
-</Directory>
-
-Alias /nominatim $USERHOME/nominatim-project/website
+ProxyPass /nominatim "unix:/run/nominatim.sock|http://localhost/"
 EOFAPACHECONF
 #DOCS:```
 
@@ -196,33 +258,9 @@ fi   #DOCS:
 #
 if [ "x$2" == "xinstall-nginx" ]; then #DOCS:
 
-# Nginx has no native support for php scripts. You need to set up php-fpm for
-# this purpose. First install nginx and php-fpm:
-
-    sudo apt install -y nginx php-fpm
+# First install nginx itself:
 
-# You need to configure php-fpm to listen on a Unix socket.
-
-#DOCS:```sh
-sudo tee /etc/php/7.4/fpm/pool.d/www.conf << EOF_PHP_FPM_CONF
-[www]
-; 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
-
-; Unix user of FPM processes
-user = www-data
-group = www-data
-
-; Choose process manager type (static, dynamic, ondemand)
-pm = ondemand
-pm.max_children = 5
-EOF_PHP_FPM_CONF
-#DOCS:```
+    sudo apt-get install -y nginx
 
 # Then create a Nginx configuration to forward http requests to that socket.
 
@@ -233,45 +271,26 @@ server {
     listen [::]:80 default_server;
 
     root $USERHOME/nominatim-project/website;
-    index search.php index.html;
-    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;
+    index /search;
+
+    location /nominatim/ {
+            proxy_set_header Host \$http_host;
+            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto \$scheme;
+            proxy_redirect off;
+            proxy_pass http://unix:/run/nominatim.sock:/;
     }
 }
 EOF_NGINX_CONF
 #DOCS:```
 
-# If you have some errors, make sure that php-fpm-nominatim.sock is well under
-# /var/run/ and not under /var/run/php. Otherwise change the Nginx configuration
-# to /var/run/php/php-fpm-nominatim.sock.
-#
 # Enable the configuration and restart Nginx
 #
 
 if [ "x$NOSYSTEMD" == "xyes" ]; then  #DOCS:
-    sudo /usr/sbin/php-fpm7.4 --nodaemonize --fpm-config /etc/php/7.4/fpm/php-fpm.conf & #DOCS:
     sudo /usr/sbin/nginx &            #DOCS:
 else                                  #DOCS:
-    sudo systemctl restart php7.4-fpm nginx
+    sudo systemctl restart nginx
 fi                                    #DOCS:
 
 # The Nominatim API is now available at `http://localhost/`.
index 174b8a771ab8ef95d277850c8699c7476ee9f48a..ae0c0dea25b12f09b172e11af2fe487ddb8cd033 100755 (executable)
@@ -16,19 +16,20 @@ export DEBIAN_FRONTEND=noninteractive #DOCS:
 # Make sure all packages are up-to-date by running:
 #
 
-    sudo apt update -qq
+    sudo apt-get update -qq
 
 # Now you can install all packages needed for Nominatim:
 
-    sudo apt install -y build-essential cmake g++ libboost-dev libboost-system-dev \
+    sudo apt-get install -y build-essential cmake g++ libboost-dev libboost-system-dev \
                         libboost-filesystem-dev libexpat1-dev zlib1g-dev \
                         libbz2-dev libpq-dev liblua5.3-dev lua5.3 lua-dkjson \
                         nlohmann-json3-dev postgresql-14-postgis-3 \
                         postgresql-contrib-14 postgresql-14-postgis-3-scripts \
-                        php-cli php-pgsql php-intl libicu-dev python3-dotenv \
+                        libicu-dev python3-dotenv \
                         python3-psycopg2 python3-psutil python3-jinja2 \
-                        python3-icu python3-datrie python3-sqlalchemy \
-                        python3-asyncpg python3-yaml git
+                        python3-sqlalchemy python3-asyncpg \
+                        python3-icu python3-datrie python3-yaml git
+
 
 #
 # System Configuration
@@ -128,20 +129,89 @@ fi                                 #DOCS:
 
 # Nominatim is now ready to use. You can continue with
 # [importing a database from OSM data](../admin/Import.md). If you want to set up
-# a webserver first, continue reading.
+# the API frontend first, continue reading.
+#
+# Setting up the Python frontend
+# ==============================
+#
+# Some of the Python packages in Ubuntu are too old. Therefore run the
+# frontend from a Python virtualenv with current packages.
 #
+# To set up the virtualenv, run:
+
+#DOCS:```sh
+sudo apt-get install -y virtualenv
+virtualenv $USERHOME/nominatim-venv
+$USERHOME/nominatim-venv/bin/pip install SQLAlchemy PyICU psycopg[binary] \
+              psycopg2-binary python-dotenv PyYAML falcon uvicorn gunicorn
+#DOCS:```
+
+# Next you need to create a systemd job that runs Nominatim on gunicorn.
+# First create a systemd job that manages the socket file:
+
+#DOCS:```sh
+sudo tee /etc/systemd/system/nominatim.socket << EOFSOCKETSYSTEMD
+[Unit]
+Description=Gunicorn socket for Nominatim
+
+[Socket]
+ListenStream=/run/nominatim.sock
+SocketUser=www-data
+
+[Install]
+WantedBy=multi-user.target
+EOFSOCKETSYSTEMD
+#DOCS:```
+
+# Then create the service for Nominatim itself.
+
+#DOCS:```sh
+sudo tee /etc/systemd/system/nominatim.service << EOFNOMINATIMSYSTEMD
+[Unit]
+Description=Nominatim running as a gunicorn application
+After=network.target
+Requires=nominatim.socket
+
+[Service]
+Type=simple
+Environment="PYTHONPATH=/usr/local/lib/nominatim/lib-python/"
+User=www-data
+Group=www-data
+WorkingDirectory=$USERHOME/nominatim-project
+ExecStart=$USERHOME/nominatim-venv/bin/gunicorn -b unix:/run/nominatim.sock -w 4 -k uvicorn.workers.UvicornWorker nominatim.server.falcon.server:run_wsgi
+ExecReload=/bin/kill -s HUP \$MAINPID
+StandardOutput=append:/var/log/gunicorn-nominatim.log
+StandardError=inherit
+PrivateTmp=true
+TimeoutStopSec=5
+KillMode=mixed
+
+[Install]
+WantedBy=multi-user.target
+EOFNOMINATIMSYSTEMD
+#DOCS:```
+
+# Activate the services:
+
+if [ "x$NOSYSTEMD" != "xyes" ]; then  #DOCS:
+    sudo systemctl daemon-reload
+    sudo systemctl enable nominatim.socket
+    sudo systemctl start nominatim.socket
+    sudo systemctl enable nominatim.service
+fi                                    #DOCS:
+
 # Setting up a webserver
 # ======================
 #
-# The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/Import.md#creating-the-project-directory).
-# This directory needs to exist when being configured.
-# Therefore set up a project directory and create a website directory:
+# The webserver is only needed as a proxy between the public interface
+# and the gunicorn service.
+#
+# The frontend will need configuration information from the project
+# directory, which will be populated later
+# [during the import process](../admin/Import.md#creating-the-project-directory)
+# Already create the project directory itself now:
 
     mkdir $USERHOME/nominatim-project
-    mkdir $USERHOME/nominatim-project/website
-
-# The import process will populate the directory later.
 
 #
 # Option 1: Using Apache
@@ -149,24 +219,18 @@ fi                                 #DOCS:
 #
 if [ "x$2" == "xinstall-apache" ]; then #DOCS:
 #
-# Apache has a PHP module that can be used to serve Nominatim. To install them
-# run:
+# First install apache itself and enable the proxy module:
 
-    sudo apt install -y apache2 libapache2-mod-php
+    sudo apt-get install -y apache2
+    sudo a2enmod proxy_http
 
-# You need to create an alias to the website directory in your apache
-# configuration. Add a separate nominatim configuration to your webserver:
+#
+# To set up proxying for Apache add the following configuration:
 
 #DOCS:```sh
 sudo tee /etc/apache2/conf-available/nominatim.conf << EOFAPACHECONF
-<Directory "$USERHOME/nominatim-project/website">
-  Options FollowSymLinks MultiViews
-  AddType text/html   .php
-  DirectoryIndex search.php
-  Require all granted
-</Directory>
-
-Alias /nominatim $USERHOME/nominatim-project/website
+
+ProxyPass /nominatim "unix:/run/nominatim.sock|http://localhost/"
 EOFAPACHECONF
 #DOCS:```
 
@@ -174,7 +238,10 @@ EOFAPACHECONF
 # Then enable the configuration and restart apache
 #
 
-    sudo a2enconf nominatim
+#DOCS:```sh
+sudo a2enconf nominatim
+#DOCS:```
+
 if [ "x$NOSYSTEMD" == "xyes" ]; then  #DOCS:
     sudo apache2ctl start             #DOCS:
 else                                  #DOCS:
@@ -191,33 +258,10 @@ fi   #DOCS:
 #
 if [ "x$2" == "xinstall-nginx" ]; then #DOCS:
 
-# Nginx has no native support for php scripts. You need to set up php-fpm for
-# this purpose. First install nginx and php-fpm:
+# First install nginx itself:
 
-    sudo apt install -y nginx php-fpm
+    sudo apt-get install -y nginx
 
-# You need to configure php-fpm to listen on a Unix socket.
-
-#DOCS:```sh
-sudo tee /etc/php/8.1/fpm/pool.d/www.conf << EOF_PHP_FPM_CONF
-[www]
-; 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
-
-; Unix user of FPM processes
-user = www-data
-group = www-data
-
-; Choose process manager type (static, dynamic, ondemand)
-pm = ondemand
-pm.max_children = 5
-EOF_PHP_FPM_CONF
-#DOCS:```
 
 # Then create a Nginx configuration to forward http requests to that socket.
 
@@ -228,49 +272,28 @@ server {
     listen [::]:80 default_server;
 
     root $USERHOME/nominatim-project/website;
-    index search.php index.html;
-    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;
+    index /search;
+
+    location /nominatim/ {
+            proxy_set_header Host \$http_host;
+            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto \$scheme;
+            proxy_redirect off;
+            proxy_pass http://unix:/run/nominatim.sock:/;
     }
 }
 EOF_NGINX_CONF
 #DOCS:```
 
-# If you have some errors, make sure that php-fpm-nominatim.sock is well under
-# /var/run/ and not under /var/run/php. Otherwise change the Nginx configuration
-# to /var/run/php/php-fpm-nominatim.sock.
-#
 # Enable the configuration and restart Nginx
 #
 
 if [ "x$NOSYSTEMD" == "xyes" ]; then  #DOCS:
-    sudo /usr/sbin/php-fpm8.1 --nodaemonize --fpm-config /etc/php/8.1/fpm/php-fpm.conf & #DOCS:
     sudo /usr/sbin/nginx &            #DOCS:
 else                                  #DOCS:
-    sudo systemctl restart php8.1-fpm nginx
+    sudo systemctl restart nginx
 fi                                    #DOCS:
 
-# The Nominatim API is now available at `http://localhost/`.
-
-
+# The Nominatim API is now available at `http://localhost/nominatim/`.
 
 fi   #DOCS: