using: "composite"
steps:
+ - name: Clean out the disk
+ run: |
+ sudo rm -rf /opt/hostedtoolcache/go /opt/hostedtoolcache/CodeQL /usr/lib/jvm /usr/local/share/chromium /usr/local/lib/android
+ df -h
+ shell: bash
- name: Install prerequisites
run: |
sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev libicu-dev
needs: create-archive
strategy:
matrix:
- ubuntu: [18, 20]
+ ubuntu: [18, 20, 22]
include:
- ubuntu: 18
postgresql: 9.6
postgis: 3
pytest: py.test-3
php: 7.4
+ - ubuntu: 22
+ postgresql: 14
+ postgis: 3
+ pytest: py.test-3
+ php: 8.1
runs-on: ubuntu-${{ matrix.ubuntu }}.04
with:
php-version: ${{ matrix.php }}
tools: phpunit, phpcs, composer
+ ini-values: opcache.jit=disable
- uses: actions/setup-python@v2
with:
if: matrix.ubuntu == 20
- name: Install test prerequsites
- run: pip3 install pytest behave==1.2.6
- if: matrix.ubuntu == 18
+ run: pip3 install pylint pytest behave==1.2.6
+ if: ${{ (matrix.ubuntu == 18) || (matrix.ubuntu == 22) }}
+
+ - name: Install test prerequsites
+ run: sudo apt-get install -y -qq python3-pytest
+ if: matrix.ubuntu == 22
- name: Install latest pylint
run: pip3 install pylint
- name: PHP unit tests
run: phpunit ./
working-directory: Nominatim/test/php
- if: matrix.ubuntu == 20
+ if: ${{ (matrix.ubuntu == 20) || (matrix.ubuntu == 22) }}
- name: Python unit tests
run: $PYTEST test/python
strategy:
matrix:
- name: [Ubuntu-18, Ubuntu-20]
+ name: [Ubuntu-18, Ubuntu-20, Ubuntu-22]
include:
- name: Ubuntu-18
flavour: ubuntu
image: "ubuntu:20.04"
ubuntu: 20
install_mode: install-apache
+ - name: Ubuntu-22
+ flavour: ubuntu
+ image: "ubuntu:22.04"
+ ubuntu: 22
+ install_mode: install-apache
container:
image: ${{ matrix.image }}
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-8.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-8.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-18.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-18.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-20.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-20.md
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-22.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-22.md
COMMAND PYTHONPATH=${PROJECT_SOURCE_DIR} mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
)
- 'Installation on CentOS 8' : 'appendix/Install-on-Centos-8.md'
- 'Installation on Ubuntu 18' : 'appendix/Install-on-Ubuntu-18.md'
- 'Installation on Ubuntu 20' : 'appendix/Install-on-Ubuntu-20.md'
+ - 'Installation on Ubuntu 22' : 'appendix/Install-on-Ubuntu-22.md'
markdown_extensions:
- codehilite
- admonition
return $sDefault;
}
- if (!in_array($this->aParams[$sName], $aValues)) {
+ if (!in_array($this->aParams[$sName], $aValues, true)) {
userError("Parameter '$sName' must be one of: ".join(', ', $aValues));
}
if ($this->bExtraTags) {
if ($aPlace['extra']) {
- $aPlace['sExtraTags'] = json_decode($aPlace['extra']);
+ $aPlace['sExtraTags'] = json_decode($aPlace['extra'], true);
} else {
$aPlace['sExtraTags'] = (object) array();
}
return (object) array();
}
- $aFullNames = json_decode($sNames);
+ $aFullNames = json_decode($sNames, true);
$aNames = array();
foreach ($aFullNames as $sKey => $sValue) {
return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
}
+ /**
+ * Custom search routine which takes two arrays. The array with the fewest
+ * items wins. If same number of items then the one with the longest first
+ * element wins.
+ */
public static function cmpByArraylen($aA, $aB)
{
$iALen = count($aA);
$iBLen = count($aB);
if ($iALen == $iBLen) {
- return 0;
+ return strlen($aB[0]) <=> strlen($aA[0]);
}
return ($iALen < $iBLen) ? -1 : 1;
}
if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson']);
+ $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
} else {
$aFilteredPlaces['geometry'] = array(
'type' => 'Point',
}
if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson']);
+ $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
} else {
$aFilteredPlaces['geometry'] = array(
'type' => 'Point',
}
if (isset($aPlace['asgeojson'])) {
- $aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson']);
+ $aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson'], true);
}
if (isset($aPlace['assvg'])) {
'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )
);
-$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson']);
+$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson'], true);
$funcMapAddressLine = function ($aFull) {
return array(
}
if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geojson'] = json_decode($aPointDetails['asgeojson']);
+ $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
}
if (isset($aPointDetails['assvg'])) {
}
if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geometry'] = json_decode($aPointDetails['asgeojson']);
+ $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
} else {
$aPlace['geometry'] = array(
'type' => 'Point',
}
if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geometry'] = json_decode($aPointDetails['asgeojson']);
+ $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
} else {
$aPlace['geometry'] = array(
'type' => 'Point',
}
if (isset($aPointDetails['asgeojson'])) {
- $aPlace['geojson'] = json_decode($aPointDetails['asgeojson']);
+ $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
}
if (isset($aPointDetails['assvg'])) {
@staticmethod
- def run(args):
- from ..tools import database_import, refresh, postcodes, freeze, country_info
+ def run(args): # pylint: disable=too-many-statements
+ from ..data import country_info
+ from ..tools import database_import, refresh, postcodes, freeze
from ..indexer.indexer import Indexer
country_info.setup_country_config(args.config)
the tokenizer.
"""
-import psycopg2.extras
-
class PlaceInfo:
""" Data class containing all information the tokenizer gets about a
place it should process the names for.
self._info = info
- def analyze(self, analyzer):
- """ Process this place with the given tokenizer and return the
- result in psycopg2-compatible Json.
- """
- return psycopg2.extras.Json(analyzer.process_place(self))
-
-
@property
def name(self):
""" A dictionary with the names of the place or None if the place
import re
from nominatim.errors import UsageError
-from nominatim.tools import country_info
+from nominatim.data import country_info
class CountryPostcodeMatcher:
""" Matches and formats a postcode according to a format definition
import functools
from psycopg2 import sql as pysql
+import psycopg2.extras
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
# pylint: disable=C0111
def _mk_valuelist(template, num):
return pysql.SQL(',').join([pysql.SQL(template)] * num)
+def _analyze_place(place, analyzer):
+ return psycopg2.extras.Json(analyzer.process_place(PlaceInfo(place)))
class AbstractPlacexRunner:
""" Returns SQL commands for indexing of the placex table.
for place in places:
for field in ('place_id', 'name', 'address', 'linked_place_id'):
values.append(place[field])
- values.append(PlaceInfo(place).analyze(self.analyzer))
+ values.append(_analyze_place(place, self.analyzer))
worker.perform(self._index_sql(len(places)), values)
values = []
for place in places:
values.extend((place[x] for x in ('place_id', 'address')))
- values.append(PlaceInfo(place).analyze(self.analyzer))
+ values.append(_analyze_place(place, self.analyzer))
worker.perform(self._index_sql(len(places)), values)
from typing import List, Tuple, Dict, Any
from nominatim.config import Configuration
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
# pylint: disable=unnecessary-pass
from nominatim.errors import UsageError
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
from nominatim.tokenizer.icu_token_analysis import ICUTokenAnalysis
-import nominatim.tools.country_info
+import nominatim.data.country_info
LOG = logging.getLogger()
config='TOKENIZER_CONFIG')
# Make sure country information is available to analyzers and sanitizers.
- nominatim.tools.country_info.setup_country_config(config)
+ nominatim.data.country_info.setup_country_config(config)
self.normalization_rules = self._cfg_to_icu_rules(rules, 'normalization')
self.transliteration_rules = self._cfg_to_icu_rules(rules, 'transliteration')
from nominatim.db.connection import connect
from nominatim.db.utils import CopyBuffer
from nominatim.db.sql_preprocessor import SQLPreprocessor
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from nominatim.tokenizer.icu_rule_loader import ICURuleLoader
from nominatim.tokenizer.base import AbstractAnalyzer, AbstractTokenizer
any analyzer tagged) is retained. (default: replace)
"""
-from nominatim.tools import country_info
+from nominatim.data import country_info
class _AnalyzerByLanguage:
""" Processor for tagging the language of names in a place.
import os
import tarfile
+from psycopg2.extras import Json
+
from nominatim.db.connection import connect
from nominatim.db.async_connection import WorkerPool
from nominatim.db.sql_preprocessor import SQLPreprocessor
from nominatim.errors import UsageError
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
LOG = logging.getLogger()
address = dict(street=row['street'], postcode=row['postcode'])
args = ('SRID=4326;' + row['geometry'],
int(row['from']), int(row['to']), row['interpolation'],
- PlaceInfo({'address': address}).analyze(analyzer),
+ Json(analyzer.process_place(PlaceInfo({'address': address}))),
analyzer.normalize_postcode(row['postcode']))
except ValueError:
continue
public function testGetSet()
{
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Parameter 'val3' must be one of: foo, bar");
+
$oParams = new ParameterParser(array(
'val1' => 'foo',
'val2' => '',
$this->assertSame('foo', $oParams->getSet('val1', array('foo', 'bar')));
$this->assertSame(false, $oParams->getSet('val2', array('foo', 'bar')));
- $this->assertSame(0, $oParams->getSet('val3', array('foo', 'bar')));
+ $oParams->getSet('val3', array('foo', 'bar'));
}
$oList = new SimpleWordList('a b c');
$this->assertEquals(
- '(a b c),(a|b c),(a b|c),(a|b|c)',
+ '(a b c),(a b|c),(a|b c),(a|b|c)',
$this->serializeSets($oList->getWordSets(new TokensFullSet()))
);
);
}
+ public function testCmpByArraylen()
+ {
+ // Array elements are phrases, we want to sort so longest phrases are first
+ $aList1 = array('hackney', 'bridge', 'london', 'england');
+ $aList2 = array('hackney', 'london', 'bridge');
+ $aList3 = array('bridge', 'hackney', 'london', 'england');
+
+ $this->assertEquals(0, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList1));
+
+ // list2 "wins". Less array elements
+ $this->assertEquals(1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList2));
+ $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList2, $aList3));
+
+ // list1 "wins". Same number of array elements but longer first element
+ $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList3));
+ }
public function testMaxWordSets()
{
import pytest
import nominatim.tools.database_import
-import nominatim.tools.country_info
+import nominatim.data.country_info
import nominatim.tools.refresh
import nominatim.tools.postcodes
import nominatim.indexer.indexer
def test_import_full(self, mock_func_factory, with_updates, place_table, property_table):
mocks = [
mock_func_factory(nominatim.tools.database_import, 'setup_database_skeleton'),
- mock_func_factory(nominatim.tools.country_info, 'setup_country_tables'),
+ mock_func_factory(nominatim.data.country_info, 'setup_country_tables'),
mock_func_factory(nominatim.tools.database_import, 'import_osm_data'),
mock_func_factory(nominatim.tools.refresh, 'import_wikipedia_articles'),
mock_func_factory(nominatim.tools.database_import, 'truncate_data_tables'),
mock_func_factory(nominatim.tools.database_import, 'create_table_triggers'),
mock_func_factory(nominatim.tools.database_import, 'create_partition_tables'),
mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
- mock_func_factory(nominatim.tools.country_info, 'create_country_names'),
+ mock_func_factory(nominatim.data.country_info, 'create_country_names'),
mock_func_factory(nominatim.tools.refresh, 'load_address_levels_from_config'),
mock_func_factory(nominatim.tools.postcodes, 'update_postcodes'),
mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
mock_func_factory(nominatim.tools.database_import, 'truncate_data_tables'),
mock_func_factory(nominatim.tools.database_import, 'load_data'),
mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
- mock_func_factory(nominatim.tools.country_info, 'create_country_names'),
+ mock_func_factory(nominatim.data.country_info, 'create_country_names'),
mock_func_factory(nominatim.tools.postcodes, 'update_postcodes'),
mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
mock_func_factory(nominatim.tools.refresh, 'setup_website'),
temp_db_conn):
mocks = [
mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
- mock_func_factory(nominatim.tools.country_info, 'create_country_names'),
+ mock_func_factory(nominatim.data.country_info, 'create_country_names'),
mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
mock_func_factory(nominatim.tools.refresh, 'setup_website'),
mock_func_factory(nominatim.db.properties, 'set_property')
def test_import_continue_postprocess(self, mock_func_factory):
mocks = [
mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
- mock_func_factory(nominatim.tools.country_info, 'create_country_names'),
+ mock_func_factory(nominatim.data.country_info, 'create_country_names'),
mock_func_factory(nominatim.tools.refresh, 'setup_website'),
mock_func_factory(nominatim.db.properties, 'set_property')
]
from textwrap import dedent
import pytest
-from nominatim.tools import country_info
+from nominatim.data import country_info
@pytest.fixture
def loaded_country(def_config):
@pytest.fixture(autouse=True)
def setup_test_table(self, table_factory):
- table_factory(self.TABLE_NAME, 'colA INT, colB TEXT')
+ table_factory(self.TABLE_NAME, 'col_a INT, col_b TEXT')
def table_rows(self, cursor):
buf.add('foo')
buf.copy_out(temp_db_cursor, self.TABLE_NAME,
- columns=['colB'])
+ columns=['col_b'])
assert self.table_rows(temp_db_cursor) == {(None, 'foo')}
buf.add(' two ', 2)
buf.copy_out(temp_db_cursor, self.TABLE_NAME,
- columns=['colB', 'colA'])
+ columns=['col_b', 'col_a'])
assert self.table_rows(temp_db_cursor) == {(1, 'one'), (2, ' two ')}
buf.add('\\N')
buf.copy_out(temp_db_cursor, self.TABLE_NAME,
- columns=['colB'])
+ columns=['col_b'])
assert self.table_rows(temp_db_cursor) == {(None, 'foo\tbar'),
(None, 'sun\nson'),
@pytest.fixture(autouse=True)
def setup_test_table(self, table_factory):
- table_factory(self.TABLE_NAME, 'colA INT, colB JSONB')
+ table_factory(self.TABLE_NAME, 'col_a INT, col_b JSONB')
def table_rows(self, cursor):
"""
Tokenizer for testing.
"""
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from nominatim.config import Configuration
def create(dsn, data_dir):
import pytest
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
@pytest.fixture
def sanitize(request):
import pytest
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
-from nominatim.indexer.place_info import PlaceInfo
-from nominatim.tools import country_info
+from nominatim.data.place_info import PlaceInfo
+from nominatim.data import country_info
@pytest.fixture
def sanitize(def_config, request):
import pytest
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from nominatim.errors import UsageError
import pytest
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
def run_sanitizer_on(**kwargs):
place = PlaceInfo({'name': kwargs})
"""
import pytest
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from nominatim.tokenizer.place_sanitizer import PlaceSanitizer
-from nominatim.tools.country_info import setup_country_config
+from nominatim.data.country_info import setup_country_config
class TestWithDefaults:
import nominatim.tokenizer.icu_rule_loader
from nominatim.db import properties
from nominatim.db.sql_preprocessor import SQLPreprocessor
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from mock_icu_word_table import MockIcuWordTable
import pytest
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
from nominatim.tokenizer import legacy_tokenizer
from nominatim.db import properties
from nominatim.errors import UsageError
from nominatim.errors import UsageError
import nominatim.tokenizer.place_sanitizer as sanitizer
-from nominatim.indexer.place_info import PlaceInfo
+from nominatim.data.place_info import PlaceInfo
def test_placeinfo_clone_new_name():
@staticmethod
def test_fail_on_error_output(tmp_path):
- (tmp_path / 'website' / 'bad.php').write_text("<?php\nfwrite(STDERR, 'WARNING'.PHP_EOL);")
+ # Starting PHP 8 the PHP CLI no longer has STDERR defined as constant
+ php = """
+ <?php
+ if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'wb'));
+ fwrite(STDERR, 'WARNING'.PHP_EOL);
+ """
+ (tmp_path / 'website' / 'bad.php').write_text(php)
assert exec_utils.run_api_script('bad', tmp_path) == 1
import pytest
-from nominatim.tools import postcodes, country_info
+from nominatim.tools import postcodes
+from nominatim.data import country_info
import dummy_tokenizer
class MockPostcodeTable:
--- /dev/null
+#!/bin/bash -e
+#
+# hacks for broken vagrant box #DOCS:
+sudo rm -f /var/lib/dpkg/lock #DOCS:
+export APT_LISTCHANGES_FRONTEND=none #DOCS:
+export DEBIAN_FRONTEND=noninteractive #DOCS:
+
+# *Note:* these installation instructions are also available in executable
+# form for use with vagrant under vagrant/Install-on-Ubuntu-22.sh.
+#
+# Installing the Required Software
+# ================================
+#
+# These instructions expect that you have a freshly installed Ubuntu 22.04.
+#
+# Make sure all packages are up-to-date by running:
+#
+
+ sudo apt update -qq
+
+# Now you can install all packages needed for Nominatim:
+
+ sudo apt install -y php-cgi
+ sudo apt install -y build-essential cmake g++ libboost-dev libboost-system-dev \
+ libboost-filesystem-dev libexpat1-dev zlib1g-dev \
+ libbz2-dev libpq-dev libproj-dev \
+ postgresql-server-dev-14 postgresql-14-postgis-3 \
+ postgresql-contrib-14 postgresql-14-postgis-3-scripts \
+ php php-pgsql php-intl libicu-dev python3-dotenv \
+ python3-psycopg2 python3-psutil python3-jinja2 \
+ python3-icu python3-datrie git
+
+#
+# System Configuration
+# ====================
+#
+# The following steps are meant to configure a fresh Ubuntu installation
+# for use with Nominatim. You may skip some of the steps if you have your
+# OS already configured.
+#
+# Creating Dedicated User Accounts
+# --------------------------------
+#
+# Nominatim will run as a global service on your machine. It is therefore
+# best to install it under its own separate user account. In the following
+# we assume this user is called nominatim and the installation will be in
+# /srv/nominatim. To create the user and directory run:
+#
+# sudo useradd -d /srv/nominatim -s /bin/bash -m nominatim
+#
+# You may find a more suitable location if you wish.
+#
+# To be able to copy and paste instructions from this manual, export
+# user name and home directory now like this:
+#
+if [ "x$USERNAME" == "x" ]; then #DOCS:
+ export USERNAME=vagrant #DOCS: export USERNAME=nominatim
+ export USERHOME=/home/vagrant #DOCS: export USERHOME=/srv/nominatim
+fi #DOCS:
+#
+# **Never, ever run the installation as a root user.** You have been warned.
+#
+# Make sure that system servers can read from the home directory:
+
+ chmod a+x $USERHOME
+
+# Setting up PostgreSQL
+# ---------------------
+#
+# Tune the postgresql configuration, which is located in
+# `/etc/postgresql/14/main/postgresql.conf`. See section *Postgres Tuning* in
+# [the installation page](../admin/Installation.md#postgresql-tuning)
+# for the parameters to change.
+#
+# Restart the postgresql service after updating this config file.
+
+if [ "x$NOSYSTEMD" == "xyes" ]; then #DOCS:
+ sudo pg_ctlcluster 14 main start #DOCS:
+else #DOCS:
+ sudo systemctl restart postgresql
+fi #DOCS:
+#
+# Finally, we need to add two postgres users: one for the user that does
+# the import and another for the webserver which should access the database
+# for reading only:
+#
+
+ sudo -u postgres createuser -s $USERNAME
+ sudo -u postgres createuser www-data
+
+#
+# Installing Nominatim
+# ====================
+#
+# Building and Configuration
+# --------------------------
+#
+# Get the source code from Github and change into the source directory
+#
+if [ "x$1" == "xyes" ]; then #DOCS: :::sh
+ cd $USERHOME
+ git clone --recursive https://github.com/openstreetmap/Nominatim.git
+ cd Nominatim
+else #DOCS:
+ cd $USERHOME/Nominatim #DOCS:
+fi #DOCS:
+
+# When installing the latest source from github, you also need to
+# download the country grid:
+
+if [ ! -f data/country_osm_grid.sql.gz ]; then #DOCS: :::sh
+ wget -O data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
+fi #DOCS:
+
+# The code must be built in a separate directory. Create this directory,
+# then configure and build Nominatim in there:
+
+ mkdir $USERHOME/build
+ cd $USERHOME/build
+ cmake $USERHOME/Nominatim
+ make
+ sudo make install
+
+# 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.
+#
+# 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:
+
+ 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:
+
+ sudo apt install -y apache2 libapache2-mod-php
+
+# You need to create an alias to the website directory in your apache
+# configuration. Add a separate nominatim configuration to your webserver:
+
+#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
+EOFAPACHECONF
+#DOCS:```
+
+#
+# Then enable the configuration and restart apache
+#
+
+ sudo a2enconf nominatim
+if [ "x$NOSYSTEMD" == "xyes" ]; then #DOCS:
+ sudo apache2ctl start #DOCS:
+else #DOCS:
+ sudo systemctl restart apache2
+fi #DOCS:
+
+# The Nominatim API is now available at `http://localhost/nominatim/`.
+
+fi #DOCS:
+
+#
+# Option 2: Using nginx
+# ---------------------
+#
+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
+
+# 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/php8.1-fpm.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.
+
+#DOCS:```sh
+sudo tee /etc/nginx/sites-available/default << EOF_NGINX_CONF
+server {
+ listen 80 default_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/php8.1-fpm.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/php7.4-fpm.sock;
+ fastcgi_index search.php;
+ include fastcgi.conf;
+ }
+}
+EOF_NGINX_CONF
+#DOCS:```
+
+# If you have some errors, make sure that php8.1-fpm.sock is well under
+# /var/run/ and not under /var/run/php. Otherwise change the Nginx configuration
+# to /var/run/php/php8.1-fpm.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
+fi #DOCS:
+
+# The Nominatim API is now available at `http://localhost/`.
+
+
+
+fi #DOCS: