mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-16 15:47:58 +00:00
Compare commits
1 Commits
docs-4.0.x
...
docs-3.6.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20a1531c1 |
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,16 +7,16 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Before opening a new feature request, please search through the open issue to check that your request hasn't been reported already. -->
|
Before opening a new feature request, please search through the open issue to check that your request hasn't been reported already.
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Describe alternatives you've considered**
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|||||||
@@ -7,23 +7,21 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Note: this template is for reporting problems with searching. If you have found an issue with the data, you need to report/fix the issue directly in OpenStreetMap. See https://www.openstreetmap.org/fixthemap for details. -->
|
|
||||||
|
|
||||||
## What did you search for?
|
## What did you search for?
|
||||||
|
|
||||||
<!-- Please try to provide a link to your search. You can go to https://nominatim.openstreetmap.org and repeat your search there. If you originally found the issue somewhere else, please tell us what software/website you were using. -->
|
Please try to provide a link to your search. You can go to https://nominatim.openstreetmap.org and repeat your search there. If you originally found the issue somewhere else, please tell us what software/website you were using.
|
||||||
|
|
||||||
## What result did you get?
|
## What result did you get?
|
||||||
|
|
||||||
## What result did you expect?
|
## What result did you expect?
|
||||||
|
|
||||||
**When the result in the right place and just named wrongly:**
|
**Is the result in the right place and just named wrongly?**
|
||||||
|
|
||||||
<!-- Please tell us the display name you expected. -->
|
Please tell us the display name you expected.
|
||||||
|
|
||||||
**When the result missing completely:**
|
**Is the result missing completely?**
|
||||||
|
|
||||||
<!-- Make sure that the data you are looking for is in OpenStreetMap. Provide a link to the OpenStreetMap object or if you cannot get it, a link to the map on https://openstreetmap.org where you expect the result to be.
|
Make sure that the data you are looking for is in OpenStreetMap. Provide a link to the OpenStreetMap object or if you cannot get it, a link to the map on https://openstreetmap.org where you expect the result to be.
|
||||||
|
|
||||||
To get the link to the OSM object, you can try the following:
|
To get the link to the OSM object, you can try the following:
|
||||||
|
|
||||||
@@ -32,8 +30,7 @@ To get the link to the OSM object, you can try the following:
|
|||||||
* Click on the question mark on the right side of the map. You get a question cursor. Use it to click on the map where your object is located.
|
* Click on the question mark on the right side of the map. You get a question cursor. Use it to click on the map where your object is located.
|
||||||
* Find the object of interest in the list that appears on the left side.
|
* Find the object of interest in the list that appears on the left side.
|
||||||
* Click on the object and report back the URL that the browser shows.
|
* Click on the object and report back the URL that the browser shows.
|
||||||
-->
|
|
||||||
|
|
||||||
## Further details
|
## Further details
|
||||||
|
|
||||||
<!-- Anything else we should know about the search. Particularities with addresses in the area etc. -->
|
Anything else we should know about the search. Particularities with addresses in the area etc.
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Note: if you are installing Nominatim through a docker image, you should report issues with the installation process with the docker repository first. -->
|
___Note: if you are installing Nominatim through a docker image, you should report issues with the installation process with the docker repository first.___
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
<!-- Please describe what you did to get to the issue. -->
|
Please describe what you did to get to the issue.
|
||||||
|
|
||||||
**Software Environment (please complete the following information):**
|
**Software Environment (please complete the following information):**
|
||||||
- Nominatim version:
|
- Nominatim version:
|
||||||
@@ -27,10 +27,5 @@ assignees: ''
|
|||||||
- type and size of disks:
|
- type and size of disks:
|
||||||
- bare metal/AWS/other cloud service:
|
- bare metal/AWS/other cloud service:
|
||||||
|
|
||||||
**Postgresql Configuration:**
|
|
||||||
|
|
||||||
<!-- List any configuration items you changed in your postgresql configuration. -->
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
<!-- Add any other context about the problem here. -->
|
|
||||||
|
|||||||
38
.github/actions/build-nominatim/action.yml
vendored
38
.github/actions/build-nominatim/action.yml
vendored
@@ -1,42 +1,28 @@
|
|||||||
name: 'Build Nominatim'
|
name: 'Build Nominatim'
|
||||||
|
|
||||||
inputs:
|
|
||||||
ubuntu:
|
|
||||||
description: 'Version of Ubuntu to install on'
|
|
||||||
required: false
|
|
||||||
default: '20'
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install prerequisites
|
- name: Install prerequisits
|
||||||
run: |
|
run: sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev python3-psycopg2 python3-pyosmium
|
||||||
sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev libicu-dev
|
|
||||||
if [ "x$UBUNTUVER" == "x18" ]; then
|
|
||||||
pip3 install python-dotenv psycopg2==2.7.7 jinja2==2.8 psutil==5.4.2 pyicu osmium PyYAML==5.1 datrie
|
|
||||||
else
|
|
||||||
sudo apt-get install -y -qq python3-icu python3-datrie python3-pyosmium python3-jinja2 python3-psutil python3-psycopg2 python3-dotenv python3-yaml
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
UBUNTUVER: ${{ inputs.ubuntu }}
|
|
||||||
|
|
||||||
- name: Download dependencies
|
|
||||||
run: |
|
|
||||||
if [ ! -f country_grid.sql.gz ]; then
|
|
||||||
wget --no-verbose https://www.nominatim.org/data/country_grid.sql.gz
|
|
||||||
fi
|
|
||||||
cp country_grid.sql.gz Nominatim/data/country_osm_grid.sql.gz
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: mkdir build && cd build && cmake ../Nominatim
|
run: mkdir build && cd build && cmake ..
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make -j2 all
|
make -j2 all
|
||||||
sudo make install
|
./utils/setup.php --setup-website
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: build
|
working-directory: build
|
||||||
|
|
||||||
|
- name: Download dependencies
|
||||||
|
run: |
|
||||||
|
if [ ! -f data/country_osm_grid.sql.gz ]; then
|
||||||
|
wget --no-verbose -O data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
|||||||
4
.github/actions/setup-postgresql/action.yml
vendored
4
.github/actions/setup-postgresql/action.yml
vendored
@@ -14,10 +14,8 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Remove existing PostgreSQL
|
- name: Remove existing PostgreSQL
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get purge -yq postgresql*
|
|
||||||
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get purge -yq postgresql*
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install PostgreSQL
|
- name: Install PostgreSQL
|
||||||
|
|||||||
338
.github/workflows/ci-tests.yml
vendored
338
.github/workflows/ci-tests.yml
vendored
@@ -3,318 +3,118 @@ name: CI Tests
|
|||||||
on: [ push, pull_request ]
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-archive:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
postgresql: [9.5, 13]
|
||||||
|
include:
|
||||||
|
- postgresql: 9.5
|
||||||
|
postgis: 2.5
|
||||||
|
- postgresql: 13
|
||||||
|
postgis: 3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: Get Date
|
||||||
|
id: get-date
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
data/country_osm_grid.sql.gz
|
data/country_osm_grid.sql.gz
|
||||||
key: nominatim-country-data-1
|
monaco-latest.osm.pbf
|
||||||
|
key: nominatim-data-${{ steps.get-date.outputs.date }}
|
||||||
|
|
||||||
- name: Package tarball
|
- uses: ./.github/actions/setup-postgresql
|
||||||
run: |
|
|
||||||
if [ ! -f data/country_osm_grid.sql.gz ]; then
|
|
||||||
wget --no-verbose -O data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
|
|
||||||
fi
|
|
||||||
cd ..
|
|
||||||
tar czf nominatim-src.tar.bz2 Nominatim
|
|
||||||
mv nominatim-src.tar.bz2 Nominatim
|
|
||||||
|
|
||||||
- name: 'Upload Artifact'
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: full-source
|
|
||||||
path: nominatim-src.tar.bz2
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
tests:
|
|
||||||
needs: create-archive
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
ubuntu: [18, 20]
|
|
||||||
include:
|
|
||||||
- ubuntu: 18
|
|
||||||
postgresql: 9.5
|
|
||||||
postgis: 2.5
|
|
||||||
pytest: pytest
|
|
||||||
php: 7.2
|
|
||||||
- ubuntu: 20
|
|
||||||
postgresql: 13
|
|
||||||
postgis: 3
|
|
||||||
pytest: py.test-3
|
|
||||||
php: 7.4
|
|
||||||
|
|
||||||
runs-on: ubuntu-${{ matrix.ubuntu }}.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
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 }}
|
|
||||||
coverage: xdebug
|
|
||||||
tools: phpunit, phpcs, composer
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.6
|
|
||||||
if: matrix.ubuntu == 18
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
|
||||||
with:
|
with:
|
||||||
postgresql-version: ${{ matrix.postgresql }}
|
postgresql-version: ${{ matrix.postgresql }}
|
||||||
postgis-version: ${{ matrix.postgis }}
|
postgis-version: ${{ matrix.postgis }}
|
||||||
|
- uses: ./.github/actions/build-nominatim
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
|
||||||
with:
|
|
||||||
ubuntu: ${{ matrix.ubuntu }}
|
|
||||||
|
|
||||||
- name: Install test prerequsites
|
- name: Install test prerequsites
|
||||||
run: sudo apt-get install -y -qq pylint python3-pytest python3-behave python3-pytest-cov php-codecoverage
|
run: |
|
||||||
if: matrix.ubuntu == 20
|
sudo apt-get install -y -qq php-codesniffer python3-tidylib
|
||||||
|
sudo pip3 install behave nose
|
||||||
- name: Install test prerequsites
|
|
||||||
run: pip3 install pylint==2.6.0 pytest pytest-cov behave==1.2.6
|
|
||||||
if: matrix.ubuntu == 18
|
|
||||||
|
|
||||||
- name: PHP linting
|
- name: PHP linting
|
||||||
run: phpcs --report-width=120 .
|
run: phpcs --report-width=120 .
|
||||||
working-directory: Nominatim
|
|
||||||
|
|
||||||
- name: Python linting
|
|
||||||
run: pylint nominatim
|
|
||||||
working-directory: Nominatim
|
|
||||||
|
|
||||||
- name: PHP unit tests
|
- name: PHP unit tests
|
||||||
run: phpunit --coverage-clover ../../coverage-php.xml ./
|
run: phpunit ./
|
||||||
working-directory: Nominatim/test/php
|
working-directory: test/php
|
||||||
if: matrix.ubuntu == 20
|
|
||||||
|
|
||||||
- name: Python unit tests
|
|
||||||
run: $PYTEST --cov=nominatim --cov-report=xml test/python
|
|
||||||
working-directory: Nominatim
|
|
||||||
env:
|
|
||||||
PYTEST: ${{ matrix.pytest }}
|
|
||||||
|
|
||||||
- name: BDD tests
|
- name: BDD tests
|
||||||
run: |
|
run: behave -DREMOVE_TEMPLATE=1 --format=progress3 db osm2pgsql
|
||||||
mkdir cov
|
working-directory: test/bdd
|
||||||
behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build --format=progress3 -DPHPCOV=./cov
|
|
||||||
composer require phpunit/phpcov:7.0.2
|
|
||||||
vendor/bin/phpcov merge --clover ../../coverage-bdd.xml ./cov
|
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
if: matrix.ubuntu == 20
|
|
||||||
|
|
||||||
- name: BDD tests
|
import:
|
||||||
run: |
|
runs-on: ubuntu-20.04
|
||||||
behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build --format=progress3
|
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
if: matrix.ubuntu == 18
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
with:
|
|
||||||
files: ./Nominatim/coverage*.xml
|
|
||||||
directory: ./
|
|
||||||
name: codecov-umbrella
|
|
||||||
fail_ci_if_error: false
|
|
||||||
path_to_write_report: ./coverage/codecov_report.txt
|
|
||||||
verbose: true
|
|
||||||
if: matrix.ubuntu == 20
|
|
||||||
|
|
||||||
|
|
||||||
icu-test:
|
|
||||||
needs: create-archive
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
ubuntu: [20]
|
|
||||||
include:
|
|
||||||
- ubuntu: 20
|
|
||||||
postgresql: 13
|
|
||||||
postgis: 3
|
|
||||||
pytest: py.test-3
|
|
||||||
php: 7.4
|
|
||||||
|
|
||||||
runs-on: ubuntu-${{ matrix.ubuntu }}.04
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
name: full-source
|
submodules: true
|
||||||
|
|
||||||
- name: Unpack Nominatim
|
- name: Get Date
|
||||||
run: tar xf nominatim-src.tar.bz2
|
id: get-date
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
coverage: xdebug
|
|
||||||
tools: phpunit, phpcs, composer
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.6
|
|
||||||
if: matrix.ubuntu == 18
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
|
||||||
with:
|
|
||||||
postgresql-version: ${{ matrix.postgresql }}
|
|
||||||
postgis-version: ${{ matrix.postgis }}
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
|
||||||
with:
|
|
||||||
ubuntu: ${{ matrix.ubuntu }}
|
|
||||||
|
|
||||||
- name: Install test prerequsites
|
|
||||||
run: sudo apt-get install -y -qq python3-behave
|
|
||||||
if: matrix.ubuntu == 20
|
|
||||||
|
|
||||||
- name: Install test prerequsites
|
|
||||||
run: pip3 install behave==1.2.6
|
|
||||||
if: matrix.ubuntu == 18
|
|
||||||
|
|
||||||
- name: BDD tests (icu tokenizer)
|
|
||||||
run: |
|
run: |
|
||||||
behave -DREMOVE_TEMPLATE=1 -DBUILDDIR=$GITHUB_WORKSPACE/build -DTOKENIZER=icu --format=progress3
|
echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
|
|
||||||
|
|
||||||
install:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: create-archive
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
name: [Ubuntu-18, Ubuntu-20, Centos-8]
|
|
||||||
include:
|
|
||||||
- name: Ubuntu-18
|
|
||||||
flavour: ubuntu
|
|
||||||
image: "ubuntu:18.04"
|
|
||||||
ubuntu: 18
|
|
||||||
install_mode: install-nginx
|
|
||||||
- name: Ubuntu-20
|
|
||||||
flavour: ubuntu
|
|
||||||
image: "ubuntu:20.04"
|
|
||||||
ubuntu: 20
|
|
||||||
install_mode: install-apache
|
|
||||||
- name: Centos-8
|
|
||||||
flavour: centos
|
|
||||||
image: "centos:8"
|
|
||||||
|
|
||||||
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
|
shell: bash
|
||||||
if: matrix.flavour == 'ubuntu'
|
|
||||||
|
|
||||||
- name: Prepare container (CentOS)
|
- uses: actions/cache@v2
|
||||||
run: |
|
|
||||||
dnf update -y
|
|
||||||
dnf install -y sudo glibc-langpack-en
|
|
||||||
shell: bash
|
|
||||||
if: matrix.flavour == 'centos'
|
|
||||||
|
|
||||||
- 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@v2
|
|
||||||
with:
|
with:
|
||||||
name: full-source
|
path: |
|
||||||
path: /home/nominatim
|
data/country_osm_grid.sql.gz
|
||||||
|
monaco-latest.osm.pbf
|
||||||
|
key: nominatim-data-${{ steps.get-date.outputs.date }}
|
||||||
|
|
||||||
- name: Install Nominatim
|
- uses: ./.github/actions/setup-postgresql
|
||||||
run: |
|
with:
|
||||||
export USERNAME=nominatim
|
postgresql-version: 13
|
||||||
export USERHOME=/home/nominatim
|
postgis-version: 3
|
||||||
export NOSYSTEMD=yes
|
- uses: ./.github/actions/build-nominatim
|
||||||
export HAVE_SELINUX=no
|
|
||||||
tar xf nominatim-src.tar.bz2
|
|
||||||
. vagrant.sh
|
|
||||||
working-directory: /home/nominatim
|
|
||||||
|
|
||||||
- name: Prepare import environment
|
- name: Create configuration
|
||||||
run: |
|
run: |
|
||||||
mv Nominatim/test/testdb/apidb-test-data.pbf test.pbf
|
echo '<?php' > settings/local.php
|
||||||
rm -rf Nominatim
|
echo " @define('CONST_Pyosmium_Binary', '/usr/lib/python3-pyosmium/pyosmium-get-changes');" >> settings/local.php
|
||||||
mkdir data-env-reverse
|
working-directory: build
|
||||||
working-directory: /home/nominatim
|
|
||||||
|
|
||||||
- name: Prepare import environment (CentOS)
|
- name: Download import data
|
||||||
run: |
|
run: |
|
||||||
sudo ln -s /usr/local/bin/nominatim /usr/bin/nominatim
|
if [ ! -f monaco-latest.osm.pbf ]; then
|
||||||
echo NOMINATIM_DATABASE_WEBUSER="apache" > nominatim-project/.env
|
wget --no-verbose https://download.geofabrik.de/europe/monaco-latest.osm.pbf
|
||||||
cp nominatim-project/.env data-env-reverse/.env
|
fi
|
||||||
working-directory: /home/nominatim
|
shell: bash
|
||||||
if: matrix.flavour == 'centos'
|
|
||||||
|
|
||||||
- name: Import
|
- name: Import
|
||||||
run: nominatim import --osm-file ../test.pbf
|
run: php ./utils/setup.php --osm-file ../monaco-latest.osm.pbf --osm2pgsql-cache 500 --all
|
||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: build
|
||||||
|
|
||||||
- name: Import special phrases
|
- name: Import special phrases
|
||||||
run: nominatim special-phrases --import-from-wiki
|
run: php ./utils/specialphrases.php --wiki-import | psql -d nominatim
|
||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: build
|
||||||
|
|
||||||
- name: Check full import
|
- name: Check import
|
||||||
run: nominatim admin --check-database
|
run: php ./utils/check_import_finished.php
|
||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: build
|
||||||
|
|
||||||
- name: Warm up database
|
|
||||||
run: nominatim admin --warm
|
|
||||||
working-directory: /home/nominatim/nominatim-project
|
|
||||||
|
|
||||||
- name: Prepare update (Ubuntu)
|
|
||||||
run: apt-get install -y python3-pip
|
|
||||||
shell: bash
|
|
||||||
if: matrix.flavour == 'ubuntu'
|
|
||||||
|
|
||||||
- name: Run update
|
- name: Run update
|
||||||
run: |
|
run: |
|
||||||
pip3 install --user osmium
|
php ./utils/update.php --init-updates
|
||||||
nominatim replication --init
|
php ./utils/update.php --import-osmosis
|
||||||
NOMINATIM_REPLICATION_MAX_DIFF=1 nominatim replication --once
|
working-directory: build
|
||||||
working-directory: /home/nominatim/nominatim-project
|
|
||||||
|
|
||||||
- name: Run reverse-only import
|
- name: Run reverse-only import
|
||||||
run : |
|
run : |
|
||||||
echo 'NOMINATIM_DATABASE_DSN="pgsql:dbname=reverse"' >> .env
|
dropdb nominatim
|
||||||
nominatim import --osm-file ../test.pbf --reverse-only --no-updates
|
php ./utils/setup.php --osm-file ../monaco-latest.osm.pbf --reverse-only --all
|
||||||
working-directory: /home/nominatim/data-env-reverse
|
working-directory: build
|
||||||
|
|
||||||
- name: Check reverse import
|
|
||||||
run: nominatim admin --check-database
|
|
||||||
working-directory: /home/nominatim/data-env-reverse
|
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,9 +1,11 @@
|
|||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
docs/develop/*.png
|
|
||||||
|
|
||||||
build
|
build
|
||||||
|
settings/local.php
|
||||||
|
|
||||||
|
data/wiki_import.sql
|
||||||
|
data/wiki_specialphrases.sql
|
||||||
|
data/osmosischange.osc
|
||||||
|
|
||||||
.vagrant
|
.vagrant
|
||||||
data/country_osm_grid.sql.gz
|
|
||||||
|
|||||||
15
.pylintrc
15
.pylintrc
@@ -1,15 +0,0 @@
|
|||||||
[MASTER]
|
|
||||||
|
|
||||||
extension-pkg-whitelist=osmium
|
|
||||||
ignored-modules=icu,datrie
|
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
|
||||||
|
|
||||||
[TYPECHECK]
|
|
||||||
|
|
||||||
# closing added here because it sometimes triggers a false positive with
|
|
||||||
# 'with' statements.
|
|
||||||
ignored-classes=NominatimArgs,closing
|
|
||||||
disable=too-few-public-methods,duplicate-code
|
|
||||||
|
|
||||||
good-names=i,x,y,fd,db
|
|
||||||
251
CMakeLists.txt
251
CMakeLists.txt
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
|||||||
|
|
||||||
project(nominatim)
|
project(nominatim)
|
||||||
|
|
||||||
set(NOMINATIM_VERSION_MAJOR 4)
|
set(NOMINATIM_VERSION_MAJOR 3)
|
||||||
set(NOMINATIM_VERSION_MINOR 0)
|
set(NOMINATIM_VERSION_MINOR 6)
|
||||||
set(NOMINATIM_VERSION_PATCH 0)
|
set(NOMINATIM_VERSION_PATCH 0)
|
||||||
|
|
||||||
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
|
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
|
||||||
@@ -36,9 +36,7 @@ set(BUILD_API on CACHE BOOL "Build everything for the API server")
|
|||||||
set(BUILD_MODULE on CACHE BOOL "Build PostgreSQL module")
|
set(BUILD_MODULE on CACHE BOOL "Build PostgreSQL module")
|
||||||
set(BUILD_TESTS on CACHE BOOL "Build test suite")
|
set(BUILD_TESTS on CACHE BOOL "Build test suite")
|
||||||
set(BUILD_DOCS on CACHE BOOL "Build documentation")
|
set(BUILD_DOCS on CACHE BOOL "Build documentation")
|
||||||
set(BUILD_MANPAGE on CACHE BOOL "Build Manual Page")
|
|
||||||
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
|
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
|
||||||
set(INSTALL_MUNIN_PLUGINS on CACHE BOOL "Install Munin plugins for supervising Nominatim")
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# osm2pgsql (imports/updates only)
|
# osm2pgsql (imports/updates only)
|
||||||
@@ -59,11 +57,20 @@ endif()
|
|||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# python (imports/updates only)
|
# python and pyosmium (imports/updates only)
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
if (BUILD_IMPORTER)
|
if (BUILD_IMPORTER)
|
||||||
find_package(PythonInterp 3.6 REQUIRED)
|
find_package(PythonInterp 3)
|
||||||
|
|
||||||
|
find_program(PYOSMIUM pyosmium-get-changes)
|
||||||
|
if (NOT EXISTS "${PYOSMIUM}")
|
||||||
|
set(PYOSMIUM_PATH "")
|
||||||
|
message(WARNING "pyosmium-get-changes not found (required for updates)")
|
||||||
|
else()
|
||||||
|
set(PYOSMIUM_PATH "${PYOSMIUM}")
|
||||||
|
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@@ -79,19 +86,8 @@ if (BUILD_API OR BUILD_IMPORTER)
|
|||||||
# sanity check if PHP binary exists
|
# sanity check if PHP binary exists
|
||||||
if (NOT EXISTS ${PHP_BIN})
|
if (NOT EXISTS ${PHP_BIN})
|
||||||
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
|
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
|
||||||
else()
|
|
||||||
message (STATUS "Using PHP binary " ${PHP_BIN})
|
|
||||||
endif()
|
|
||||||
if (NOT PHPCGI_BIN)
|
|
||||||
find_program (PHPCGI_BIN php-cgi)
|
|
||||||
endif()
|
|
||||||
# sanity check if PHP binary exists
|
|
||||||
if (NOT EXISTS ${PHPCGI_BIN})
|
|
||||||
message(WARNING "php-cgi binary not found. nominatim tool will not provide query functions.")
|
|
||||||
set (PHPCGI_BIN "")
|
|
||||||
else()
|
|
||||||
message (STATUS "Using php-cgi binary " ${PHPCGI_BIN})
|
|
||||||
endif()
|
endif()
|
||||||
|
message (STATUS "Using PHP binary " ${PHP_BIN})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@@ -99,21 +95,70 @@ endif()
|
|||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
if (BUILD_IMPORTER)
|
if (BUILD_IMPORTER)
|
||||||
find_file(COUNTRY_GRID_FILE country_osm_grid.sql.gz
|
set(CUSTOMSCRIPTS
|
||||||
PATHS ${PROJECT_SOURCE_DIR}/data
|
utils/check_import_finished.php
|
||||||
NO_DEFAULT_PATH
|
utils/country_languages.php
|
||||||
DOC "Location of the country grid file."
|
utils/importWikipedia.php
|
||||||
)
|
utils/export.php
|
||||||
|
utils/query.php
|
||||||
|
utils/setup.php
|
||||||
|
utils/specialphrases.php
|
||||||
|
utils/update.php
|
||||||
|
utils/warm.php
|
||||||
|
)
|
||||||
|
|
||||||
if (NOT COUNTRY_GRID_FILE)
|
foreach (script_source ${CUSTOMSCRIPTS})
|
||||||
message(FATAL_ERROR "\nYou need to download the country_osm_grid first:\n"
|
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl
|
||||||
" wget -O ${PROJECT_SOURCE_DIR}/data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz")
|
${PROJECT_BINARY_DIR}/${script_source})
|
||||||
endif()
|
endforeach()
|
||||||
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/tool.tmpl
|
|
||||||
${PROJECT_BINARY_DIR}/nominatim)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# webserver scripts (API only)
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if (BUILD_API)
|
||||||
|
set(WEBSITESCRIPTS
|
||||||
|
website/deletable.php
|
||||||
|
website/details.php
|
||||||
|
website/lookup.php
|
||||||
|
website/polygons.php
|
||||||
|
website/reverse.php
|
||||||
|
website/search.php
|
||||||
|
website/status.php
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach (script_source ${WEBSITESCRIPTS})
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
|
||||||
|
${PROJECT_BINARY_DIR}/${script_source})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(WEBPATHS css images js)
|
||||||
|
|
||||||
|
foreach (wp ${WEBPATHS})
|
||||||
|
execute_process(
|
||||||
|
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_custom_target(serve
|
||||||
|
php -S 127.0.0.1:8088
|
||||||
|
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/website
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(serve-global
|
||||||
|
php -S 0.0.0.0:8088
|
||||||
|
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/website
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# default settings
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php
|
||||||
|
${PROJECT_BINARY_DIR}/settings/settings.php)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# Tests
|
# Tests
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@@ -123,60 +168,21 @@ if (BUILD_TESTS)
|
|||||||
|
|
||||||
set(TEST_BDD db osm2pgsql api)
|
set(TEST_BDD db osm2pgsql api)
|
||||||
|
|
||||||
find_program(PYTHON_BEHAVE behave)
|
foreach (test ${TEST_BDD})
|
||||||
find_program(PYLINT NAMES pylint3 pylint)
|
add_test(NAME bdd_${test}
|
||||||
find_program(PYTEST NAMES pytest py.test-3 py.test)
|
COMMAND behave ${test}
|
||||||
find_program(PHPCS phpcs)
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
|
||||||
find_program(PHPUNIT phpunit)
|
set_tests_properties(bdd_${test}
|
||||||
|
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
if (PYTHON_BEHAVE)
|
add_test(NAME php
|
||||||
message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}")
|
COMMAND phpunit ./
|
||||||
foreach (test ${TEST_BDD})
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
|
||||||
add_test(NAME bdd_${test}
|
|
||||||
COMMAND ${PYTHON_BEHAVE} ${test}
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
|
|
||||||
set_tests_properties(bdd_${test}
|
|
||||||
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
|
||||||
endforeach()
|
|
||||||
else()
|
|
||||||
message(WARNING "behave not found. BDD tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PHPUNIT)
|
add_test(NAME phpcs
|
||||||
message(STATUS "Using phpunit binary ${PHPUNIT}")
|
COMMAND phpcs --report-width=120 --colors lib website utils
|
||||||
add_test(NAME php
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||||
COMMAND ${PHPUNIT} ./
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
|
|
||||||
else()
|
|
||||||
message(WARNING "phpunit not found. PHP unit tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PHPCS)
|
|
||||||
message(STATUS "Using phpcs binary ${PHPCS}")
|
|
||||||
add_test(NAME phpcs
|
|
||||||
COMMAND ${PHPCS} --report-width=120 --colors lib-php
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "phpcs not found. PHP linting tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PYLINT)
|
|
||||||
message(STATUS "Using pylint binary ${PYLINT}")
|
|
||||||
add_test(NAME pylint
|
|
||||||
COMMAND ${PYLINT} nominatim
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "pylint not found. Python linting tests disabled.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PYTEST)
|
|
||||||
message(STATUS "Using pytest binary ${PYTEST}")
|
|
||||||
add_test(NAME pytest
|
|
||||||
COMMAND ${PYTEST} test/python
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "pytest not found. Python tests disabled." )
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@@ -194,82 +200,3 @@ endif()
|
|||||||
if (BUILD_DOCS)
|
if (BUILD_DOCS)
|
||||||
add_subdirectory(docs)
|
add_subdirectory(docs)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Manual page
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (BUILD_MANPAGE)
|
|
||||||
add_subdirectory(man)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Installation
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
set(NOMINATIM_DATADIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_CONFIGDIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_MUNINDIR ${CMAKE_INSTALL_FULL_DATADIR}/munin/plugins)
|
|
||||||
|
|
||||||
if (BUILD_IMPORTER)
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/tool-installed.tmpl installed.bin)
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/installed.bin
|
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
RENAME nominatim)
|
|
||||||
|
|
||||||
install(DIRECTORY nominatim
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR}/lib-python
|
|
||||||
FILES_MATCHING PATTERN "*.py"
|
|
||||||
PATTERN __pycache__ EXCLUDE)
|
|
||||||
install(DIRECTORY lib-sql DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
|
|
||||||
install(FILES data/country_name.sql
|
|
||||||
${COUNTRY_GRID_FILE}
|
|
||||||
data/words.sql
|
|
||||||
DESTINATION ${NOMINATIM_DATADIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_OSM2PGSQL)
|
|
||||||
if (${CMAKE_VERSION} VERSION_LESS 3.13)
|
|
||||||
# Installation of subdirectory targets was only introduced in 3.13.
|
|
||||||
# So just copy the osm2pgsql file for older versions.
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/osm2pgsql/osm2pgsql
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
else()
|
|
||||||
install(TARGETS osm2pgsql RUNTIME DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_MODULE)
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/module/nominatim.so
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR}/module)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_API)
|
|
||||||
install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(FILES settings/env.defaults
|
|
||||||
settings/address-levels.json
|
|
||||||
settings/phrase-settings.json
|
|
||||||
settings/import-admin.style
|
|
||||||
settings/import-street.style
|
|
||||||
settings/import-address.style
|
|
||||||
settings/import-full.style
|
|
||||||
settings/import-extratags.style
|
|
||||||
settings/icu_tokenizer.yaml
|
|
||||||
settings/country_settings.yaml
|
|
||||||
DESTINATION ${NOMINATIM_CONFIGDIR})
|
|
||||||
|
|
||||||
install(DIRECTORY settings/icu-rules
|
|
||||||
DESTINATION ${NOMINATIM_CONFIGDIR})
|
|
||||||
|
|
||||||
if (INSTALL_MUNIN_PLUGINS)
|
|
||||||
install(FILES munin/nominatim_importlag
|
|
||||||
munin/nominatim_query_speed
|
|
||||||
munin/nominatim_requests
|
|
||||||
DESTINATION ${NOMINATIM_MUNINDIR})
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -49,18 +49,22 @@ are in process of consolidating the style. The following rules apply:
|
|||||||
* for PHP variables use CamelCase with a prefixing letter indicating the type
|
* for PHP variables use CamelCase with a prefixing letter indicating the type
|
||||||
(i - integer, f - float, a - array, s - string, o - object)
|
(i - integer, f - float, a - array, s - string, o - object)
|
||||||
|
|
||||||
The coding style is enforced with PHPCS and pylint. It can be tested with:
|
The coding style is enforced with PHPCS and can be tested with:
|
||||||
|
|
||||||
```
|
```
|
||||||
phpcs --report-width=120 --colors .
|
phpcs --report-width=120 --colors .
|
||||||
pylint3 --extension-pkg-whitelist=osmium nominatim
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Before submitting a pull request make sure that the tests pass:
|
Before submitting a pull request make sure that the following tests pass:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd build
|
cd test/bdd
|
||||||
make test
|
behave -DBUILDDIR=<builddir> db osm2pgsql
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
cd test/php
|
||||||
|
phpunit ./
|
||||||
```
|
```
|
||||||
|
|||||||
67
ChangeLog
67
ChangeLog
@@ -1,70 +1,3 @@
|
|||||||
4.0.0
|
|
||||||
|
|
||||||
* refactor name token computation and introduce ICU tokenizer
|
|
||||||
* name processing now happens in the indexer outside the DB
|
|
||||||
* reorganizes abbreviation handling and moves it to the indexing phases
|
|
||||||
* adds preprocessing of names
|
|
||||||
* add country-specific ranking for Spain, Slovakia
|
|
||||||
* partially switch to using SP-GIST indexes
|
|
||||||
* better updating of dependent addresses for name changes in streets
|
|
||||||
* remove unused/broken tables for external housenumbers
|
|
||||||
* move external postcodes to CSV format and no longer save them in tables
|
|
||||||
(adds support for postcodes for arbitrary countries)
|
|
||||||
* remove postcode helper entries from placex (thanks @AntoJvlt)
|
|
||||||
* change required format for TIGER data to CSV
|
|
||||||
* move configuration of default languages from wiki into config file
|
|
||||||
* expect customized configuration files in project directory by default
|
|
||||||
* disable search API for reverse-only import (thanks @darkshredder)
|
|
||||||
* port most of maintenance/import code to Python and remove PHP utils
|
|
||||||
* add catch-up mode for replication
|
|
||||||
* add updating of special phrases (thanks @AntoJvlt)
|
|
||||||
* add support for special phrases in CSV files (thanks @AntoJvlt)
|
|
||||||
* switch to case-independent matching between place and boundary names
|
|
||||||
* remove disabling of reverse query parsing
|
|
||||||
* minor tweaks to search algorithm to avoid more false positives
|
|
||||||
* major overhaul of the administrator and developer documentation
|
|
||||||
* add security disclosure policy
|
|
||||||
* add testing of installation scripts via CI
|
|
||||||
* drop support for Python < 3.6 and Postgresql < 9.5
|
|
||||||
|
|
||||||
3.7.2
|
|
||||||
|
|
||||||
* fix database check for reverse-only imports
|
|
||||||
* do not error out in status API result when import date is missing
|
|
||||||
* add array_key_last function for PHP < 7.3 (thanks to @woodpeck)
|
|
||||||
* fix more url when server name is unknown (thanks to @mogita)
|
|
||||||
* commit changes to replication log table
|
|
||||||
|
|
||||||
3.7.1
|
|
||||||
|
|
||||||
* fix smaller issues with special phrases import (thanks @AntoJvlt)
|
|
||||||
* add index to speed up continued indexing during import
|
|
||||||
* fix index on location_property_tiger(parent_place_id) (thanks @changpingc)
|
|
||||||
* make sure Python code is backward-compatible with Python 3.5
|
|
||||||
* various documentation fixes
|
|
||||||
|
|
||||||
3.7.0
|
|
||||||
|
|
||||||
* switch to dotenv for configuration file
|
|
||||||
* introduce 'make install' (reorganising most of the code)
|
|
||||||
* introduce nominatim tool as replacement for various php scripts
|
|
||||||
* introduce project directories and allow multiple installations from same build
|
|
||||||
* clean up BDD tests: drop nose, reorganise step code
|
|
||||||
* simplify test database for API BDD tests and autoinstall database
|
|
||||||
* port most of the code for command-line tools to Python
|
|
||||||
(thanks to @darkshredder and @AntoJvlt)
|
|
||||||
* add tests for all tooling
|
|
||||||
* replace pyosmium-get-changes with custom internal implementation using
|
|
||||||
pyosmium
|
|
||||||
* improve search for queries with housenumber and partial terms
|
|
||||||
* add database versioning
|
|
||||||
* use jinja2 for preprocessing SQL files
|
|
||||||
* introduce automatic migrations
|
|
||||||
* reverse fix preference of interpolations over housenumbers
|
|
||||||
* parallelize indexing of postcodes
|
|
||||||
* add non-key indexes to speed up housenumber + street searches
|
|
||||||
* switch housenumber field in placex to save transliterated names
|
|
||||||
|
|
||||||
3.6.0
|
3.6.0
|
||||||
|
|
||||||
* add full support for searching by and displaying of addr:* tags
|
* add full support for searching by and displaying of addr:* tags
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -1,5 +1,4 @@
|
|||||||
[](https://github.com/osm-search/Nominatim/actions?query=workflow%3A%22CI+Tests%22)
|
[](https://github.com/osm-search/Nominatim/actions?query=workflow%3A%22CI+Tests%22)
|
||||||
[](https://codecov.io/gh/osm-search/Nominatim)
|
|
||||||
|
|
||||||
Nominatim
|
Nominatim
|
||||||
=========
|
=========
|
||||||
@@ -20,11 +19,20 @@ https://nominatim.org/release-docs/develop/ .
|
|||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
|
**Nominatim is a complex piece of software and runs in a complex environment.
|
||||||
|
Installing and running Nominatim is something for experienced system
|
||||||
|
administrators only who can do some trouble-shooting themselves. We are sorry,
|
||||||
|
but we can not provide installation support. We are all doing this in our free
|
||||||
|
time and there is just so much of that time to go around. Do not open issues in
|
||||||
|
our bug tracker if you need help. You can ask questions on the mailing list
|
||||||
|
(see below) or on [help.openstreetmap.org](https://help.openstreetmap.org/).**
|
||||||
|
|
||||||
The latest stable release can be downloaded from https://nominatim.org.
|
The latest stable release can be downloaded from https://nominatim.org.
|
||||||
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation), as well as an extensive [Troubleshooting/FAQ section](https://nominatim.org/release-docs/latest/admin/Faq/).
|
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation), as well as an extensive [Troubleshooting/FAQ section](https://nominatim.org/release-docs/latest/admin/Faq/).
|
||||||
|
|
||||||
[Detailed installation instructions for current master](https://nominatim.org/release-docs/develop/admin/Installation)
|
Detailed installation instructions for the development version can be
|
||||||
can be found at nominatim.org as well.
|
found at [nominatim.org](https://nominatim.org/release-docs/develop/admin/Installation)
|
||||||
|
as well.
|
||||||
|
|
||||||
A quick summary of the necessary steps:
|
A quick summary of the necessary steps:
|
||||||
|
|
||||||
@@ -34,15 +42,12 @@ A quick summary of the necessary steps:
|
|||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
make
|
make
|
||||||
sudo make install
|
|
||||||
|
|
||||||
2. Create a project directory, get OSM data and import:
|
2. Get OSM data and import:
|
||||||
|
|
||||||
mkdir nominatim-project
|
./build/utils/setup.php --osm-file <your planet file> --all
|
||||||
cd nominatim-project
|
|
||||||
nominatim import --osm-file <your planet file>
|
|
||||||
|
|
||||||
3. Point your webserver to the nominatim-project/website directory.
|
3. Point your webserver to the ./build/website directory.
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
@@ -54,14 +59,13 @@ The source code is available under a GPLv2 license.
|
|||||||
Contributing
|
Contributing
|
||||||
============
|
============
|
||||||
|
|
||||||
Contributions, bugreport and pull requests are welcome.
|
Contributions are welcome. For details see [contribution guide](CONTRIBUTING.md).
|
||||||
For details see [contribution guide](CONTRIBUTING.md).
|
|
||||||
|
Both bug reports and pull requests are welcome.
|
||||||
|
|
||||||
|
|
||||||
Questions and help
|
Mailing list
|
||||||
==================
|
============
|
||||||
|
|
||||||
For questions, community help and discussions you can use the
|
For questions you can join the geocoding mailing list, see
|
||||||
[Github discussions forum](https://github.com/osm-search/Nominatim/discussions)
|
https://lists.openstreetmap.org/listinfo/geocoding
|
||||||
or join the
|
|
||||||
[geocoding mailing list](https://lists.openstreetmap.org/listinfo/geocoding).
|
|
||||||
|
|||||||
39
SECURITY.md
39
SECURITY.md
@@ -1,39 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
All Nominatim releases receive security updates for two years.
|
|
||||||
|
|
||||||
The following table lists the end of support for all currently supported
|
|
||||||
versions.
|
|
||||||
|
|
||||||
| Version | End of support for security updates |
|
|
||||||
| ------- | ----------------------------------- |
|
|
||||||
| 3.7.x | 2023-04-05 |
|
|
||||||
| 3.6.x | 2022-12-12 |
|
|
||||||
| 3.5.x | 2022-06-05 |
|
|
||||||
| 3.4.x | 2021-10-24 |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
If you believe, you have found an issue in Nominatim that has implications on
|
|
||||||
security, please send a description of the issue to **security@nominatim.org**.
|
|
||||||
You will receive an acknowledgement of your mail within 3 work days where we
|
|
||||||
also notify you of the next steps.
|
|
||||||
|
|
||||||
## How we Disclose Security Issues
|
|
||||||
|
|
||||||
** The following section only applies to security issues found in released
|
|
||||||
versions. Issues that concern the master development branch only will be
|
|
||||||
fixed immediately on the branch with the corresponding PR containing the
|
|
||||||
description of the nature and severity of the issue. **
|
|
||||||
|
|
||||||
Patches for identified security issues are applied to all affected versions and
|
|
||||||
new minor versions are released. At the same time we release a statement at
|
|
||||||
the [Nominatim blog](https://nominatim.org/blog/) describing the nature of the
|
|
||||||
incident. Announcements will also be published at the
|
|
||||||
[geocoding mailinglist](https://lists.openstreetmap.org/listinfo/geocoding).
|
|
||||||
|
|
||||||
## List of Previous Incidents
|
|
||||||
|
|
||||||
* 2020-05-04 - [SQL injection issue on /details endpoint](https://lists.openstreetmap.org/pipermail/geocoding/2020-May/002012.html)
|
|
||||||
4
cmake/script.tmpl
Executable file
4
cmake/script.tmpl
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!@PHP_BIN@ -Cq
|
||||||
|
<?php
|
||||||
|
require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
|
||||||
|
require_once(CONST_BasePath.'/@script_source@');
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.insert(1, '@NOMINATIM_LIBDIR@/lib-python')
|
|
||||||
|
|
||||||
os.environ['NOMINATIM_NOMINATIM_TOOL'] = os.path.abspath(__file__)
|
|
||||||
|
|
||||||
from nominatim import cli
|
|
||||||
|
|
||||||
exit(cli.nominatim(module_dir='@NOMINATIM_LIBDIR@/module',
|
|
||||||
osm2pgsql_path='@NOMINATIM_LIBDIR@/osm2pgsql',
|
|
||||||
phplib_dir='@NOMINATIM_LIBDIR@/lib-php',
|
|
||||||
sqllib_dir='@NOMINATIM_LIBDIR@/lib-sql',
|
|
||||||
data_dir='@NOMINATIM_DATADIR@',
|
|
||||||
config_dir='@NOMINATIM_CONFIGDIR@',
|
|
||||||
phpcgi_path='@PHPCGI_BIN@'))
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.insert(1, '@CMAKE_SOURCE_DIR@')
|
|
||||||
|
|
||||||
os.environ['NOMINATIM_NOMINATIM_TOOL'] = os.path.abspath(__file__)
|
|
||||||
|
|
||||||
from nominatim import cli
|
|
||||||
|
|
||||||
exit(cli.nominatim(module_dir='@CMAKE_BINARY_DIR@/module',
|
|
||||||
osm2pgsql_path='@CMAKE_BINARY_DIR@/osm2pgsql/osm2pgsql',
|
|
||||||
phplib_dir='@CMAKE_SOURCE_DIR@/lib-php',
|
|
||||||
sqllib_dir='@CMAKE_SOURCE_DIR@/lib-sql',
|
|
||||||
data_dir='@CMAKE_SOURCE_DIR@/data',
|
|
||||||
config_dir='@CMAKE_SOURCE_DIR@/settings',
|
|
||||||
phpcgi_path='@PHPCGI_BIN@'))
|
|
||||||
5
cmake/website.tmpl
Executable file
5
cmake/website.tmpl
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
@define('CONST_Debug', (isset($_GET['debug']) && $_GET['debug']));
|
||||||
|
require_once(dirname(dirname(__FILE__)).'/settings/settings-frontend.php');
|
||||||
|
|
||||||
|
require_once(CONST_BasePath.'/@script_source@');
|
||||||
14
codecov.yml
14
codecov.yml
@@ -1,14 +0,0 @@
|
|||||||
codecov:
|
|
||||||
require_ci_to_pass: yes
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
status:
|
|
||||||
project: off
|
|
||||||
patch: off
|
|
||||||
|
|
||||||
comment:
|
|
||||||
require_changes: true
|
|
||||||
after_n_builds: 2
|
|
||||||
|
|
||||||
fixes:
|
|
||||||
- "Nominatim/::"
|
|
||||||
File diff suppressed because one or more lines are too long
26
data/gb_postcode_table.sql
Normal file
26
data/gb_postcode_table.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-- This data contains Ordnance Survey data © Crown copyright and database right 2010.
|
||||||
|
-- Code-Point Open contains Royal Mail data © Royal Mail copyright and database right 2010.
|
||||||
|
-- OS data may be used under the terms of the OS OpenData licence:
|
||||||
|
-- http://www.ordnancesurvey.co.uk/oswebsite/opendata/licence/docs/licence.pdf
|
||||||
|
|
||||||
|
SET statement_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET standard_conforming_strings = off;
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
SET escape_string_warning = off;
|
||||||
|
|
||||||
|
SET search_path = public, pg_catalog;
|
||||||
|
|
||||||
|
SET default_tablespace = '';
|
||||||
|
|
||||||
|
SET default_with_oids = false;
|
||||||
|
|
||||||
|
CREATE TABLE gb_postcode (
|
||||||
|
id integer,
|
||||||
|
postcode character varying(9),
|
||||||
|
geometry geometry,
|
||||||
|
CONSTRAINT enforce_dims_geometry CHECK ((st_ndims(geometry) = 2)),
|
||||||
|
CONSTRAINT enforce_srid_geometry CHECK ((st_srid(geometry) = 4326))
|
||||||
|
);
|
||||||
|
|
||||||
16
data/us_postcode_table.sql
Normal file
16
data/us_postcode_table.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
SET statement_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
|
||||||
|
SET search_path = public, pg_catalog;
|
||||||
|
|
||||||
|
SET default_tablespace = '';
|
||||||
|
|
||||||
|
SET default_with_oids = false;
|
||||||
|
|
||||||
|
CREATE TABLE us_postcode (
|
||||||
|
postcode text,
|
||||||
|
x double precision,
|
||||||
|
y double precision
|
||||||
|
);
|
||||||
@@ -29787,7 +29787,7 @@ st 5557484
|
|||||||
|
|
||||||
-- prefill word table
|
-- prefill word table
|
||||||
|
|
||||||
select count(precompute_words(v)) from (select distinct svals(name) as v from place) as w where v is not null;
|
select count(make_keywords(v)) from (select distinct svals(name) as v from place) as w where v is not null;
|
||||||
select count(getorcreate_housenumber_id(make_standard_name(v))) from (select distinct address->'housenumber' as v from place where address ? 'housenumber') as w;
|
select count(getorcreate_housenumber_id(make_standard_name(v))) from (select distinct address->'housenumber' as v from place where address ? 'housenumber') as w;
|
||||||
|
|
||||||
-- copy the word frequencies
|
-- copy the word frequencies
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ set (DOC_SOURCES
|
|||||||
admin
|
admin
|
||||||
develop
|
develop
|
||||||
api
|
api
|
||||||
customize
|
|
||||||
index.md
|
index.md
|
||||||
extra.css
|
extra.css
|
||||||
styles.css
|
styles.css
|
||||||
@@ -27,10 +26,7 @@ ADD_CUSTOM_TARGET(doc
|
|||||||
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-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-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-20.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-20.md
|
||||||
COMMAND PYTHONPATH=${PROJECT_SOURCE_DIR} mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
|
COMMAND mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
|
||||||
)
|
)
|
||||||
|
|
||||||
ADD_CUSTOM_TARGET(serve-doc
|
|
||||||
COMMAND PYTHONPATH=${PROJECT_SOURCE_DIR} mkdocs serve
|
|
||||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,34 +5,9 @@ your Nominatim database. It is assumed that you have already successfully
|
|||||||
installed the Nominatim software itself, if not return to the
|
installed the Nominatim software itself, if not return to the
|
||||||
[installation page](Installation.md).
|
[installation page](Installation.md).
|
||||||
|
|
||||||
## Importing multiple regions (without updates)
|
## Importing multiple regions
|
||||||
|
|
||||||
To import multiple regions in your database you can simply give multiple
|
To import multiple regions in your database, you need to configure and run `utils/import_multiple_regions.sh` file. This script will set up the update directory which has the following structure:
|
||||||
OSM files to the import command:
|
|
||||||
|
|
||||||
```
|
|
||||||
nominatim import --osm-file file1.pbf --osm-file file2.pbf
|
|
||||||
```
|
|
||||||
|
|
||||||
If you already have imported a file and want to add another one, you can
|
|
||||||
use the add-data function to import the additional data as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
nominatim add-data --file <FILE>
|
|
||||||
nominatim refresh --postcodes
|
|
||||||
nominatim index -j <NUMBER OF THREADS>
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note that adding additional data is always significantly slower than
|
|
||||||
the original import.
|
|
||||||
|
|
||||||
## Importing multiple regions (with updates)
|
|
||||||
|
|
||||||
If you want to import multiple regions _and_ be able to keep them up-to-date
|
|
||||||
with updates, then you can use the scripts provided in the `utils` directory.
|
|
||||||
|
|
||||||
These scripts will set up an `update` directory in your project directory,
|
|
||||||
which has the following structure:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
update
|
update
|
||||||
@@ -42,6 +17,7 @@ update
|
|||||||
│ └── monaco
|
│ └── monaco
|
||||||
│ └── sequence.state
|
│ └── sequence.state
|
||||||
└── tmp
|
└── tmp
|
||||||
|
├── combined.osm.pbf
|
||||||
└── europe
|
└── europe
|
||||||
├── andorra-latest.osm.pbf
|
├── andorra-latest.osm.pbf
|
||||||
└── monaco-latest.osm.pbf
|
└── monaco-latest.osm.pbf
|
||||||
@@ -49,59 +25,86 @@ update
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `sequence.state` files contain the sequence ID for each region. They will
|
The `sequence.state` files will contain the sequence ID, which will be used by pyosmium to get updates. The tmp folder is used for import dump.
|
||||||
be used by pyosmium to get updates. The `tmp` folder is used for import dump and
|
|
||||||
can be deleted once the import is complete.
|
|
||||||
|
|
||||||
|
### Configuring multiple regions
|
||||||
|
|
||||||
### Setting up multiple regions
|
The file `import_multiple_regions.sh` needs to be edited as per your requirement:
|
||||||
|
|
||||||
Create a project directory as described for the
|
1. List of countries. eg:
|
||||||
[simple import](Import.md#creating-the-project-directory). If necessary,
|
|
||||||
you can also add an `.env` configuration with customized options. In particular,
|
|
||||||
you need to make sure that `NOMINATIM_REPLICATION_UPDATE_INTERVAL` and
|
|
||||||
`NOMINATIM_REPLICATION_RECHECK_INTERVAL` are set according to the update
|
|
||||||
interval of the extract server you use.
|
|
||||||
|
|
||||||
Copy the scripts `utils/import_multiple_regions.sh` and `utils/update_database.sh`
|
|
||||||
into the project directory.
|
|
||||||
|
|
||||||
Now customize both files as per your requirements
|
|
||||||
|
|
||||||
1. List of countries. e.g.
|
|
||||||
|
|
||||||
COUNTRIES="europe/monaco europe/andorra"
|
COUNTRIES="europe/monaco europe/andorra"
|
||||||
|
|
||||||
2. URL to the service providing the extracts and updates. eg:
|
2. Path to Build directory. eg:
|
||||||
|
|
||||||
|
NOMINATIMBUILD="/srv/nominatim/build"
|
||||||
|
|
||||||
|
3. Path to Update directory. eg:
|
||||||
|
|
||||||
|
UPDATEDIR="/srv/nominatim/update"
|
||||||
|
|
||||||
|
4. Replication URL. eg:
|
||||||
|
|
||||||
BASEURL="https://download.geofabrik.de"
|
BASEURL="https://download.geofabrik.de"
|
||||||
DOWNCOUNTRYPOSTFIX="-latest.osm.pbf"
|
DOWNCOUNTRYPOSTFIX="-latest.osm.pbf"
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
If your database already exists and you want to add more countries, replace the setting up part
|
||||||
|
`${SETUPFILE} --osm-file ${UPDATEDIR}/tmp/combined.osm.pbf --all 2>&1`
|
||||||
|
with `${UPDATEFILE} --import-file ${UPDATEDIR}/tmp/combined.osm.pbf --index --index-instances N 2>&1`
|
||||||
|
where N is the numbers of CPUs in your system.
|
||||||
|
|
||||||
5. Followup in the update script can be set according to your installation.
|
### Setting up multiple regions
|
||||||
E.g. for Photon,
|
|
||||||
|
Run the following command from your Nominatim directory after configuring the file.
|
||||||
|
|
||||||
|
bash ./utils/import_multiple_regions.sh
|
||||||
|
|
||||||
|
!!! danger "Important"
|
||||||
|
This file uses osmium-tool. It must be installed before executing the import script.
|
||||||
|
Installation instructions can be found [here](https://osmcode.org/osmium-tool/manual.html#installation).
|
||||||
|
|
||||||
|
### Updating multiple regions
|
||||||
|
|
||||||
|
To import multiple regions in your database, you need to configure and run ```utils/update_database.sh```.
|
||||||
|
This uses the update directory set up while setting up the DB.
|
||||||
|
|
||||||
|
### Configuring multiple regions
|
||||||
|
|
||||||
|
The file `update_database.sh` needs to be edited as per your requirement:
|
||||||
|
|
||||||
|
1. List of countries. eg:
|
||||||
|
|
||||||
|
COUNTRIES="europe/monaco europe/andorra"
|
||||||
|
|
||||||
|
2. Path to Build directory. eg:
|
||||||
|
|
||||||
|
NOMINATIMBUILD="/srv/nominatim/build"
|
||||||
|
|
||||||
|
3. Path to Update directory. eg:
|
||||||
|
|
||||||
|
UPDATEDIR="/srv/nominatim/update"
|
||||||
|
|
||||||
|
4. Replication URL. eg:
|
||||||
|
|
||||||
|
BASEURL="https://download.geofabrik.de"
|
||||||
|
DOWNCOUNTRYPOSTFIX="-updates"
|
||||||
|
|
||||||
|
5. Followup can be set according to your installation. eg: For Photon,
|
||||||
|
|
||||||
FOLLOWUP="curl http://localhost:2322/nominatim-update"
|
FOLLOWUP="curl http://localhost:2322/nominatim-update"
|
||||||
|
|
||||||
will handle the indexing.
|
will handle the indexing.
|
||||||
|
|
||||||
|
|
||||||
To start the initial import, change into the project directory and run
|
|
||||||
|
|
||||||
```
|
|
||||||
bash import_multiple_regions.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating the database
|
### Updating the database
|
||||||
|
|
||||||
Change into the project directory and run the following command:
|
Run the following command from your Nominatim directory after configuring the file.
|
||||||
|
|
||||||
bash update_database.sh
|
bash ./utils/update_database.sh
|
||||||
|
|
||||||
This will get diffs from the replication server, import diffs and index
|
This will get diffs from the replication server, import diffs and index the database. The default replication server in the script([Geofabrik](https://download.geofabrik.de)) provides daily updates.
|
||||||
the database. The default replication server in the
|
|
||||||
script([Geofabrik](https://download.geofabrik.de)) provides daily updates.
|
|
||||||
|
|
||||||
## Using an external PostgreSQL database
|
## Importing Nominatim to an external PostgreSQL database
|
||||||
|
|
||||||
You can install Nominatim using a database that runs on a different server when
|
You can install Nominatim using a database that runs on a different server when
|
||||||
you have physical access to the file system on the other server. Nominatim
|
you have physical access to the file system on the other server. Nominatim
|
||||||
@@ -109,11 +112,6 @@ uses a custom normalization library that needs to be made accessible to the
|
|||||||
PostgreSQL server. This section explains how to set up the normalization
|
PostgreSQL server. This section explains how to set up the normalization
|
||||||
library.
|
library.
|
||||||
|
|
||||||
!!! note
|
|
||||||
The external module is only needed when using the legacy tokenizer.
|
|
||||||
If you have choosen the ICU tokenizer, then you can ignore this section
|
|
||||||
and follow the standard import documentation.
|
|
||||||
|
|
||||||
### Option 1: Compiling the library on the database server
|
### Option 1: Compiling the library on the database server
|
||||||
|
|
||||||
The most sure way to get a working library is to compile it on the database
|
The most sure way to get a working library is to compile it on the database
|
||||||
@@ -156,7 +154,7 @@ Make sure that the PostgreSQL server package is installed on the machine
|
|||||||
the PostgreSQL server itself.
|
the PostgreSQL server itself.
|
||||||
|
|
||||||
Download and compile Nominatim as per standard instructions. Once done, you find
|
Download and compile Nominatim as per standard instructions. Once done, you find
|
||||||
the normalization library in `build/module/nominatim.so`. Copy the file to
|
the nomrmalization library in `build/module/nominatim.so`. Copy the file to
|
||||||
the database server at a location where it is readable and executable by the
|
the database server at a location where it is readable and executable by the
|
||||||
PostgreSQL server process.
|
PostgreSQL server process.
|
||||||
|
|
||||||
@@ -164,52 +162,11 @@ PostgreSQL server process.
|
|||||||
|
|
||||||
On the client side you now need to configure the import to point to the
|
On the client side you now need to configure the import to point to the
|
||||||
correct location of the library **on the database server**. Add the following
|
correct location of the library **on the database server**. Add the following
|
||||||
line to your your `.env` file:
|
line to your your `settings/local.php` file:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
NOMINATIM_DATABASE_MODULE_PATH="<directory on the database server where nominatim.so resides>"
|
@define('CONST_Database_Module_Path', '<directory on the database server where nominatim.so resides>');
|
||||||
```
|
```
|
||||||
|
|
||||||
Now change the `NOMINATIM_DATABASE_DSN` to point to your remote server and continue
|
Now change the `CONST_Database_DSN` to point to your remote server and continue
|
||||||
to follow the [standard instructions for importing](Import.md).
|
to follow the [standard instructions for importing](/admin/Import).
|
||||||
|
|
||||||
|
|
||||||
## Moving the database to another machine
|
|
||||||
|
|
||||||
For some configurations it may be useful to run the import on one machine, then
|
|
||||||
move the database to another machine and run the Nominatim service from there.
|
|
||||||
For example, you might want to use a large machine to be able to run the import
|
|
||||||
quickly but only want a smaller machine for production because there is not so
|
|
||||||
much load. Or you might want to do the import once and then replicate the
|
|
||||||
database to many machines.
|
|
||||||
|
|
||||||
The important thing to keep in mind when transferring the Nominatim installation
|
|
||||||
is that you need to transfer the database _and the project directory_. Both
|
|
||||||
parts are essential for your installation.
|
|
||||||
|
|
||||||
The Nominatim database can be transferred using the `pg_dump`/`pg_restore` tool.
|
|
||||||
Make sure to use the same version of PostgreSQL and PostGIS on source and
|
|
||||||
target machine.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
Before creating a dump of your Nominatim database, consider running
|
|
||||||
`nominatim freeze` first. Your database looses the ability to receive further
|
|
||||||
data updates but the resulting database is only about a third of the size
|
|
||||||
of a full database.
|
|
||||||
|
|
||||||
Next install Nominatim on the target machine by following the standard installation
|
|
||||||
instructions. Again make sure to use the same version as the source machine.
|
|
||||||
|
|
||||||
You can now copy the project directory from the source machine to the new machine.
|
|
||||||
If necessary, edit the `.env` file to point it to the restored database.
|
|
||||||
Finally run
|
|
||||||
|
|
||||||
nominatim refresh --website
|
|
||||||
|
|
||||||
to make sure that the local installation of Nominatim will be used.
|
|
||||||
|
|
||||||
If you are using the legacy tokenizer you might also have to switch to the
|
|
||||||
PostgreSQL module that was compiled on your target machine. If you get errors
|
|
||||||
that PostgreSQL cannot find or access `nominatim.so` then copy the installed
|
|
||||||
version into the `module` directory of your project directory. The installed
|
|
||||||
copy can usually be found under `/usr/local/lib/nominatim/module/nominatim.so`.
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Deploying Nominatim
|
# Deploying Nominatim
|
||||||
|
|
||||||
The Nominatim API is implemented as a PHP application. The `website/` directory
|
The Nominatim API is implemented as a PHP application. The `website/` directory
|
||||||
in the project directory contains the configured website. You can serve this
|
in the build directory contains the configured website. You can serve this
|
||||||
in a production environment with any web server that is capable to run
|
in a production environment with any web server that is capable to run
|
||||||
PHP scripts.
|
PHP scripts.
|
||||||
|
|
||||||
@@ -13,11 +13,10 @@ to run a web service. Please refer to the documentation of
|
|||||||
for background information on configuring the services.
|
for background information on configuring the services.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Throughout this page, we assume that your Nominatim project directory is
|
Throughout this page, we assume that your Nominatim build directory is
|
||||||
located in `/srv/nominatim-project` and that you have installed Nominatim
|
located in `/srv/nominatim/build` and the source code in
|
||||||
using the default installation prefix `/usr/local`. If you have put it
|
`/srv/nominatim/Nominatim`. If you have put it somewhere else, you
|
||||||
somewhere else, you need to adjust the commands and configuration
|
need to adjust the commands and configuration accordingly.
|
||||||
accordingly.
|
|
||||||
|
|
||||||
We further assume that your web server runs as user `www-data`. Older
|
We further assume that your web server runs as user `www-data`. Older
|
||||||
versions of CentOS may still use the user name `apache`. You also need
|
versions of CentOS may still use the user name `apache`. You also need
|
||||||
@@ -30,7 +29,7 @@ web server user. You can check that the permissions are correct by accessing
|
|||||||
on of the php files as the web server user:
|
on of the php files as the web server user:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
sudo -u www-data head -n 1 /srv/nominatim-project/website/search.php
|
sudo -u www-data head -n 1 /srv/nominatim/build/website/search.php
|
||||||
```
|
```
|
||||||
|
|
||||||
If this shows a permission error, then you need to adapt the permissions of
|
If this shows a permission error, then you need to adapt the permissions of
|
||||||
@@ -41,11 +40,11 @@ web server access. At a minimum the following SELinux labelling should be done
|
|||||||
for Nominatim:
|
for Nominatim:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
sudo semanage fcontext -a -t httpd_sys_content_t "/usr/local/nominatim/lib/lib-php(/.*)?"
|
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim/Nominatim/(website|lib|settings)(/.*)?"
|
||||||
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim-project/website(/.*)?"
|
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim/build/(website|settings)(/.*)?"
|
||||||
sudo semanage fcontext -a -t lib_t "/srv/nominatim-project/module/nominatim.so"
|
sudo semanage fcontext -a -t lib_t "/srv/nominatim/build/module/nominatim.so"
|
||||||
sudo restorecon -R -v /usr/local/lib/nominatim
|
sudo restorecon -R -v /srv/nominatim/Nominatim
|
||||||
sudo restorecon -R -v /srv/nominatim-project
|
sudo restorecon -R -v /srv/nominatim/build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nominatim with Apache
|
## Nominatim with Apache
|
||||||
@@ -66,13 +65,13 @@ Make sure your Apache configuration contains the required permissions for the
|
|||||||
directory and create an alias:
|
directory and create an alias:
|
||||||
|
|
||||||
``` apache
|
``` apache
|
||||||
<Directory "/srv/nominatim-project/website">
|
<Directory "/srv/nominatim/build/website">
|
||||||
Options FollowSymLinks MultiViews
|
Options FollowSymLinks MultiViews
|
||||||
AddType text/html .php
|
AddType text/html .php
|
||||||
DirectoryIndex search.php
|
DirectoryIndex search.php
|
||||||
Require all granted
|
Require all granted
|
||||||
</Directory>
|
</Directory>
|
||||||
Alias /nominatim /srv/nominatim-project/website
|
Alias /nominatim /srv/nominatim/build/website
|
||||||
```
|
```
|
||||||
|
|
||||||
After making changes in the apache config you need to restart apache.
|
After making changes in the apache config you need to restart apache.
|
||||||
@@ -111,7 +110,7 @@ Tell nginx that php files are special and to fastcgi_pass to the php-fpm
|
|||||||
unix socket by adding the location definition to the default configuration.
|
unix socket by adding the location definition to the default configuration.
|
||||||
|
|
||||||
``` nginx
|
``` nginx
|
||||||
root /srv/nominatim-project/website;
|
root /srv/nominatim/build/website;
|
||||||
index search.php;
|
index search.php;
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ @php;
|
try_files $uri $uri/ @php;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ was killed. If it looks like this:
|
|||||||
then you can resume with the following command:
|
then you can resume with the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim import --continue indexing
|
./utils/setup.php --index --create-search-indices --create-country-names
|
||||||
```
|
```
|
||||||
|
|
||||||
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
|
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
|
||||||
@@ -31,7 +31,7 @@ list for hints.
|
|||||||
If it happened during index creation you can try rerunning the step with
|
If it happened during index creation you can try rerunning the step with
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim import --continue indexing
|
./utils/setup.php --create-search-indices --ignore-errors
|
||||||
```
|
```
|
||||||
|
|
||||||
Otherwise it's best to start the full setup from the beginning.
|
Otherwise it's best to start the full setup from the beginning.
|
||||||
@@ -93,7 +93,7 @@ on a non-managed machine.
|
|||||||
|
|
||||||
### I see the error: "function transliteration(text) does not exist"
|
### I see the error: "function transliteration(text) does not exist"
|
||||||
|
|
||||||
Reinstall the nominatim functions with `nominatim refresh --functions`
|
Reinstall the nominatim functions with `setup.php --create--functions`
|
||||||
and check for any errors, e.g. a missing `nominatim.so` file.
|
and check for any errors, e.g. a missing `nominatim.so` file.
|
||||||
|
|
||||||
### I see the error: "ERROR: mmap (remap) failed"
|
### I see the error: "ERROR: mmap (remap) failed"
|
||||||
@@ -113,8 +113,7 @@ Double-check clang is installed. Instead of `make` try running `make CLANG=true`
|
|||||||
|
|
||||||
### nominatim UPDATE failed: ERROR: buffer 179261 is not owned by resource owner Portal
|
### nominatim UPDATE failed: ERROR: buffer 179261 is not owned by resource owner Portal
|
||||||
|
|
||||||
Several users [reported this](https://github.com/openstreetmap/Nominatim/issues/1168)
|
Several users [reported this](https://github.com/openstreetmap/Nominatim/issues/1168) during the initial import of the database. It's
|
||||||
during the initial import of the database. It's
|
|
||||||
something PostgreSQL internal Nominatim doesn't control. And PostgreSQL forums
|
something PostgreSQL internal Nominatim doesn't control. And PostgreSQL forums
|
||||||
suggest it's threading related but definitely some kind of crash of a process.
|
suggest it's threading related but definitely some kind of crash of a process.
|
||||||
Users reported either rebooting the server, different hardware or just trying
|
Users reported either rebooting the server, different hardware or just trying
|
||||||
@@ -134,7 +133,7 @@ On CentOS v7 the PostgreSQL server is started with `systemd`. Check if
|
|||||||
`/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`. If
|
`/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`. If
|
||||||
so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security
|
so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security
|
||||||
feature, so use the
|
feature, so use the
|
||||||
[preferred solution](../appendix/Install-on-Centos-7.md#adding-selinux-security-settings).
|
[preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||||
|
|
||||||
However, you can solve this the quick and dirty way by commenting out that line and then run
|
However, you can solve this the quick and dirty way by commenting out that line and then run
|
||||||
|
|
||||||
@@ -182,7 +181,7 @@ by everybody, e.g.
|
|||||||
Try `chmod a+r nominatim.so; chmod a+x nominatim.so`.
|
Try `chmod a+r nominatim.so; chmod a+x nominatim.so`.
|
||||||
|
|
||||||
When running SELinux, make sure that the
|
When running SELinux, make sure that the
|
||||||
[context is set up correctly](../appendix/Install-on-Centos-7.md#adding-selinux-security-settings).
|
[context is set up correctly](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||||
|
|
||||||
When you recently updated your operating system, updated PostgreSQL to
|
When you recently updated your operating system, updated PostgreSQL to
|
||||||
a new version or moved files (e.g. the build directory) you should
|
a new version or moved files (e.g. the build directory) you should
|
||||||
@@ -203,7 +202,7 @@ See the installation instructions for a full list of required packages.
|
|||||||
### I forgot to delete the flatnodes file before starting an import.
|
### I forgot to delete the flatnodes file before starting an import.
|
||||||
|
|
||||||
That's fine. For each import the flatnodes file get overwritten.
|
That's fine. For each import the flatnodes file get overwritten.
|
||||||
See [https://help.openstreetmap.org/questions/52419/nominatim-flatnode-storage](https://help.openstreetmap.org/questions/52419/nominatim-flatnode-storage)
|
See [https://help.openstreetmap.org/questions/52419/nominatim-flatnode-storage]()
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
|
||||||
@@ -212,3 +211,11 @@ for more information.
|
|||||||
### Can I import negative OSM ids into Nominatim?
|
### Can I import negative OSM ids into Nominatim?
|
||||||
|
|
||||||
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).
|
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).
|
||||||
|
|
||||||
|
### Missing XML or text declaration
|
||||||
|
|
||||||
|
The website might show: `XML Parsing Error: XML or text declaration not at start of entity Location.`
|
||||||
|
|
||||||
|
Make sure there are no spaces at the beginning of your `settings/local.php` file.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,22 @@
|
|||||||
# Importing the Database
|
# Importing the Database
|
||||||
|
|
||||||
The following instructions explain how to create a Nominatim database
|
The following instructions explain how to create a Nominatim database
|
||||||
from an OSM planet file. It is assumed that you have already successfully
|
from an OSM planet file and how to keep the database up to date. It
|
||||||
installed the Nominatim software itself and the `nominatim` tool can be found
|
is assumed that you have already successfully installed the Nominatim
|
||||||
in your `PATH`. If this is not the case, return to the
|
software itself, if not return to the [installation page](Installation.md).
|
||||||
[installation page](Installation.md).
|
|
||||||
|
|
||||||
## Creating the project directory
|
## Configuration setup in settings/local.php
|
||||||
|
|
||||||
Before you start the import, you should create a project directory for your
|
The Nominatim server can be customized via the file `settings/local.php`
|
||||||
new database installation. This directory receives all data that is related
|
in the build directory. Note that this is a PHP file, so it must always
|
||||||
to a single Nominatim setup: configuration, extra data, etc. Create a project
|
start like this:
|
||||||
directory apart from the Nominatim software and change into the directory:
|
|
||||||
|
|
||||||
```
|
<?php
|
||||||
mkdir ~/nominatim-planet
|
|
||||||
cd ~/nominatim-planet
|
|
||||||
```
|
|
||||||
|
|
||||||
In the following, we refer to the project directory as `$PROJECT_DIR`. To be
|
without any leading spaces.
|
||||||
able to copy&paste instructions, you can export the appropriate variable:
|
|
||||||
|
|
||||||
```
|
There are lots of configuration settings you can tweak. Have a look
|
||||||
export PROJECT_DIR=~/nominatim-planet
|
at `settings/default.php` for a full list. Most should have a sensible default.
|
||||||
```
|
|
||||||
|
|
||||||
The Nominatim tool assumes per default that the current working directory is
|
|
||||||
the project directory but you may explicitly state a different directory using
|
|
||||||
the `--project-dir` parameter. The following instructions assume that you run
|
|
||||||
all commands from the project directory.
|
|
||||||
|
|
||||||
!!! tip "Migration Tip"
|
|
||||||
|
|
||||||
Nominatim used to be run directly from the build directory until version 3.6.
|
|
||||||
Essentially, the build directory functioned as the project directory
|
|
||||||
for the database installation. This setup still works and can be useful for
|
|
||||||
development purposes. It is not recommended anymore for production setups.
|
|
||||||
Create a project directory that is separate from the Nominatim software.
|
|
||||||
|
|
||||||
### Configuration setup in `.env`
|
|
||||||
|
|
||||||
The Nominatim server can be customized via an `.env` configuration file in the
|
|
||||||
project directory. This is a file in [dotenv](https://github.com/theskumar/python-dotenv)
|
|
||||||
format which looks the same as variable settings in a standard shell environment.
|
|
||||||
You can also set the same configuration via environment variables. All
|
|
||||||
settings have a `NOMINATIM_` prefix to avoid conflicts with other environment
|
|
||||||
variables.
|
|
||||||
|
|
||||||
There are lots of configuration settings you can tweak. A full reference
|
|
||||||
can be found in the chapter [Configuration Settings](../customize/Settings.md).
|
|
||||||
Most should have a sensible default.
|
|
||||||
|
|
||||||
#### Flatnode files
|
#### Flatnode files
|
||||||
|
|
||||||
@@ -57,9 +24,9 @@ If you plan to import a large dataset (e.g. Europe, North America, planet),
|
|||||||
you should also enable flatnode storage of node locations. With this
|
you should also enable flatnode storage of node locations. With this
|
||||||
setting enabled, node coordinates are stored in a simple file instead
|
setting enabled, node coordinates are stored in a simple file instead
|
||||||
of the database. This will save you import time and disk storage.
|
of the database. This will save you import time and disk storage.
|
||||||
Add to your `.env`:
|
Add to your `settings/local.php`:
|
||||||
|
|
||||||
NOMINATIM_FLATNODE_FILE="/path/to/flatnode.file"
|
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
|
||||||
|
|
||||||
Replace the second part with a suitable path on your system and make sure
|
Replace the second part with a suitable path on your system and make sure
|
||||||
the directory exists. There should be at least 75GB of free space.
|
the directory exists. There should be at least 75GB of free space.
|
||||||
@@ -71,9 +38,9 @@ the directory exists. There should be at least 75GB of free space.
|
|||||||
Wikipedia can be used as an optional auxiliary data source to help indicate
|
Wikipedia can be used as an optional auxiliary data source to help indicate
|
||||||
the importance of OSM features. Nominatim will work without this information
|
the importance of OSM features. Nominatim will work without this information
|
||||||
but it will improve the quality of the results if this is installed.
|
but it will improve the quality of the results if this is installed.
|
||||||
This data is available as a binary download. Put it into your project directory:
|
This data is available as a binary download:
|
||||||
|
|
||||||
cd $PROJECT_DIR
|
cd $NOMINATIM_SOURCE_DIR/data
|
||||||
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
|
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
|
||||||
|
|
||||||
The file is about 400MB and adds around 4GB to the Nominatim database.
|
The file is about 400MB and adds around 4GB to the Nominatim database.
|
||||||
@@ -81,22 +48,17 @@ The file is about 400MB and adds around 4GB to the Nominatim database.
|
|||||||
!!! tip
|
!!! tip
|
||||||
If you forgot to download the wikipedia rankings, you can also add
|
If you forgot to download the wikipedia rankings, you can also add
|
||||||
importances after the import. Download the files, then run
|
importances after the import. Download the files, then run
|
||||||
`nominatim refresh --wiki-data --importance`. Updating importances for
|
`./utils/setup.php --import-wikipedia-articles`
|
||||||
a planet can take a couple of hours.
|
and `./utils/update.php --recompute-importance`.
|
||||||
|
|
||||||
### External postcodes
|
### Great Britain, USA postcodes
|
||||||
|
|
||||||
Nominatim can use postcodes from an external source to improve searching with
|
Nominatim can use postcodes from an external source to improve searches that
|
||||||
postcodes. We provide precomputed postcodes sets for the US (using TIGER data)
|
involve a GB or US postcode. This data can be optionally downloaded:
|
||||||
and the UK (using the [CodePoint OpenData set](https://osdatahub.os.uk/downloads/open/CodePointOpen).
|
|
||||||
This data can be optionally downloaded into the project directory:
|
|
||||||
|
|
||||||
cd $PROJECT_DIR
|
cd $NOMINATIM_SOURCE_DIR/data
|
||||||
wget https://www.nominatim.org/data/gb_postcodes.csv.gz
|
wget https://www.nominatim.org/data/gb_postcode_data.sql.gz
|
||||||
wget https://www.nominatim.org/data/us_postcodes.csv.gz
|
wget https://www.nominatim.org/data/us_postcode_data.sql.gz
|
||||||
|
|
||||||
You can also add your own custom postcode sources, see
|
|
||||||
[Customization of postcodes](../customize/Postcodes.md).
|
|
||||||
|
|
||||||
## Choosing the data to import
|
## Choosing the data to import
|
||||||
|
|
||||||
@@ -112,7 +74,7 @@ If you only need geocoding for a smaller region, then precomputed OSM extracts
|
|||||||
are a good way to reduce the database size and import time.
|
are a good way to reduce the database size and import time.
|
||||||
[Geofabrik](https://download.geofabrik.de) offers extracts for most countries.
|
[Geofabrik](https://download.geofabrik.de) offers extracts for most countries.
|
||||||
They even have daily updates which can be used with the update process described
|
They even have daily updates which can be used with the update process described
|
||||||
[in the next section](Update.md). There are also
|
[in the next section](../Update). There are also
|
||||||
[other providers for extracts](https://wiki.openstreetmap.org/wiki/Planet.osm#Downloading).
|
[other providers for extracts](https://wiki.openstreetmap.org/wiki/Planet.osm#Downloading).
|
||||||
|
|
||||||
Please be aware that some extracts are not cut exactly along the country
|
Please be aware that some extracts are not cut exactly along the country
|
||||||
@@ -124,28 +86,17 @@ that Nominatim cannot compute the areas for some administrative areas.
|
|||||||
About half of the data in Nominatim's database is not really used for serving
|
About half of the data in Nominatim's database is not really used for serving
|
||||||
the API. It is only there to allow the data to be updated from the latest
|
the API. It is only there to allow the data to be updated from the latest
|
||||||
changes from OSM. For many uses these dynamic updates are not really required.
|
changes from OSM. For many uses these dynamic updates are not really required.
|
||||||
If you don't plan to apply updates, you can run the import with the
|
If you don't plan to apply updates, the dynamic part of the database can be
|
||||||
`--no-updates` parameter. This will drop the dynamic part of the database as
|
safely dropped using the following command:
|
||||||
soon as it is not required anymore.
|
|
||||||
|
|
||||||
You can also drop the dynamic part later using the following command:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
nominatim freeze
|
./utils/setup.php --drop
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that you still need to provide for sufficient disk space for the initial
|
Note that you still need to provide for sufficient disk space for the initial
|
||||||
import. So this option is particularly interesting if you plan to transfer the
|
import. So this option is particularly interesting if you plan to transfer the
|
||||||
database or reuse the space later.
|
database or reuse the space later.
|
||||||
|
|
||||||
!!! warning
|
|
||||||
The datastructure for updates are also required when adding additional data
|
|
||||||
after the import, for example [TIGER housenumber data](../customize/Tiger.md).
|
|
||||||
If you plan to use those, you must not use the `--no-updates` parameter.
|
|
||||||
Do a normal import, add the external data and once you are done with
|
|
||||||
everything run `nominatim freeze`.
|
|
||||||
|
|
||||||
|
|
||||||
### Reverse-only Imports
|
### Reverse-only Imports
|
||||||
|
|
||||||
If you only want to use the Nominatim database for reverse lookups or
|
If you only want to use the Nominatim database for reverse lookups or
|
||||||
@@ -161,19 +112,19 @@ Nominatim normally sets up a full search database containing administrative
|
|||||||
boundaries, places, streets, addresses and POI data. There are also other
|
boundaries, places, streets, addresses and POI data. There are also other
|
||||||
import styles available which only read selected data:
|
import styles available which only read selected data:
|
||||||
|
|
||||||
* **admin**
|
* **settings/import-admin.style**
|
||||||
Only import administrative boundaries and places.
|
Only import administrative boundaries and places.
|
||||||
* **street**
|
* **settings/import-street.style**
|
||||||
Like the admin style but also adds streets.
|
Like the admin style but also adds streets.
|
||||||
* **address**
|
* **settings/import-address.style**
|
||||||
Import all data necessary to compute addresses down to house number level.
|
Import all data necessary to compute addresses down to house number level.
|
||||||
* **full**
|
* **settings/import-full.style**
|
||||||
Default style that also includes points of interest.
|
Default style that also includes points of interest.
|
||||||
* **extratags**
|
* **settings/import-extratags.style**
|
||||||
Like the full style but also adds most of the OSM tags into the extratags
|
Like the full style but also adds most of the OSM tags into the extratags
|
||||||
column.
|
column.
|
||||||
|
|
||||||
The style can be changed with the configuration `NOMINATIM_IMPORT_STYLE`.
|
The style can be changed with the configuration `CONST_Import_Style`.
|
||||||
|
|
||||||
To give you an idea of the impact of using the different styles, the table
|
To give you an idea of the impact of using the different styles, the table
|
||||||
below gives rough estimates of the final database size after import of a
|
below gives rough estimates of the final database size after import of a
|
||||||
@@ -192,8 +143,8 @@ full | 54h | 640 GB | 330 GB
|
|||||||
extratags | 54h | 650 GB | 340 GB
|
extratags | 54h | 650 GB | 340 GB
|
||||||
|
|
||||||
You can also customize the styles further.
|
You can also customize the styles further.
|
||||||
A [description of the style format](../customize/Import-Styles.md)
|
A [description of the style format](../develop/Import.md#configuring-the-import)
|
||||||
can be found in the customization guide.
|
can be found in the development section.
|
||||||
|
|
||||||
## Initial import of the data
|
## Initial import of the data
|
||||||
|
|
||||||
@@ -202,15 +153,12 @@ can be found in the customization guide.
|
|||||||
[Geofabrik](https://download.geofabrik.de).
|
[Geofabrik](https://download.geofabrik.de).
|
||||||
|
|
||||||
Download the data to import. Then issue the following command
|
Download the data to import. Then issue the following command
|
||||||
from the **project directory** to start the import:
|
from the **build directory** to start the import:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim import --osm-file <data file> 2>&1 | tee setup.log
|
./utils/setup.php --osm-file <data file> --all 2>&1 | tee setup.log
|
||||||
```
|
```
|
||||||
|
|
||||||
The **project directory** is the one that you have set up at the beginning.
|
|
||||||
See [creating the project directory](#creating-the-project-directory).
|
|
||||||
|
|
||||||
### Notes on full planet imports
|
### Notes on full planet imports
|
||||||
|
|
||||||
Even on a perfectly configured machine
|
Even on a perfectly configured machine
|
||||||
@@ -228,7 +176,7 @@ to load the OSM data into the PostgreSQL database. This step is very demanding
|
|||||||
in terms of RAM usage. osm2pgsql and PostgreSQL are running in parallel at
|
in terms of RAM usage. osm2pgsql and PostgreSQL are running in parallel at
|
||||||
this point. PostgreSQL blocks at least the part of RAM that has been configured
|
this point. PostgreSQL blocks at least the part of RAM that has been configured
|
||||||
with the `shared_buffers` parameter during
|
with the `shared_buffers` parameter during
|
||||||
[PostgreSQL tuning](Installation.md#postgresql-tuning)
|
[PostgreSQL tuning](Installation#postgresql-tuning)
|
||||||
and needs some memory on top of that. osm2pgsql needs at least 2GB of RAM for
|
and needs some memory on top of that. osm2pgsql needs at least 2GB of RAM for
|
||||||
its internal data structures, potentially more when it has to process very large
|
its internal data structures, potentially more when it has to process very large
|
||||||
relations. In addition it needs to maintain a cache for node locations. The size
|
relations. In addition it needs to maintain a cache for node locations. The size
|
||||||
@@ -244,20 +192,29 @@ MB. Make sure you leave enough RAM for PostgreSQL and osm2pgsql as mentioned
|
|||||||
above. If the system starts swapping or you are getting out-of-memory errors,
|
above. If the system starts swapping or you are getting out-of-memory errors,
|
||||||
reduce the cache size or even consider using a flatnode file.
|
reduce the cache size or even consider using a flatnode file.
|
||||||
|
|
||||||
|
### Verify the import
|
||||||
|
|
||||||
### Testing the installation
|
Run this script to verify all required tables and indices got created successfully.
|
||||||
|
|
||||||
Run this script to verify that all required tables and indices got created
|
|
||||||
successfully.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim admin --check-database
|
./utils/check_import_finished.php
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Setting up the website
|
||||||
|
|
||||||
|
Run the following command to set up the configuration file for the API frontend
|
||||||
|
`settings/settings-frontend.php`. These settings are used in website/*.php files.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./utils/setup.php --setup-website
|
||||||
|
```
|
||||||
|
!!! Note
|
||||||
|
This step is not necessary if you use `--all` option while setting up the DB.
|
||||||
|
|
||||||
Now you can try out your installation by running:
|
Now you can try out your installation by running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim serve
|
make serve
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs a small test server normally used for development. You can use it
|
This runs a small test server normally used for development. You can use it
|
||||||
@@ -265,24 +222,64 @@ to verify that your installation is working. Go to
|
|||||||
`http://localhost:8088/status.php` and you should see the message `OK`.
|
`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`.
|
You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`.
|
||||||
|
|
||||||
Note that search query is not supported for reverse-only imports. You can run a
|
|
||||||
reverse query, e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`.
|
|
||||||
|
|
||||||
To run Nominatim via webservers like Apache or nginx, please read the
|
To run Nominatim via webservers like Apache or nginx, please read the
|
||||||
[Deployment chapter](Deployment.md).
|
[Deployment chapter](Deployment.md).
|
||||||
|
|
||||||
## Adding search through category phrases
|
## Tuning the database
|
||||||
|
|
||||||
If you want to be able to search for places by their type through
|
Accurate word frequency information for search terms helps PostgreSQL's query
|
||||||
[special phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
|
planner to make the right decisions. Recomputing them can improve the performance
|
||||||
you also need to import these key phrases like this:
|
of forward geocoding in particular under high load. To recompute word counts run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nominatim special-phrases --import-from-wiki
|
./utils/update.php --recompute-word-counts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will take a couple of hours for a full planet installation. You can
|
||||||
|
also defer that step to a later point in time when you realise that
|
||||||
|
performance becomes an issue. Just make sure that updates are stopped before
|
||||||
|
running this function.
|
||||||
|
|
||||||
|
If you want to be able to search for places by their type through
|
||||||
|
[special key phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
|
||||||
|
you also need to enable these key phrases like this:
|
||||||
|
|
||||||
|
./utils/specialphrases.php --wiki-import > specialphrases.sql
|
||||||
|
psql -d nominatim -f specialphrases.sql
|
||||||
|
|
||||||
Note that this command downloads the phrases from the wiki link above. You
|
Note that this command downloads the phrases from the wiki link above. You
|
||||||
need internet access for the step.
|
need internet access for the step.
|
||||||
|
|
||||||
You can also import special phrases from a csv file, for more
|
|
||||||
information please see the [Customization part](../customize/Special-Phrases.md).
|
## Installing Tiger housenumber data for the US
|
||||||
|
|
||||||
|
Nominatim is able to use the official [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
|
||||||
|
address set to complement the OSM house number data in the US. You can add
|
||||||
|
TIGER data to your own Nominatim instance by following these steps. The
|
||||||
|
entire US adds about 10GB to your database.
|
||||||
|
|
||||||
|
1. Get preprocessed TIGER 2019 data and unpack it into the
|
||||||
|
data directory in your Nominatim sources:
|
||||||
|
|
||||||
|
cd Nominatim/data
|
||||||
|
wget https://nominatim.org/data/tiger2019-nominatim-preprocessed.tar.gz
|
||||||
|
tar xf tiger2019-nominatim-preprocessed.tar.gz
|
||||||
|
|
||||||
|
2. Import the data into your Nominatim database:
|
||||||
|
|
||||||
|
./utils/setup.php --import-tiger-data
|
||||||
|
|
||||||
|
3. Enable use of the Tiger data in your `settings/local.php` by adding:
|
||||||
|
|
||||||
|
@define('CONST_Use_US_Tiger_Data', true);
|
||||||
|
|
||||||
|
4. Apply the new settings:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
See the [developer's guide](../develop/data-sources.md#us-census-tiger) for more
|
||||||
|
information on how the data got preprocessed.
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,12 @@ and can't offer support.
|
|||||||
|
|
||||||
* [Docker](https://github.com/mediagis/nominatim-docker)
|
* [Docker](https://github.com/mediagis/nominatim-docker)
|
||||||
* [Docker on Kubernetes](https://github.com/peter-evans/nominatim-k8s)
|
* [Docker on Kubernetes](https://github.com/peter-evans/nominatim-k8s)
|
||||||
* [Kubernetes with Helm](https://github.com/robjuz/helm-charts/blob/master/charts/nominatim/README.md)
|
|
||||||
* [Ansible](https://github.com/synthesio/infra-ansible-nominatim)
|
* [Ansible](https://github.com/synthesio/infra-ansible-nominatim)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
### Software
|
### Software
|
||||||
|
|
||||||
!!! Warning
|
|
||||||
For larger installations you **must have** PostgreSQL 11+ and Postgis 3+
|
|
||||||
otherwise import and queries will be slow to the point of being unusable.
|
|
||||||
|
|
||||||
For compiling:
|
For compiling:
|
||||||
|
|
||||||
* [cmake](https://cmake.org/)
|
* [cmake](https://cmake.org/)
|
||||||
@@ -35,31 +30,23 @@ For compiling:
|
|||||||
* [proj](https://proj.org/)
|
* [proj](https://proj.org/)
|
||||||
* [bzip2](http://www.bzip.org/)
|
* [bzip2](http://www.bzip.org/)
|
||||||
* [zlib](https://www.zlib.net/)
|
* [zlib](https://www.zlib.net/)
|
||||||
* [ICU](http://site.icu-project.org/)
|
|
||||||
* [Boost libraries](https://www.boost.org/), including system and filesystem
|
* [Boost libraries](https://www.boost.org/), including system and filesystem
|
||||||
* PostgreSQL client libraries
|
* PostgreSQL client libraries
|
||||||
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
|
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
|
||||||
|
|
||||||
For running Nominatim:
|
For running Nominatim:
|
||||||
|
|
||||||
* [PostgreSQL](https://www.postgresql.org) (9.5+ will work, 11+ strongly recommended)
|
* [PostgreSQL](https://www.postgresql.org) (9.3+)
|
||||||
* [PostGIS](https://postgis.net) (2.2+ will work, 3.0+ strongly recommended)
|
* [PostGIS](https://postgis.net) (2.2+)
|
||||||
* [Python 3](https://www.python.org/) (3.6+)
|
* [Python 3](https://www.python.org/)
|
||||||
* [Psycopg2](https://www.psycopg.org) (2.7+)
|
* [Psycopg2](https://www.psycopg.org)
|
||||||
* [Python Dotenv](https://github.com/theskumar/python-dotenv)
|
|
||||||
* [psutil](https://github.com/giampaolo/psutil)
|
|
||||||
* [Jinja2](https://palletsprojects.com/p/jinja/)
|
|
||||||
* [PyICU](https://pypi.org/project/PyICU/)
|
|
||||||
* [PyYaml](https://pyyaml.org/) (5.1+)
|
|
||||||
* [datrie](https://github.com/pytries/datrie)
|
|
||||||
* [PHP](https://php.net) (7.0 or later)
|
* [PHP](https://php.net) (7.0 or later)
|
||||||
* PHP-pgsql
|
* PHP-pgsql
|
||||||
* PHP-intl (bundled with PHP)
|
* PHP-intl (bundled with PHP)
|
||||||
* PHP-cgi (for running queries from the command line)
|
|
||||||
|
|
||||||
For running continuous updates:
|
For running continuous updates:
|
||||||
|
|
||||||
* [pyosmium](https://osmcode.org/pyosmium/)
|
* [pyosmium](https://osmcode.org/pyosmium/) (with Python 3)
|
||||||
|
|
||||||
For dependencies for running tests and building documentation, see
|
For dependencies for running tests and building documentation, see
|
||||||
the [Development section](../develop/Development-Environment.md).
|
the [Development section](../develop/Development-Environment.md).
|
||||||
@@ -155,16 +142,6 @@ build at the same level as the Nominatim source directory run:
|
|||||||
```
|
```
|
||||||
cmake ../Nominatim
|
cmake ../Nominatim
|
||||||
make
|
make
|
||||||
sudo make install
|
|
||||||
```
|
|
||||||
|
|
||||||
Nominatim installs itself into `/usr/local` per default. To choose a different
|
|
||||||
installation directory add `-DCMAKE_INSTALL_PREFIX=<install root>` to the
|
|
||||||
cmake command. Make sure that the `bin` directory is available in your path
|
|
||||||
in that case, e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
export PATH=<install root>/bin:$PATH
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now continue with [importing the database](Import.md).
|
Now continue with [importing the database](Import.md).
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
This chapter describes the various operations the Nominatim database administrator
|
|
||||||
may use to clean and maintain the database. None of these operations is mandatory
|
|
||||||
but they may help improve the performance and accuracy of results.
|
|
||||||
|
|
||||||
|
|
||||||
## Updating postcodes
|
|
||||||
|
|
||||||
Command: `nominatim refresh --postcodes`
|
|
||||||
|
|
||||||
Postcode centroids (aka 'calculated postcodes') are generated by looking at all
|
|
||||||
postcodes of a country, grouping them and calculating the geometric centroid.
|
|
||||||
There is currently no logic to deal with extreme outliers (typos or other
|
|
||||||
mistakes in OSM data). There is also no check if a postcodes adheres to a
|
|
||||||
country's format, e.g. if Swiss postcodes are 4 digits.
|
|
||||||
|
|
||||||
When running regular updates, postcodes results can be improved by running
|
|
||||||
this command on a regular basis. Note that only the postcode table and the
|
|
||||||
postcode search terms are updated. The postcode that is assigned to each place
|
|
||||||
is only updated when the place is updated.
|
|
||||||
|
|
||||||
The command takes around 70min to run on the planet and needs ca. 40GB of
|
|
||||||
temporary disk space.
|
|
||||||
|
|
||||||
|
|
||||||
## Updating word counts
|
|
||||||
|
|
||||||
Command: `nominatim refresh --word-counts`
|
|
||||||
|
|
||||||
Nominatim keeps frequency statistics about all search terms it indexes. These
|
|
||||||
statistics are currently used to optimise queries to the database. Thus better
|
|
||||||
statistics mean better performance. Word counts are created once after import
|
|
||||||
and are usually sufficient even when running regular updates. You might want
|
|
||||||
to rerun the statistics computation when adding larger amounts of new data,
|
|
||||||
for example, when adding an additional country via `nominatim add-data`.
|
|
||||||
|
|
||||||
|
|
||||||
## Removing large deleted objects
|
|
||||||
|
|
||||||
Nominatim refuses to delete very large areas because often these deletions are
|
|
||||||
accidental and are reverted within hours. Instead the deletions are logged in
|
|
||||||
the `import_polygon_delete` table and left to the administrator to clean up.
|
|
||||||
|
|
||||||
There is currently no command to do that. You can use the following SQL
|
|
||||||
query to force a deletion on all objects that have been deleted more than
|
|
||||||
a certain timespan ago (here: 1 month):
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT place_force_delete(p.place_id) FROM import_polygon_delete d, placex p
|
|
||||||
WHERE p.osm_type = d.osm_type and p.osm_id = d.osm_id
|
|
||||||
and age(p.indexed_date) > '1 month'::interval
|
|
||||||
```
|
|
||||||
@@ -1,87 +1,10 @@
|
|||||||
# Database Migrations
|
# Database Migrations
|
||||||
|
|
||||||
Since version 3.7.0 Nominatim offers automatic migrations. Please follow
|
This page describes database migrations necessary to update existing databases
|
||||||
the following steps:
|
to newer versions of Nominatim.
|
||||||
|
|
||||||
* stop any updates that are potentially running
|
SQL statements should be executed from the PostgreSQL commandline. Execute
|
||||||
* update Nominatim to the newer version
|
`psql nominatim` to enter command line mode.
|
||||||
* go to your project directory and run `nominatim admin --migrate`
|
|
||||||
* (optionally) restart updates
|
|
||||||
|
|
||||||
Below you find additional migrations and hints about other structural and
|
|
||||||
breaking changes. **Please read them before running the migration.**
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
If you are migrating from a version <3.6, then you still have to follow
|
|
||||||
the manual migration steps up to 3.6.
|
|
||||||
|
|
||||||
## 3.7.0 -> 4.0.0
|
|
||||||
|
|
||||||
### NOMINATIM_PHRASE_CONFIG removed
|
|
||||||
|
|
||||||
Custom blacklist configurations for special phrases now need to be handed
|
|
||||||
with the `--config` parameter to `nominatim special-phrases`. Alternatively
|
|
||||||
you can put your custom configuration in the project directory in a file
|
|
||||||
named `phrase-settings.json`.
|
|
||||||
|
|
||||||
Version 3.8 also removes the automatic converter for the php format of
|
|
||||||
the configuration in older versions. If you are updating from Nominatim < 3.7
|
|
||||||
and still work with a custom `phrase-settings.php`, you need to manually
|
|
||||||
convert it into a json format.
|
|
||||||
|
|
||||||
### PHP utils removed
|
|
||||||
|
|
||||||
The old PHP utils have now been removed completely. You need to switch to
|
|
||||||
the appropriate functions of the nominatim command line tool. See
|
|
||||||
[Introducing `nominatim` command line tool](#introducing-nominatim-command-line-tool)
|
|
||||||
below.
|
|
||||||
|
|
||||||
## 3.6.0 -> 3.7.0
|
|
||||||
|
|
||||||
### New format and name of configuration file
|
|
||||||
|
|
||||||
The configuration for an import is now saved in a `.env` file in the project
|
|
||||||
directory. This file follows the dotenv format. For more information, see
|
|
||||||
the [installation chapter](Import.md#configuration-setup-in-env).
|
|
||||||
|
|
||||||
To migrate to the new system, create a new project directory, add the `.env`
|
|
||||||
file and port your custom configuration from `settings/local.php`. Most
|
|
||||||
settings are named similar and only have received a `NOMINATIM_` prefix.
|
|
||||||
Use the default settings in `settings/env.defaults` as a reference.
|
|
||||||
|
|
||||||
### New location for data files
|
|
||||||
|
|
||||||
External data files for Wikipedia importance, postcodes etc. are no longer
|
|
||||||
expected to reside in the source tree by default. Instead they will be searched
|
|
||||||
in the project directory. If you have an automated setup script you must
|
|
||||||
either adapt the download location or explicitly set the location of the
|
|
||||||
files to the old place in your `.env`.
|
|
||||||
|
|
||||||
### Introducing `nominatim` command line tool
|
|
||||||
|
|
||||||
The various php utilities have been replaced with a single `nominatim`
|
|
||||||
command line tool. Make sure to adapt any scripts. There is no direct 1:1
|
|
||||||
matching between the old utilities and the commands of nominatim CLI. The
|
|
||||||
following list gives you a list of nominatim sub-commands that contain
|
|
||||||
functionality of each script:
|
|
||||||
|
|
||||||
* ./utils/setup.php: `import`, `freeze`, `refresh`
|
|
||||||
* ./utils/update.php: `replication`, `add-data`, `index`, `refresh`
|
|
||||||
* ./utils/specialphrases.php: `special-phrases`
|
|
||||||
* ./utils/check_import_finished.php: `admin`
|
|
||||||
* ./utils/warm.php: `admin`
|
|
||||||
* ./utils/export.php: `export`
|
|
||||||
|
|
||||||
Try `nominatim <command> --help` for more information about each subcommand.
|
|
||||||
|
|
||||||
`./utils/query.php` no longer exists in its old form. `nominatim search`
|
|
||||||
provides a replacement but returns different output.
|
|
||||||
|
|
||||||
### Switch to normalized house numbers
|
|
||||||
|
|
||||||
The housenumber column in the placex table uses now normalized version.
|
|
||||||
The automatic migration step will convert the column but this may take a
|
|
||||||
very long time. It is advisable to take the machine offline while doing that.
|
|
||||||
|
|
||||||
## 3.5.0 -> 3.6.0
|
## 3.5.0 -> 3.6.0
|
||||||
|
|
||||||
@@ -101,7 +24,7 @@ done
|
|||||||
|
|
||||||
The debugging UI is no longer directly provided with Nominatim. Instead we
|
The debugging UI is no longer directly provided with Nominatim. Instead we
|
||||||
now provide a simple Javascript application. Please refer to
|
now provide a simple Javascript application. Please refer to
|
||||||
[Setting up the Nominatim UI](Setup-Nominatim-UI.md) for details on how to
|
[Setting up the Nominatim UI](../Setup-Nominatim-UI) for details on how to
|
||||||
set up the UI.
|
set up the UI.
|
||||||
|
|
||||||
The icons served together with the API responses have been moved to the
|
The icons served together with the API responses have been moved to the
|
||||||
@@ -145,14 +68,6 @@ configuration file, run the following command after updating:
|
|||||||
./utils/setup.php --setup-website
|
./utils/setup.php --setup-website
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update SQL code
|
|
||||||
|
|
||||||
To update the SQL code to the leatest version run:
|
|
||||||
|
|
||||||
```
|
|
||||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3.4.0 -> 3.5.0
|
## 3.4.0 -> 3.5.0
|
||||||
|
|
||||||
### New Wikipedia/Wikidata importance tables
|
### New Wikipedia/Wikidata importance tables
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ installation. For more details, please also have a look at the
|
|||||||
|
|
||||||
## Installing nominatim-ui
|
## Installing nominatim-ui
|
||||||
|
|
||||||
We provide regular releases of nominatim-ui that contain the packaged website.
|
nominatim-ui does not need any special installation, just download, configure
|
||||||
They do not need any special installation. Just download, configure
|
and run it.
|
||||||
and run it. Grab the latest release from
|
|
||||||
[nominatim-ui's Github release page](https://github.com/osm-search/nominatim-ui/releases)
|
|
||||||
and unpack it. You can use `nominatim-ui-x.x.x.tar.gz` or `nominatim-ui-x.x.x.zip`.
|
|
||||||
|
|
||||||
Next you need to adapt the UI yo your installation. Custom settings need to be
|
Clone the source from github:
|
||||||
put into `dist/theme/config.theme.js`. At a minimum you need to
|
|
||||||
set `Nominatim_API_Endpoint` to point to your Nominatim installation:
|
git clone https://github.com/osm-search/nominatim-ui
|
||||||
|
|
||||||
|
Copy the example configuration into the right place:
|
||||||
|
|
||||||
cd nominatim-ui
|
cd nominatim-ui
|
||||||
echo "Nominatim_Config.Nominatim_API_Endpoint='https:\\myserver.org\nominatim';" > dist/theme/config.theme.js
|
cp dist/config.example.js dist/config.js
|
||||||
|
|
||||||
For the full set of available settings, have a look at `dist/config.defaults.js`.
|
Now adapt the configuration to your needs. You need at least
|
||||||
|
to change the `Nominatim_API_Endpoint` to point to your Nominatim installation.
|
||||||
|
|
||||||
Then you can just test it locally by spinning up a webserver in the `dist`
|
Then you can just test it locally by spinning up a webserver in the `dist`
|
||||||
directory. For example, with Python:
|
directory. For example, with Python:
|
||||||
|
|||||||
@@ -1,166 +1,67 @@
|
|||||||
# Updating the Database
|
# Updating the Database
|
||||||
|
|
||||||
There are many different ways to update your Nominatim database.
|
There are many different ways to update your Nominatim database.
|
||||||
The following section describes how to keep it up-to-date using
|
The following section describes how to keep it up-to-date with Pyosmium.
|
||||||
an [online replication service for OpenStreetMap data](https://wiki.openstreetmap.org/wiki/Planet.osm/diffs)
|
For a list of other methods see the output of `./utils/update.php --help`.
|
||||||
For a list of other methods to add or update data see the output of
|
|
||||||
`nominatim add-data --help`.
|
|
||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
If you have configured a flatnode file for the import, then you
|
If you have configured a flatnode file for the import, then you
|
||||||
need to keep this flatnode file around for updates.
|
need to keep this flatnode file around for updates.
|
||||||
|
|
||||||
### Installing the newest version of Pyosmium
|
#### Installing the newest version of Pyosmium
|
||||||
|
|
||||||
The replication process uses
|
It is recommended to install Pyosmium via pip. Make sure to use python3.
|
||||||
[Pyosmium](https://docs.osmcode.org/pyosmium/latest/updating_osm_data.html)
|
|
||||||
to download update data from the server.
|
|
||||||
It is recommended to install Pyosmium via pip.
|
|
||||||
Run (as the same user who will later run the updates):
|
Run (as the same user who will later run the updates):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip3 install --user osmium
|
pip3 install --user osmium
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting up the update process
|
Nominatim needs a tool called `pyosmium-get-changes` which comes with
|
||||||
|
Pyosmium. You need to tell Nominatim where to find it. Add the
|
||||||
|
following line to your `settings/local.php`:
|
||||||
|
|
||||||
Next the update process needs to be initialised. By default Nominatim is configured
|
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
|
||||||
|
|
||||||
|
The path above is fine if you used the `--user` parameter with pip.
|
||||||
|
Replace `user` with your user name.
|
||||||
|
|
||||||
|
#### Setting up the update process
|
||||||
|
|
||||||
|
Next the update needs to be initialised. By default Nominatim is configured
|
||||||
to update using the global minutely diffs.
|
to update using the global minutely diffs.
|
||||||
|
|
||||||
If you want a different update source you will need to add some settings
|
If you want a different update source you will need to add some settings
|
||||||
to `.env`. For example, to use the daily country extracts
|
to `settings/local.php`. For example, to use the daily country extracts
|
||||||
diffs for Ireland from Geofabrik add the following:
|
diffs for Ireland from Geofabrik add the following:
|
||||||
|
|
||||||
# base URL of the replication service
|
// base URL of the replication service
|
||||||
NOMINATIM_REPLICATION_URL="https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates"
|
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
|
||||||
# How often upstream publishes diffs (in seconds)
|
// How often upstream publishes diffs
|
||||||
NOMINATIM_REPLICATION_UPDATE_INTERVAL=86400
|
@define('CONST_Replication_Update_Interval', '86400');
|
||||||
# How long to sleep if no update found yet (in seconds)
|
// How long to sleep if no update found yet
|
||||||
NOMINATIM_REPLICATION_RECHECK_INTERVAL=900
|
@define('CONST_Replication_Recheck_Interval', '900');
|
||||||
|
|
||||||
To set up the update process now run the following command:
|
To set up the update process now run the following command:
|
||||||
|
|
||||||
nominatim replication --init
|
./utils/update.php --init-updates
|
||||||
|
|
||||||
It outputs the date where updates will start. Recheck that this date is
|
It outputs the date where updates will start. Recheck that this date is
|
||||||
what you expect.
|
what you expect.
|
||||||
|
|
||||||
The `replication --init` command needs to be rerun whenever the replication
|
The `--init-updates` command needs to be rerun whenever the replication service
|
||||||
service is changed.
|
is changed.
|
||||||
|
|
||||||
### Updating Nominatim
|
#### Updating Nominatim
|
||||||
|
|
||||||
Nominatim supports different modes how to retrieve the update data from the
|
The following command will keep your database constantly up to date:
|
||||||
server. Which one you want to use depends on your exact setup and how often you
|
|
||||||
want to retrieve updates.
|
|
||||||
|
|
||||||
These instructions are for using a single source of updates. If you have
|
./utils/update.php --import-osmosis-all
|
||||||
imported multiple country extracts and want to keep them
|
|
||||||
up-to-date, [Advanced installations section](Advanced-Installations.md)
|
|
||||||
contains instructions to set up and update multiple country extracts.
|
|
||||||
|
|
||||||
#### Continuous updates
|
(Note that even though the old name "import-osmosis-all" has been kept for
|
||||||
|
compatibility reasons, Osmosis is not required to run this - it uses pyosmium
|
||||||
|
behind the scenes.)
|
||||||
|
|
||||||
This is the easiest mode. Simply run the replication command without any
|
If you have imported multiple country extracts and want to keep them
|
||||||
parameters:
|
up-to-date, [Advanced installations section](Advanced-Installations.md) contains instructions
|
||||||
|
to set up and update multiple country extracts.
|
||||||
nominatim replication
|
|
||||||
|
|
||||||
The update application keeps running forever and retrieves and applies
|
|
||||||
new updates from the server as they are published.
|
|
||||||
|
|
||||||
You can run this command as a simple systemd service. Create a service
|
|
||||||
description like that in `/etc/systemd/system/nominatim-update.service`:
|
|
||||||
|
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=Continuous updates of Nominatim
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
WorkingDirectory=/srv/nominatim
|
|
||||||
ExecStart=nominatim replication
|
|
||||||
StandardOutput=append:/var/log/nominatim-updates.log
|
|
||||||
StandardError=append:/var/log/nominatim-updates.error.log
|
|
||||||
User=nominatim
|
|
||||||
Group=nominatim
|
|
||||||
Type=simple
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the `WorkingDirectory` with your project directory. Also adapt user
|
|
||||||
and group names as required.
|
|
||||||
|
|
||||||
Now activate the service and start the updates:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable nominatim-updates
|
|
||||||
sudo systemctl start nominatim-updates
|
|
||||||
```
|
|
||||||
|
|
||||||
#### One-time mode
|
|
||||||
|
|
||||||
When the `--once` parameter is given, then Nominatim will download exactly one
|
|
||||||
batch of updates and then exit. This one-time mode still respects the
|
|
||||||
`NOMINATIM_REPLICATION_UPDATE_INTERVAL` that you have set. If according to
|
|
||||||
the update interval no new data has been published yet, it will go to sleep
|
|
||||||
until the next expected update and only then attempt to download the next batch.
|
|
||||||
|
|
||||||
The one-time mode is particularly useful if you want to run updates continuously
|
|
||||||
but need to schedule other work in between updates. For example, the main
|
|
||||||
service at osm.org uses it, to regularly recompute postcodes -- a process that
|
|
||||||
must not be run while updates are in progress. Its update script
|
|
||||||
looks like this:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Switch to your project directory.
|
|
||||||
cd /srv/nominatim
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
nominatim replication --once
|
|
||||||
if [ -f "/srv/nominatim/schedule-mainenance" ]; then
|
|
||||||
rm /srv/nominatim/schedule-mainenance
|
|
||||||
nominatim refresh --postcodes
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
A cron job then creates the file `/srv/nominatim/need-mainenance` once per night.
|
|
||||||
|
|
||||||
|
|
||||||
#### Catch-up mode
|
|
||||||
|
|
||||||
With the `--catch-up` parameter, Nominatim will immediately try to download
|
|
||||||
all changes from the server until the database is up-to-date. The catch-up mode
|
|
||||||
still respects the parameter `NOMINATIM_REPLICATION_MAX_DIFF`. It downloads and
|
|
||||||
applies the changes in appropriate batches until all is done.
|
|
||||||
|
|
||||||
The catch-up mode is foremost useful to bring the database up to speed after the
|
|
||||||
initial import. Give that the service usually is not in production at this
|
|
||||||
point, you can temporarily be a bit more generous with the batch size and
|
|
||||||
number of threads you use for the updates by running catch-up like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd /srv/nominatim
|
|
||||||
NOMINATIM_REPLICATION_MAX_DIFF=5000 nominatim replication --catch-up --threads 15
|
|
||||||
```
|
|
||||||
|
|
||||||
The catch-up mode is also useful when you want to apply updates at a lower
|
|
||||||
frequency than what the source publishes. You can set up a cron job to run
|
|
||||||
replication catch-up at whatever interval you desire.
|
|
||||||
|
|
||||||
!!! hint
|
|
||||||
When running scheduled updates with catch-up, it is a good idea to choose
|
|
||||||
a replication source with an update frequency that is an order of magnitude
|
|
||||||
lower. For example, if you want to update once a day, use an hourly updated
|
|
||||||
source. This makes sure that you don't miss an entire day of updates when
|
|
||||||
the source is unexpectely late to publish its update.
|
|
||||||
|
|
||||||
If you want to use the source with the same update frequency (e.g. a daily
|
|
||||||
updated source with daily updates), use the
|
|
||||||
continuous update mode. It ensures to re-request the newest update until it
|
|
||||||
is published.
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ it contains the county/state/country across the border.
|
|||||||
#### 3. I get different counties/states/countries when I change the zoom parameter in the reverse query. How is that possible?
|
#### 3. I get different counties/states/countries when I change the zoom parameter in the reverse query. How is that possible?
|
||||||
|
|
||||||
This is basically the same problem as in the previous answer.
|
This is basically the same problem as in the previous answer.
|
||||||
The zoom level influences at which [search rank](../customize/Ranking.md#search-rank) Nominatim starts looking
|
The zoom level influences at which [search rank](https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level) Nominatim starts looking
|
||||||
for the closest object. So the closest house number maybe on one side of the
|
for the closest object. So the closest house number maybe on one side of the
|
||||||
border while the closest street is on the other. As the address details contain
|
border while the closest street is on the other. As the address details contain
|
||||||
the address of the closest object found, you might sometimes get one result,
|
the address of the closest object found, you might sometimes get one result,
|
||||||
@@ -58,4 +58,4 @@ The [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) is more
|
|||||||
suited for these kinds of queries.
|
suited for these kinds of queries.
|
||||||
|
|
||||||
That said if you installed your own Nominatim instance you can use the
|
That said if you installed your own Nominatim instance you can use the
|
||||||
`nominatim export` PHP script as basis to return such lists.
|
`/utils/export.php` PHP script as basis to return such lists.
|
||||||
|
|||||||
@@ -290,7 +290,6 @@ with a designation label. Per default the following labels may appear:
|
|||||||
* emergency, historic, military, natural, landuse, place, railway,
|
* emergency, historic, military, natural, landuse, place, railway,
|
||||||
man_made, aerialway, boundary, amenity, aeroway, club, craft, leisure,
|
man_made, aerialway, boundary, amenity, aeroway, club, craft, leisure,
|
||||||
office, mountain_pass, shop, tourism, bridge, tunnel, waterway
|
office, mountain_pass, shop, tourism, bridge, tunnel, waterway
|
||||||
* postcode
|
|
||||||
|
|
||||||
They roughly correspond to the classification of the OpenStreetMap data
|
They roughly correspond to the classification of the OpenStreetMap data
|
||||||
according to either the `place` tag or the main key of the object.
|
according to either the `place` tag or the main key of the object.
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ This overrides the specified machine readable format. (Default: 0)
|
|||||||
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https:\/\/www.openstreetmap.org\/copyright",
|
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https:\/\/www.openstreetmap.org\/copyright",
|
||||||
"osm_type":"way",
|
"osm_type":"way",
|
||||||
"osm_id":"280940520",
|
"osm_id":"280940520",
|
||||||
"lat":"-34.4391708",
|
"lat":"-34.4391708",
|
||||||
"lon":"-58.7064573",
|
"lon":"-58.7064573",
|
||||||
"place_rank":"26",
|
"place_rank":"26",
|
||||||
"category":"highway",
|
"category":"highway",
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ The search term may be specified with two different sets of parameters:
|
|||||||
|
|
||||||
Free-form query string to search for.
|
Free-form query string to search for.
|
||||||
Free-form queries are processed first left-to-right and then right-to-left if that fails. So you may search for
|
Free-form queries are processed first left-to-right and then right-to-left if that fails. So you may search for
|
||||||
[pilkington avenue, birmingham](https://nominatim.openstreetmap.org/search?q=pilkington+avenue,birmingham) as well as for
|
[pilkington avenue, birmingham](//nominatim.openstreetmap.org/search?q=pilkington+avenue,birmingham) as well as for
|
||||||
[birmingham, pilkington avenue](https://nominatim.openstreetmap.org/search?q=birmingham,+pilkington+avenue).
|
[birmingham, pilkington avenue](//nominatim.openstreetmap.org/search?q=birmingham,+pilkington+avenue).
|
||||||
Commas are optional, but improve performance by reducing the complexity of the search.
|
Commas are optional, but improve performance by reducing the complexity of the search.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,16 +35,10 @@ will return HTTP code 200 and a structure
|
|||||||
{
|
{
|
||||||
"status": 0,
|
"status": 0,
|
||||||
"message": "OK",
|
"message": "OK",
|
||||||
"data_updated": "2020-05-04T14:47:00+00:00",
|
"data_updated": "2020-05-04T14:47:00+00:00"
|
||||||
"software_version": "3.6.0-0",
|
|
||||||
"database_version": "3.6.0-0"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `software_version` field contains the version of Nominatim used to serve
|
|
||||||
the API. The `database_version` field contains the version of the data format
|
|
||||||
in the database.
|
|
||||||
|
|
||||||
On error will also return HTTP status code 200 and a structure with error
|
On error will also return HTTP status code 200 and a structure with error
|
||||||
code and message, e.g.
|
code and message, e.g.
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
Nominatim comes with a predefined set of configuration options that should
|
|
||||||
work for most standard installations. If you have special requirements, there
|
|
||||||
are many places where the configuration can be adapted. This chapter describes
|
|
||||||
the following configurable parts:
|
|
||||||
|
|
||||||
* [Global Settings](Settings.md) has a detailed description of all parameters that
|
|
||||||
can be set in your local `.env` configuration
|
|
||||||
* [Import styles](Import-Styles.md) explains how to write your own import style
|
|
||||||
in order to control what kind of OSM data will be imported
|
|
||||||
* [Place ranking](Ranking.md) describes the configuration around classifing
|
|
||||||
places in terms of their importance and their role in an address
|
|
||||||
* [Tokenizers](Tokenizers.md) describes the configuration of the module
|
|
||||||
responsible for analysing and indexing names
|
|
||||||
* [Special Phrases](Special-Phrases.md) are common nouns or phrases that
|
|
||||||
can be used in search to identify a class of places
|
|
||||||
|
|
||||||
There are also guides for adding the following external data:
|
|
||||||
|
|
||||||
* [US house numbers from the TIGER dataset](Tiger.md)
|
|
||||||
* [External postcodes](Postcodes.md)
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# External postcode data
|
|
||||||
|
|
||||||
Nominatim creates a table of known postcode centroids during import. This table
|
|
||||||
is used for searches of postcodes and for adding postcodes to places where the
|
|
||||||
OSM data does not provide one. These postcode centroids are mainly computed
|
|
||||||
from the OSM data itself. In addition, Nominatim supports reading postcode
|
|
||||||
information from an external CSV file, to supplement the postcodes that are
|
|
||||||
missing in OSM.
|
|
||||||
|
|
||||||
To enable external postcode support, simply put one CSV file per country into
|
|
||||||
your project directory and name it `<CC>_postcodes.csv`. `<CC>` must be the
|
|
||||||
two-letter country code for which to apply the file. The file may also be
|
|
||||||
gzipped. Then it must be called `<CC>_postcodes.csv.gz`.
|
|
||||||
|
|
||||||
The CSV file must use commas as a delimiter and have a header line. Nominatim
|
|
||||||
expects three columns to be present: `postcode`, `lat` and `lon`. All other
|
|
||||||
columns are ignored. `lon` and `lat` must describe the x and y coordinates of the
|
|
||||||
postcode centroids in WGS84.
|
|
||||||
|
|
||||||
The postcode files are loaded only when there is data for the given country
|
|
||||||
in your database. For example, if there is a `us_postcodes.csv` file in your
|
|
||||||
project directory but you import only an excerpt of Italy, then the US postcodes
|
|
||||||
will simply be ignored.
|
|
||||||
|
|
||||||
As a rule, the external postcode data should be put into the project directory
|
|
||||||
**before** starting the initial import. Still, you can add, remove and update the
|
|
||||||
external postcode data at any time. Simply
|
|
||||||
run:
|
|
||||||
|
|
||||||
```
|
|
||||||
nominatim refresh --postcodes
|
|
||||||
```
|
|
||||||
|
|
||||||
to make the changes visible in your database. Be aware, however, that the changes
|
|
||||||
only have an immediate effect on searches for postcodes. Postcodes that were
|
|
||||||
added to places are only updated, when they are reindexed. That usually happens
|
|
||||||
only during replication updates.
|
|
||||||
@@ -1,649 +0,0 @@
|
|||||||
This section provides a reference of all configuration parameters that can
|
|
||||||
be used with Nominatim.
|
|
||||||
|
|
||||||
# Configuring Nominatim
|
|
||||||
|
|
||||||
Nominatim uses [dotenv](https://github.com/theskumar/python-dotenv) to manage
|
|
||||||
its configuration settings. There are two means to set configuration
|
|
||||||
variables: through an `.env` configuration file or through an environment
|
|
||||||
variable.
|
|
||||||
|
|
||||||
The `.env` configuration file needs to be placed into the
|
|
||||||
[project directory](../admin/Import.md#creating-the-project-directory). It
|
|
||||||
must contain configuration parameters in `<parameter>=<value>` format.
|
|
||||||
Please refer to the dotenv documentation for details.
|
|
||||||
|
|
||||||
The configuration options may also be set in the form of shell environment
|
|
||||||
variables. This is particularly useful, when you want to temporarily change
|
|
||||||
a configuration option. For example, to force the replication serve to
|
|
||||||
download the next change, you can temporarily disable the update interval:
|
|
||||||
|
|
||||||
NOMINATIM_REPLICATION_UPDATE_INTERVAL=0 nominatim replication --once
|
|
||||||
|
|
||||||
If a configuration option is defined through .env file and environment
|
|
||||||
variable, then the latter takes precedence.
|
|
||||||
|
|
||||||
## Configuration Parameter Reference
|
|
||||||
|
|
||||||
### Import and Database Settings
|
|
||||||
|
|
||||||
#### NOMINATIM_DATABASE_DSN
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Database connection string |
|
|
||||||
| **Format:** | string: `pgsql:<param1>=<value1>;<param2>=<value2>;...` |
|
|
||||||
| **Default:** | pgsql:dbname=nominatim |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Sets the connection parameters for the Nominatim database. At a minimum
|
|
||||||
the name of the database (`dbname`) is required. You can set any additional
|
|
||||||
parameter that is understood by libpq. See the [Postgres documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS) for a full list.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
It is usually recommended not to set the password directly in this
|
|
||||||
configuration parameter. Use a
|
|
||||||
[password file](https://www.postgresql.org/docs/current/libpq-pgpass.html)
|
|
||||||
instead.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_DATABASE_WEBUSER
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Database query user |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | www-data |
|
|
||||||
| **After Changes:** | cannot be changed after import |
|
|
||||||
|
|
||||||
Defines the name of the database user that will run search queries. Usually
|
|
||||||
this is the user under which the webserver is executed. When running Nominatim
|
|
||||||
via php-fpm, you can also define a separate query user. The Postgres user
|
|
||||||
needs to be set up before starting the import.
|
|
||||||
|
|
||||||
Nominatim grants minimal rights to this user to all tables that are needed
|
|
||||||
for running geocoding queries.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_DATABASE_MODULE_PATH
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Directory where to find the PostgreSQL server module |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (use `<project_directory>/module`) |
|
|
||||||
| **After Changes:** | run `nominatim refresh --functions` |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
Defines the directory in which the PostgreSQL server module `nominatim.so`
|
|
||||||
is stored. The directory and module must be accessible by the PostgreSQL
|
|
||||||
server.
|
|
||||||
|
|
||||||
For information on how to use this setting when working with external databases,
|
|
||||||
see [Advanced Installations](../admin/Advanced-Installations.md).
|
|
||||||
|
|
||||||
The option is only used by the Legacy tokenizer and ignored otherwise.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TOKENIZER
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Tokenizer used for normalizing and parsing queries and names |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | legacy |
|
|
||||||
| **After Changes:** | cannot be changed after import |
|
|
||||||
|
|
||||||
Sets the tokenizer type to use for the import. For more information on
|
|
||||||
available tokenizers and how they are configured, see
|
|
||||||
[Tokenizers](../customize/Tokenizers.md).
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TOKENIZER_CONFIG
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Configuration file for the tokenizer |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (default file depends on tokenizer) |
|
|
||||||
| **After Changes:** | see documentation for each tokenizer |
|
|
||||||
|
|
||||||
Points to the file with additional configuration for the tokenizer.
|
|
||||||
See the [Tokenizer](../customize/Tokenizers.md) descriptions for details
|
|
||||||
on the file format.
|
|
||||||
|
|
||||||
If a relative path is given, then the file is searched first relative to the
|
|
||||||
project directory and then in the global settings directory.
|
|
||||||
|
|
||||||
#### NOMINATIM_MAX_WORD_FREQUENCY
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Number of occurrences before a word is considered frequent |
|
|
||||||
| **Format:** | int |
|
|
||||||
| **Default:** | 50000 |
|
|
||||||
| **After Changes:** | cannot be changed after import |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
The word frequency count is used by the Legacy tokenizer to automatically
|
|
||||||
identify _stop words_. Any partial term that occurs more often then what
|
|
||||||
is defined in this setting, is effectively ignored during search.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_LIMIT_REINDEXING
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Avoid invalidating large areas |
|
|
||||||
| **Format:** | bool |
|
|
||||||
| **Default:** | yes |
|
|
||||||
|
|
||||||
Nominatim computes the address of each place at indexing time. This has the
|
|
||||||
advantage to make search faster but also means that more objects needs to
|
|
||||||
be invalidated when the data changes. For example, changing the name of
|
|
||||||
the state of Florida would require recomputing every single address point
|
|
||||||
in the state to make the new name searchable in conjunction with addresses.
|
|
||||||
|
|
||||||
Setting this option to 'yes' means that Nominatim skips reindexing of contained
|
|
||||||
objects when the area becomes too large.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_LANGUAGES
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Restrict search languages |
|
|
||||||
| **Format:** | string: comma-separated list of language codes |
|
|
||||||
| **Default:** | _empty_ |
|
|
||||||
|
|
||||||
Normally Nominatim will include all language variants of name:XX
|
|
||||||
in the search index. Set this to a comma separated list of language
|
|
||||||
codes, to restrict import to a subset of languages.
|
|
||||||
|
|
||||||
Currently only affects the initial import of country names and special phrases.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TERM_NORMALIZATION
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Rules for normalizing terms for comparisons |
|
|
||||||
| **Format:** | string: semicolon-separated list of ICU rules |
|
|
||||||
| **Default:** | :: NFD (); [[:Nonspacing Mark:] [:Cf:]] >; :: lower (); [[:Punctuation:][:Space:]]+ > ' '; :: NFC (); |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
[Special phrases](Special-Phrases.md) have stricter matching requirements than
|
|
||||||
normal search terms. They must appear exactly in the query after this term
|
|
||||||
normalization has been applied.
|
|
||||||
|
|
||||||
Only has an effect on the Legacy tokenizer. For the ICU tokenizer the rules
|
|
||||||
defined in the
|
|
||||||
[normalization section](Tokenizers.md#normalization-and-transliteration)
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_USE_US_TIGER_DATA
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Enable searching for Tiger house number data |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
| **After Changes:** | run `nominatim --refresh --functions` |
|
|
||||||
|
|
||||||
When this setting is enabled, search and reverse queries also take data
|
|
||||||
from [Tiger house number data](Tiger.md) into account.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_USE_AUX_LOCATION_DATA
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Enable searching in external house number tables |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
| **After Changes:** | run `nominatim --refresh --functions` |
|
|
||||||
| **Comment:** | Do not use. |
|
|
||||||
|
|
||||||
When this setting is enabled, search queries also take data from external
|
|
||||||
house number tables into account.
|
|
||||||
|
|
||||||
*Warning:* This feature is currently unmaintained and should not be used.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_HTTP_PROXY
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Use HTTP proxy when downloading data |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
|
|
||||||
When this setting is enabled and at least
|
|
||||||
[NOMINATIM_HTTP_PROXY_HOST](#nominatim_http_proxy_host) and
|
|
||||||
[NOMINATIM_HTTP_PROXY_PORT](#nominatim_http_proxy_port) are set, the
|
|
||||||
configured proxy will be used, when downloading external data like
|
|
||||||
replication diffs.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_HTTP_PROXY_HOST
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Host name of the proxy to use |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | _empty_ |
|
|
||||||
|
|
||||||
When [NOMINATIM_HTTP_PROXY](#nominatim_http_proxy) is enabled, this setting
|
|
||||||
configures the proxy host name.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_HTTP_PROXY_PORT
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Port number of the proxy to use |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 3128 |
|
|
||||||
|
|
||||||
When [NOMINATIM_HTTP_PROXY](#nominatim_http_proxy) is enabled, this setting
|
|
||||||
configures the port number to use with the proxy.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_HTTP_PROXY_LOGIN
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Username for proxies that require login |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | _empty_ |
|
|
||||||
|
|
||||||
When [NOMINATIM_HTTP_PROXY](#nominatim_http_proxy) is enabled, use this
|
|
||||||
setting to define the username for proxies that require a login.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_HTTP_PROXY_PASSWORD
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Password for proxies that require login |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | _empty_ |
|
|
||||||
|
|
||||||
When [NOMINATIM_HTTP_PROXY](#nominatim_http_proxy) is enabled, use this
|
|
||||||
setting to define the password for proxies that require a login.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_OSM2PGSQL_BINARY
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Location of the osm2pgsql binary |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (use binary shipped with Nominatim) |
|
|
||||||
| **Comment:** | EXPERT ONLY |
|
|
||||||
|
|
||||||
Nominatim uses [osm2pgsql](https://osm2pgsql.org) to load the OSM data
|
|
||||||
initially into the database. Nominatim comes bundled with a version of
|
|
||||||
osm2pgsql that is guaranteed to be compatible. Use this setting to use
|
|
||||||
a different binary instead. You should do this only when you know exactly
|
|
||||||
what you are doing. If the osm2pgsql version is not compatible, then the
|
|
||||||
result is undefined.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_WIKIPEDIA_DATA_PATH
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Directory with the wikipedia importance data |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (project directory) |
|
|
||||||
|
|
||||||
Set a custom location for the
|
|
||||||
[wikipedia ranking file](../admin/Import.md#wikipediawikidata-rankings). When
|
|
||||||
unset, Nominatim expects the data to be saved in the project directory.
|
|
||||||
|
|
||||||
#### NOMINATIM_ADDRESS_LEVEL_CONFIG
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Configuration file for rank assignments |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | address-levels.json |
|
|
||||||
|
|
||||||
The _address level configuration_ defines the rank assignments for places. See
|
|
||||||
[Place Ranking](Ranking.md) for a detailed explanation what rank assignments
|
|
||||||
are and what the configuration file must look like.
|
|
||||||
|
|
||||||
When a relative path is given, then the file is searched first relative to the
|
|
||||||
project directory and then in the global settings directory.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_IMPORT_STYLE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Configuration to use for the initial OSM data import |
|
|
||||||
| **Format:** | string or path |
|
|
||||||
| **Default:** | extratags |
|
|
||||||
|
|
||||||
The _style configuration_ describes which OSM objects and tags are taken
|
|
||||||
into consideration for the search database. Nominatim comes with a set
|
|
||||||
of pre-configured styles, that may be configured here.
|
|
||||||
|
|
||||||
You can also write your own custom style and point the setting to the file
|
|
||||||
with the style. When a relative path is given, then the style file is searched
|
|
||||||
first relative to the project directory and then in the global settings
|
|
||||||
directory.
|
|
||||||
|
|
||||||
See [Import Styles](Import-Styles.md)
|
|
||||||
for more information on the available internal styles and the format of the
|
|
||||||
configuration file.
|
|
||||||
|
|
||||||
#### NOMINATIM_FLATNODE_FILE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Location of osm2pgsql flatnode file |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (do not use a flatnote file) |
|
|
||||||
| **After Changes:** | Only change when moving the file physically. |
|
|
||||||
|
|
||||||
The `osm2pgsql flatnode file` is file that efficiently stores geographic
|
|
||||||
location for OSM nodes. For larger imports it can significantly speed up
|
|
||||||
the import. When this option is unset, then osm2pgsql uses a PsotgreSQL table
|
|
||||||
to store the locations.
|
|
||||||
|
|
||||||
When a relative path is given, then the flatnode file is created/searched
|
|
||||||
relative to the project directory.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
The flatnode file is not only used during the initial import but also
|
|
||||||
when adding new data with `nominatim add-data` or `nominatim replication`.
|
|
||||||
Make sure you keep the flatnode file around and this setting unmodified,
|
|
||||||
if you plan to add more data or run regular updates.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TABLESPACE_*
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Group of settings for distributing the database over tablespaces |
|
|
||||||
| **Format:** | string |
|
|
||||||
| **Default:** | _empty_ (do not use a table space) |
|
|
||||||
| **After Changes:** | no effect after initial import |
|
|
||||||
|
|
||||||
Nominatim allows to distribute the search database over up to 10 different
|
|
||||||
[PostgreSQL tablespaces](https://www.postgresql.org/docs/current/manage-ag-tablespaces.html).
|
|
||||||
If you use this option, make sure that the tablespaces exist before starting
|
|
||||||
the import.
|
|
||||||
|
|
||||||
The available tablespace groups are:
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_SEARCH_DATA
|
|
||||||
: Data used by the geocoding frontend.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_SEARCH_INDEX
|
|
||||||
: Indexes used by the geocoding frontend.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_OSM_DATA
|
|
||||||
: Raw OSM data cache used for import and updates.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_OSM_DATA
|
|
||||||
: Indexes on the raw OSM data cache.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_PLACE_DATA
|
|
||||||
: Data table with the pre-filtered but still unprocessed OSM data.
|
|
||||||
Used only during imports and updates.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_PLACE_INDEX
|
|
||||||
: Indexes on raw data table. Used only during imports and updates.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_ADDRESS_DATA
|
|
||||||
: Data tables used for computing search terms and addresses of places
|
|
||||||
during import and updates.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_ADDRESS_INDEX
|
|
||||||
: Indexes on the data tables for search term and address computation.
|
|
||||||
Used only for import and updates.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_AUX_DATA
|
|
||||||
: Auxiliary data tables for non-OSM data, e.g. for Tiger house number data.
|
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_AUX_INDEX
|
|
||||||
: Indexes on auxiliary data tables.
|
|
||||||
|
|
||||||
|
|
||||||
### Replication Update Settings
|
|
||||||
|
|
||||||
#### NOMINATIM_REPLICATION_URL
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Base URL of the replication service |
|
|
||||||
| **Format:** | url |
|
|
||||||
| **Default:** | https://planet.openstreetmap.org/replication/minute |
|
|
||||||
| **After Changes:** | run `nominatim replication --init` |
|
|
||||||
|
|
||||||
Replication services deliver updates to OSM data. Use this setting to choose
|
|
||||||
which replication service to use. See [Updates](../admin/Update.md) for more
|
|
||||||
information on how to set up regular updates.
|
|
||||||
|
|
||||||
#### NOMINATIM_REPLICATION_MAX_DIFF
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Maximum amount of data to download per update cycle (in MB) |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 50 |
|
|
||||||
| **After Changes:** | restart the replication process |
|
|
||||||
|
|
||||||
At each update cycle Nominatim downloads diffs until either no more diffs
|
|
||||||
are available on the server (i.e. the database is up-to-date) or the limit
|
|
||||||
given in this setting is exceeded. Nominatim guarantees to downloads at least
|
|
||||||
one diff, if one is available, no matter how small the setting.
|
|
||||||
|
|
||||||
The default for this setting is fairly conservative because Nominatim keeps
|
|
||||||
all data downloaded in one cycle in RAM. Using large values in a production
|
|
||||||
server may interfere badly with the search frontend because it evicts data
|
|
||||||
from RAM that is needed for speedy answers to incoming requests. It is usually
|
|
||||||
a better idea to keep this setting lower and run multiple update cycles
|
|
||||||
to catch up with updates.
|
|
||||||
|
|
||||||
When catching up in non-production mode, for example after the initial import,
|
|
||||||
the setting can easily be changed temporarily on the command line:
|
|
||||||
|
|
||||||
NOMINATIM_REPLICATION_MAX_DIFF=3000 nominatim replication
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_REPLICATION_UPDATE_INTERVAL
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Publication interval of the replication service (in seconds) |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 75 |
|
|
||||||
| **After Changes:** | restart the replication process |
|
|
||||||
|
|
||||||
This setting determines when Nominatim will attempt to download again a new
|
|
||||||
update. The time is computed from the publication date of the last diff
|
|
||||||
downloaded. Setting this to a slightly higher value than the actual
|
|
||||||
publication interval avoids unnecessary rechecks.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_REPLICATION_RECHECK_INTERVAL
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Wait time to recheck for a pending update (in seconds) |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 60 |
|
|
||||||
| **After Changes:** | restart the replication process |
|
|
||||||
|
|
||||||
When replication updates are run in continuous mode (using `nominatim replication`),
|
|
||||||
this setting determines how long Nominatim waits until it looks for updates
|
|
||||||
again when updates were not available on the server.
|
|
||||||
|
|
||||||
Note that this is different from
|
|
||||||
[NOMINATIM_REPLICATION_UPDATE_INTERVAL](#nominatim_replication_update_interval).
|
|
||||||
Nominatim will never attempt to query for new updates for UPDATE_INTERVAL
|
|
||||||
seconds after the current database date. Only after the update interval has
|
|
||||||
passed it asks for new data. If then no new data is found, it waits for
|
|
||||||
RECHECK_INTERVAL seconds before it attempts again.
|
|
||||||
|
|
||||||
### API Settings
|
|
||||||
|
|
||||||
#### NOMINATIM_CORS_NOACCESSCONTROL
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Send permissive CORS access headers |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | yes |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
When this setting is enabled, API HTTP responses include the HTTP
|
|
||||||
[CORS](https://en.wikipedia.org/wiki/CORS) headers
|
|
||||||
`access-control-allow-origin: *` and `access-control-allow-methods: OPTIONS,GET`.
|
|
||||||
|
|
||||||
#### NOMINATIM_MAPICON_URL
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | URL prefix for static icon images |
|
|
||||||
| **Format:** | url |
|
|
||||||
| **Default:** | _empty_ |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
When a mapicon URL is configured, then Nominatim includes an additional `icon`
|
|
||||||
field in the responses, pointing to an appropriate icon for the place type.
|
|
||||||
|
|
||||||
Map icons used to be included in Nominatim itself but now have moved to the
|
|
||||||
[nominatim-ui](https://github.com/osm-search/nominatim-ui/) project. If you
|
|
||||||
want the URL to be included in API responses, make the `/mapicon`
|
|
||||||
directory of the project available under a public URL and point this setting
|
|
||||||
to the directory.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_DEFAULT_LANGUAGE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Language of responses when no language is requested |
|
|
||||||
| **Format:** | language code |
|
|
||||||
| **Default:** | _empty_ (use the local language of the feature) |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Nominatim localizes the place names in responses when the corresponding
|
|
||||||
translation is available. Users can request a custom language setting through
|
|
||||||
the HTTP accept-languages header or through the explicit parameter
|
|
||||||
[accept-languages](../api/Search.md#language-of-results). If neither is
|
|
||||||
given, it falls back to this setting. If the setting is also empty, then
|
|
||||||
the local languages (in OSM: the name tag without any language suffix) is
|
|
||||||
used.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_SEARCH_BATCH_MODE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Enable a special batch query mode |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
This feature is currently undocumented and potentially broken.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Threshold for switching the search index lookup strategy |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 500 |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
This setting defines the threshold over which a name is no longer considered
|
|
||||||
as rare. When searching for places with rare names, only the name is used
|
|
||||||
for place lookups. Otherwise the name and any address information is used.
|
|
||||||
|
|
||||||
This setting only has an effect after `nominatim refresh --word-counts` has
|
|
||||||
been called to compute the word frequencies.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_LOOKUP_MAX_COUNT
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Maximum number of OSM ids accepted by /lookup |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 50 |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
The /lookup point accepts list of ids to look up address details for. This
|
|
||||||
setting restricts the number of places a user may look up with a single
|
|
||||||
request.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_POLYGON_OUTPUT_MAX_TYPES
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Number of different geometry formats that may be returned |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 1 |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Nominatim supports returning full geometries of places. The geometries may
|
|
||||||
be requested in different formats with one of the
|
|
||||||
[`polygon_*` parameters](../api/Search.md#polygon-output). Use this
|
|
||||||
setting to restrict the number of geometry types that may be requested
|
|
||||||
with a single query.
|
|
||||||
|
|
||||||
Setting this parameter to 0 disables polygon output completely.
|
|
||||||
|
|
||||||
### Logging Settings
|
|
||||||
|
|
||||||
#### NOMINATIM_LOG_DB
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Log requests into the database |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Enable logging requests into a database table with this setting. The logs
|
|
||||||
can be found in the table `new_query_log`.
|
|
||||||
|
|
||||||
When using this logging method, it is advisable to set up a job that
|
|
||||||
regularly clears out old logging information. Nominatim will not do that
|
|
||||||
on its own.
|
|
||||||
|
|
||||||
Can be used as the same time as NOMINATIM_LOG_FILE.
|
|
||||||
|
|
||||||
#### NOMINATIM_LOG_FILE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Log requests into a file |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (logging disabled) |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Enable logging of requests into a file with this setting by setting the log
|
|
||||||
file where to log to. A relative file name is assumed to be relative to
|
|
||||||
the project directory.
|
|
||||||
|
|
||||||
|
|
||||||
The entries in the log file have the following format:
|
|
||||||
|
|
||||||
<request time> <execution time in s> <number of results> <type> "<query string>"
|
|
||||||
|
|
||||||
Request time is the time when the request was started. The execution time is
|
|
||||||
given in ms and corresponds to the time the query took executing in PHP.
|
|
||||||
type contains the name of the endpoint used.
|
|
||||||
|
|
||||||
Can be used as the same time as NOMINATIM_LOG_DB.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Special phrases
|
|
||||||
|
|
||||||
## Importing OSM user-maintained special phrases
|
|
||||||
|
|
||||||
As described in the [Import section](../admin/Import.md), it is possible to
|
|
||||||
import special phrases from the wiki with the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nominatim special-phrases --import-from-wiki
|
|
||||||
```
|
|
||||||
|
|
||||||
## Importing custom special phrases
|
|
||||||
|
|
||||||
But, it is also possible to import some phrases from a csv file.
|
|
||||||
To do so, you have access to the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nominatim special-phrases --import-from-csv <csv file>
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the two previous import commands will update the phrases from your database.
|
|
||||||
This means that if you import some phrases from a csv file, only the phrases
|
|
||||||
present in the csv file will be kept into the database. All other phrases will
|
|
||||||
be removed.
|
|
||||||
|
|
||||||
If you want to only add new phrases and not update the other ones you can add
|
|
||||||
the argument `--no-replace` to the import command. For example:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nominatim special-phrases --import-from-csv <csv file> --no-replace
|
|
||||||
```
|
|
||||||
|
|
||||||
This will add the phrases present in the csv file into the database without
|
|
||||||
removing the other ones.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Installing TIGER housenumber data for the US
|
|
||||||
|
|
||||||
Nominatim is able to use the official [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
|
|
||||||
address set to complement the OSM house number data in the US. You can add
|
|
||||||
TIGER data to your own Nominatim instance by following these steps. The
|
|
||||||
entire US adds about 10GB to your database.
|
|
||||||
|
|
||||||
1. Get preprocessed TIGER 2021 data:
|
|
||||||
|
|
||||||
cd $PROJECT_DIR
|
|
||||||
wget https://nominatim.org/data/tiger2021-nominatim-preprocessed.csv.tar.gz
|
|
||||||
|
|
||||||
2. Import the data into your Nominatim database:
|
|
||||||
|
|
||||||
nominatim add-data --tiger-data tiger2021-nominatim-preprocessed.csv.tar.gz
|
|
||||||
|
|
||||||
3. Enable use of the Tiger data in your `.env` by adding:
|
|
||||||
|
|
||||||
echo NOMINATIM_USE_US_TIGER_DATA=yes >> .env
|
|
||||||
|
|
||||||
4. Apply the new settings:
|
|
||||||
|
|
||||||
nominatim refresh --functions
|
|
||||||
|
|
||||||
|
|
||||||
See the [TIGER-data project](https://github.com/osm-search/TIGER-data) for more
|
|
||||||
information on how the data got preprocessed.
|
|
||||||
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
# Tokenizers
|
|
||||||
|
|
||||||
The tokenizer module in Nominatim is responsible for analysing the names given
|
|
||||||
to OSM objects and the terms of an incoming query in order to make sure, they
|
|
||||||
can be matched appropriately.
|
|
||||||
|
|
||||||
Nominatim offers different tokenizer modules, which behave differently and have
|
|
||||||
different configuration options. This sections describes the tokenizers and how
|
|
||||||
they can be configured.
|
|
||||||
|
|
||||||
!!! important
|
|
||||||
The use of a tokenizer is tied to a database installation. You need to choose
|
|
||||||
and configure the tokenizer before starting the initial import. Once the import
|
|
||||||
is done, you cannot switch to another tokenizer anymore. Reconfiguring the
|
|
||||||
chosen tokenizer is very limited as well. See the comments in each tokenizer
|
|
||||||
section.
|
|
||||||
|
|
||||||
## Legacy tokenizer
|
|
||||||
|
|
||||||
The legacy tokenizer implements the analysis algorithms of older Nominatim
|
|
||||||
versions. It uses a special Postgresql module to normalize names and queries.
|
|
||||||
This tokenizer is currently the default.
|
|
||||||
|
|
||||||
To enable the tokenizer add the following line to your project configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
NOMINATIM_TOKENIZER=legacy
|
|
||||||
```
|
|
||||||
|
|
||||||
The Postgresql module for the tokenizer is available in the `module` directory
|
|
||||||
and also installed with the remainder of the software under
|
|
||||||
`lib/nominatim/module/nominatim.so`. You can specify a custom location for
|
|
||||||
the module with
|
|
||||||
|
|
||||||
```
|
|
||||||
NOMINATIM_DATABASE_MODULE_PATH=<path to directory where nominatim.so resides>
|
|
||||||
```
|
|
||||||
|
|
||||||
This is in particular useful when the database runs on a different server.
|
|
||||||
See [Advanced installations](../admin/Advanced-Installations.md#importing-nominatim-to-an-external-postgresql-database) for details.
|
|
||||||
|
|
||||||
There are no other configuration options for the legacy tokenizer. All
|
|
||||||
normalization functions are hard-coded.
|
|
||||||
|
|
||||||
## ICU tokenizer
|
|
||||||
|
|
||||||
The ICU tokenizer uses the [ICU library](http://site.icu-project.org/) to
|
|
||||||
normalize names and queries. It also offers configurable decomposition and
|
|
||||||
abbreviation handling.
|
|
||||||
|
|
||||||
To enable the tokenizer add the following line to your project configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
NOMINATIM_TOKENIZER=icu
|
|
||||||
```
|
|
||||||
|
|
||||||
### How it works
|
|
||||||
|
|
||||||
On import the tokenizer processes names in the following three stages:
|
|
||||||
|
|
||||||
1. During the **Sanitizer step** incoming names are cleaned up and converted to
|
|
||||||
**full names**. This step can be used to regularize spelling, split multi-name
|
|
||||||
tags into their parts and tag names with additional attributes. See the
|
|
||||||
[Sanitizers section](#sanitizers) below for available cleaning routines.
|
|
||||||
2. The **Normalization** part removes all information from the full names
|
|
||||||
that are not relevant for search.
|
|
||||||
3. The **Token analysis** step takes the normalized full names and creates
|
|
||||||
all transliterated variants under which the name should be searchable.
|
|
||||||
See the [Token analysis](#token-analysis) section below for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
During query time, only normalization and transliteration are relevant.
|
|
||||||
An incoming query is first split into name chunks (this usually means splitting
|
|
||||||
the string at the commas) and the each part is normalised and transliterated.
|
|
||||||
The result is used to look up places in the search index.
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
The ICU tokenizer is configured using a YAML file which can be configured using
|
|
||||||
`NOMINATIM_TOKENIZER_CONFIG`. The configuration is read on import and then
|
|
||||||
saved as part of the internal database status. Later changes to the variable
|
|
||||||
have no effect.
|
|
||||||
|
|
||||||
Here is an example configuration file:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
normalization:
|
|
||||||
- ":: lower ()"
|
|
||||||
- "ß > 'ss'" # German szet is unimbigiously equal to double ss
|
|
||||||
transliteration:
|
|
||||||
- !include /etc/nominatim/icu-rules/extended-unicode-to-asccii.yaml
|
|
||||||
- ":: Ascii ()"
|
|
||||||
sanitizers:
|
|
||||||
- step: split-name-list
|
|
||||||
token-analysis:
|
|
||||||
- analyzer: generic
|
|
||||||
variants:
|
|
||||||
- !include icu-rules/variants-ca.yaml
|
|
||||||
- words:
|
|
||||||
- road -> rd
|
|
||||||
- bridge -> bdge,br,brdg,bri,brg
|
|
||||||
```
|
|
||||||
|
|
||||||
The configuration file contains four sections:
|
|
||||||
`normalization`, `transliteration`, `sanitizers` and `token-analysis`.
|
|
||||||
|
|
||||||
#### Normalization and Transliteration
|
|
||||||
|
|
||||||
The normalization and transliteration sections each define a set of
|
|
||||||
ICU rules that are applied to the names.
|
|
||||||
|
|
||||||
The **normalisation** rules are applied after sanitation. They should remove
|
|
||||||
any information that is not relevant for search at all. Usual rules to be
|
|
||||||
applied here are: lower-casing, removing of special characters, cleanup of
|
|
||||||
spaces.
|
|
||||||
|
|
||||||
The **transliteration** rules are applied at the end of the tokenization
|
|
||||||
process to transfer the name into an ASCII representation. Transliteration can
|
|
||||||
be useful to allow for further fuzzy matching, especially between different
|
|
||||||
scripts.
|
|
||||||
|
|
||||||
Each section must contain a list of
|
|
||||||
[ICU transformation rules](https://unicode-org.github.io/icu/userguide/transforms/general/rules.html).
|
|
||||||
The rules are applied in the order in which they appear in the file.
|
|
||||||
You can also include additional rules from external yaml file using the
|
|
||||||
`!include` tag. The included file must contain a valid YAML list of ICU rules
|
|
||||||
and may again include other files.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
The ICU rule syntax contains special characters that conflict with the
|
|
||||||
YAML syntax. You should therefore always enclose the ICU rules in
|
|
||||||
double-quotes.
|
|
||||||
|
|
||||||
#### Sanitizers
|
|
||||||
|
|
||||||
The sanitizers section defines an ordered list of functions that are applied
|
|
||||||
to the name and address tags before they are further processed by the tokenizer.
|
|
||||||
They allows to clean up the tagging and bring it to a standardized form more
|
|
||||||
suitable for building the search index.
|
|
||||||
|
|
||||||
!!! hint
|
|
||||||
Sanitizers only have an effect on how the search index is built. They
|
|
||||||
do not change the information about each place that is saved in the
|
|
||||||
database. In particular, they have no influence on how the results are
|
|
||||||
displayed. The returned results always show the original information as
|
|
||||||
stored in the OpenStreetMap database.
|
|
||||||
|
|
||||||
Each entry contains information of a sanitizer to be applied. It has a
|
|
||||||
mandatory parameter `step` which gives the name of the sanitizer. Depending
|
|
||||||
on the type, it may have additional parameters to configure its operation.
|
|
||||||
|
|
||||||
The order of the list matters. The sanitizers are applied exactly in the order
|
|
||||||
that is configured. Each sanitizer works on the results of the previous one.
|
|
||||||
|
|
||||||
The following is a list of sanitizers that are shipped with Nominatim.
|
|
||||||
|
|
||||||
##### split-name-list
|
|
||||||
|
|
||||||
::: nominatim.tokenizer.sanitizers.split_name_list
|
|
||||||
selection:
|
|
||||||
members: False
|
|
||||||
rendering:
|
|
||||||
heading_level: 6
|
|
||||||
|
|
||||||
##### strip-brace-terms
|
|
||||||
|
|
||||||
::: nominatim.tokenizer.sanitizers.strip_brace_terms
|
|
||||||
selection:
|
|
||||||
members: False
|
|
||||||
rendering:
|
|
||||||
heading_level: 6
|
|
||||||
|
|
||||||
##### tag-analyzer-by-language
|
|
||||||
|
|
||||||
::: nominatim.tokenizer.sanitizers.tag_analyzer_by_language
|
|
||||||
selection:
|
|
||||||
members: False
|
|
||||||
rendering:
|
|
||||||
heading_level: 6
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Token Analysis
|
|
||||||
|
|
||||||
Token analyzers take a full name and transform it into one or more normalized
|
|
||||||
form that are then saved in the search index. In its simplest form, the
|
|
||||||
analyzer only applies the transliteration rules. More complex analyzers
|
|
||||||
create additional spelling variants of a name. This is useful to handle
|
|
||||||
decomposition and abbreviation.
|
|
||||||
|
|
||||||
The ICU tokenizer may use different analyzers for different names. To select
|
|
||||||
the analyzer to be used, the name must be tagged with the `analyzer` attribute
|
|
||||||
by a sanitizer (see for example the
|
|
||||||
[tag-analyzer-by-language sanitizer](#tag-analyzer-by-language)).
|
|
||||||
|
|
||||||
The token-analysis section contains the list of configured analyzers. Each
|
|
||||||
analyzer must have an `id` parameter that uniquely identifies the analyzer.
|
|
||||||
The only exception is the default analyzer that is used when no special
|
|
||||||
analyzer was selected.
|
|
||||||
|
|
||||||
Different analyzer implementations may exist. To select the implementation,
|
|
||||||
the `analyzer` parameter must be set. Currently there is only one implementation
|
|
||||||
`generic` which is described in the following.
|
|
||||||
|
|
||||||
##### Generic token analyzer
|
|
||||||
|
|
||||||
The generic analyzer is able to create variants from a list of given
|
|
||||||
abbreviation and decomposition replacements. It takes one optional parameter
|
|
||||||
`variants` which lists the replacements to apply. If the section is
|
|
||||||
omitted, then the generic analyzer becomes a simple analyzer that only
|
|
||||||
applies the transliteration.
|
|
||||||
|
|
||||||
The variants section defines lists of replacements which create alternative
|
|
||||||
spellings of a name. To create the variants, a name is scanned from left to
|
|
||||||
right and the longest matching replacement is applied until the end of the
|
|
||||||
string is reached.
|
|
||||||
|
|
||||||
The variants section must contain a list of replacement groups. Each group
|
|
||||||
defines a set of properties that describes where the replacements are
|
|
||||||
applicable. In addition, the word section defines the list of replacements
|
|
||||||
to be made. The basic replacement description is of the form:
|
|
||||||
|
|
||||||
```
|
|
||||||
<source>[,<source>[...]] => <target>[,<target>[...]]
|
|
||||||
```
|
|
||||||
|
|
||||||
The left side contains one or more `source` terms to be replaced. The right side
|
|
||||||
lists one or more replacements. Each source is replaced with each replacement
|
|
||||||
term.
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
The source and target terms are internally normalized using the
|
|
||||||
normalization rules given in the configuration. This ensures that the
|
|
||||||
strings match as expected. In fact, it is better to use unnormalized
|
|
||||||
words in the configuration because then it is possible to change the
|
|
||||||
rules for normalization later without having to adapt the variant rules.
|
|
||||||
|
|
||||||
###### Decomposition
|
|
||||||
|
|
||||||
In its standard form, only full words match against the source. There
|
|
||||||
is a special notation to match the prefix and suffix of a word:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
- ~strasse => str # matches "strasse" as full word and in suffix position
|
|
||||||
- hinter~ => hntr # matches "hinter" as full word and in prefix position
|
|
||||||
```
|
|
||||||
|
|
||||||
There is no facility to match a string in the middle of the word. The suffix
|
|
||||||
and prefix notation automatically trigger the decomposition mode: two variants
|
|
||||||
are created for each replacement, one with the replacement attached to the word
|
|
||||||
and one separate. So in above example, the tokenization of "hauptstrasse" will
|
|
||||||
create the variants "hauptstr" and "haupt str". Similarly, the name "rote strasse"
|
|
||||||
triggers the variants "rote str" and "rotestr". By having decomposition work
|
|
||||||
both ways, it is sufficient to create the variants at index time. The variant
|
|
||||||
rules are not applied at query time.
|
|
||||||
|
|
||||||
To avoid automatic decomposition, use the '|' notation:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
- ~strasse |=> str
|
|
||||||
```
|
|
||||||
|
|
||||||
simply changes "hauptstrasse" to "hauptstr" and "rote strasse" to "rote str".
|
|
||||||
|
|
||||||
###### Initial and final terms
|
|
||||||
|
|
||||||
It is also possible to restrict replacements to the beginning and end of a
|
|
||||||
name:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
- ^south => s # matches only at the beginning of the name
|
|
||||||
- road$ => rd # matches only at the end of the name
|
|
||||||
```
|
|
||||||
|
|
||||||
So the first example would trigger a replacement for "south 45th street" but
|
|
||||||
not for "the south beach restaurant".
|
|
||||||
|
|
||||||
###### Replacements vs. variants
|
|
||||||
|
|
||||||
The replacement syntax `source => target` works as a pure replacement. It changes
|
|
||||||
the name instead of creating a variant. To create an additional version, you'd
|
|
||||||
have to write `source => source,target`. As this is a frequent case, there is
|
|
||||||
a shortcut notation for it:
|
|
||||||
|
|
||||||
```
|
|
||||||
<source>[,<source>[...]] -> <target>[,<target>[...]]
|
|
||||||
```
|
|
||||||
|
|
||||||
The simple arrow causes an additional variant to be added. Note that
|
|
||||||
decomposition has an effect here on the source as well. So a rule
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
- "~strasse -> str"
|
|
||||||
```
|
|
||||||
|
|
||||||
means that for a word like `hauptstrasse` four variants are created:
|
|
||||||
`hauptstrasse`, `haupt strasse`, `hauptstr` and `haupt str`.
|
|
||||||
|
|
||||||
### Reconfiguration
|
|
||||||
|
|
||||||
Changing the configuration after the import is currently not possible, although
|
|
||||||
this feature may be added at a later time.
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
# Database Layout
|
|
||||||
|
|
||||||
### Import tables
|
|
||||||
|
|
||||||
OSM data is initially imported using [osm2pgsql](https://osm2pgsql.org).
|
|
||||||
Nominatim uses its own data output style 'gazetteer', which differs from the
|
|
||||||
output style created for map rendering.
|
|
||||||
|
|
||||||
The import process creates the following tables:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The `planet_osm_*` tables are the usual backing tables for OSM data. Note
|
|
||||||
that Nominatim uses them to look up special relations and to find nodes on
|
|
||||||
ways.
|
|
||||||
|
|
||||||
The gazetteer style produces a single table `place` as output with the following
|
|
||||||
columns:
|
|
||||||
|
|
||||||
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
|
|
||||||
* `osm_id` - original OSM ID
|
|
||||||
* `class` - key of principal tag defining the object type
|
|
||||||
* `type` - value of principal tag defining the object type
|
|
||||||
* `name` - collection of tags that contain a name or reference
|
|
||||||
* `admin_level` - numerical value of the tagged administrative level
|
|
||||||
* `address` - collection of tags defining the address of an object
|
|
||||||
* `extratags` - collection of additional interesting tags that are not
|
|
||||||
directly relevant for searching
|
|
||||||
* `geometry` - geometry of the object (in WGS84)
|
|
||||||
|
|
||||||
A single OSM object may appear multiple times in this table when it is tagged
|
|
||||||
with multiple tags that may constitute a principal tag. Take for example a
|
|
||||||
motorway bridge. In OSM, this would be a way which is tagged with
|
|
||||||
`highway=motorway` and `bridge=yes`. This way would appear in the `place` table
|
|
||||||
once with `class` of `highway` and once with a `class` of `bridge`. Thus the
|
|
||||||
*unique key* for `place` is (`osm_type`, `osm_id`, `class`).
|
|
||||||
|
|
||||||
How raw OSM tags are mapped to the columns in the place table is to a certain
|
|
||||||
degree configurable. See [Customizing Import Styles](../customize/Import-Styles.md)
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
### Search tables
|
|
||||||
|
|
||||||
The following tables carry all information needed to do the search:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The **placex** table is the central table that saves all information about the
|
|
||||||
searchable places in Nominatim. The basic columns are the same as for the
|
|
||||||
place table and have the same meaning. The placex tables adds the following
|
|
||||||
additional columns:
|
|
||||||
|
|
||||||
* `place_id` - the internal unique ID to identify the place
|
|
||||||
* `partition` - the id to use with partitioned tables (see below)
|
|
||||||
* `geometry_sector` - a location hash used for geographically close ordering
|
|
||||||
* `parent_place_id` - the next higher place in the address hierarchy, only
|
|
||||||
relevant for POI-type places (with rank 30)
|
|
||||||
* `linked_place_id` - place ID of the place this object has been merged with.
|
|
||||||
When this ID is set, then the place is invisible for search.
|
|
||||||
* `importance` - measure how well known the place is
|
|
||||||
* `rank_search`, `rank_address` - search and address rank (see [Customizing ranking](../customize/Ranking.md)
|
|
||||||
* `wikipedia` - the wikipedia page used for computing the importance of the place
|
|
||||||
* `country_code` - the country the place is located in
|
|
||||||
* `housenumber` - normalized housenumber, if the place has one
|
|
||||||
* `postcode` - computed postcode for the place
|
|
||||||
* `indexed_status` - processing status of the place (0 - ready, 1 - freshly inserted, 2 - needs updating, 100 - needs deletion)
|
|
||||||
* `indexed_date` - timestamp when the place was processed last
|
|
||||||
* `centroid` - a point feature for the place
|
|
||||||
|
|
||||||
The **location_property_osmline** table is a special table for
|
|
||||||
[address interpolations](https://wiki.openstreetmap.org/wiki/Addresses#Using_interpolation).
|
|
||||||
The columns have the same meaning and use as the columns with the same name in
|
|
||||||
the placex table. Only three columns are special:
|
|
||||||
|
|
||||||
* `startnumber` and `endnumber` - beginning and end of the number range
|
|
||||||
for the interpolation
|
|
||||||
* `interpolationtype` - a string `odd`, `even` or `all` to indicate
|
|
||||||
the interval between the numbers
|
|
||||||
|
|
||||||
Address interpolations are always ways in OSM, which is why there is no column
|
|
||||||
`osm_type`.
|
|
||||||
|
|
||||||
The **location_postcode** table holds computed centroids of all postcodes that
|
|
||||||
can be found in the OSM data. The meaning of the columns is again the same
|
|
||||||
as that of the placex table.
|
|
||||||
|
|
||||||
Every place needs an address, a set of surrounding places that describe the
|
|
||||||
location of the place. The set of address places is made up of OSM places
|
|
||||||
themselves. The **place_addressline** table cross-references for each place
|
|
||||||
all the places that make up its address. Two columns define the address
|
|
||||||
relation:
|
|
||||||
|
|
||||||
* `place_id` - reference to the place being addressed
|
|
||||||
* `address_place_id` - reference to the place serving as an address part
|
|
||||||
|
|
||||||
The most of the columns cache information from the placex entry of the address
|
|
||||||
part. The exceptions are:
|
|
||||||
|
|
||||||
* `fromarea` - is true if the address part has an area geometry and can
|
|
||||||
therefore be considered preceise
|
|
||||||
* `isaddress` - is true if the address part should show up in the address
|
|
||||||
output. Sometimes there are multiple places competing for for same address
|
|
||||||
type (e.g. multiple cities) and this field resolves the tie.
|
|
||||||
|
|
||||||
The **search_name** table contains the search index proper. It saves for each
|
|
||||||
place the terms with which the place can be found. The terms are split into
|
|
||||||
the name itself and all terms that make up the address. The table mirrors some
|
|
||||||
of the columns from placex for faster lookup.
|
|
||||||
|
|
||||||
Search terms are not saved as strings. Each term is assigned an integer and those
|
|
||||||
integers are saved in the name and address vectors of the search_name table. The
|
|
||||||
**word** table serves as the lookup table from string to such a word ID. The
|
|
||||||
exact content of the word table depends on the [tokenizer](Tokenizers.md) used.
|
|
||||||
|
|
||||||
## Address computation tables
|
|
||||||
|
|
||||||
Next to the main search tables, there is a set of secondary helper tables used
|
|
||||||
to compute the address relations between places. These tables are partitioned.
|
|
||||||
Each country is assigned a partition number in the country_name table (see
|
|
||||||
below) and the data is then split between a set of tables, one for each
|
|
||||||
partition. Note that Nominatim still manually manages partitioned tables.
|
|
||||||
Native support for partitions in PostgreSQL only became useable with version 13.
|
|
||||||
It will be a little while before Nominatim drops support for older versions.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The **search_name_X** tables are used to look up streets that appear in the
|
|
||||||
`addr:street` tag.
|
|
||||||
|
|
||||||
The **location_area_large_X** tables are used to look up larger areas
|
|
||||||
(administrative boundaries and place nodes) either through their geographic
|
|
||||||
closeness or through `addr:*` entries.
|
|
||||||
|
|
||||||
The **location_road_X** tables are used to find the closest street for a
|
|
||||||
dependent place.
|
|
||||||
|
|
||||||
All three table cache specific information from the placex table for their
|
|
||||||
selected subset of places:
|
|
||||||
|
|
||||||
* `keywords` and `name_vector` contain lists of term ids (from the word table)
|
|
||||||
that the full name of the place should match against
|
|
||||||
* `isguess` is true for places that are not described by an area
|
|
||||||
|
|
||||||
All other columns reflect their counterpart in the placex table.
|
|
||||||
|
|
||||||
## Static data tables
|
|
||||||
|
|
||||||
Nominatim also creates a number of static tables at import:
|
|
||||||
|
|
||||||
* `nominatim_properties` saves settings that must not be changed after
|
|
||||||
import
|
|
||||||
* `address_levels` save the rank information from the
|
|
||||||
[ranking configuration](../customize/Ranking.md)
|
|
||||||
* `country_name` contains a fallback of names for all countries, their
|
|
||||||
default languages and saves the assignment of countries to partitions.
|
|
||||||
* `country_osm_grid` provides a fallback for country geometries
|
|
||||||
|
|
||||||
## Auxilary data tables
|
|
||||||
|
|
||||||
Finally there are some table for auxillary data:
|
|
||||||
|
|
||||||
* `location_property_tiger` - saves housenumber from the Tiger import. Its
|
|
||||||
layout is similar to that of `location_propoerty_osmline`.
|
|
||||||
* `place_class_*` tables are helper tables to facilitate lookup of POIs
|
|
||||||
by their class and type. They exist because it is not possible to create
|
|
||||||
combined indexes with geometries.
|
|
||||||
|
|
||||||
@@ -26,19 +26,16 @@ following packages should get you started:
|
|||||||
## Prerequisites for testing and documentation
|
## Prerequisites for testing and documentation
|
||||||
|
|
||||||
The Nominatim test suite consists of behavioural tests (using behave) and
|
The Nominatim test suite consists of behavioural tests (using behave) and
|
||||||
unit tests (using PHPUnit for PHP code and pytest for Python code).
|
unit tests (using PHPUnit). It has the following additional requirements:
|
||||||
It has the following additional requirements:
|
|
||||||
|
|
||||||
* [behave test framework](https://behave.readthedocs.io) >= 1.2.6
|
* [behave test framework](https://behave.readthedocs.io) >= 1.2.5
|
||||||
|
* [nose](https://nose.readthedocs.io)
|
||||||
* [phpunit](https://phpunit.de) >= 7.3
|
* [phpunit](https://phpunit.de) >= 7.3
|
||||||
* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)
|
* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)
|
||||||
* [Pylint](https://pylint.org/) (2.6.0 is used for the CI)
|
|
||||||
* [pytest](https://pytest.org)
|
|
||||||
|
|
||||||
The documentation is built with mkdocs:
|
The documentation is built with mkdocs:
|
||||||
|
|
||||||
* [mkdocs](https://www.mkdocs.org/) >= 1.1.2
|
* [mkdocs](https://www.mkdocs.org/) >= 1.1.2
|
||||||
* [mkdocstrings](https://mkdocstrings.github.io/)
|
|
||||||
|
|
||||||
### Installing prerequisites on Ubuntu/Debian
|
### Installing prerequisites on Ubuntu/Debian
|
||||||
|
|
||||||
@@ -50,9 +47,9 @@ To install all necessary packages run:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install php-cgi phpunit php-codesniffer \
|
sudo apt install php-cgi phpunit php-codesniffer \
|
||||||
python3-pip python3-setuptools python3-dev pylint
|
python3-pip python3-setuptools python3-dev
|
||||||
|
|
||||||
pip3 install --user behave mkdocs mkdocstrings pytest
|
pip3 install --user behave nose mkdocs
|
||||||
```
|
```
|
||||||
|
|
||||||
The `mkdocs` executable will be located in `.local/bin`. You may have to add
|
The `mkdocs` executable will be located in `.local/bin`. You may have to add
|
||||||
@@ -81,15 +78,58 @@ echo 'export PATH=~/.config/composer/vendor/bin:$PATH' > ~/.profile
|
|||||||
|
|
||||||
## Executing Tests
|
## Executing Tests
|
||||||
|
|
||||||
All tests are located in the `/test` directory.
|
All tests are located in the `\test` directory.
|
||||||
|
|
||||||
To run all tests just go to the build directory and run make:
|
### Preparing the test database
|
||||||
|
|
||||||
|
Some of the behavioural test expect a test database to be present. You need at
|
||||||
|
least 2GB RAM and 10GB disk space to create the database.
|
||||||
|
|
||||||
|
First create a separate directory for the test DB and fetch the test planet
|
||||||
|
data and the Tiger data for South Dakota:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir testdb
|
||||||
|
cd testdb
|
||||||
|
wget https://www.nominatim.org/data/test/nominatim-api-testdata.pbf
|
||||||
|
wget -O - https://nominatim.org/data/tiger2018-nominatim-preprocessed.tar.gz | tar xz --wildcards --no-anchored '46*'
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure and build Nominatim in the usual way:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake $USERNAME/Nominatim
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the test settings:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp $USERNAME/Nominatim/test/testdb/local.php settings/
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the file to check that all settings are correct for your local setup.
|
||||||
|
|
||||||
|
Now you can import the test database:
|
||||||
|
|
||||||
|
```
|
||||||
|
dropdb --if-exists test_api_nominatim
|
||||||
|
./utils/setup.php --all --osm-file nominatim-api-testdb.pbf 2>&1 | tee import.log
|
||||||
|
./utils/specialphrases.php --wiki-import | psql -d test_api_nominatim 2>&1 | tee -a import.log
|
||||||
|
./utils/setup.php --import-tiger-data 2>&1 | tee -a import.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the tests
|
||||||
|
|
||||||
|
To run all tests just go to the test directory and run make:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd build
|
cd test
|
||||||
make test
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To skip tests that require the test database, run `make no-test-db` instead.
|
||||||
|
|
||||||
For more information about the structure of the tests and how to change and
|
For more information about the structure of the tests and how to change and
|
||||||
extend the test suite, see the [Testing chapter](Testing.md).
|
extend the test suite, see the [Testing chapter](Testing.md).
|
||||||
|
|
||||||
@@ -114,7 +154,7 @@ symlinks (see `CMakeLists.txt` for the exact steps).
|
|||||||
Now you can start webserver for local testing
|
Now you can start webserver for local testing
|
||||||
|
|
||||||
```
|
```
|
||||||
build> make serve-doc
|
build> mkdocs serve
|
||||||
[server:296] Serving on http://127.0.0.1:8000
|
[server:296] Serving on http://127.0.0.1:8000
|
||||||
[handlers:62] Start watching changes
|
[handlers:62] Start watching changes
|
||||||
```
|
```
|
||||||
@@ -123,7 +163,7 @@ If you develop inside a Vagrant virtual machine, use a port that is forwarded
|
|||||||
to your host:
|
to your host:
|
||||||
|
|
||||||
```
|
```
|
||||||
build> PYTHONPATH=$SRCDIR mkdocs serve --dev-addr 0.0.0.0:8088
|
build> mkdocs serve --dev-addr 0.0.0.0:8088
|
||||||
[server:296] Serving on http://0.0.0.0:8088
|
[server:296] Serving on http://0.0.0.0:8088
|
||||||
[handlers:62] Start watching changes
|
[handlers:62] Start watching changes
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
|
# OSM Data Import
|
||||||
|
|
||||||
|
OSM data is initially imported using [osm2pgsql](https://osm2pgsql.org).
|
||||||
|
Nominatim uses its own data output style 'gazetteer', which differs from the
|
||||||
|
output style created for map rendering.
|
||||||
|
|
||||||
|
## Database Layout
|
||||||
|
|
||||||
|
The gazetteer style produces a single table `place` with the following rows:
|
||||||
|
|
||||||
|
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
|
||||||
|
* `osm_id` - original OSM ID
|
||||||
|
* `class` - key of principal tag defining the object type
|
||||||
|
* `type` - value of principal tag defining the object type
|
||||||
|
* `name` - collection of tags that contain a name or reference
|
||||||
|
* `admin_level` - numerical value of the tagged administrative level
|
||||||
|
* `address` - collection of tags defining the address of an object
|
||||||
|
* `extratags` - collection of additional interesting tags that are not
|
||||||
|
directly relevant for searching
|
||||||
|
* `geometry` - geometry of the object (in WGS84)
|
||||||
|
|
||||||
|
A single OSM object may appear multiple times in this table when it is tagged
|
||||||
|
with multiple tags that may constitute a principal tag. Take for example a
|
||||||
|
motorway bridge. In OSM, this would be a way which is tagged with
|
||||||
|
`highway=motorway` and `bridge=yes`. This way would appear in the `place` table
|
||||||
|
once with `class` of `highway` and once with a `class` of `bridge`. Thus the
|
||||||
|
*unique key* for `place` is (`osm_type`, `osm_id`, `class`).
|
||||||
|
|
||||||
## Configuring the Import
|
## Configuring the Import
|
||||||
|
|
||||||
Which OSM objects are added to the database and which of the tags are used
|
How tags are interpreted and assigned to the different `place` columns can be
|
||||||
can be configured via the import style configuration file. This
|
configured via the import style configuration file (`CONST_Import_style`). This
|
||||||
is a JSON file which contains a list of rules which are matched against every
|
is a JSON file which contains a list of rules which are matched against every
|
||||||
tag of every object and then assign the tag its specific role.
|
tag of every object and then assign the tag its specific role.
|
||||||
|
|
||||||
The style to use is given by the `NOMINATIM_IMPORT_STYLE` configuration
|
|
||||||
option. There are a number of default styles, which are explained in detail
|
|
||||||
in the [Import section](../admin/Import.md#filtering-imported-data). These
|
|
||||||
standard styles may be referenced by their name.
|
|
||||||
|
|
||||||
You can also create your own custom syle. Put the style file into your
|
|
||||||
project directory and then set `NOMINATIM_IMPORT_STYLE` to the name of the file.
|
|
||||||
It is always recommended to start with one of the standard styles and customize
|
|
||||||
those. You find the standard styles under the name `import-<stylename>.style`
|
|
||||||
in the standard Nominatim configuration path (usually `/etc/nominatim` or
|
|
||||||
`/usr/local/etc/nominatim`).
|
|
||||||
|
|
||||||
The remainder of the page describes the format of the file.
|
|
||||||
|
|
||||||
### Configuration Rules
|
### Configuration Rules
|
||||||
|
|
||||||
A single rule looks like this:
|
A single rule looks like this:
|
||||||
@@ -145,6 +159,9 @@ A rule can define as many of these properties for one match as it likes. For
|
|||||||
example, if the property is `"main,extra"` then the tag will open a new row
|
example, if the property is `"main,extra"` then the tag will open a new row
|
||||||
but also have the tag appear in the list of extra tags.
|
but also have the tag appear in the list of extra tags.
|
||||||
|
|
||||||
|
There are a number of pre-defined styles in the `settings/` directory. It is
|
||||||
|
advisable to start from one of these styles when defining your own.
|
||||||
|
|
||||||
### Changing the Style of Existing Databases
|
### Changing the Style of Existing Databases
|
||||||
|
|
||||||
There is normally no issue changing the style of a database that is already
|
There is normally no issue changing the style of a database that is already
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
# Indexing Places
|
|
||||||
|
|
||||||
In Nominatim, the word __indexing__ refers to the process that takes the raw
|
|
||||||
OpenStreetMap data from the place table, enriches it with address information
|
|
||||||
and creates the search indexes. This section explains the basic data flow.
|
|
||||||
|
|
||||||
|
|
||||||
## Initial import
|
|
||||||
|
|
||||||
After osm2pgsql has loaded the raw OSM data into the place table,
|
|
||||||
the data is copied to the final search tables placex and location_property_osmline.
|
|
||||||
While they are copied, some basic properties are added:
|
|
||||||
|
|
||||||
* country_code, geometry_sector and partition
|
|
||||||
* initial search and address rank
|
|
||||||
|
|
||||||
In addition the column `indexed_status` is set to `1` marking the place as one
|
|
||||||
that needs to be indexed.
|
|
||||||
|
|
||||||
All this happens in the triggers `placex_insert` and `osmline_insert`.
|
|
||||||
|
|
||||||
## Indexing
|
|
||||||
|
|
||||||
The main work horse of the data import is the indexing step, where Nominatim
|
|
||||||
takes every place from the placex and location_property_osmline tables where
|
|
||||||
the indexed_status != 0 and computes the search terms and the address parts
|
|
||||||
of the place.
|
|
||||||
|
|
||||||
The indexing happens in three major steps:
|
|
||||||
|
|
||||||
1. **Data preparation** - The indexer gets the data for the place to be indexed
|
|
||||||
from the database.
|
|
||||||
|
|
||||||
2. **Search name processing** - The prepared data is given to the
|
|
||||||
tokenizer which computes the search terms from the names
|
|
||||||
and potentially other information.
|
|
||||||
|
|
||||||
3. **Address processing** - The indexer then hands the prepared data and the
|
|
||||||
tokenizer information back to the database via an `INSERT` statement which
|
|
||||||
also sets the indexed_status to `0`. This triggers the update triggers
|
|
||||||
`placex_update`/`osmline_update` which do the work of computing address
|
|
||||||
parts and filling all the search tables.
|
|
||||||
|
|
||||||
When computing the address terms of a place, Nominatim relies on the processed
|
|
||||||
search names of all the address parts. That is why places are processed in rank
|
|
||||||
order, from smallest rank to largest. To ensure correct handling of linked
|
|
||||||
place nodes, administrative boundaries are processed before all other places.
|
|
||||||
|
|
||||||
Apart from these restrictions, each place can be indexed independently
|
|
||||||
from the others. This allows a large degree of parallelization during the indexing.
|
|
||||||
It also means that the indexing process can be interrupted at any time and
|
|
||||||
will simply pick up where it left of when restarted.
|
|
||||||
|
|
||||||
### Data preparation
|
|
||||||
|
|
||||||
The data preparation step computes and retrieves all data for a place that
|
|
||||||
might be needed for the next step of processing the search name. That includes
|
|
||||||
|
|
||||||
* location information (country code)
|
|
||||||
* place classification (class, type, ranks)
|
|
||||||
* names (including names of linked places)
|
|
||||||
* address information (`addr:*` tags)
|
|
||||||
|
|
||||||
Data preparation is implemented in pl/PgSQL mostly in the functions
|
|
||||||
`placex_indexing_prepare()` and `get_interpolation_address()`.
|
|
||||||
|
|
||||||
#### `addr:*` tag inheritance
|
|
||||||
|
|
||||||
Nominatim has limited support for inheriting address tags from a building
|
|
||||||
to POIs inside the building. This only works when the address tags are on the
|
|
||||||
building outline. Any rank 30 object inside such a building or on its outline
|
|
||||||
inherits all address tags when it does not have any address tags of its own.
|
|
||||||
|
|
||||||
The inheritance is computed in the data preparation step.
|
|
||||||
|
|
||||||
### Search name processing
|
|
||||||
|
|
||||||
The prepared place information is handed to the tokenizer next. This is a
|
|
||||||
Python module responsible for processing the names from both name and address
|
|
||||||
terms and building up the word index from them. The process is explained in
|
|
||||||
more detail in the [Tokenizer chapter](Tokenizer.md).
|
|
||||||
|
|
||||||
### Address processing
|
|
||||||
|
|
||||||
Finally, the preprocessed place information and the results of the search name
|
|
||||||
processing are written back to the database. At this point the update trigger
|
|
||||||
of the placex/location_property_osmline tables take over and fill all the
|
|
||||||
dependent tables. This makes up the most work-intensive part of the indexing.
|
|
||||||
|
|
||||||
Nominatim distinguishes between dependent and independent places.
|
|
||||||
**Dependent places** are all places on rank 30: house numbers, POIs etc. These
|
|
||||||
places don't have a full address of their own. Instead they are attached to
|
|
||||||
a parent street or place and use the information of the parent for searching
|
|
||||||
and displaying information. Everything else are **independent places**: streets,
|
|
||||||
parks, water bodies, suburbs, cities, states etc. They receive a full address
|
|
||||||
on their own.
|
|
||||||
|
|
||||||
The address processing for both types of places is very different.
|
|
||||||
|
|
||||||
#### Independent places
|
|
||||||
|
|
||||||
To compute the address of an independent place Nominatim searches for all
|
|
||||||
places that cover the place to compute the address for at least partially.
|
|
||||||
For places with an area, that area is used to check for coverage. For place
|
|
||||||
nodes an artificial square area is computed according to the rank of
|
|
||||||
the place. The lower the rank the lager the area. The `location_area_large_X`
|
|
||||||
tables are there to facilitate the lookup. All places that can function as
|
|
||||||
the address of another place are saved in those tables.
|
|
||||||
|
|
||||||
`addr:*` and `isin:*` tags are taken into account to compute the address, too.
|
|
||||||
Nominatim will give preference to places with the same name as in these tags
|
|
||||||
when looking for places in the vicinity. If there are no matching place names
|
|
||||||
at all, then the tags are at least added to the search index. That means that
|
|
||||||
the names will not be shown in the result as the 'address' of the place, but
|
|
||||||
searching by them still works.
|
|
||||||
|
|
||||||
Independent places are always added to the global search index `search_name`.
|
|
||||||
|
|
||||||
#### Dependent places
|
|
||||||
|
|
||||||
Dependent places skip the full address computation for performance reasons.
|
|
||||||
Instead they just find a parent place to attach themselves to.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
By default a POI
|
|
||||||
or house number will be attached to the closest street. That can be any major
|
|
||||||
or minor street indexed by Nominatim. In the default configuration that means
|
|
||||||
that it can attach itself to a footway but only when it has a name.
|
|
||||||
|
|
||||||
When the dependent place has an `addr:street` tag, then Nominatim will first
|
|
||||||
try to find a street with the same name before falling back to the closest
|
|
||||||
street.
|
|
||||||
|
|
||||||
There are also addresses in OSM, where the housenumber does not belong
|
|
||||||
to a street at all. These have an `addr:place` tag. For these places, Nominatim
|
|
||||||
tries to find a place with the given name in the indexed places with an
|
|
||||||
address rank between 16 and 25. If none is found, then the dependent place
|
|
||||||
is attached to the closest place in that category and the addr:place name is
|
|
||||||
added as *unlisted* place, which indicates to Nominatim that it needs to add
|
|
||||||
it to the address output, no matter what. This special case is necessary to
|
|
||||||
cover addresses that don't really refer to an existing object.
|
|
||||||
|
|
||||||
When an address has both the `addr:street` and `addr:place` tag, then Nominatim
|
|
||||||
assumes that the `addr:place` tag in fact should be the city part of the address
|
|
||||||
and give the POI the usual street number address.
|
|
||||||
|
|
||||||
Dependent places are only added to the global search index `search_name` when
|
|
||||||
they have either a name themselves or when they have address tags that are not
|
|
||||||
covered by the places that make up their address. The latter ensures that
|
|
||||||
addresses are always searchable by those address tags.
|
|
||||||
|
|
||||||
45
docs/develop/Postcodes.md
Normal file
45
docs/develop/Postcodes.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Postcodes in Nominatim
|
||||||
|
|
||||||
|
The blog post
|
||||||
|
[Nominatim and Postcodes](https://www.openstreetmap.org/user/lonvia/diary/43143)
|
||||||
|
describes the handling implemented since Nominatim 3.1.
|
||||||
|
|
||||||
|
Postcode centroids (aka 'calculated postcodes') are generated by looking at all
|
||||||
|
postcodes of a country, grouping them and calculating the geometric centroid.
|
||||||
|
There is currently no logic to deal with extreme outliers (typos or other
|
||||||
|
mistakes in OSM data). There is also no check if a postcodes adheres to a
|
||||||
|
country's format, e.g. if Swiss postcodes are 4 digits.
|
||||||
|
|
||||||
|
|
||||||
|
## Regular updating calculated postcodes
|
||||||
|
|
||||||
|
The script to rerun the calculation is
|
||||||
|
`build/utils/update.php --calculate-postcodes`
|
||||||
|
and runs once per night on nominatim.openstreetmap.org.
|
||||||
|
|
||||||
|
|
||||||
|
## Finding places that share a specific postcode
|
||||||
|
|
||||||
|
In the Nominatim database run
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT address->'postcode' as pc,
|
||||||
|
osm_type, osm_id, class, type,
|
||||||
|
st_x(centroid) as lon, st_y(centroid) as lat
|
||||||
|
FROM placex
|
||||||
|
WHERE country_code='fr'
|
||||||
|
AND upper(trim (both ' ' from address->'postcode')) = '33210';
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively on [Overpass](https://overpass-turbo.eu/) run the following query
|
||||||
|
|
||||||
|
```
|
||||||
|
[out:json][timeout:250];
|
||||||
|
area["name"="France"]->.boundaryarea;
|
||||||
|
(
|
||||||
|
nwr(area.boundaryarea)["addr:postcode"="33210"];
|
||||||
|
);
|
||||||
|
out body;
|
||||||
|
>;
|
||||||
|
out skel qt;
|
||||||
|
```
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# Place Ranking in Nominatim
|
# Place Ranking in Nominatim
|
||||||
|
|
||||||
Nominatim uses two metrics to rank a place: search rank and address rank.
|
Nominatim uses two metrics to rank a place: search rank and address rank.
|
||||||
This chapter explains what place ranking means and how it can be customized.
|
Both can be assigned a value between 0 and 30. They serve slightly
|
||||||
|
different purposes, which are explained in this chapter.
|
||||||
|
|
||||||
## Search rank
|
## Search rank
|
||||||
|
|
||||||
@@ -86,9 +87,9 @@ into the database. There are a few hard-coded rules for the assignment:
|
|||||||
* highway nodes
|
* highway nodes
|
||||||
* landuse that is not an area
|
* landuse that is not an area
|
||||||
|
|
||||||
Other than that, the ranks can be freely assigned via the JSON file according
|
Other than that, the ranks can be freely assigned via the JSON file
|
||||||
to their type and the country they are in. The name of the config file to be
|
defined with `CONST_Address_Level_Config` according to their type and
|
||||||
used can be changed with the setting `NOMINATIM_ADDRESS_LEVEL_CONFIG`.
|
the country they are in.
|
||||||
|
|
||||||
The address level configuration must consist of an array of configuration
|
The address level configuration must consist of an array of configuration
|
||||||
entries, each containing a tag definition and an optional country array:
|
entries, each containing a tag definition and an optional country array:
|
||||||
@@ -21,15 +21,14 @@ This test directory is sturctured as follows:
|
|||||||
| +- api Tests for API endpoints (search, reverse, etc.)
|
| +- api Tests for API endpoints (search, reverse, etc.)
|
||||||
|
|
|
|
||||||
+- php PHP unit tests
|
+- php PHP unit tests
|
||||||
+- python Python unit tests
|
|
||||||
+- scenes Geometry test data
|
+- scenes Geometry test data
|
||||||
+- testdb Base data for generating API test database
|
+- testdb Base data for generating API test database
|
||||||
```
|
```
|
||||||
|
|
||||||
## PHP Unit Tests (`test/php`)
|
## PHP Unit Tests (`test/php`)
|
||||||
|
|
||||||
Unit tests for PHP code can be found in the `php/` directory. They test selected
|
Unit tests can be found in the php/ directory. They test selected php functions.
|
||||||
PHP functions. Very low coverage.
|
Very low coverage.
|
||||||
|
|
||||||
To execute the test suite run
|
To execute the test suite run
|
||||||
|
|
||||||
@@ -37,26 +36,11 @@ To execute the test suite run
|
|||||||
UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../
|
UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../
|
||||||
|
|
||||||
It will read phpunit.xml which points to the library, test path, bootstrap
|
It will read phpunit.xml which points to the library, test path, bootstrap
|
||||||
strip and sets other parameters.
|
strip and set other parameters.
|
||||||
|
|
||||||
It will use (and destroy) a local database 'nominatim_unit_tests'. You can set
|
It will use (and destroy) a local database 'nominatim_unit_tests'. You can set
|
||||||
a different connection string with e.g. UNIT_TEST_DSN='pgsql:dbname=foo_unit_tests'.
|
a different connection string with e.g. UNIT_TEST_DSN='pgsql:dbname=foo_unit_tests'.
|
||||||
|
|
||||||
## Python Unit Tests (`test/python`)
|
|
||||||
|
|
||||||
Unit tests for Python code can be found in the `python/` directory. The goal is
|
|
||||||
to have complete coverage of the Python library in `nominatim`.
|
|
||||||
|
|
||||||
To execute the tests run
|
|
||||||
|
|
||||||
py.test-3 test/python
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
pytest test/python
|
|
||||||
|
|
||||||
The name of the pytest binary depends on your installation.
|
|
||||||
|
|
||||||
## BDD Functional Tests (`test/bdd`)
|
## BDD Functional Tests (`test/bdd`)
|
||||||
|
|
||||||
Functional tests are written as BDD instructions. For more information on
|
Functional tests are written as BDD instructions. For more information on
|
||||||
@@ -83,17 +67,17 @@ The tests can be configured with a set of environment variables (`behave -D key=
|
|||||||
the test databases (db tests)
|
the test databases (db tests)
|
||||||
* `TEST_DB` - name of test database (db tests)
|
* `TEST_DB` - name of test database (db tests)
|
||||||
* `API_TEST_DB` - name of the database containing the API test data (api tests)
|
* `API_TEST_DB` - name of the database containing the API test data (api tests)
|
||||||
* `API_TEST_FILE` - OSM file to be imported into the API test database (api tests)
|
|
||||||
* `DB_HOST` - (optional) hostname of database host
|
* `DB_HOST` - (optional) hostname of database host
|
||||||
* `DB_PORT` - (optional) port of database on host
|
* `DB_PORT` - (optional) port of database on host
|
||||||
* `DB_USER` - (optional) username of database login
|
* `DB_USER` - (optional) username of database login
|
||||||
* `DB_PASS` - (optional) password for database login
|
* `DB_PASS` - (optional) password for database login
|
||||||
* `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
|
* `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
|
||||||
module shared library file
|
module shared library file
|
||||||
* `REMOVE_TEMPLATE` - if true, the template and API database will not be reused
|
* `TEST_SETTINGS_TEMPLATE` - file to write temporary Nominatim settings to
|
||||||
during the next run. Reusing the base templates speeds
|
* `REMOVE_TEMPLATE` - if true, the template database will not be reused during
|
||||||
up tests considerably but might lead to outdated errors
|
the next run. Reusing the base templates speeds up tests
|
||||||
for some changes in the database layout.
|
considerably but might lead to outdated errors for some
|
||||||
|
changes in the database layout.
|
||||||
* `KEEP_TEST_DB` - if true, the test database will not be dropped after a test
|
* `KEEP_TEST_DB` - if true, the test database will not be dropped after a test
|
||||||
is finished. Should only be used if one single scenario is
|
is finished. Should only be used if one single scenario is
|
||||||
run, otherwise the result is undefined.
|
run, otherwise the result is undefined.
|
||||||
@@ -105,20 +89,23 @@ feature of behave which comes in handy when writing new tests.
|
|||||||
### API Tests (`test/bdd/api`)
|
### API Tests (`test/bdd/api`)
|
||||||
|
|
||||||
These tests are meant to test the different API endpoints and their parameters.
|
These tests are meant to test the different API endpoints and their parameters.
|
||||||
They require to import several datasets into a test database. This is normally
|
They require to import several datasets into a test database.
|
||||||
done automatically during setup of the test. The API test database is then
|
See the [Development Setup chapter](Development-Environment.md#preparing-the-test-database)
|
||||||
kept around and reused in subsequent runs of behave. Use `behave -DREMOVE_TEMPLATE`
|
for instructions on how to set up this database.
|
||||||
to force a reimport of the database.
|
|
||||||
|
|
||||||
The official test dataset is saved in the file `test/testdb/apidb-test-data.pbf`
|
The official test dataset was derived from the 180924 planet (note: such
|
||||||
and compromises the following data:
|
file no longer exists at https://planet.openstreetmap.org/planet/2018/).
|
||||||
|
Newer planets are likely to work as well but you may see isolated test
|
||||||
|
failures where the data has changed.
|
||||||
|
|
||||||
* Geofabrik extract of Liechtenstein
|
The official test dataset can always be downloaded from
|
||||||
* extract of Autauga country, Alabama, US (for tests against Tiger data)
|
[nominatim.org](https://www.nominatim.org/data/test/nominatim-api-testdata.pbf)
|
||||||
* additional data from `test/testdb/additional_api_test.data.osm`
|
To recreate the input data for the test database run:
|
||||||
|
|
||||||
API tests should only be testing the functionality of the website PHP code.
|
```
|
||||||
Most tests should be formulated as BDD DB creation tests (see below) instead.
|
wget https://ftp5.gwdg.de/pub/misc/openstreetmap/planet.openstreetmap.org/pbf/planet-180924.osm.pbf
|
||||||
|
osmconvert planet-180924.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf
|
||||||
|
```
|
||||||
|
|
||||||
#### Code Coverage
|
#### Code Coverage
|
||||||
|
|
||||||
@@ -153,7 +140,3 @@ needs superuser rights for postgres.
|
|||||||
|
|
||||||
These tests check that data is imported correctly into the place table. They
|
These tests check that data is imported correctly into the place table. They
|
||||||
use the same template database as the DB Creation tests, so the same remarks apply.
|
use the same template database as the DB Creation tests, so the same remarks apply.
|
||||||
|
|
||||||
Note that most testing of the gazetteer output of osm2pgsql is done in the tests
|
|
||||||
of osm2pgsql itself. The BDD tests are just there to ensure compatibility of
|
|
||||||
the osm2pgsql and Nominatim code.
|
|
||||||
|
|||||||
@@ -1,332 +0,0 @@
|
|||||||
# Tokenizers
|
|
||||||
|
|
||||||
The tokenizer is the component of Nominatim that is responsible for
|
|
||||||
analysing names of OSM objects and queries. Nominatim provides different
|
|
||||||
tokenizers that use different strategies for normalisation. This page describes
|
|
||||||
how tokenizers are expected to work and the public API that needs to be
|
|
||||||
implemented when creating a new tokenizer. For information on how to configure
|
|
||||||
a specific tokenizer for a database see the
|
|
||||||
[tokenizer chapter in the Customization Guide](../customize/Tokenizers.md).
|
|
||||||
|
|
||||||
## Generic Architecture
|
|
||||||
|
|
||||||
### About Search Tokens
|
|
||||||
|
|
||||||
Search in Nominatim is organised around search tokens. Such a token represents
|
|
||||||
string that can be part of the search query. Tokens are used so that the search
|
|
||||||
index does not need to be organised around strings. Instead the database saves
|
|
||||||
for each place which tokens match this place's name, address, house number etc.
|
|
||||||
To be able to distinguish between these different types of information stored
|
|
||||||
with the place, a search token also always has a certain type: name, house number,
|
|
||||||
postcode etc.
|
|
||||||
|
|
||||||
During search an incoming query is transformed into a ordered list of such
|
|
||||||
search tokens (or rather many lists, see below) and this list is then converted
|
|
||||||
into a database query to find the right place.
|
|
||||||
|
|
||||||
It is the core task of the tokenizer to create, manage and assign the search
|
|
||||||
tokens. The tokenizer is involved in two distinct operations:
|
|
||||||
|
|
||||||
* __at import time__: scanning names of OSM objects, normalizing them and
|
|
||||||
building up the list of search tokens.
|
|
||||||
* __at query time__: scanning the query and returning the appropriate search
|
|
||||||
tokens.
|
|
||||||
|
|
||||||
|
|
||||||
### Importing
|
|
||||||
|
|
||||||
The indexer is responsible to enrich an OSM object (or place) with all data
|
|
||||||
required for geocoding. It is split into two parts: the controller collects
|
|
||||||
the places that require updating, enriches the place information as required
|
|
||||||
and hands the place to Postgresql. The collector is part of the Nominatim
|
|
||||||
library written in Python. Within Postgresql, the `placex_update`
|
|
||||||
trigger is responsible to fill out all secondary tables with extra geocoding
|
|
||||||
information. This part is written in PL/pgSQL.
|
|
||||||
|
|
||||||
The tokenizer is involved in both parts. When the indexer prepares a place,
|
|
||||||
it hands it over to the tokenizer to inspect the names and create all the
|
|
||||||
search tokens applicable for the place. This usually involves updating the
|
|
||||||
tokenizer's internal token lists and creating a list of all token IDs for
|
|
||||||
the specific place. This list is later needed in the PL/pgSQL part where the
|
|
||||||
indexer needs to add the token IDs to the appropriate search tables. To be
|
|
||||||
able to communicate the list between the Python part and the pl/pgSQL trigger,
|
|
||||||
the `placex` table contains a special JSONB column `token_info` which is there
|
|
||||||
for the exclusive use of the tokenizer.
|
|
||||||
|
|
||||||
The Python part of the tokenizer returns a structured information about the
|
|
||||||
tokens of a place to the indexer which converts it to JSON and inserts it into
|
|
||||||
the `token_info` column. The content of the column is then handed to the PL/pqSQL
|
|
||||||
callbacks of the tokenizer which extracts the required information. Usually
|
|
||||||
the tokenizer then removes all information from the `token_info` structure,
|
|
||||||
so that no information is ever persistently saved in the table. All information
|
|
||||||
that went in should have been processed after all and put into secondary tables.
|
|
||||||
This is however not a hard requirement. If the tokenizer needs to store
|
|
||||||
additional information about a place permanently, it may do so in the
|
|
||||||
`token_info` column. It just may never execute searches over it and
|
|
||||||
consequently not create any special indexes on it.
|
|
||||||
|
|
||||||
### Querying
|
|
||||||
|
|
||||||
At query time, Nominatim builds up multiple _interpretations_ of the search
|
|
||||||
query. Each of these interpretations is tried against the database in order
|
|
||||||
of the likelihood with which they match to the search query. The first
|
|
||||||
interpretation that yields results wins.
|
|
||||||
|
|
||||||
The interpretations are encapsulated in the `SearchDescription` class. An
|
|
||||||
instance of this class is created by applying a sequence of
|
|
||||||
_search tokens_ to an initially empty SearchDescription. It is the
|
|
||||||
responsibility of the tokenizer to parse the search query and derive all
|
|
||||||
possible sequences of search tokens. To that end the tokenizer needs to parse
|
|
||||||
the search query and look up matching words in its own data structures.
|
|
||||||
|
|
||||||
## Tokenizer API
|
|
||||||
|
|
||||||
The following section describes the functions that need to be implemented
|
|
||||||
for a custom tokenizer implementation.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
This API is currently in early alpha status. While this API is meant to
|
|
||||||
be a public API on which other tokenizers may be implemented, the API is
|
|
||||||
far away from being stable at the moment.
|
|
||||||
|
|
||||||
### Directory Structure
|
|
||||||
|
|
||||||
Nominatim expects two files for a tokenizer:
|
|
||||||
|
|
||||||
* `nominiatim/tokenizer/<NAME>_tokenizer.py` containing the Python part of the
|
|
||||||
implementation
|
|
||||||
* `lib-php/tokenizer/<NAME>_tokenizer.php` with the PHP part of the
|
|
||||||
implementation
|
|
||||||
|
|
||||||
where `<NAME>` is a unique name for the tokenizer consisting of only lower-case
|
|
||||||
letters, digits and underscore. A tokenizer also needs to install some SQL
|
|
||||||
functions. By convention, these should be placed in `lib-sql/tokenizer`.
|
|
||||||
|
|
||||||
If the tokenizer has a default configuration file, this should be saved in
|
|
||||||
the `settings/<NAME>_tokenizer.<SUFFIX>`.
|
|
||||||
|
|
||||||
### Configuration and Persistance
|
|
||||||
|
|
||||||
Tokenizers may define custom settings for their configuration. All settings
|
|
||||||
must be prefixed with `NOMINATIM_TOKENIZER_`. Settings may be transient or
|
|
||||||
persistent. Transient settings are loaded from the configuration file when
|
|
||||||
Nominatim is started and may thus be changed at any time. Persistent settings
|
|
||||||
are tied to a database installation and must only be read during installation
|
|
||||||
time. If they are needed for the runtime then they must be saved into the
|
|
||||||
`nominatim_properties` table and later loaded from there.
|
|
||||||
|
|
||||||
### The Python module
|
|
||||||
|
|
||||||
The Python module is expect to export a single factory function:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def create(dsn: str, data_dir: Path) -> AbstractTokenizer
|
|
||||||
```
|
|
||||||
|
|
||||||
The `dsn` parameter contains the DSN of the Nominatim database. The `data_dir`
|
|
||||||
is a directory in the project directory that the tokenizer may use to save
|
|
||||||
database-specific data. The function must return the instance of the tokenizer
|
|
||||||
class as defined below.
|
|
||||||
|
|
||||||
### Python Tokenizer Class
|
|
||||||
|
|
||||||
All tokenizers must inherit from `nominatim.tokenizer.base.AbstractTokenizer`
|
|
||||||
and implement the abstract functions defined there.
|
|
||||||
|
|
||||||
::: nominatim.tokenizer.base.AbstractTokenizer
|
|
||||||
rendering:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
### Python Analyzer Class
|
|
||||||
|
|
||||||
::: nominatim.tokenizer.base.AbstractAnalyzer
|
|
||||||
rendering:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
### PL/pgSQL Functions
|
|
||||||
|
|
||||||
The tokenizer must provide access functions for the `token_info` column
|
|
||||||
to the indexer which extracts the necessary information for the global
|
|
||||||
search tables. If the tokenizer needs additional SQL functions for private
|
|
||||||
use, then these functions must be prefixed with `token_` in order to ensure
|
|
||||||
that there are no naming conflicts with the SQL indexer code.
|
|
||||||
|
|
||||||
The following functions are expected:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_get_name_search_tokens(info JSONB) RETURNS INTEGER[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Return an array of token IDs of search terms that should match
|
|
||||||
the name(s) for the given place. These tokens are used to look up the place
|
|
||||||
by name and, where the place functions as part of an address for another place,
|
|
||||||
by address. Must return NULL when the place has no name.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_get_name_match_tokens(info JSONB) RETURNS INTEGER[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Return an array of token IDs of full names of the place that should be used
|
|
||||||
to match addresses. The list of match tokens is usually more strict than
|
|
||||||
search tokens as it is used to find a match between two OSM tag values which
|
|
||||||
are expected to contain matching full names. Partial terms should not be
|
|
||||||
used for match tokens. Must return NULL when the place has no name.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_get_housenumber_search_tokens(info JSONB) RETURNS INTEGER[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Return an array of token IDs of house number tokens that apply to the place.
|
|
||||||
Note that a place may have multiple house numbers, for example when apartments
|
|
||||||
each have their own number. Must be NULL when the place has no house numbers.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_normalized_housenumber(info JSONB) RETURNS TEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the house number(s) in the normalized form that can be matched against
|
|
||||||
a house number token text. If a place has multiple house numbers they must
|
|
||||||
be listed with a semicolon as delimiter. Must be NULL when the place has no
|
|
||||||
house numbers.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[]) RETURNS BOOLEAN
|
|
||||||
```
|
|
||||||
|
|
||||||
Check if the given tokens (previously saved from `token_get_name_match_tokens()`)
|
|
||||||
match against the `addr:street` tag name. Must return either NULL or FALSE
|
|
||||||
when the place has no `addr:street` tag.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[]) RETURNS BOOLEAN
|
|
||||||
```
|
|
||||||
|
|
||||||
Check if the given tokens (previously saved from `token_get_name_match_tokens()`)
|
|
||||||
match against the `addr:place` tag name. Must return either NULL or FALSE
|
|
||||||
when the place has no `addr:place` tag.
|
|
||||||
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_addr_place_search_tokens(info JSONB) RETURNS INTEGER[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the search token IDs extracted from the `addr:place` tag. These tokens
|
|
||||||
are used for searches by address when no matching place can be found in the
|
|
||||||
database. Must be NULL when the place has no `addr:place` tag.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_get_address_keys(info JSONB) RETURNS SETOF TEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the set of keys for which address information is provided. This
|
|
||||||
should correspond to the list of (relevant) `addr:*` tags with the `addr:`
|
|
||||||
prefix removed or the keys used in the `address` dictionary of the place info.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_get_address_search_tokens(info JSONB, key TEXT) RETURNS INTEGER[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the array of search tokens for the given address part. `key` can be
|
|
||||||
expected to be one of those returned with `token_get_address_keys()`. The
|
|
||||||
search tokens are added to the address search vector of the place, when no
|
|
||||||
corresponding OSM object could be found for the given address part from which
|
|
||||||
to copy the name information.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
|
|
||||||
```
|
|
||||||
|
|
||||||
Check if the given tokens match against the address part `key`.
|
|
||||||
|
|
||||||
__Warning:__ the tokens that are handed in are the lists previously saved
|
|
||||||
from `token_get_name_search_tokens()`, _not_ from the match token list. This
|
|
||||||
is an historical oddity which will be fixed at some point in the future.
|
|
||||||
Currently, tokenizers are encouraged to make sure that matching works against
|
|
||||||
both the search token list and the match token list.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_normalized_postcode(postcode TEXT) RETURNS TEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the normalized version of the given postcode. This function must return
|
|
||||||
the same value as the Python function `AbstractAnalyzer->normalize_postcode()`.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
FUNCTION token_strip_info(info JSONB) RETURNS JSONB
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the part of the `token_info` field that should be stored in the database
|
|
||||||
permanently. The indexer calls this function when all processing is done and
|
|
||||||
replaces the content of the `token_info` column with the returned value before
|
|
||||||
the trigger stores the information in the database. May return NULL if no
|
|
||||||
information should be stored permanently.
|
|
||||||
|
|
||||||
### PHP Tokenizer class
|
|
||||||
|
|
||||||
The PHP tokenizer class is instantiated once per request and responsible for
|
|
||||||
analyzing the incoming query. Multiple requests may be in flight in
|
|
||||||
parallel.
|
|
||||||
|
|
||||||
The class is expected to be found under the
|
|
||||||
name of `\Nominatim\Tokenizer`. To find the class the PHP code includes the file
|
|
||||||
`tokenizer/tokenizer.php` in the project directory. This file must be created
|
|
||||||
when the tokenizer is first set up on import. The file should initialize any
|
|
||||||
configuration variables by setting PHP constants and then require the file
|
|
||||||
with the actual implementation of the tokenizer.
|
|
||||||
|
|
||||||
The tokenizer class must implement the following functions:
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function __construct(object &$oDB)
|
|
||||||
```
|
|
||||||
|
|
||||||
The constructor of the class receives a database connection that can be used
|
|
||||||
to query persistent data in the database.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function checkStatus()
|
|
||||||
```
|
|
||||||
|
|
||||||
Check that the tokenizer can access its persistent data structures. If there
|
|
||||||
is an issue, throw an `\Exception`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function normalizeString(string $sTerm) : string
|
|
||||||
```
|
|
||||||
|
|
||||||
Normalize string to a form to be used for comparisons when reordering results.
|
|
||||||
Nominatim reweighs results how well the final display string matches the actual
|
|
||||||
query. Before comparing result and query, names and query are normalised against
|
|
||||||
this function. The tokenizer can thus remove all properties that should not be
|
|
||||||
taken into account for reweighing, e.g. special characters or case.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function tokensForSpecialTerm(string $sTerm) : array
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the list of special term tokens that match the given term.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function extractTokensFromPhrases(array &$aPhrases) : TokenList
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse the given phrases, splitting them into word lists and retrieve the
|
|
||||||
matching tokens.
|
|
||||||
|
|
||||||
The phrase array may take on two forms. In unstructured searches (using `q=`
|
|
||||||
parameter) the search query is split at the commas and the elements are
|
|
||||||
put into a sorted list. For structured searches the phrase array is an
|
|
||||||
associative array where the key designates the type of the term (street, city,
|
|
||||||
county etc.) The tokenizer may ignore the phrase type at this stage in parsing.
|
|
||||||
Matching phrase type and appropriate search token type will be done later
|
|
||||||
when the SearchDescription is built.
|
|
||||||
|
|
||||||
For each phrase in the list of phrases, the function must analyse the phrase
|
|
||||||
string and then call `setWordSets()` to communicate the result of the analysis.
|
|
||||||
A word set is a list of strings, where each string refers to a search token.
|
|
||||||
A phrase may have multiple interpretations. Therefore a list of word sets is
|
|
||||||
usually attached to the phrase. The search tokens themselves are returned
|
|
||||||
by the function in an associative array, where the key corresponds to the
|
|
||||||
strings given in the word sets. The value is a list of search tokens. Thus
|
|
||||||
a single string in the list of word sets may refer to multiple search tokens.
|
|
||||||
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
@startuml
|
|
||||||
skinparam monochrome true
|
|
||||||
skinparam ObjectFontStyle bold
|
|
||||||
|
|
||||||
map search_name_X {
|
|
||||||
place_id => BIGINT
|
|
||||||
address_rank => SMALLINT
|
|
||||||
name_vector => INT[]
|
|
||||||
centroid => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
map location_area_large_X {
|
|
||||||
place_id => BIGINT
|
|
||||||
keywords => INT[]
|
|
||||||
partition => SMALLINT
|
|
||||||
rank_search => SMALLINT
|
|
||||||
rank_address => SMALLINT
|
|
||||||
country_code => VARCHR(2)
|
|
||||||
isguess => BOOLEAN
|
|
||||||
postcode => TEXT
|
|
||||||
centroid => POINT
|
|
||||||
geometry => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
map location_road_X {
|
|
||||||
place_id => BIGINT
|
|
||||||
partition => SMALLINT
|
|
||||||
country_code => VARCHR(2)
|
|
||||||
geometry => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
search_name_X -[hidden]> location_area_large_X
|
|
||||||
location_area_large_X -[hidden]> location_road_X
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,44 +0,0 @@
|
|||||||
@startuml
|
|
||||||
skinparam monochrome true
|
|
||||||
skinparam ObjectFontStyle bold
|
|
||||||
|
|
||||||
map planet_osm_nodes #eee {
|
|
||||||
id => BIGINT
|
|
||||||
lat => INT
|
|
||||||
lon => INT
|
|
||||||
}
|
|
||||||
|
|
||||||
map planet_osm_ways #eee {
|
|
||||||
id => BIGINT
|
|
||||||
nodes => BIGINT[]
|
|
||||||
tags => TEXT[]
|
|
||||||
}
|
|
||||||
|
|
||||||
map planet_osm_rels #eee {
|
|
||||||
id => BIGINT
|
|
||||||
parts => BIGINT[]
|
|
||||||
members => TEXT[]
|
|
||||||
tags => TEXT[]
|
|
||||||
way_off => SMALLINT
|
|
||||||
rel_off => SMALLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
map place {
|
|
||||||
osm_type => CHAR(1)
|
|
||||||
osm_id => BIGINT
|
|
||||||
class => TEXT
|
|
||||||
type => TEXT
|
|
||||||
name => HSTORE
|
|
||||||
address => HSTORE
|
|
||||||
extratags => HSTORE
|
|
||||||
admin_level => SMALLINT
|
|
||||||
geometry => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
planet_osm_nodes -[hidden]> planet_osm_ways
|
|
||||||
planet_osm_ways -[hidden]> planet_osm_rels
|
|
||||||
planet_osm_ways -[hidden]-> place
|
|
||||||
|
|
||||||
planet_osm_nodes::id <- planet_osm_ways::nodes
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,31 +0,0 @@
|
|||||||
@startuml
|
|
||||||
skinparam monochrome true
|
|
||||||
|
|
||||||
start
|
|
||||||
|
|
||||||
if (has 'addr:street'?) then (yes)
|
|
||||||
if (street with that name\n nearby?) then (yes)
|
|
||||||
:**Use closest street**
|
|
||||||
**with same name**;
|
|
||||||
kill
|
|
||||||
else (no)
|
|
||||||
:** Use closest**\n**street**;
|
|
||||||
kill
|
|
||||||
endif
|
|
||||||
elseif (has 'addr:place'?) then (yes)
|
|
||||||
if (place with that name\n nearby?) then (yes)
|
|
||||||
:**Use closest place**
|
|
||||||
**with same name**;
|
|
||||||
kill
|
|
||||||
else (no)
|
|
||||||
:add addr:place to adress;
|
|
||||||
:**Use closest place**\n**rank 16 to 25**;
|
|
||||||
kill
|
|
||||||
endif
|
|
||||||
else (otherwise)
|
|
||||||
:**Use closest**\n**street**;
|
|
||||||
kill
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.8 KiB |
@@ -1,99 +0,0 @@
|
|||||||
@startuml
|
|
||||||
skinparam monochrome true
|
|
||||||
skinparam ObjectFontStyle bold
|
|
||||||
|
|
||||||
left to right direction
|
|
||||||
|
|
||||||
map placex {
|
|
||||||
place_id => BIGINT
|
|
||||||
osm_type => CHAR(1)
|
|
||||||
osm_id => BIGINT
|
|
||||||
class => TEXT
|
|
||||||
type => TEXT
|
|
||||||
name => HSTORE
|
|
||||||
address => HSTORE
|
|
||||||
extratags => HSTORE
|
|
||||||
admin_level => SMALLINT
|
|
||||||
partition => SMALLINT
|
|
||||||
geometry_sector => INT
|
|
||||||
parent_place_id => BIGINT
|
|
||||||
linked_place_id => BIGINT
|
|
||||||
importance => DOUBLE
|
|
||||||
rank_search => SMALLINT
|
|
||||||
rank_address => SMALLINT
|
|
||||||
wikipedia => TEXT
|
|
||||||
country_code => VARCHAR(2)
|
|
||||||
housenumber => TEXT
|
|
||||||
postcode => TEXT
|
|
||||||
indexed_status => SMALLINT
|
|
||||||
indexed_date => TIMESTAMP
|
|
||||||
centroid => GEOMETRY
|
|
||||||
geometry => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
map search_name {
|
|
||||||
place_id => BIGINT
|
|
||||||
importance => DOUBLE
|
|
||||||
search_rank => SMALLINT
|
|
||||||
address_rank => SMALLINT
|
|
||||||
name_vector => INT[]
|
|
||||||
nameaddress_vector => INT[]
|
|
||||||
country_code => VARCHAR(2)
|
|
||||||
centroid => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
map word {
|
|
||||||
word_id => INT
|
|
||||||
word_token => TEXT
|
|
||||||
... =>
|
|
||||||
}
|
|
||||||
|
|
||||||
map location_property_osmline {
|
|
||||||
place_id => BIGINT
|
|
||||||
osm_id => BIGINT
|
|
||||||
startnumber => INT
|
|
||||||
endnumber => INT
|
|
||||||
interpolationtype => TEXT
|
|
||||||
address => HSTORE
|
|
||||||
partition => SMALLINT
|
|
||||||
geometry_sector => INT
|
|
||||||
parent_place_id => BIGINT
|
|
||||||
country_code => VARCHAR(2)
|
|
||||||
postcode => text
|
|
||||||
indexed_status => SMALLINT
|
|
||||||
indexed_date => TIMESTAMP
|
|
||||||
linegeo => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
map place_addressline {
|
|
||||||
place_id => BIGINT
|
|
||||||
address_place_id => BIGINT
|
|
||||||
distance => DOUBLE
|
|
||||||
cached_rank_address => SMALLINT
|
|
||||||
fromarea => BOOLEAN
|
|
||||||
isaddress => BOOLEAN
|
|
||||||
}
|
|
||||||
|
|
||||||
map location_postcode {
|
|
||||||
place_id => BIGINT
|
|
||||||
postcode => TEXT
|
|
||||||
parent_place_id => BIGINT
|
|
||||||
rank_search => SMALLINT
|
|
||||||
rank_address => SMALLINT
|
|
||||||
indexed_status => SMALLINT
|
|
||||||
indexed_date => TIMESTAMP
|
|
||||||
geometry => GEOMETRY
|
|
||||||
}
|
|
||||||
|
|
||||||
placex::place_id <-- search_name::place_id
|
|
||||||
placex::place_id <-- place_addressline::place_id
|
|
||||||
placex::place_id <-- place_addressline::address_place_id
|
|
||||||
|
|
||||||
search_name::name_vector --> word::word_id
|
|
||||||
search_name::nameaddress_vector --> word::word_id
|
|
||||||
|
|
||||||
place_addressline -[hidden]> location_property_osmline
|
|
||||||
search_name -[hidden]> place_addressline
|
|
||||||
location_property_osmline -[hidden]-> location_postcode
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 35 KiB |
@@ -13,11 +13,3 @@ th, td {
|
|||||||
th {
|
th {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Indentation for mkdocstrings.
|
|
||||||
div.doc-contents:not(.first) {
|
|
||||||
padding-left: 25px;
|
|
||||||
border-left: 4px solid rgba(230, 230, 230);
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
Nominatim (from the Latin, 'by name') is a tool to search OSM data by name and address and to generate synthetic addresses of OSM points (reverse geocoding).
|
Nominatim (from the Latin, 'by name') is a tool to search OSM data by name and address and to generate synthetic addresses of OSM points (reverse geocoding).
|
||||||
|
|
||||||
This guide comes in four parts:
|
This guide comes in three parts:
|
||||||
|
|
||||||
* __[API reference](api/Overview.md)__ for users of Nominatim
|
* __[API reference](api/Overview.md)__ for users of Nominatim
|
||||||
* __[Administration Guide](admin/Installation.md)__ for those who want
|
* __[Administration Guide](admin/Installation.md)__ for those who want
|
||||||
to install their own Nominatim server
|
to install their own Nominatim server
|
||||||
* __[Customization Guide](customize/Overview.md)__ for those who want to
|
|
||||||
adapt their own installation to their special requirements
|
|
||||||
* __[Developer's Guide](develop/overview.md)__ for developers of the software
|
* __[Developer's Guide](develop/overview.md)__ for developers of the software
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
site_name: Nominatim 4.0.1
|
site_name: Nominatim 3.6.0
|
||||||
theme: readthedocs
|
theme: readthedocs
|
||||||
docs_dir: ${CMAKE_CURRENT_BINARY_DIR}
|
docs_dir: ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
site_url: https://nominatim.org
|
site_url: https://nominatim.org
|
||||||
@@ -21,24 +21,14 @@ pages:
|
|||||||
- 'Deploy' : 'admin/Deployment.md'
|
- 'Deploy' : 'admin/Deployment.md'
|
||||||
- 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md'
|
- 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md'
|
||||||
- 'Advanced Installations' : 'admin/Advanced-Installations.md'
|
- 'Advanced Installations' : 'admin/Advanced-Installations.md'
|
||||||
- 'Maintenance' : 'admin/Maintenance.md'
|
|
||||||
- 'Migration from older Versions' : 'admin/Migration.md'
|
- 'Migration from older Versions' : 'admin/Migration.md'
|
||||||
- 'Troubleshooting' : 'admin/Faq.md'
|
- 'Troubleshooting' : 'admin/Faq.md'
|
||||||
- 'Customization Guide':
|
|
||||||
- 'Overview': 'customize/Overview.md'
|
|
||||||
- 'Import Styles': 'customize/Import-Styles.md'
|
|
||||||
- 'Configuration Settings': 'customize/Settings.md'
|
|
||||||
- 'Place Ranking' : 'customize/Ranking.md'
|
|
||||||
- 'Tokenizers' : 'customize/Tokenizers.md'
|
|
||||||
- 'Special Phrases': 'customize/Special-Phrases.md'
|
|
||||||
- 'External data: US housenumbers from TIGER': 'customize/Tiger.md'
|
|
||||||
- 'External data: Postcodes': 'customize/Postcodes.md'
|
|
||||||
- 'Developers Guide':
|
- 'Developers Guide':
|
||||||
- 'Architecture Overview' : 'develop/overview.md'
|
|
||||||
- 'Database Layout' : 'develop/Database-Layout.md'
|
|
||||||
- 'Indexing' : 'develop/Indexing.md'
|
|
||||||
- 'Tokenizers' : 'develop/Tokenizers.md'
|
|
||||||
- 'Setup for Development' : 'develop/Development-Environment.md'
|
- 'Setup for Development' : 'develop/Development-Environment.md'
|
||||||
|
- 'Architecture Overview' : 'develop/overview.md'
|
||||||
|
- 'OSM Data Import' : 'develop/Import.md'
|
||||||
|
- 'Place Ranking' : 'develop/Ranking.md'
|
||||||
|
- 'Postcodes' : 'develop/Postcodes.md'
|
||||||
- 'Testing' : 'develop/Testing.md'
|
- 'Testing' : 'develop/Testing.md'
|
||||||
- 'External Data Sources': 'develop/data-sources.md'
|
- 'External Data Sources': 'develop/data-sources.md'
|
||||||
- 'Appendix':
|
- 'Appendix':
|
||||||
@@ -49,15 +39,6 @@ pages:
|
|||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- codehilite
|
- codehilite
|
||||||
- admonition
|
- admonition
|
||||||
- def_list
|
|
||||||
- toc:
|
- toc:
|
||||||
permalink:
|
permalink:
|
||||||
extra_css: [extra.css, styles.css]
|
extra_css: [extra.css, styles.css]
|
||||||
plugins:
|
|
||||||
- search
|
|
||||||
- mkdocstrings:
|
|
||||||
handlers:
|
|
||||||
python:
|
|
||||||
rendering:
|
|
||||||
show_source: false
|
|
||||||
show_signature_annotations: false
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Segment of a query string.
|
|
||||||
*
|
|
||||||
* The parts of a query strings are usually separated by commas.
|
|
||||||
*/
|
|
||||||
class Phrase
|
|
||||||
{
|
|
||||||
// Complete phrase as a string (guaranteed to have no leading or trailing
|
|
||||||
// spaces).
|
|
||||||
private $sPhrase;
|
|
||||||
// Element type for structured searches.
|
|
||||||
private $sPhraseType;
|
|
||||||
// Possible segmentations of the phrase.
|
|
||||||
private $aWordSets;
|
|
||||||
|
|
||||||
public function __construct($sPhrase, $sPhraseType)
|
|
||||||
{
|
|
||||||
$this->sPhrase = trim($sPhrase);
|
|
||||||
$this->sPhraseType = $sPhraseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the orginal phrase of the string.
|
|
||||||
*/
|
|
||||||
public function getPhrase()
|
|
||||||
{
|
|
||||||
return $this->sPhrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the element type of the phrase.
|
|
||||||
*
|
|
||||||
* @return string Pharse type if the phrase comes from a structured query
|
|
||||||
* or empty string otherwise.
|
|
||||||
*/
|
|
||||||
public function getPhraseType()
|
|
||||||
{
|
|
||||||
return $this->sPhraseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setWordSets($aWordSets)
|
|
||||||
{
|
|
||||||
$this->aWordSets = $aWordSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the array of possible segmentations of the phrase.
|
|
||||||
*
|
|
||||||
* @return string[][] Array of segmentations, each consisting of an
|
|
||||||
* array of terms.
|
|
||||||
*/
|
|
||||||
public function getWordSets()
|
|
||||||
{
|
|
||||||
return $this->aWordSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invert the set of possible segmentations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function invertWordSets()
|
|
||||||
{
|
|
||||||
foreach ($this->aWordSets as $i => $aSet) {
|
|
||||||
$this->aWordSets[$i] = array_reverse($aSet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Type' => $this->sPhraseType,
|
|
||||||
'Phrase' => $this->sPhrase,
|
|
||||||
'WordSets' => $this->aWordSets
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description of the position of a token within a query.
|
|
||||||
*/
|
|
||||||
class SearchPosition
|
|
||||||
{
|
|
||||||
private $sPhraseType;
|
|
||||||
|
|
||||||
private $iPhrase;
|
|
||||||
private $iNumPhrases;
|
|
||||||
|
|
||||||
private $iToken;
|
|
||||||
private $iNumTokens;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct($sPhraseType, $iPhrase, $iNumPhrases)
|
|
||||||
{
|
|
||||||
$this->sPhraseType = $sPhraseType;
|
|
||||||
$this->iPhrase = $iPhrase;
|
|
||||||
$this->iNumPhrases = $iNumPhrases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTokenPosition($iToken, $iNumTokens)
|
|
||||||
{
|
|
||||||
$this->iToken = $iToken;
|
|
||||||
$this->iNumTokens = $iNumTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the phrase can be of the given type.
|
|
||||||
*
|
|
||||||
* @param string $sType Type of phrse requested.
|
|
||||||
*
|
|
||||||
* @return True if the phrase is untyped or of the given type.
|
|
||||||
*/
|
|
||||||
public function maybePhrase($sType)
|
|
||||||
{
|
|
||||||
return $this->sPhraseType == '' || $this->sPhraseType == $sType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the phrase is exactly of the given type.
|
|
||||||
*
|
|
||||||
* @param string $sType Type of phrse requested.
|
|
||||||
*
|
|
||||||
* @return True if the phrase of the given type.
|
|
||||||
*/
|
|
||||||
public function isPhrase($sType)
|
|
||||||
{
|
|
||||||
return $this->sPhraseType == $sType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the token is the very first in the query.
|
|
||||||
*/
|
|
||||||
public function isFirstToken()
|
|
||||||
{
|
|
||||||
return $this->iPhrase == 0 && $this->iToken == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token is the final one in the query.
|
|
||||||
*/
|
|
||||||
public function isLastToken()
|
|
||||||
{
|
|
||||||
return $this->iToken + 1 == $this->iNumTokens && $this->iPhrase + 1 == $this->iNumPhrases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current token is part of the first phrase in the query.
|
|
||||||
*/
|
|
||||||
public function isFirstPhrase()
|
|
||||||
{
|
|
||||||
return $this->iPhrase == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the phrase position in the query.
|
|
||||||
*/
|
|
||||||
public function getPhrase()
|
|
||||||
{
|
|
||||||
return $this->iPhrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_TokenizerDir.'/tokenizer.php');
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class Status
|
|
||||||
{
|
|
||||||
protected $oDB;
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status()
|
|
||||||
{
|
|
||||||
if (!$this->oDB) {
|
|
||||||
throw new Exception('No database', 700);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->oDB->connect();
|
|
||||||
} catch (\Nominatim\DatabaseError $e) {
|
|
||||||
throw new Exception('Database connection failed', 700);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oTokenizer = new \Nominatim\Tokenizer($this->oDB);
|
|
||||||
$oTokenizer->checkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function dataDate()
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
|
|
||||||
$iDataDateEpoch = $this->oDB->getOne($sSQL);
|
|
||||||
|
|
||||||
if ($iDataDateEpoch === false) {
|
|
||||||
throw new Exception('Import date is not available', 705);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $iDataDateEpoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function databaseVersion()
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\'';
|
|
||||||
return $this->oDB->getOne($sSQL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A country token.
|
|
||||||
*/
|
|
||||||
class Country
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Two-letter country code (lower-cased).
|
|
||||||
private $sCountryCode;
|
|
||||||
|
|
||||||
public function __construct($iId, $sCountryCode)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->sCountryCode = $sCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasCountry()
|
|
||||||
&& $oPosition->maybePhrase('country')
|
|
||||||
&& $oSearch->getContext()->isCountryApplicable($this->sCountryCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$oNewSearch = $oSearch->clone($oPosition->isLastToken() ? 1 : 6);
|
|
||||||
$oNewSearch->setCountry($this->sCountryCode);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'country',
|
|
||||||
'Info' => $this->sCountryCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'C';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A house number token.
|
|
||||||
*/
|
|
||||||
class HouseNumber
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Normalized house number.
|
|
||||||
private $sToken;
|
|
||||||
|
|
||||||
public function __construct($iId, $sToken)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->sToken = $sToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasHousenumber()
|
|
||||||
&& !$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
|
|
||||||
&& $oPosition->maybePhrase('street');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// sanity check: if the housenumber is not mainly made
|
|
||||||
// up of numbers, add a penalty
|
|
||||||
$iSearchCost = 1;
|
|
||||||
if (preg_match('/\\d/', $this->sToken) === 0
|
|
||||||
|| preg_match_all('/[^0-9]/', $this->sToken, $aMatches) > 2) {
|
|
||||||
$iSearchCost += strlen($this->sToken) - 1;
|
|
||||||
}
|
|
||||||
if (!$oSearch->hasOperator(\Nominatim\Operator::NONE)) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
if (empty($this->iId)) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
// also must not appear in the middle of the address
|
|
||||||
if ($oSearch->hasAddress() || $oSearch->hasPostcode()) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setHousenumber($this->sToken);
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
|
|
||||||
// Housenumbers may appear in the name when the place has its own
|
|
||||||
// address terms.
|
|
||||||
if ($this->iId !== null
|
|
||||||
&& ($oSearch->getNamePhrase() >= 0 || !$oSearch->hasName())
|
|
||||||
&& !$oSearch->hasAddress()
|
|
||||||
) {
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setHousenumberAsName($this->iId);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'house number',
|
|
||||||
'Info' => array('nr' => $this->sToken)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'H';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/TokenCountry.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenHousenumber.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenPostcode.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenSpecialTerm.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenWord.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenPartial.php');
|
|
||||||
require_once(CONST_LibDir.'/SpecialSearchOperator.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves information about the tokens that appear in a search query.
|
|
||||||
*
|
|
||||||
* Tokens are sorted by their normalized form, the token word. There are different
|
|
||||||
* kinds of tokens, represented by different Token* classes. Note that
|
|
||||||
* tokens do not have a common base class. All tokens need to have a field
|
|
||||||
* with the word id that points to an entry in the `word` database table
|
|
||||||
* but otherwise the information saved about a token can be very different.
|
|
||||||
*/
|
|
||||||
class TokenList
|
|
||||||
{
|
|
||||||
// List of list of tokens indexed by their word_token.
|
|
||||||
private $aTokens = array();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return total number of tokens.
|
|
||||||
*
|
|
||||||
* @return Integer
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
return count($this->aTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are tokens for the given token word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return bool True if there is one or more token for the token word.
|
|
||||||
*/
|
|
||||||
public function contains($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are partial or full tokens for the given word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return bool True if there is one or more token for the token word.
|
|
||||||
*/
|
|
||||||
public function containsAny($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of tokens for the given token word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return object[] Array of tokens for the given token word or an
|
|
||||||
* empty array if no tokens could be found.
|
|
||||||
*/
|
|
||||||
public function get($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]) ? $this->aTokens[$sWord] : array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFullWordIDs()
|
|
||||||
{
|
|
||||||
$ids = array();
|
|
||||||
|
|
||||||
foreach ($this->aTokens as $aTokenList) {
|
|
||||||
foreach ($aTokenList as $oToken) {
|
|
||||||
if (is_a($oToken, '\Nominatim\Token\Word')) {
|
|
||||||
$ids[$oToken->getId()] = $oToken->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new token for the given word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Word the token describes.
|
|
||||||
* @param object $oToken Token object to add.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addToken($sWord, $oToken)
|
|
||||||
{
|
|
||||||
if (isset($this->aTokens[$sWord])) {
|
|
||||||
$this->aTokens[$sWord][] = $oToken;
|
|
||||||
} else {
|
|
||||||
$this->aTokens[$sWord] = array($oToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugTokenByWordIdList()
|
|
||||||
{
|
|
||||||
$aWordsIDs = array();
|
|
||||||
foreach ($this->aTokens as $sToken => $aWords) {
|
|
||||||
foreach ($aWords as $aToken) {
|
|
||||||
$iId = $aToken->getId();
|
|
||||||
if ($iId !== null) {
|
|
||||||
$aWordsIDs[$iId] = '#'.$sToken.'('.$aToken->debugCode().' '.$iId.')#';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aWordsIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return $this->aTokens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A standard word token.
|
|
||||||
*/
|
|
||||||
class Partial
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Number of appearances in the database.
|
|
||||||
private $iSearchNameCount;
|
|
||||||
/// True, if the token consists exclusively of digits and spaces.
|
|
||||||
private $bNumberToken;
|
|
||||||
|
|
||||||
public function __construct($iId, $sToken, $iSearchNameCount)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->bNumberToken = (bool) preg_match('#^[0-9 ]+$#', $sToken);
|
|
||||||
$this->iSearchNameCount = $iSearchNameCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oPosition->isPhrase('country');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// Partial token in Address.
|
|
||||||
if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
|
|
||||||
&& $oSearch->hasName()
|
|
||||||
) {
|
|
||||||
$iSearchCost = $this->bNumberToken ? 2 : 1;
|
|
||||||
if ($this->iSearchNameCount >= CONST_Max_Word_Frequency) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->addAddressToken(
|
|
||||||
$this->iId,
|
|
||||||
$this->iSearchNameCount < CONST_Max_Word_Frequency
|
|
||||||
);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Partial token in Name.
|
|
||||||
if ((!$oSearch->hasPostcode() && !$oSearch->hasAddress())
|
|
||||||
&& (!$oSearch->hasName(true)
|
|
||||||
|| $oSearch->getNamePhrase() == $oPosition->getPhrase())
|
|
||||||
) {
|
|
||||||
$iSearchCost = 1;
|
|
||||||
if (!$oSearch->hasName(true)) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
if ($this->bNumberToken) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->addPartialNameToken(
|
|
||||||
$this->iId,
|
|
||||||
$this->iSearchNameCount < CONST_Max_Word_Frequency,
|
|
||||||
$this->iSearchNameCount > CONST_Search_NameOnlySearchFrequencyThreshold,
|
|
||||||
$oPosition->getPhrase()
|
|
||||||
);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'partial',
|
|
||||||
'Info' => array(
|
|
||||||
'count' => $this->iSearchNameCount
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'w';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A postcode token.
|
|
||||||
*/
|
|
||||||
class Postcode
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Full nomralized postcode (upper cased).
|
|
||||||
private $sPostcode;
|
|
||||||
// Optional country code the postcode belongs to (currently unused).
|
|
||||||
private $sCountryCode;
|
|
||||||
|
|
||||||
public function __construct($iId, $sPostcode, $sCountryCode = '')
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->sPostcode = $sPostcode;
|
|
||||||
$this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasPostcode() && $oPosition->maybePhrase('postalcode');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// If we have structured search or this is the first term,
|
|
||||||
// make the postcode the primary search element.
|
|
||||||
if ($oSearch->hasOperator(\Nominatim\Operator::NONE) && $oPosition->isFirstToken()) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->setPostcodeAsName($this->iId, $this->sPostcode);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a structured search or this is not the first term,
|
|
||||||
// add the postcode as an addendum.
|
|
||||||
if (!$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
|
|
||||||
&& ($oPosition->isPhrase('postalcode') || $oSearch->hasName())
|
|
||||||
) {
|
|
||||||
$iPenalty = 1;
|
|
||||||
if (strlen($this->sPostcode) < 4) {
|
|
||||||
$iPenalty += 4 - strlen($this->sPostcode);
|
|
||||||
}
|
|
||||||
$oNewSearch = $oSearch->clone($iPenalty);
|
|
||||||
$oNewSearch->setPostcode($this->sPostcode);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'postcode',
|
|
||||||
'Info' => $this->sPostcode.'('.$this->sCountryCode.')'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'P';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/SpecialSearchOperator.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A word token describing a place type.
|
|
||||||
*/
|
|
||||||
class SpecialTerm
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Class (or OSM tag key) of the place to look for.
|
|
||||||
private $sClass;
|
|
||||||
/// Type (or OSM tag value) of the place to look for.
|
|
||||||
private $sType;
|
|
||||||
/// Relationship of the operator to the object (see Operator class).
|
|
||||||
private $iOperator;
|
|
||||||
|
|
||||||
public function __construct($iID, $sClass, $sType, $iOperator)
|
|
||||||
{
|
|
||||||
$this->iId = $iID;
|
|
||||||
$this->sClass = $sClass;
|
|
||||||
$this->sType = $sType;
|
|
||||||
$this->iOperator = $iOperator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasOperator()
|
|
||||||
&& $oPosition->isPhrase('')
|
|
||||||
&& ($this->iOperator != \Nominatim\Operator::NONE
|
|
||||||
|| (!$oSearch->hasAddress() && !$oSearch->hasHousenumber() && !$oSearch->hasCountry()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$iSearchCost = 2;
|
|
||||||
|
|
||||||
$iOp = $this->iOperator;
|
|
||||||
if ($iOp == \Nominatim\Operator::NONE) {
|
|
||||||
if ($oSearch->hasName() || $oSearch->getContext()->isBoundedSearch()) {
|
|
||||||
$iOp = \Nominatim\Operator::NAME;
|
|
||||||
} else {
|
|
||||||
$iOp = \Nominatim\Operator::NEAR;
|
|
||||||
$iSearchCost += 2;
|
|
||||||
}
|
|
||||||
} elseif (!$oPosition->isFirstToken() && !$oPosition->isLastToken()) {
|
|
||||||
$iSearchCost += 2;
|
|
||||||
}
|
|
||||||
if ($oSearch->hasHousenumber()) {
|
|
||||||
$iSearchCost ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setPoiSearch($iOp, $this->sClass, $this->sType);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'special term',
|
|
||||||
'Info' => array(
|
|
||||||
'class' => $this->sClass,
|
|
||||||
'type' => $this->sType,
|
|
||||||
'operator' => \Nominatim\Operator::toString($this->iOperator)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'S';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A standard word token.
|
|
||||||
*/
|
|
||||||
class Word
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Number of appearances in the database.
|
|
||||||
private $iSearchNameCount;
|
|
||||||
/// Number of terms in the word.
|
|
||||||
private $iTermCount;
|
|
||||||
|
|
||||||
public function __construct($iId, $iSearchNameCount, $iTermCount)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->iSearchNameCount = $iSearchNameCount;
|
|
||||||
$this->iTermCount = $iTermCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oPosition->isPhrase('country');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
// Full words can only be a name if they appear at the beginning
|
|
||||||
// of the phrase. In structured search the name must forcably in
|
|
||||||
// the first phrase. In unstructured search it may be in a later
|
|
||||||
// phrase when the first phrase is a house number.
|
|
||||||
if ($oSearch->hasName()
|
|
||||||
|| !($oPosition->isFirstPhrase() || $oPosition->isPhrase(''))
|
|
||||||
) {
|
|
||||||
if ($this->iTermCount > 1
|
|
||||||
&& ($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
|
|
||||||
) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->addAddressToken($this->iId);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
} elseif (!$oSearch->hasName(true)) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->addNameToken(
|
|
||||||
$this->iId,
|
|
||||||
CONST_Search_NameOnlySearchFrequencyThreshold
|
|
||||||
&& $this->iSearchNameCount
|
|
||||||
< CONST_Search_NameOnlySearchFrequencyThreshold
|
|
||||||
);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'word',
|
|
||||||
'Info' => array(
|
|
||||||
'count' => $this->iSearchNameCount,
|
|
||||||
'terms' => $this->iTermCount
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'W';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require('Symfony/Component/Dotenv/autoload.php');
|
|
||||||
|
|
||||||
function loadDotEnv()
|
|
||||||
{
|
|
||||||
$dotenv = new \Symfony\Component\Dotenv\Dotenv();
|
|
||||||
$dotenv->load(CONST_ConfigDir.'/env.defaults');
|
|
||||||
|
|
||||||
if (file_exists('.env')) {
|
|
||||||
$dotenv->load('.env');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once('init.php');
|
|
||||||
require_once('cmd.php');
|
|
||||||
require_once('DebugNone.php');
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/lib.php');
|
|
||||||
require_once(CONST_LibDir.'/DB.php');
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
|
|
||||||
function formatOSMType($sType, $bIncludeExternal = true)
|
|
||||||
{
|
|
||||||
if ($sType == 'N') {
|
|
||||||
return 'node';
|
|
||||||
}
|
|
||||||
if ($sType == 'W') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
if ($sType == 'R') {
|
|
||||||
return 'relation';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$bIncludeExternal) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sType == 'T') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
if ($sType == 'I') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
|
|
||||||
// not handled: P, L
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
function getOsm2pgsqlBinary()
|
|
||||||
{
|
|
||||||
$sBinary = getSetting('OSM2PGSQL_BINARY');
|
|
||||||
|
|
||||||
return $sBinary ? $sBinary : CONST_Default_Osm2pgsql;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImportStyle()
|
|
||||||
{
|
|
||||||
$sStyle = getSetting('IMPORT_STYLE');
|
|
||||||
|
|
||||||
if (in_array($sStyle, array('admin', 'street', 'address', 'full', 'extratags'))) {
|
|
||||||
return CONST_ConfigDir.'/import-'.$sStyle.'.style';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sStyle;
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/SimpleWordList.php');
|
|
||||||
|
|
||||||
class Tokenizer
|
|
||||||
{
|
|
||||||
private $oDB;
|
|
||||||
|
|
||||||
private $oNormalizer;
|
|
||||||
private $oTransliterator;
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
$this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
|
|
||||||
$this->oTransliterator = \Transliterator::createFromRules(CONST_Transliteration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkStatus()
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT word_id FROM word WHERE word_id is not null limit 1';
|
|
||||||
$iWordID = $this->oDB->getOne($sSQL);
|
|
||||||
if ($iWordID === false) {
|
|
||||||
throw new \Exception('Query failed', 703);
|
|
||||||
}
|
|
||||||
if (!$iWordID) {
|
|
||||||
throw new \Exception('No value', 704);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function normalizeString($sTerm)
|
|
||||||
{
|
|
||||||
if ($this->oNormalizer === null) {
|
|
||||||
return $sTerm;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->oNormalizer->transliterate($sTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function mostFrequentWords($iNum)
|
|
||||||
{
|
|
||||||
$sSQL = "SELECT word FROM word WHERE type = 'W'";
|
|
||||||
$sSQL .= "ORDER BY info->'count' DESC LIMIT ".$iNum;
|
|
||||||
return $this->oDB->getCol($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function makeStandardWord($sTerm)
|
|
||||||
{
|
|
||||||
return trim($this->oTransliterator->transliterate(' '.$sTerm.' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function tokensForSpecialTerm($sTerm)
|
|
||||||
{
|
|
||||||
$aResults = array();
|
|
||||||
|
|
||||||
$sSQL = "SELECT word_id, info->>'class' as class, info->>'type' as type ";
|
|
||||||
$sSQL .= ' FROM word WHERE word_token = :term and type = \'S\'';
|
|
||||||
|
|
||||||
Debug::printVar('Term', $sTerm);
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $this->makeStandardWord($sTerm)));
|
|
||||||
|
|
||||||
Debug::printVar('Results', $aSearchWords);
|
|
||||||
|
|
||||||
foreach ($aSearchWords as $aSearchTerm) {
|
|
||||||
$aResults[] = new \Nominatim\Token\SpecialTerm(
|
|
||||||
$aSearchTerm['word_id'],
|
|
||||||
$aSearchTerm['class'],
|
|
||||||
$aSearchTerm['type'],
|
|
||||||
\Nominatim\Operator::TYPE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Special term tokens', $aResults);
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function extractTokensFromPhrases(&$aPhrases)
|
|
||||||
{
|
|
||||||
$sNormQuery = '';
|
|
||||||
$aWordLists = array();
|
|
||||||
$aTokens = array();
|
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
|
||||||
$sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
|
|
||||||
$sPhrase = $this->makeStandardWord($oPhrase->getPhrase());
|
|
||||||
Debug::printVar('Phrase', $sPhrase);
|
|
||||||
|
|
||||||
$oWordList = new SimpleWordList($sPhrase);
|
|
||||||
$aTokens = array_merge($aTokens, $oWordList->getTokens());
|
|
||||||
$aWordLists[] = $oWordList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Tokens', $aTokens);
|
|
||||||
Debug::printVar('WordLists', $aWordLists);
|
|
||||||
|
|
||||||
$oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
|
|
||||||
|
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
|
||||||
$oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $oValidTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function computeValidTokens($aTokens, $sNormQuery)
|
|
||||||
{
|
|
||||||
$oValidTokens = new TokenList();
|
|
||||||
|
|
||||||
if (!empty($aTokens)) {
|
|
||||||
$this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
|
|
||||||
|
|
||||||
// Try more interpretations for Tokens that could not be matched.
|
|
||||||
foreach ($aTokens as $sToken) {
|
|
||||||
if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
|
|
||||||
if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
|
|
||||||
// US ZIP+4 codes - merge in the 5-digit ZIP code
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sToken,
|
|
||||||
new Token\Postcode(null, $aData[1], 'us')
|
|
||||||
);
|
|
||||||
} elseif (preg_match('/^[0-9]+$/', $sToken)) {
|
|
||||||
// Unknown single word token with a number.
|
|
||||||
// Assume it is a house number.
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sToken,
|
|
||||||
new Token\HouseNumber(null, trim($sToken))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $oValidTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
|
|
||||||
{
|
|
||||||
// Check which tokens we have, get the ID numbers
|
|
||||||
$sSQL = 'SELECT word_id, word_token, type, word,';
|
|
||||||
$sSQL .= " info->>'op' as operator,";
|
|
||||||
$sSQL .= " info->>'class' as class, info->>'type' as ctype,";
|
|
||||||
$sSQL .= " info->>'count' as count";
|
|
||||||
$sSQL .= ' FROM word WHERE word_token in (';
|
|
||||||
$sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
|
|
||||||
|
|
||||||
foreach ($aDBWords as $aWord) {
|
|
||||||
$iId = (int) $aWord['word_id'];
|
|
||||||
$sTok = $aWord['word_token'];
|
|
||||||
|
|
||||||
switch ($aWord['type']) {
|
|
||||||
case 'C': // country name tokens
|
|
||||||
if ($aWord['word'] !== null) {
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sTok,
|
|
||||||
new Token\Country($iId, $aWord['word'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'H': // house number tokens
|
|
||||||
$oValidTokens->addToken($sTok, new Token\HouseNumber($iId, $aWord['word_token']));
|
|
||||||
break;
|
|
||||||
case 'P': // postcode tokens
|
|
||||||
// Postcodes are not normalized, so they may have content
|
|
||||||
// that makes SQL injection possible. Reject postcodes
|
|
||||||
// that would need special escaping.
|
|
||||||
if ($aWord['word'] !== null
|
|
||||||
&& pg_escape_string($aWord['word']) == $aWord['word']
|
|
||||||
) {
|
|
||||||
$sNormPostcode = $this->normalizeString($aWord['word']);
|
|
||||||
if (strpos($sNormQuery, $sNormPostcode) !== false) {
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sTok,
|
|
||||||
new Token\Postcode($iId, $aWord['word'], null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'S': // tokens for classification terms (special phrases)
|
|
||||||
if ($aWord['class'] !== null && $aWord['ctype'] !== null) {
|
|
||||||
$oValidTokens->addToken($sTok, new Token\SpecialTerm(
|
|
||||||
$iId,
|
|
||||||
$aWord['class'],
|
|
||||||
$aWord['ctype'],
|
|
||||||
(isset($aWord['operator'])) ? Operator::NEAR : Operator::NONE
|
|
||||||
));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'W': // full-word tokens
|
|
||||||
$oValidTokens->addToken($sTok, new Token\Word(
|
|
||||||
$iId,
|
|
||||||
(int) $aWord['count'],
|
|
||||||
substr_count($aWord['word_token'], ' ')
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
case 'w': // partial word terms
|
|
||||||
$oValidTokens->addToken($sTok, new Token\Partial(
|
|
||||||
$iId,
|
|
||||||
$aWord['word_token'],
|
|
||||||
(int) $aWord['count']
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/SimpleWordList.php');
|
|
||||||
|
|
||||||
class Tokenizer
|
|
||||||
{
|
|
||||||
private $oDB;
|
|
||||||
|
|
||||||
private $oNormalizer = null;
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
$this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkStatus()
|
|
||||||
{
|
|
||||||
$sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
|
|
||||||
if ($sStandardWord === false) {
|
|
||||||
throw new \Exception('Module failed', 701);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sStandardWord != 'a') {
|
|
||||||
throw new \Exception('Module call failed', 702);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = "SELECT word_id FROM word WHERE word_token IN (' a')";
|
|
||||||
$iWordID = $this->oDB->getOne($sSQL);
|
|
||||||
if ($iWordID === false) {
|
|
||||||
throw new \Exception('Query failed', 703);
|
|
||||||
}
|
|
||||||
if (!$iWordID) {
|
|
||||||
throw new \Exception('No value', 704);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function normalizeString($sTerm)
|
|
||||||
{
|
|
||||||
if ($this->oNormalizer === null) {
|
|
||||||
return $sTerm;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->oNormalizer->transliterate($sTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function mostFrequentWords($iNum)
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT word FROM word WHERE word is not null ';
|
|
||||||
$sSQL .= 'ORDER BY search_name_count DESC LIMIT '.$iNum;
|
|
||||||
return $this->oDB->getCol($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function tokensForSpecialTerm($sTerm)
|
|
||||||
{
|
|
||||||
$aResults = array();
|
|
||||||
|
|
||||||
$sSQL = 'SELECT word_id, class, type FROM word ';
|
|
||||||
$sSQL .= ' WHERE word_token = \' \' || make_standard_name(:term)';
|
|
||||||
$sSQL .= ' AND class is not null AND class not in (\'place\')';
|
|
||||||
|
|
||||||
Debug::printVar('Term', $sTerm);
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $sTerm));
|
|
||||||
|
|
||||||
Debug::printVar('Results', $aSearchWords);
|
|
||||||
|
|
||||||
foreach ($aSearchWords as $aSearchTerm) {
|
|
||||||
$aResults[] = new \Nominatim\Token\SpecialTerm(
|
|
||||||
$aSearchTerm['word_id'],
|
|
||||||
$aSearchTerm['class'],
|
|
||||||
$aSearchTerm['type'],
|
|
||||||
\Nominatim\Operator::TYPE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Special term tokens', $aResults);
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function extractTokensFromPhrases(&$aPhrases)
|
|
||||||
{
|
|
||||||
// First get the normalized version of all phrases
|
|
||||||
$sNormQuery = '';
|
|
||||||
$sSQL = 'SELECT ';
|
|
||||||
$aParams = array();
|
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
|
||||||
$sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
|
|
||||||
$sSQL .= 'make_standard_name(:' .$iPhrase.') as p'.$iPhrase.',';
|
|
||||||
$aParams[':'.$iPhrase] = $oPhrase->getPhrase();
|
|
||||||
|
|
||||||
// Conflicts between US state abbreviations and various words
|
|
||||||
// for 'the' in different languages
|
|
||||||
switch (strtolower($oPhrase->getPhrase())) {
|
|
||||||
case 'il':
|
|
||||||
$aParams[':'.$iPhrase] = 'illinois';
|
|
||||||
break;
|
|
||||||
case 'al':
|
|
||||||
$aParams[':'.$iPhrase] = 'alabama';
|
|
||||||
break;
|
|
||||||
case 'la':
|
|
||||||
$aParams[':'.$iPhrase] = 'louisiana';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$aParams[':'.$iPhrase] = $oPhrase->getPhrase();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sSQL = substr($sSQL, 0, -1);
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
Debug::printVar('SQL parameters', $aParams);
|
|
||||||
|
|
||||||
$aNormPhrases = $this->oDB->getRow($sSQL, $aParams);
|
|
||||||
|
|
||||||
Debug::printVar('SQL result', $aNormPhrases);
|
|
||||||
|
|
||||||
// now compute all possible tokens
|
|
||||||
$aWordLists = array();
|
|
||||||
$aTokens = array();
|
|
||||||
foreach ($aNormPhrases as $sPhrase) {
|
|
||||||
$oWordList = new SimpleWordList($sPhrase);
|
|
||||||
|
|
||||||
foreach ($oWordList->getTokens() as $sToken) {
|
|
||||||
$aTokens[' '.$sToken] = ' '.$sToken;
|
|
||||||
$aTokens[$sToken] = $sToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aWordLists[] = $oWordList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Tokens', $aTokens);
|
|
||||||
Debug::printVar('WordLists', $aWordLists);
|
|
||||||
|
|
||||||
$oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
|
|
||||||
|
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
|
||||||
$oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $oValidTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function computeValidTokens($aTokens, $sNormQuery)
|
|
||||||
{
|
|
||||||
$oValidTokens = new TokenList();
|
|
||||||
|
|
||||||
if (!empty($aTokens)) {
|
|
||||||
$this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
|
|
||||||
|
|
||||||
// Try more interpretations for Tokens that could not be matched.
|
|
||||||
foreach ($aTokens as $sToken) {
|
|
||||||
if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
|
|
||||||
if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
|
|
||||||
// US ZIP+4 codes - merge in the 5-digit ZIP code
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sToken,
|
|
||||||
new Token\Postcode(null, $aData[1], 'us')
|
|
||||||
);
|
|
||||||
} elseif (preg_match('/^[0-9]+$/', $sToken)) {
|
|
||||||
// Unknown single word token with a number.
|
|
||||||
// Assume it is a house number.
|
|
||||||
$oValidTokens->addToken(
|
|
||||||
$sToken,
|
|
||||||
new Token\HouseNumber(null, trim($sToken))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $oValidTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
|
|
||||||
{
|
|
||||||
// Check which tokens we have, get the ID numbers
|
|
||||||
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
|
|
||||||
$sSQL .= ' operator, coalesce(search_name_count, 0) as count';
|
|
||||||
$sSQL .= ' FROM word WHERE word_token in (';
|
|
||||||
$sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
|
|
||||||
|
|
||||||
foreach ($aDBWords as $aWord) {
|
|
||||||
$oToken = null;
|
|
||||||
$iId = (int) $aWord['word_id'];
|
|
||||||
|
|
||||||
if ($aWord['class']) {
|
|
||||||
// Special terms need to appear in their normalized form.
|
|
||||||
// (postcodes are not normalized in the word table)
|
|
||||||
$sNormWord = $this->normalizeString($aWord['word']);
|
|
||||||
if ($aWord['word'] && strpos($sNormQuery, $sNormWord) === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aWord['class'] == 'place' && $aWord['type'] == 'house') {
|
|
||||||
$oToken = new Token\HouseNumber($iId, trim($aWord['word_token']));
|
|
||||||
} elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') {
|
|
||||||
if ($aWord['word']
|
|
||||||
&& pg_escape_string($aWord['word']) == $aWord['word']
|
|
||||||
) {
|
|
||||||
$oToken = new Token\Postcode(
|
|
||||||
$iId,
|
|
||||||
$aWord['word'],
|
|
||||||
$aWord['country_code']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// near and in operator the same at the moment
|
|
||||||
$oToken = new Token\SpecialTerm(
|
|
||||||
$iId,
|
|
||||||
$aWord['class'],
|
|
||||||
$aWord['type'],
|
|
||||||
$aWord['operator'] ? Operator::NEAR : Operator::NONE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif ($aWord['country_code']) {
|
|
||||||
$oToken = new Token\Country($iId, $aWord['country_code']);
|
|
||||||
} elseif ($aWord['word_token'][0] == ' ') {
|
|
||||||
$oToken = new Token\Word(
|
|
||||||
$iId,
|
|
||||||
(int) $aWord['count'],
|
|
||||||
substr_count($aWord['word_token'], ' ')
|
|
||||||
);
|
|
||||||
// For backward compatibility: ignore all partial tokens with more
|
|
||||||
// than one word.
|
|
||||||
} elseif (strpos($aWord['word_token'], ' ') === false) {
|
|
||||||
$oToken = new Token\Partial(
|
|
||||||
$iId,
|
|
||||||
$aWord['word_token'],
|
|
||||||
(int) $aWord['count']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($oToken) {
|
|
||||||
// remove any leading spaces
|
|
||||||
if ($aWord['word_token'][0] == ' ') {
|
|
||||||
$oValidTokens->addToken(substr($aWord['word_token'], 1), $oToken);
|
|
||||||
} else {
|
|
||||||
$oValidTokens->addToken($aWord['word_token'], $oToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/init-website.php');
|
|
||||||
require_once(CONST_LibDir.'/ParameterParser.php');
|
|
||||||
|
|
||||||
$oParams = new Nominatim\ParameterParser();
|
|
||||||
|
|
||||||
// Format for output
|
|
||||||
$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2');
|
|
||||||
set_exception_handler_by_format($sOutputFormat);
|
|
||||||
|
|
||||||
throw new Exception('Reverse-only import does not support forward searching.', 404);
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/init-website.php');
|
|
||||||
require_once(CONST_LibDir.'/ParameterParser.php');
|
|
||||||
require_once(CONST_LibDir.'/Status.php');
|
|
||||||
|
|
||||||
$oParams = new Nominatim\ParameterParser();
|
|
||||||
$sOutputFormat = $oParams->getSet('format', array('text', 'json'), 'text');
|
|
||||||
|
|
||||||
$oDB = new Nominatim\DB(CONST_Database_DSN);
|
|
||||||
|
|
||||||
if ($sOutputFormat == 'json') {
|
|
||||||
header('content-type: application/json; charset=UTF-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
$oStatus = new Nominatim\Status($oDB);
|
|
||||||
$oStatus->status();
|
|
||||||
|
|
||||||
if ($sOutputFormat == 'json') {
|
|
||||||
$epoch = $oStatus->dataDate();
|
|
||||||
$aResponse = array(
|
|
||||||
'status' => 0,
|
|
||||||
'message' => 'OK',
|
|
||||||
'data_updated' => (new DateTime('@'.$epoch))->format(DateTime::RFC3339),
|
|
||||||
'software_version' => CONST_NominatimVersion
|
|
||||||
);
|
|
||||||
$sDatabaseVersion = $oStatus->databaseVersion();
|
|
||||||
if ($sDatabaseVersion) {
|
|
||||||
$aResponse['database_version'] = $sDatabaseVersion;
|
|
||||||
}
|
|
||||||
javascript_renderData($aResponse);
|
|
||||||
} else {
|
|
||||||
echo 'OK';
|
|
||||||
}
|
|
||||||
} catch (Exception $oErr) {
|
|
||||||
if ($sOutputFormat == 'json') {
|
|
||||||
$aResponse = array(
|
|
||||||
'status' => $oErr->getCode(),
|
|
||||||
'message' => $oErr->getMessage()
|
|
||||||
);
|
|
||||||
javascript_renderData($aResponse);
|
|
||||||
} else {
|
|
||||||
header('HTTP/1.0 500 Internal Server Error');
|
|
||||||
echo 'ERROR: '.$oErr->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{% include('functions/utils.sql') %}
|
|
||||||
{% include('functions/ranking.sql') %}
|
|
||||||
{% include('functions/importance.sql') %}
|
|
||||||
{% include('functions/address_lookup.sql') %}
|
|
||||||
{% include('functions/interpolation.sql') %}
|
|
||||||
|
|
||||||
{% if 'place' in db.tables %}
|
|
||||||
{% include 'functions/place_triggers.sql' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if 'placex' in db.tables %}
|
|
||||||
{% include 'functions/placex_triggers.sql' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if 'location_postcode' in db.tables %}
|
|
||||||
{% include 'functions/postcode_triggers.sql' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include('functions/partition-functions.sql') %}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
-- Indices used only during search and update.
|
|
||||||
-- These indices are created only after the indexing process is done.
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_place_addressline_address_place_id
|
|
||||||
ON place_addressline USING BTREE (address_place_id) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_rank_search
|
|
||||||
ON placex USING BTREE (rank_search) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_rank_address
|
|
||||||
ON placex USING BTREE (rank_address) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_parent_place_id
|
|
||||||
ON placex USING BTREE (parent_place_id) {{db.tablespace.search_index}}
|
|
||||||
WHERE parent_place_id IS NOT NULL;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_geometry_reverse_lookupPolygon
|
|
||||||
ON placex USING gist (geometry) {{db.tablespace.search_index}}
|
|
||||||
WHERE St_GeometryType(geometry) in ('ST_Polygon', 'ST_MultiPolygon')
|
|
||||||
AND rank_address between 4 and 25 AND type != 'postcode'
|
|
||||||
AND name is not null AND indexed_status = 0 AND linked_place_id is null;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_osmline_parent_place_id
|
|
||||||
ON location_property_osmline USING BTREE (parent_place_id) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_osmline_parent_osm_id
|
|
||||||
ON location_property_osmline USING BTREE (osm_id) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_postcode_postcode
|
|
||||||
ON location_postcode USING BTREE (postcode) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
-- Indices only needed for updating.
|
|
||||||
|
|
||||||
{% if not drop %}
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_pendingsector
|
|
||||||
ON placex USING BTREE (rank_address,geometry_sector) {{db.tablespace.address_index}}
|
|
||||||
WHERE indexed_status > 0;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_location_area_country_place_id
|
|
||||||
ON location_area_country USING BTREE (place_id) {{db.tablespace.address_index}};
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_place_osm_unique
|
|
||||||
ON place USING btree(osm_id, osm_type, class, type) {{db.tablespace.address_index}};
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
-- Indices only needed for search.
|
|
||||||
|
|
||||||
{% if 'search_name' in db.tables %}
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_search_name_nameaddress_vector
|
|
||||||
ON search_name USING GIN (nameaddress_vector) WITH (fastupdate = off) {{db.tablespace.search_index}};
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_search_name_name_vector
|
|
||||||
ON search_name USING GIN (name_vector) WITH (fastupdate = off) {{db.tablespace.search_index}};
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_search_name_centroid
|
|
||||||
ON search_name USING GIST (centroid) {{db.tablespace.search_index}};
|
|
||||||
|
|
||||||
{% if postgres.has_index_non_key_column %}
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_placex_housenumber
|
|
||||||
ON placex USING btree (parent_place_id) INCLUDE (housenumber) WHERE housenumber is not null;
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_osmline_parent_osm_id_with_hnr
|
|
||||||
ON location_property_osmline USING btree(parent_place_id) INCLUDE (startnumber, endnumber);
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
drop table IF EXISTS search_name_blank CASCADE;
|
|
||||||
CREATE TABLE search_name_blank (
|
|
||||||
place_id BIGINT,
|
|
||||||
address_rank smallint,
|
|
||||||
name_vector integer[],
|
|
||||||
centroid GEOMETRY(Geometry, 4326)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
{% for partition in db.partitions %}
|
|
||||||
CREATE TABLE location_area_large_{{ partition }} () INHERITS (location_area_large) {{db.tablespace.address_data}};
|
|
||||||
CREATE INDEX idx_location_area_large_{{ partition }}_place_id ON location_area_large_{{ partition }} USING BTREE (place_id) {{db.tablespace.address_index}};
|
|
||||||
CREATE INDEX idx_location_area_large_{{ partition }}_geometry ON location_area_large_{{ partition }} USING GIST (geometry) {{db.tablespace.address_index}};
|
|
||||||
|
|
||||||
CREATE TABLE search_name_{{ partition }} () INHERITS (search_name_blank) {{db.tablespace.address_data}};
|
|
||||||
CREATE INDEX idx_search_name_{{ partition }}_place_id ON search_name_{{ partition }} USING BTREE (place_id) {{db.tablespace.address_index}};
|
|
||||||
CREATE INDEX idx_search_name_{{ partition }}_centroid_street ON search_name_{{ partition }} USING GIST (centroid) {{db.tablespace.address_index}} where address_rank between 26 and 27;
|
|
||||||
CREATE INDEX idx_search_name_{{ partition }}_centroid_place ON search_name_{{ partition }} USING GIST (centroid) {{db.tablespace.address_index}} where address_rank between 2 and 25;
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS location_road_{{ partition }};
|
|
||||||
CREATE TABLE location_road_{{ partition }} (
|
|
||||||
place_id BIGINT,
|
|
||||||
partition SMALLINT,
|
|
||||||
country_code VARCHAR(2),
|
|
||||||
geometry GEOMETRY(Geometry, 4326)
|
|
||||||
) {{db.tablespace.address_data}};
|
|
||||||
CREATE INDEX idx_location_road_{{ partition }}_geometry ON location_road_{{ partition }} USING GIST (geometry) {{db.tablespace.address_index}};
|
|
||||||
CREATE INDEX idx_location_road_{{ partition }}_place_id ON location_road_{{ partition }} USING BTREE (place_id) {{db.tablespace.address_index}};
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS gb_postcode;
|
|
||||||
CREATE TABLE gb_postcode (
|
|
||||||
id integer,
|
|
||||||
postcode character varying(9),
|
|
||||||
geometry geometry,
|
|
||||||
CONSTRAINT enforce_dims_geometry CHECK ((st_ndims(geometry) = 2)),
|
|
||||||
CONSTRAINT enforce_srid_geometry CHECK ((st_srid(geometry) = 4326))
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS us_postcode;
|
|
||||||
CREATE TABLE us_postcode (
|
|
||||||
postcode text,
|
|
||||||
x double precision,
|
|
||||||
y double precision
|
|
||||||
);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
--index only on parent_place_id
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_location_property_tiger_parent_place_id_imp
|
|
||||||
ON location_property_tiger_import (parent_place_id) {{db.tablespace.aux_index}};
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_location_property_tiger_place_id_imp
|
|
||||||
ON location_property_tiger_import (place_id) {{db.tablespace.aux_index}};
|
|
||||||
|
|
||||||
GRANT SELECT ON location_property_tiger_import TO "{{config.DATABASE_WEBUSER}}";
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS location_property_tiger;
|
|
||||||
ALTER TABLE location_property_tiger_import RENAME TO location_property_tiger;
|
|
||||||
|
|
||||||
ALTER INDEX IF EXISTS idx_location_property_tiger_parent_place_id_imp RENAME TO idx_location_property_tiger_housenumber_parent_place_id;
|
|
||||||
ALTER INDEX IF EXISTS idx_location_property_tiger_place_id_imp RENAME TO idx_location_property_tiger_place_id;
|
|
||||||
|
|
||||||
DROP FUNCTION tiger_line_import (linegeo GEOMETRY, in_startnumber INTEGER,
|
|
||||||
in_endnumber INTEGER, interpolationtype TEXT,
|
|
||||||
token_info JSONB, in_postcode TEXT);
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
-- Get tokens used for searching the given place.
|
|
||||||
--
|
|
||||||
-- These are the tokens that will be saved in the search_name table.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_name_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'names')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Get tokens for matching the place name against others.
|
|
||||||
--
|
|
||||||
-- This should usually be restricted to full name tokens.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_name_match_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'names')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return the housenumber tokens applicable for the place.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_housenumber_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'hnr_tokens')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return the housenumber in the form that it can be matched during search.
|
|
||||||
CREATE OR REPLACE FUNCTION token_normalized_housenumber(info JSONB)
|
|
||||||
RETURNS TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'hnr';
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_has_addr_street(info JSONB)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'street' is not null;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_has_addr_place(info JSONB)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'place' is not null;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'street')::INTEGER[] <@ street_tokens
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'place')::INTEGER[] <@ place_tokens
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_addr_place_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'place')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_address_keys(info JSONB)
|
|
||||||
RETURNS SETOF TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT * FROM jsonb_object_keys(info->'addr');
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_address_search_tokens(info JSONB, key TEXT)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->'addr'->>key)::INTEGER[];
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->'addr'->>key)::INTEGER[] <@ tokens;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_normalized_postcode(postcode TEXT)
|
|
||||||
RETURNS TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT CASE WHEN postcode SIMILAR TO '%(,|;)%' THEN NULL ELSE upper(trim(postcode))END;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return token info that should be saved permanently in the database.
|
|
||||||
CREATE OR REPLACE FUNCTION token_strip_info(info JSONB)
|
|
||||||
RETURNS JSONB
|
|
||||||
AS $$
|
|
||||||
SELECT NULL::JSONB;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
--------------- private functions ----------------------------------------------
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_full_word(norm_term TEXT, lookup_terms TEXT[],
|
|
||||||
OUT full_token INT,
|
|
||||||
OUT partial_tokens INT[])
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
partial_terms TEXT[] = '{}'::TEXT[];
|
|
||||||
term TEXT;
|
|
||||||
term_id INTEGER;
|
|
||||||
term_count INTEGER;
|
|
||||||
BEGIN
|
|
||||||
SELECT min(word_id) INTO full_token
|
|
||||||
FROM word WHERE word = norm_term and type = 'W';
|
|
||||||
|
|
||||||
IF full_token IS NULL THEN
|
|
||||||
full_token := nextval('seq_word');
|
|
||||||
INSERT INTO word (word_id, word_token, type, word, info)
|
|
||||||
SELECT full_token, lookup_term, 'W', norm_term,
|
|
||||||
json_build_object('count', 0)
|
|
||||||
FROM unnest(lookup_terms) as lookup_term;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
FOR term IN SELECT unnest(string_to_array(unnest(lookup_terms), ' ')) LOOP
|
|
||||||
term := trim(term);
|
|
||||||
IF NOT (ARRAY[term] <@ partial_terms) THEN
|
|
||||||
partial_terms := partial_terms || term;
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
|
|
||||||
partial_tokens := '{}'::INT[];
|
|
||||||
FOR term IN SELECT unnest(partial_terms) LOOP
|
|
||||||
SELECT min(word_id), max(info->>'count') INTO term_id, term_count
|
|
||||||
FROM word WHERE word_token = term and type = 'w';
|
|
||||||
|
|
||||||
IF term_id IS NULL THEN
|
|
||||||
term_id := nextval('seq_word');
|
|
||||||
term_count := 0;
|
|
||||||
INSERT INTO word (word_id, word_token, type, info)
|
|
||||||
VALUES (term_id, term, 'w', json_build_object('count', term_count));
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
partial_tokens := array_merge(partial_tokens, ARRAY[term_id]);
|
|
||||||
END LOOP;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_partial_word(partial TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
token INTEGER;
|
|
||||||
BEGIN
|
|
||||||
SELECT min(word_id) INTO token
|
|
||||||
FROM word WHERE word_token = partial and type = 'w';
|
|
||||||
|
|
||||||
IF token IS NULL THEN
|
|
||||||
token := nextval('seq_word');
|
|
||||||
INSERT INTO word (word_id, word_token, type, info)
|
|
||||||
VALUES (token, partial, 'w', json_build_object('count', 0));
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN token;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_hnr_id(lookup_term TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
return_id INTEGER;
|
|
||||||
BEGIN
|
|
||||||
SELECT min(word_id) INTO return_id FROM word
|
|
||||||
WHERE word_token = lookup_term and type = 'H';
|
|
||||||
|
|
||||||
IF return_id IS NULL THEN
|
|
||||||
return_id := nextval('seq_word');
|
|
||||||
INSERT INTO word (word_id, word_token, type)
|
|
||||||
VALUES (return_id, lookup_term, 'H');
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN return_id;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS word;
|
|
||||||
CREATE TABLE word (
|
|
||||||
word_id INTEGER,
|
|
||||||
word_token text NOT NULL,
|
|
||||||
type text NOT NULL,
|
|
||||||
word text,
|
|
||||||
info jsonb
|
|
||||||
) {{db.tablespace.search_data}};
|
|
||||||
|
|
||||||
CREATE INDEX idx_word_word_token ON word
|
|
||||||
USING BTREE (word_token) {{db.tablespace.search_index}};
|
|
||||||
-- Used when updating country names from the boundary relation.
|
|
||||||
CREATE INDEX idx_word_country_names ON word
|
|
||||||
USING btree(word) {{db.tablespace.address_index}}
|
|
||||||
WHERE type = 'C';
|
|
||||||
-- Used when inserting new postcodes on updates.
|
|
||||||
CREATE INDEX idx_word_postcodes ON word
|
|
||||||
USING btree(word) {{db.tablespace.address_index}}
|
|
||||||
WHERE type = 'P';
|
|
||||||
-- Used when inserting full words.
|
|
||||||
CREATE INDEX idx_word_full_word ON word
|
|
||||||
USING btree(word) {{db.tablespace.address_index}}
|
|
||||||
WHERE type = 'W';
|
|
||||||
|
|
||||||
GRANT SELECT ON word TO "{{config.DATABASE_WEBUSER}}";
|
|
||||||
|
|
||||||
DROP SEQUENCE IF EXISTS seq_word;
|
|
||||||
CREATE SEQUENCE seq_word start 1;
|
|
||||||
GRANT SELECT ON seq_word to "{{config.DATABASE_WEBUSER}}";
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
-- Get tokens used for searching the given place.
|
|
||||||
--
|
|
||||||
-- These are the tokens that will be saved in the search_name table.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_name_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'names')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Get tokens for matching the place name against others.
|
|
||||||
--
|
|
||||||
-- This should usually be restricted to full name tokens.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_name_match_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'names')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return the housenumber tokens applicable for the place.
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_housenumber_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'hnr_tokens')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return the housenumber in the form that it can be matched during search.
|
|
||||||
CREATE OR REPLACE FUNCTION token_normalized_housenumber(info JSONB)
|
|
||||||
RETURNS TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'hnr';
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_has_addr_street(info JSONB)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'street' is not null;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_has_addr_place(info JSONB)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT info->>'place_match' is not null;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'street')::INTEGER[] && street_tokens
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'place_match')::INTEGER[] && place_tokens
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_addr_place_search_tokens(info JSONB)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->>'place_search')::INTEGER[]
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_address_keys(info JSONB)
|
|
||||||
RETURNS SETOF TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT * FROM jsonb_object_keys(info->'addr');
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_get_address_search_tokens(info JSONB, key TEXT)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
SELECT (info->'addr'->key->>0)::INTEGER[];
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
SELECT (info->'addr'->key->>1)::INTEGER[] && tokens;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION token_normalized_postcode(postcode TEXT)
|
|
||||||
RETURNS TEXT
|
|
||||||
AS $$
|
|
||||||
SELECT CASE WHEN postcode SIMILAR TO '%(,|;)%' THEN NULL ELSE upper(trim(postcode))END;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
-- Return token info that should be saved permanently in the database.
|
|
||||||
CREATE OR REPLACE FUNCTION token_strip_info(info JSONB)
|
|
||||||
RETURNS JSONB
|
|
||||||
AS $$
|
|
||||||
SELECT NULL::JSONB;
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
--------------- private functions ----------------------------------------------
|
|
||||||
|
|
||||||
-- Functions for term normalisation and access to the 'word' table.
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
|
|
||||||
AS '{{ modulepath }}/nominatim.so', 'transliteration'
|
|
||||||
LANGUAGE c IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
|
|
||||||
AS '{{ modulepath }}/nominatim.so', 'gettokenstring'
|
|
||||||
LANGUAGE c IMMUTABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
o TEXT;
|
|
||||||
BEGIN
|
|
||||||
o := public.gettokenstring(public.transliteration(name));
|
|
||||||
RETURN trim(substr(o,1,length(o)));
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql IMMUTABLE;
|
|
||||||
|
|
||||||
-- returns NULL if the word is too common
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
lookup_token TEXT;
|
|
||||||
return_word_id INTEGER;
|
|
||||||
count INTEGER;
|
|
||||||
BEGIN
|
|
||||||
lookup_token := trim(lookup_word);
|
|
||||||
SELECT min(word_id), max(search_name_count) FROM word
|
|
||||||
WHERE word_token = lookup_token and class is null and type is null
|
|
||||||
INTO return_word_id, count;
|
|
||||||
IF return_word_id IS NULL THEN
|
|
||||||
return_word_id := nextval('seq_word');
|
|
||||||
INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0);
|
|
||||||
ELSE
|
|
||||||
IF count > {{ max_word_freq }} THEN
|
|
||||||
return_word_id := NULL;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
RETURN return_word_id;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
-- Create housenumber tokens from an OSM addr:housenumber.
|
|
||||||
-- The housnumber is split at comma and semicolon as necessary.
|
|
||||||
-- The function returns the normalized form of the housenumber suitable
|
|
||||||
-- for comparison.
|
|
||||||
CREATE OR REPLACE FUNCTION create_housenumbers(housenumbers TEXT[],
|
|
||||||
OUT tokens TEXT,
|
|
||||||
OUT normtext TEXT)
|
|
||||||
AS $$
|
|
||||||
BEGIN
|
|
||||||
SELECT array_to_string(array_agg(trans), ';'), array_agg(tid)::TEXT
|
|
||||||
INTO normtext, tokens
|
|
||||||
FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word) as tid
|
|
||||||
FROM (SELECT make_standard_name(h) as lookup_word
|
|
||||||
FROM unnest(housenumbers) h) x) y;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql STABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
lookup_token TEXT;
|
|
||||||
return_word_id INTEGER;
|
|
||||||
BEGIN
|
|
||||||
lookup_token := ' ' || trim(lookup_word);
|
|
||||||
SELECT min(word_id) FROM word
|
|
||||||
WHERE word_token = lookup_token and class='place' and type='house'
|
|
||||||
INTO return_word_id;
|
|
||||||
IF return_word_id IS NULL THEN
|
|
||||||
return_word_id := nextval('seq_word');
|
|
||||||
INSERT INTO word VALUES (return_word_id, lookup_token, null,
|
|
||||||
'place', 'house', null, 0);
|
|
||||||
END IF;
|
|
||||||
RETURN return_word_id;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION create_postcode_id(postcode TEXT)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
r RECORD;
|
|
||||||
lookup_token TEXT;
|
|
||||||
return_word_id INTEGER;
|
|
||||||
BEGIN
|
|
||||||
lookup_token := ' ' || make_standard_name(postcode);
|
|
||||||
FOR r IN
|
|
||||||
SELECT word_id FROM word
|
|
||||||
WHERE word_token = lookup_token and word = postcode
|
|
||||||
and class='place' and type='postcode'
|
|
||||||
LOOP
|
|
||||||
RETURN false;
|
|
||||||
END LOOP;
|
|
||||||
|
|
||||||
INSERT INTO word VALUES (nextval('seq_word'), lookup_token, postcode,
|
|
||||||
'place', 'postcode', null, 0);
|
|
||||||
RETURN true;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
lookup_token TEXT;
|
|
||||||
nospace_lookup_token TEXT;
|
|
||||||
return_word_id INTEGER;
|
|
||||||
BEGIN
|
|
||||||
lookup_token := ' '||trim(lookup_word);
|
|
||||||
SELECT min(word_id) FROM word
|
|
||||||
WHERE word_token = lookup_token and class is null and type is null
|
|
||||||
INTO return_word_id;
|
|
||||||
IF return_word_id IS NULL THEN
|
|
||||||
return_word_id := nextval('seq_word');
|
|
||||||
INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
|
|
||||||
null, null, null, 0);
|
|
||||||
END IF;
|
|
||||||
RETURN return_word_id;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
-- Normalize a string and lookup its word ids (partial words).
|
|
||||||
CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
words TEXT[];
|
|
||||||
id INTEGER;
|
|
||||||
return_word_id INTEGER[];
|
|
||||||
word_ids INTEGER[];
|
|
||||||
j INTEGER;
|
|
||||||
BEGIN
|
|
||||||
words := string_to_array(make_standard_name(lookup_word), ' ');
|
|
||||||
IF array_upper(words, 1) IS NOT NULL THEN
|
|
||||||
FOR j IN 1..array_upper(words, 1) LOOP
|
|
||||||
IF (words[j] != '') THEN
|
|
||||||
SELECT array_agg(word_id) INTO word_ids
|
|
||||||
FROM word
|
|
||||||
WHERE word_token = words[j] and class is null and type is null;
|
|
||||||
|
|
||||||
IF word_ids IS NULL THEN
|
|
||||||
id := nextval('seq_word');
|
|
||||||
INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
|
|
||||||
return_word_id := return_word_id || id;
|
|
||||||
ELSE
|
|
||||||
return_word_id := array_merge(return_word_id, word_ids);
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN return_word_id;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
-- Normalize a string and look up its name ids (full words).
|
|
||||||
CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
lookup_token TEXT;
|
|
||||||
return_word_ids INTEGER[];
|
|
||||||
BEGIN
|
|
||||||
lookup_token := ' '|| make_standard_name(lookup_word);
|
|
||||||
SELECT array_agg(word_id) FROM word
|
|
||||||
WHERE word_token = lookup_token and class is null and type is null
|
|
||||||
INTO return_word_ids;
|
|
||||||
RETURN return_word_ids;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql STABLE STRICT;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
|
|
||||||
RETURNS INTEGER[]
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
result INTEGER[];
|
|
||||||
s TEXT;
|
|
||||||
w INTEGER;
|
|
||||||
words TEXT[];
|
|
||||||
value TEXT;
|
|
||||||
j INTEGER;
|
|
||||||
BEGIN
|
|
||||||
result := '{}'::INTEGER[];
|
|
||||||
|
|
||||||
FOR value IN SELECT unnest(regexp_split_to_array(svals(src), E'[,;]')) LOOP
|
|
||||||
-- full name
|
|
||||||
s := make_standard_name(value);
|
|
||||||
w := getorcreate_name_id(s, value);
|
|
||||||
|
|
||||||
IF not(ARRAY[w] <@ result) THEN
|
|
||||||
result := result || w;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- partial single-word terms
|
|
||||||
words := string_to_array(s, ' ');
|
|
||||||
IF array_upper(words, 1) IS NOT NULL THEN
|
|
||||||
FOR j IN 1..array_upper(words, 1) LOOP
|
|
||||||
IF (words[j] != '') THEN
|
|
||||||
w = getorcreate_word_id(words[j]);
|
|
||||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
|
||||||
result := result || w;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- consider parts before an opening braket 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]);
|
|
||||||
IF s != '' THEN
|
|
||||||
w := getorcreate_name_id(s, words[1]);
|
|
||||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
|
||||||
result := result || w;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
s := regexp_replace(value, '市$', '');
|
|
||||||
IF s != value THEN
|
|
||||||
s := make_standard_name(s);
|
|
||||||
IF s != '' THEN
|
|
||||||
w := getorcreate_name_id(s, value);
|
|
||||||
IF NOT (ARRAY[w] <@ result) THEN
|
|
||||||
result := result || w;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
END LOOP;
|
|
||||||
|
|
||||||
RETURN result;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION precompute_words(src TEXT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
s TEXT;
|
|
||||||
w INTEGER;
|
|
||||||
words TEXT[];
|
|
||||||
i INTEGER;
|
|
||||||
j INTEGER;
|
|
||||||
BEGIN
|
|
||||||
s := make_standard_name(src);
|
|
||||||
w := getorcreate_name_id(s, src);
|
|
||||||
|
|
||||||
w := getorcreate_word_id(s);
|
|
||||||
|
|
||||||
words := string_to_array(s, ' ');
|
|
||||||
IF array_upper(words, 1) IS NOT NULL THEN
|
|
||||||
FOR j IN 1..array_upper(words, 1) LOOP
|
|
||||||
IF (words[j] != '') THEN
|
|
||||||
w := getorcreate_word_id(words[j]);
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
words := regexp_split_to_array(src, E'[,;()]');
|
|
||||||
IF array_upper(words, 1) != 1 THEN
|
|
||||||
FOR j IN 1..array_upper(words, 1) LOOP
|
|
||||||
s := make_standard_name(words[j]);
|
|
||||||
IF s != '' THEN
|
|
||||||
w := getorcreate_word_id(s);
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
s := regexp_replace(src, '市$', '');
|
|
||||||
IF s != src THEN
|
|
||||||
s := make_standard_name(s);
|
|
||||||
IF s != '' THEN
|
|
||||||
w := getorcreate_name_id(s, src);
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN 1;
|
|
||||||
END;
|
|
||||||
$$
|
|
||||||
LANGUAGE plpgsql;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- Required for details lookup.
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_word_word_id
|
|
||||||
ON word USING BTREE (word_id) {{db.tablespace.search_index}};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS word;
|
|
||||||
CREATE TABLE word (
|
|
||||||
word_id INTEGER,
|
|
||||||
word_token text NOT NULL,
|
|
||||||
word text,
|
|
||||||
class text,
|
|
||||||
type text,
|
|
||||||
country_code varchar(2),
|
|
||||||
search_name_count INTEGER,
|
|
||||||
operator TEXT
|
|
||||||
) {{db.tablespace.search_data}};
|
|
||||||
|
|
||||||
CREATE INDEX idx_word_word_token ON word
|
|
||||||
USING BTREE (word_token) {{db.tablespace.search_index}};
|
|
||||||
CREATE INDEX idx_word_word ON word
|
|
||||||
USING BTREE (word) {{db.tablespace.search_index}} WHERE word is not null;
|
|
||||||
GRANT SELECT ON word TO "{{config.DATABASE_WEBUSER}}";
|
|
||||||
|
|
||||||
DROP SEQUENCE IF EXISTS seq_word;
|
|
||||||
CREATE SEQUENCE seq_word start 1;
|
|
||||||
GRANT SELECT ON seq_word to "{{config.DATABASE_WEBUSER}}";
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Nominatim;
|
namespace Nominatim;
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/ClassTypes.php');
|
require_once(CONST_BasePath.'/lib/ClassTypes.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detailed list of address parts for a single result
|
* Detailed list of address parts for a single result
|
||||||
@@ -61,7 +61,7 @@ class AddressDetails
|
|||||||
return join(', ', $aParts);
|
return join(', ', $aParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAddressNames()
|
public function getAddressNames($sCountry = null)
|
||||||
{
|
{
|
||||||
$aAddress = array();
|
$aAddress = array();
|
||||||
|
|
||||||
@@ -79,11 +79,13 @@ class AddressDetails
|
|||||||
$sName = $aLine['housenumber'];
|
$sName = $aLine['housenumber'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($sName)
|
if (isset($sName)) {
|
||||||
&& (!isset($aAddress[$sTypeLabel])
|
$sTypeLabel = strtolower(str_replace(' ', '_', $sTypeLabel));
|
||||||
|| $aLine['class'] == 'place')
|
if (!isset($aAddress[$sTypeLabel])
|
||||||
) {
|
|| $aLine['class'] == 'place'
|
||||||
$aAddress[$sTypeLabel] = $sName;
|
) {
|
||||||
|
$aAddress[$sTypeLabel] = $sName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Nominatim;
|
namespace Nominatim;
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/DatabaseError.php');
|
require_once(CONST_BasePath.'/lib/DatabaseError.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses PDO to access the database specified in the CONST_Database_DSN
|
* Uses PDO to access the database specified in the CONST_Database_DSN
|
||||||
@@ -12,9 +12,9 @@ class DB
|
|||||||
{
|
{
|
||||||
protected $connection;
|
protected $connection;
|
||||||
|
|
||||||
public function __construct($sDSN = null)
|
public function __construct($sDSN = CONST_Database_DSN)
|
||||||
{
|
{
|
||||||
$this->sDSN = $sDSN ?? getSetting('DATABASE_DSN');
|
$this->sDSN = $sDSN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect($bNew = false, $bPersistent = true)
|
public function connect($bNew = false, $bPersistent = true)
|
||||||
@@ -39,9 +39,7 @@ class DB
|
|||||||
$conn->exec("SET DateStyle TO 'sql,european'");
|
$conn->exec("SET DateStyle TO 'sql,european'");
|
||||||
$conn->exec("SET client_encoding TO 'utf-8'");
|
$conn->exec("SET client_encoding TO 'utf-8'");
|
||||||
$iMaxExecution = ini_get('max_execution_time');
|
$iMaxExecution = ini_get('max_execution_time');
|
||||||
if ($iMaxExecution > 0) {
|
if ($iMaxExecution > 0) $conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
|
||||||
$conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->connection = $conn;
|
$this->connection = $conn;
|
||||||
return true;
|
return true;
|
||||||
@@ -97,9 +95,7 @@ class DB
|
|||||||
try {
|
try {
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||||
$row = $stmt->fetch(\PDO::FETCH_NUM);
|
$row = $stmt->fetch(\PDO::FETCH_NUM);
|
||||||
if ($row === false) {
|
if ($row === false) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||||
}
|
}
|
||||||
@@ -244,6 +240,16 @@ class DB
|
|||||||
return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
|
return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of table names in the database
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
public function getListOfTables()
|
||||||
|
{
|
||||||
|
return $this->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a table. Returns true if deleted or didn't exist.
|
* Deletes a table. Returns true if deleted or didn't exist.
|
||||||
*
|
*
|
||||||
@@ -256,6 +262,76 @@ class DB
|
|||||||
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
|
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an index exists in the database. Optional filtered by tablename
|
||||||
|
*
|
||||||
|
* @param string $sTableName
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function indexExists($sIndexName, $sTableName = null)
|
||||||
|
{
|
||||||
|
return in_array($sIndexName, $this->getListOfIndices($sTableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of index names in the database, optional filtered by tablename
|
||||||
|
*
|
||||||
|
* @param string $sTableName
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getListOfIndices($sTableName = null)
|
||||||
|
{
|
||||||
|
// table_name | index_name | column_name
|
||||||
|
// -----------------------+---------------------------------+--------------
|
||||||
|
// country_name | idx_country_name_country_code | country_code
|
||||||
|
// country_osm_grid | idx_country_osm_grid_geometry | geometry
|
||||||
|
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_id
|
||||||
|
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_type
|
||||||
|
// import_polygon_error | idx_import_polygon_error_osmid | osm_id
|
||||||
|
// import_polygon_error | idx_import_polygon_error_osmid | osm_type
|
||||||
|
$sSql = <<< END
|
||||||
|
SELECT
|
||||||
|
t.relname as table_name,
|
||||||
|
i.relname as index_name,
|
||||||
|
a.attname as column_name
|
||||||
|
FROM
|
||||||
|
pg_class t,
|
||||||
|
pg_class i,
|
||||||
|
pg_index ix,
|
||||||
|
pg_attribute a
|
||||||
|
WHERE
|
||||||
|
t.oid = ix.indrelid
|
||||||
|
and i.oid = ix.indexrelid
|
||||||
|
and a.attrelid = t.oid
|
||||||
|
and a.attnum = ANY(ix.indkey)
|
||||||
|
and t.relkind = 'r'
|
||||||
|
and i.relname NOT LIKE 'pg_%'
|
||||||
|
FILTERS
|
||||||
|
ORDER BY
|
||||||
|
t.relname,
|
||||||
|
i.relname,
|
||||||
|
a.attname
|
||||||
|
END;
|
||||||
|
|
||||||
|
$aRows = null;
|
||||||
|
if ($sTableName) {
|
||||||
|
$sSql = str_replace('FILTERS', 'and t.relname = :tablename', $sSql);
|
||||||
|
$aRows = $this->getAll($sSql, array(':tablename' => $sTableName));
|
||||||
|
} else {
|
||||||
|
$sSql = str_replace('FILTERS', '', $sSql);
|
||||||
|
$aRows = $this->getAll($sSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aIndexNames = array_unique(array_map(function ($aRow) {
|
||||||
|
return $aRow['index_name'];
|
||||||
|
}, $aRows));
|
||||||
|
sort($aIndexNames);
|
||||||
|
|
||||||
|
return $aIndexNames;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to connect to the database but on failure doesn't throw an exception.
|
* Tries to connect to the database but on failure doesn't throw an exception.
|
||||||
*
|
*
|
||||||
@@ -310,13 +386,9 @@ class DB
|
|||||||
if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
|
if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
|
||||||
foreach (explode(';', $aMatches[1]) as $sKeyVal) {
|
foreach (explode(';', $aMatches[1]) as $sKeyVal) {
|
||||||
list($sKey, $sVal) = explode('=', $sKeyVal, 2);
|
list($sKey, $sVal) = explode('=', $sKeyVal, 2);
|
||||||
if ($sKey == 'host') {
|
if ($sKey == 'host') $sKey = 'hostspec';
|
||||||
$sKey = 'hostspec';
|
if ($sKey == 'dbname') $sKey = 'database';
|
||||||
} elseif ($sKey == 'dbname') {
|
if ($sKey == 'user') $sKey = 'username';
|
||||||
$sKey = 'database';
|
|
||||||
} elseif ($sKey == 'user') {
|
|
||||||
$sKey = 'username';
|
|
||||||
}
|
|
||||||
$aInfo[$sKey] = $sVal;
|
$aInfo[$sKey] = $sVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace Nominatim;
|
|||||||
class DatabaseError extends \Exception
|
class DatabaseError extends \Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct($message, $code, $previous, $oPDOErr, $sSql = null)
|
public function __construct($message, $code = 500, Exception $previous = null, $oPDOErr, $sSql = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
// https://secure.php.net/manual/en/class.pdoexception.php
|
// https://secure.php.net/manual/en/class.pdoexception.php
|
||||||
@@ -78,7 +78,7 @@ class Debug
|
|||||||
echo '<th>Address Tokens</th><th>Address Not</th>';
|
echo '<th>Address Tokens</th><th>Address Not</th>';
|
||||||
echo '<th>country</th><th>operator</th>';
|
echo '<th>country</th><th>operator</th>';
|
||||||
echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
|
echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
|
||||||
foreach ($aSearches as $aRankedSet) {
|
foreach ($aSearches as $iRank => $aRankedSet) {
|
||||||
foreach ($aRankedSet as $aRow) {
|
foreach ($aRankedSet as $aRow) {
|
||||||
$aRow->dumpAsHtmlTableRow($aWordsIDs);
|
$aRow->dumpAsHtmlTableRow($aWordsIDs);
|
||||||
}
|
}
|
||||||
@@ -2,25 +2,23 @@
|
|||||||
|
|
||||||
namespace Nominatim;
|
namespace Nominatim;
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/PlaceLookup.php');
|
require_once(CONST_BasePath.'/lib/PlaceLookup.php');
|
||||||
require_once(CONST_LibDir.'/Phrase.php');
|
require_once(CONST_BasePath.'/lib/Phrase.php');
|
||||||
require_once(CONST_LibDir.'/ReverseGeocode.php');
|
require_once(CONST_BasePath.'/lib/ReverseGeocode.php');
|
||||||
require_once(CONST_LibDir.'/SearchDescription.php');
|
require_once(CONST_BasePath.'/lib/SearchDescription.php');
|
||||||
require_once(CONST_LibDir.'/SearchContext.php');
|
require_once(CONST_BasePath.'/lib/SearchContext.php');
|
||||||
require_once(CONST_LibDir.'/SearchPosition.php');
|
require_once(CONST_BasePath.'/lib/TokenList.php');
|
||||||
require_once(CONST_LibDir.'/TokenList.php');
|
|
||||||
require_once(CONST_TokenizerDir.'/tokenizer.php');
|
|
||||||
|
|
||||||
class Geocode
|
class Geocode
|
||||||
{
|
{
|
||||||
protected $oDB;
|
protected $oDB;
|
||||||
|
|
||||||
protected $oPlaceLookup;
|
protected $oPlaceLookup;
|
||||||
protected $oTokenizer;
|
|
||||||
|
|
||||||
protected $aLangPrefOrder = array();
|
protected $aLangPrefOrder = array();
|
||||||
|
|
||||||
protected $aExcludePlaceIDs = array();
|
protected $aExcludePlaceIDs = array();
|
||||||
|
protected $bReverseInPlan = false;
|
||||||
|
|
||||||
protected $iLimit = 20;
|
protected $iLimit = 20;
|
||||||
protected $iFinalLimit = 10;
|
protected $iFinalLimit = 10;
|
||||||
@@ -44,12 +42,28 @@ class Geocode
|
|||||||
protected $sQuery = false;
|
protected $sQuery = false;
|
||||||
protected $aStructuredQuery = false;
|
protected $aStructuredQuery = false;
|
||||||
|
|
||||||
|
protected $oNormalizer = null;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
public function __construct(&$oDB)
|
||||||
{
|
{
|
||||||
$this->oDB =& $oDB;
|
$this->oDB =& $oDB;
|
||||||
$this->oPlaceLookup = new PlaceLookup($this->oDB);
|
$this->oPlaceLookup = new PlaceLookup($this->oDB);
|
||||||
$this->oTokenizer = new \Nominatim\Tokenizer($this->oDB);
|
$this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normTerm($sTerm)
|
||||||
|
{
|
||||||
|
if ($this->oNormalizer === null) {
|
||||||
|
return $sTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->oNormalizer->transliterate($sTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReverseInPlan($bReverse)
|
||||||
|
{
|
||||||
|
$this->bReverseInPlan = $bReverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setLanguagePreference($aLangPref)
|
public function setLanguagePreference($aLangPref)
|
||||||
@@ -71,9 +85,7 @@ class Geocode
|
|||||||
$aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
|
$aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->bBoundedSearch) {
|
if ($this->bBoundedSearch) $aParams['bounded'] = '1';
|
||||||
$aParams['bounded'] = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->aCountryCodes) {
|
if ($this->aCountryCodes) {
|
||||||
$aParams['countrycodes'] = implode(',', $this->aCountryCodes);
|
$aParams['countrycodes'] = implode(',', $this->aCountryCodes);
|
||||||
@@ -88,11 +100,8 @@ class Geocode
|
|||||||
|
|
||||||
public function setLimit($iLimit = 10)
|
public function setLimit($iLimit = 10)
|
||||||
{
|
{
|
||||||
if ($iLimit > 50) {
|
if ($iLimit > 50) $iLimit = 50;
|
||||||
$iLimit = 50;
|
if ($iLimit < 1) $iLimit = 1;
|
||||||
} elseif ($iLimit < 1) {
|
|
||||||
$iLimit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->iFinalLimit = $iLimit;
|
$this->iFinalLimit = $iLimit;
|
||||||
$this->iLimit = $iLimit + min($iLimit, 10);
|
$this->iLimit = $iLimit + min($iLimit, 10);
|
||||||
@@ -187,24 +196,18 @@ class Geocode
|
|||||||
if ($sExcluded) {
|
if ($sExcluded) {
|
||||||
foreach ($sExcluded as $iExcludedPlaceID) {
|
foreach ($sExcluded as $iExcludedPlaceID) {
|
||||||
$iExcludedPlaceID = (int)$iExcludedPlaceID;
|
$iExcludedPlaceID = (int)$iExcludedPlaceID;
|
||||||
if ($iExcludedPlaceID) {
|
if ($iExcludedPlaceID)
|
||||||
$aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
|
$aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($aExcludePlaceIDs)) {
|
if (isset($aExcludePlaceIDs))
|
||||||
$this->aExcludePlaceIDs = $aExcludePlaceIDs;
|
$this->aExcludePlaceIDs = $aExcludePlaceIDs;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only certain ranks of feature
|
// Only certain ranks of feature
|
||||||
$sFeatureType = $oParams->getString('featureType');
|
$sFeatureType = $oParams->getString('featureType');
|
||||||
if (!$sFeatureType) {
|
if (!$sFeatureType) $sFeatureType = $oParams->getString('featuretype');
|
||||||
$sFeatureType = $oParams->getString('featuretype');
|
if ($sFeatureType) $this->setFeatureType($sFeatureType);
|
||||||
}
|
|
||||||
if ($sFeatureType) {
|
|
||||||
$this->setFeatureType($sFeatureType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Country code list
|
// Country code list
|
||||||
$sCountries = $oParams->getStringList('countrycodes');
|
$sCountries = $oParams->getStringList('countrycodes');
|
||||||
@@ -214,9 +217,8 @@ class Geocode
|
|||||||
$aCountries[] = strtolower($sCountryCode);
|
$aCountries[] = strtolower($sCountryCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isset($aCountries)) {
|
if (isset($aCountries))
|
||||||
$this->aCountryCodes = $aCountries;
|
$this->aCountryCodes = $aCountries;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$aViewbox = $oParams->getStringList('viewboxlbrt');
|
$aViewbox = $oParams->getStringList('viewboxlbrt');
|
||||||
@@ -260,6 +262,7 @@ class Geocode
|
|||||||
$oParams->getString('country'),
|
$oParams->getString('country'),
|
||||||
$oParams->getString('postalcode')
|
$oParams->getString('postalcode')
|
||||||
);
|
);
|
||||||
|
$this->setReverseInPlan(false);
|
||||||
} else {
|
} else {
|
||||||
$this->setQuery($sQuery);
|
$this->setQuery($sQuery);
|
||||||
}
|
}
|
||||||
@@ -268,17 +271,13 @@ class Geocode
|
|||||||
public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
|
public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
|
||||||
{
|
{
|
||||||
$sValue = trim($sValue);
|
$sValue = trim($sValue);
|
||||||
if (!$sValue) {
|
if (!$sValue) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$this->aStructuredQuery[$sKey] = $sValue;
|
$this->aStructuredQuery[$sKey] = $sValue;
|
||||||
if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
|
if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
|
||||||
$this->iMinAddressRank = $iNewMinAddressRank;
|
$this->iMinAddressRank = $iNewMinAddressRank;
|
||||||
$this->iMaxAddressRank = $iNewMaxAddressRank;
|
$this->iMaxAddressRank = $iNewMaxAddressRank;
|
||||||
}
|
}
|
||||||
if ($aItemListValues) {
|
if ($aItemListValues) $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
|
||||||
$this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,11 +311,11 @@ class Geocode
|
|||||||
|
|
||||||
public function fallbackStructuredQuery()
|
public function fallbackStructuredQuery()
|
||||||
{
|
{
|
||||||
|
if (!$this->aStructuredQuery) return false;
|
||||||
|
|
||||||
$aParams = $this->aStructuredQuery;
|
$aParams = $this->aStructuredQuery;
|
||||||
|
|
||||||
if (!$aParams || count($aParams) == 1) {
|
if (count($aParams) == 1) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
|
$aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
|
||||||
|
|
||||||
@@ -331,7 +330,7 @@ class Geocode
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
|
public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens, $bIsStructured)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Calculate all searches using oValidTokens i.e.
|
Calculate all searches using oValidTokens i.e.
|
||||||
@@ -346,26 +345,52 @@ class Geocode
|
|||||||
*/
|
*/
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
||||||
$aNewPhraseSearches = array();
|
$aNewPhraseSearches = array();
|
||||||
$oPosition = new SearchPosition(
|
$sPhraseType = $bIsStructured ? $oPhrase->getPhraseType() : '';
|
||||||
$oPhrase->getPhraseType(),
|
|
||||||
$iPhrase,
|
|
||||||
count($aPhrases)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($oPhrase->getWordSets() as $aWordset) {
|
foreach ($oPhrase->getWordSets() as $aWordset) {
|
||||||
$aWordsetSearches = $aSearches;
|
$aWordsetSearches = $aSearches;
|
||||||
|
|
||||||
// Add all words from this wordset
|
// Add all words from this wordset
|
||||||
foreach ($aWordset as $iToken => $sToken) {
|
foreach ($aWordset as $iToken => $sToken) {
|
||||||
|
//echo "<br><b>$sToken</b>";
|
||||||
$aNewWordsetSearches = array();
|
$aNewWordsetSearches = array();
|
||||||
$oPosition->setTokenPosition($iToken, count($aWordset));
|
|
||||||
|
|
||||||
foreach ($aWordsetSearches as $oCurrentSearch) {
|
foreach ($aWordsetSearches as $oCurrentSearch) {
|
||||||
foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
|
//echo "<i>";
|
||||||
if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) {
|
//var_dump($oCurrentSearch);
|
||||||
$aNewSearches = $oSearchTerm->extendSearch(
|
//echo "</i>";
|
||||||
$oCurrentSearch,
|
|
||||||
$oPosition
|
// Tokens with full name matches.
|
||||||
|
foreach ($oValidTokens->get(' '.$sToken) as $oSearchTerm) {
|
||||||
|
$aNewSearches = $oCurrentSearch->extendWithFullTerm(
|
||||||
|
$oSearchTerm,
|
||||||
|
$oValidTokens->contains($sToken)
|
||||||
|
&& strpos($sToken, ' ') === false,
|
||||||
|
$sPhraseType,
|
||||||
|
$iToken == 0 && $iPhrase == 0,
|
||||||
|
$iPhrase == 0,
|
||||||
|
$iToken + 1 == count($aWordset)
|
||||||
|
&& $iPhrase + 1 == count($aPhrases)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($aNewSearches as $oSearch) {
|
||||||
|
if ($oSearch->getRank() < $this->iMaxRank) {
|
||||||
|
$aNewWordsetSearches[] = $oSearch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Look for partial matches.
|
||||||
|
// Note that there is no point in adding country terms here
|
||||||
|
// because country is omitted in the address.
|
||||||
|
if ($sPhraseType != 'country') {
|
||||||
|
// Allow searching for a word - but at extra cost
|
||||||
|
foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
|
||||||
|
$aNewSearches = $oCurrentSearch->extendWithPartialTerm(
|
||||||
|
$sToken,
|
||||||
|
$oSearchTerm,
|
||||||
|
$bIsStructured,
|
||||||
|
$iPhrase,
|
||||||
|
$oValidTokens->get(' '.$sToken)
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($aNewSearches as $oSearch) {
|
foreach ($aNewSearches as $oSearch) {
|
||||||
@@ -380,6 +405,7 @@ class Geocode
|
|||||||
usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
||||||
$aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
|
$aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
|
||||||
}
|
}
|
||||||
|
//var_Dump('<hr>',count($aWordsetSearches)); exit;
|
||||||
|
|
||||||
$aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
|
$aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
|
||||||
usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
||||||
@@ -387,11 +413,8 @@ class Geocode
|
|||||||
$aSearchHash = array();
|
$aSearchHash = array();
|
||||||
foreach ($aNewPhraseSearches as $iSearch => $aSearch) {
|
foreach ($aNewPhraseSearches as $iSearch => $aSearch) {
|
||||||
$sHash = serialize($aSearch);
|
$sHash = serialize($aSearch);
|
||||||
if (isset($aSearchHash[$sHash])) {
|
if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]);
|
||||||
unset($aNewPhraseSearches[$iSearch]);
|
else $aSearchHash[$sHash] = 1;
|
||||||
} else {
|
|
||||||
$aSearchHash[$sHash] = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
|
$aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
|
||||||
@@ -412,12 +435,10 @@ class Geocode
|
|||||||
|
|
||||||
$iSearchCount = 0;
|
$iSearchCount = 0;
|
||||||
$aSearches = array();
|
$aSearches = array();
|
||||||
foreach ($aGroupedSearches as $aNewSearches) {
|
foreach ($aGroupedSearches as $iScore => $aNewSearches) {
|
||||||
$iSearchCount += count($aNewSearches);
|
$iSearchCount += count($aNewSearches);
|
||||||
$aSearches = array_merge($aSearches, $aNewSearches);
|
$aSearches = array_merge($aSearches, $aNewSearches);
|
||||||
if ($iSearchCount > 50) {
|
if ($iSearchCount > 50) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,9 +495,7 @@ class Geocode
|
|||||||
public function lookup()
|
public function lookup()
|
||||||
{
|
{
|
||||||
Debug::newFunction('Geocode::lookup');
|
Debug::newFunction('Geocode::lookup');
|
||||||
if (!$this->sQuery && !$this->aStructuredQuery) {
|
if (!$this->sQuery && !$this->aStructuredQuery) return array();
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printDebugArray('Geocode', $this);
|
Debug::printDebugArray('Geocode', $this);
|
||||||
|
|
||||||
@@ -501,11 +520,25 @@ class Geocode
|
|||||||
|
|
||||||
Debug::newSection('Query Preprocessing');
|
Debug::newSection('Query Preprocessing');
|
||||||
|
|
||||||
|
$sNormQuery = $this->normTerm($this->sQuery);
|
||||||
|
Debug::printVar('Normalized query', $sNormQuery);
|
||||||
|
|
||||||
|
$sLanguagePrefArraySQL = $this->oDB->getArraySQL(
|
||||||
|
$this->oDB->getDBQuotedList($this->aLangPrefOrder)
|
||||||
|
);
|
||||||
|
|
||||||
$sQuery = $this->sQuery;
|
$sQuery = $this->sQuery;
|
||||||
if (!preg_match('//u', $sQuery)) {
|
if (!preg_match('//u', $sQuery)) {
|
||||||
userError('Query string is not UTF-8 encoded.');
|
userError('Query string is not UTF-8 encoded.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conflicts between US state abreviations and various words for 'the' in different languages
|
||||||
|
if (isset($this->aLangPrefOrder['name:en'])) {
|
||||||
|
$sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/i', '\1illinois\2', $sQuery);
|
||||||
|
$sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/i', '\1alabama\2', $sQuery);
|
||||||
|
$sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/i', '\1louisiana\2', $sQuery);
|
||||||
|
}
|
||||||
|
|
||||||
// Do we have anything that looks like a lat/lon pair?
|
// Do we have anything that looks like a lat/lon pair?
|
||||||
$sQuery = $oCtx->setNearPointFromQuery($sQuery);
|
$sQuery = $oCtx->setNearPointFromQuery($sQuery);
|
||||||
|
|
||||||
@@ -543,62 +576,117 @@ class Geocode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
|
if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
|
||||||
$aTokens = $this->oTokenizer->tokensForSpecialTerm($sSpecialTerm);
|
$sSpecialTerm = pg_escape_string($sSpecialTerm);
|
||||||
|
$sToken = $this->oDB->getOne(
|
||||||
|
'SELECT make_standard_name(:term)',
|
||||||
|
array(':term' => $sSpecialTerm),
|
||||||
|
'Cannot decode query. Wrong encoding?'
|
||||||
|
);
|
||||||
|
$sSQL = 'SELECT class, type FROM word ';
|
||||||
|
$sSQL .= ' WHERE word_token in (\' '.$sToken.'\')';
|
||||||
|
$sSQL .= ' AND class is not null AND class not in (\'place\')';
|
||||||
|
|
||||||
if (!empty($aTokens)) {
|
Debug::printSQL($sSQL);
|
||||||
$aNewSearches = array();
|
$aSearchWords = $this->oDB->getAll($sSQL);
|
||||||
$oPosition = new SearchPosition('', 0, 1);
|
$aNewSearches = array();
|
||||||
$oPosition->setTokenPosition(0, 1);
|
foreach ($aSearches as $oSearch) {
|
||||||
|
foreach ($aSearchWords as $aSearchTerm) {
|
||||||
foreach ($aSearches as $oSearch) {
|
$oNewSearch = clone $oSearch;
|
||||||
foreach ($aTokens as $oToken) {
|
$oNewSearch->setPoiSearch(
|
||||||
$aNewSearches = array_merge(
|
Operator::TYPE,
|
||||||
$aNewSearches,
|
$aSearchTerm['class'],
|
||||||
$oToken->extendSearch($oSearch, $oPosition)
|
$aSearchTerm['type']
|
||||||
);
|
);
|
||||||
}
|
$aNewSearches[] = $oNewSearch;
|
||||||
}
|
}
|
||||||
$aSearches = $aNewSearches;
|
|
||||||
}
|
}
|
||||||
|
$aSearches = $aNewSearches;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split query into phrases
|
// Split query into phrases
|
||||||
// Commas are used to reduce the search space by indicating where phrases split
|
// Commas are used to reduce the search space by indicating where phrases split
|
||||||
$aPhrases = array();
|
|
||||||
if ($this->aStructuredQuery) {
|
if ($this->aStructuredQuery) {
|
||||||
foreach ($this->aStructuredQuery as $iPhrase => $sPhrase) {
|
$aInPhrases = $this->aStructuredQuery;
|
||||||
$aPhrases[] = new Phrase($sPhrase, $iPhrase);
|
$bStructuredPhrases = true;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
foreach (explode(',', $sQuery) as $sPhrase) {
|
$aInPhrases = explode(',', $sQuery);
|
||||||
$aPhrases[] = new Phrase($sPhrase, '');
|
$bStructuredPhrases = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug::printDebugArray('Search context', $oCtx);
|
Debug::printDebugArray('Search context', $oCtx);
|
||||||
Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]);
|
Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]);
|
||||||
|
Debug::printVar('Final query phrases', $aInPhrases);
|
||||||
|
|
||||||
|
// Convert each phrase to standard form
|
||||||
|
// Create a list of standard words
|
||||||
|
// Get all 'sets' of words
|
||||||
|
// Generate a complete list of all
|
||||||
Debug::newSection('Tokenization');
|
Debug::newSection('Tokenization');
|
||||||
$oValidTokens = $this->oTokenizer->extractTokensFromPhrases($aPhrases);
|
$aTokens = array();
|
||||||
|
$aPhrases = array();
|
||||||
|
foreach ($aInPhrases as $iPhrase => $sPhrase) {
|
||||||
|
$sPhrase = $this->oDB->getOne(
|
||||||
|
'SELECT make_standard_name(:phrase)',
|
||||||
|
array(':phrase' => $sPhrase),
|
||||||
|
'Cannot normalize query string (is it a UTF-8 string?)'
|
||||||
|
);
|
||||||
|
if (trim($sPhrase)) {
|
||||||
|
$oPhrase = new Phrase($sPhrase, is_string($iPhrase) ? $iPhrase : '');
|
||||||
|
$oPhrase->addTokens($aTokens);
|
||||||
|
$aPhrases[] = $oPhrase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug::printVar('Tokens', $aTokens);
|
||||||
|
|
||||||
|
$oValidTokens = new TokenList();
|
||||||
|
|
||||||
|
if (!empty($aTokens)) {
|
||||||
|
$oValidTokens->addTokensFromDB(
|
||||||
|
$this->oDB,
|
||||||
|
$aTokens,
|
||||||
|
$this->aCountryCodes,
|
||||||
|
$sNormQuery,
|
||||||
|
$this->oNormalizer
|
||||||
|
);
|
||||||
|
|
||||||
if ($oValidTokens->count() > 0) {
|
|
||||||
$oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
|
$oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
|
||||||
|
|
||||||
$aPhrases = array_filter($aPhrases, function ($oPhrase) {
|
// Try more interpretations for Tokens that could not be matched.
|
||||||
return $oPhrase->getWordSets() !== null;
|
foreach ($aTokens as $sToken) {
|
||||||
});
|
if ($sToken[0] == ' ' && !$oValidTokens->contains($sToken)) {
|
||||||
|
if (preg_match('/^ ([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
|
||||||
|
// US ZIP+4 codes - merge in the 5-digit ZIP code
|
||||||
|
$oValidTokens->addToken(
|
||||||
|
$sToken,
|
||||||
|
new Token\Postcode(null, $aData[1], 'us')
|
||||||
|
);
|
||||||
|
} elseif (preg_match('/^ [0-9]+$/', $sToken)) {
|
||||||
|
// Unknown single word token with a number.
|
||||||
|
// Assume it is a house number.
|
||||||
|
$oValidTokens->addToken(
|
||||||
|
$sToken,
|
||||||
|
new Token\HouseNumber(null, trim($sToken))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Any words that have failed completely?
|
// Any words that have failed completely?
|
||||||
// TODO: suggestions
|
// TODO: suggestions
|
||||||
|
|
||||||
Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
|
Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
|
||||||
|
|
||||||
|
foreach ($aPhrases as $oPhrase) {
|
||||||
|
$oPhrase->computeWordSets($oValidTokens);
|
||||||
|
}
|
||||||
Debug::printDebugTable('Phrases', $aPhrases);
|
Debug::printDebugTable('Phrases', $aPhrases);
|
||||||
|
|
||||||
Debug::newSection('Search candidates');
|
Debug::newSection('Search candidates');
|
||||||
|
|
||||||
$aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
|
$aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens, $bStructuredPhrases);
|
||||||
|
|
||||||
if (!$this->aStructuredQuery) {
|
if ($this->bReverseInPlan) {
|
||||||
// Reverse phrase array and also reverse the order of the wordsets in
|
// Reverse phrase array and also reverse the order of the wordsets in
|
||||||
// the first and final phrase. Don't bother about phrases in the middle
|
// the first and final phrase. Don't bother about phrases in the middle
|
||||||
// because order in the address doesn't matter.
|
// because order in the address doesn't matter.
|
||||||
@@ -607,7 +695,7 @@ class Geocode
|
|||||||
if (count($aPhrases) > 1) {
|
if (count($aPhrases) > 1) {
|
||||||
$aPhrases[count($aPhrases)-1]->invertWordSets();
|
$aPhrases[count($aPhrases)-1]->invertWordSets();
|
||||||
}
|
}
|
||||||
$aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
|
$aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens, false);
|
||||||
|
|
||||||
foreach ($aGroupedSearches as $aSearches) {
|
foreach ($aGroupedSearches as $aSearches) {
|
||||||
foreach ($aSearches as $aSearch) {
|
foreach ($aSearches as $aSearch) {
|
||||||
@@ -626,9 +714,7 @@ class Geocode
|
|||||||
$aGroupedSearches = array();
|
$aGroupedSearches = array();
|
||||||
foreach ($aSearches as $aSearch) {
|
foreach ($aSearches as $aSearch) {
|
||||||
if ($aSearch->getRank() < $this->iMaxRank) {
|
if ($aSearch->getRank() < $this->iMaxRank) {
|
||||||
if (!isset($aGroupedSearches[$aSearch->getRank()])) {
|
if (!isset($aGroupedSearches[$aSearch->getRank()])) $aGroupedSearches[$aSearch->getRank()] = array();
|
||||||
$aGroupedSearches[$aSearch->getRank()] = array();
|
|
||||||
}
|
|
||||||
$aGroupedSearches[$aSearch->getRank()][] = $aSearch;
|
$aGroupedSearches[$aSearch->getRank()][] = $aSearch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,9 +728,7 @@ class Geocode
|
|||||||
$sHash = serialize($aSearch);
|
$sHash = serialize($aSearch);
|
||||||
if (isset($aSearchHash[$sHash])) {
|
if (isset($aSearchHash[$sHash])) {
|
||||||
unset($aGroupedSearches[$iGroup][$iSearch]);
|
unset($aGroupedSearches[$iGroup][$iSearch]);
|
||||||
if (empty($aGroupedSearches[$iGroup])) {
|
if (empty($aGroupedSearches[$iGroup])) unset($aGroupedSearches[$iGroup]);
|
||||||
unset($aGroupedSearches[$iGroup]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$aSearchHash[$sHash] = 1;
|
$aSearchHash[$sHash] = 1;
|
||||||
}
|
}
|
||||||
@@ -688,27 +772,20 @@ class Geocode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($iQueryLoop > 20) {
|
if ($iQueryLoop > 20) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($aResults)) {
|
if (!empty($aResults)) {
|
||||||
$aSplitResults = Result::splitResults($aResults);
|
$aSplitResults = Result::splitResults($aResults);
|
||||||
Debug::printVar('Split results', $aSplitResults);
|
Debug::printVar('Split results', $aSplitResults);
|
||||||
if ($iGroupLoop <= 4
|
if ($iGroupLoop <= 4 && empty($aSplitResults['tail'])
|
||||||
&& reset($aSplitResults['head'])->iResultRank > 0
|
&& reset($aSplitResults['head'])->iResultRank > 0) {
|
||||||
&& $iGroupedRank !== array_key_last($aGroupedSearches)) {
|
|
||||||
// Haven't found an exact match for the query yet.
|
// Haven't found an exact match for the query yet.
|
||||||
// Therefore add result from the next group level.
|
// Therefore add result from the next group level.
|
||||||
$aNextResults = $aSplitResults['head'];
|
$aNextResults = $aSplitResults['head'];
|
||||||
foreach ($aNextResults as $oRes) {
|
foreach ($aNextResults as $oRes) {
|
||||||
$oRes->iResultRank--;
|
$oRes->iResultRank--;
|
||||||
}
|
}
|
||||||
foreach ($aSplitResults['tail'] as $oRes) {
|
|
||||||
$oRes->iResultRank--;
|
|
||||||
$aNextResults[$oRes->iId] = $oRes;
|
|
||||||
}
|
|
||||||
$aResults = array();
|
$aResults = array();
|
||||||
} else {
|
} else {
|
||||||
$aResults = $aSplitResults['head'];
|
$aResults = $aSplitResults['head'];
|
||||||
@@ -756,6 +833,7 @@ class Geocode
|
|||||||
foreach ($aResults as $oResult) {
|
foreach ($aResults as $oResult) {
|
||||||
if (($this->iMaxAddressRank == 30 &&
|
if (($this->iMaxAddressRank == 30 &&
|
||||||
($oResult->iTable == Result::TABLE_OSMLINE
|
($oResult->iTable == Result::TABLE_OSMLINE
|
||||||
|
|| $oResult->iTable == Result::TABLE_AUX
|
||||||
|| $oResult->iTable == Result::TABLE_TIGER))
|
|| $oResult->iTable == Result::TABLE_TIGER))
|
||||||
|| in_array($oResult->iId, $aFilteredIDs)
|
|| in_array($oResult->iId, $aFilteredIDs)
|
||||||
) {
|
) {
|
||||||
@@ -765,9 +843,9 @@ class Geocode
|
|||||||
$aResults = $tempIDs;
|
$aResults = $tempIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($aResults) || $iGroupLoop > 4 || $iQueryLoop > 30) {
|
if (!empty($aResults)) break;
|
||||||
break;
|
if ($iGroupLoop > 4) break;
|
||||||
}
|
if ($iQueryLoop > 30) break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Just interpret as a reverse geocode
|
// Just interpret as a reverse geocode
|
||||||
@@ -785,8 +863,10 @@ class Geocode
|
|||||||
|
|
||||||
// No results? Done
|
// No results? Done
|
||||||
if (empty($aResults)) {
|
if (empty($aResults)) {
|
||||||
if ($this->bFallback && $this->fallbackStructuredQuery()) {
|
if ($this->bFallback) {
|
||||||
return $this->lookup();
|
if ($this->fallbackStructuredQuery()) {
|
||||||
|
return $this->lookup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array();
|
return array();
|
||||||
@@ -805,9 +885,7 @@ class Geocode
|
|||||||
|
|
||||||
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
|
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
|
||||||
foreach ($aRecheckWords as $i => $sWord) {
|
foreach ($aRecheckWords as $i => $sWord) {
|
||||||
if (!preg_match('/[\pL\pN]/', $sWord)) {
|
if (!preg_match('/[\pL\pN]/', $sWord)) unset($aRecheckWords[$i]);
|
||||||
unset($aRecheckWords[$i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug::printVar('Recheck words', $aRecheckWords);
|
Debug::printVar('Recheck words', $aRecheckWords);
|
||||||
@@ -867,9 +945,7 @@ class Geocode
|
|||||||
foreach ($aRecheckWords as $i => $sWord) {
|
foreach ($aRecheckWords as $i => $sWord) {
|
||||||
if (stripos($sAddress, $sWord)!==false) {
|
if (stripos($sAddress, $sWord)!==false) {
|
||||||
$iCountWords++;
|
$iCountWords++;
|
||||||
if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) {
|
if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) $iCountWords += 0.1;
|
||||||
$iCountWords += 0.1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,8 +962,15 @@ class Geocode
|
|||||||
$aToFilter = $aSearchResults;
|
$aToFilter = $aSearchResults;
|
||||||
$aSearchResults = array();
|
$aSearchResults = array();
|
||||||
|
|
||||||
|
$bFirst = true;
|
||||||
foreach ($aToFilter as $aResult) {
|
foreach ($aToFilter as $aResult) {
|
||||||
$this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
|
$this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
|
||||||
|
if ($bFirst) {
|
||||||
|
$fLat = $aResult['lat'];
|
||||||
|
$fLon = $aResult['lon'];
|
||||||
|
if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
|
||||||
|
$bFirst = false;
|
||||||
|
}
|
||||||
if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
|
if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
|
||||||
&& !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
|
&& !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
|
||||||
) {
|
) {
|
||||||
@@ -897,9 +980,7 @@ class Geocode
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Absolute limit on number of results
|
// Absolute limit on number of results
|
||||||
if (count($aSearchResults) >= $this->iFinalLimit) {
|
if (count($aSearchResults) >= $this->iFinalLimit) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug::printVar('Post-filter results', $aSearchResults);
|
Debug::printVar('Post-filter results', $aSearchResults);
|
||||||
@@ -913,6 +994,7 @@ class Geocode
|
|||||||
'Structured query' => $this->aStructuredQuery,
|
'Structured query' => $this->aStructuredQuery,
|
||||||
'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
|
'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
|
||||||
'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
|
'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
|
||||||
|
'Try reversed query'=> $this->bReverseInPlan,
|
||||||
'Limit (for searches)' => $this->iLimit,
|
'Limit (for searches)' => $this->iLimit,
|
||||||
'Limit (for results)'=> $this->iFinalLimit,
|
'Limit (for results)'=> $this->iFinalLimit,
|
||||||
'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
|
'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
|
||||||
@@ -90,16 +90,14 @@ class ParameterParser
|
|||||||
$aLanguages = array();
|
$aLanguages = array();
|
||||||
$sLangString = $this->getString('accept-language', $sFallback);
|
$sLangString = $this->getString('accept-language', $sFallback);
|
||||||
|
|
||||||
if ($sLangString
|
if ($sLangString) {
|
||||||
&& preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)
|
if (preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) {
|
||||||
) {
|
foreach ($aLanguagesParse as $iLang => $aLanguage) {
|
||||||
foreach ($aLanguagesParse as $iLang => $aLanguage) {
|
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
|
||||||
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
|
if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
|
||||||
if (!isset($aLanguages[$aLanguage[2]])) {
|
|
||||||
$aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
|
|
||||||
}
|
}
|
||||||
|
arsort($aLanguages);
|
||||||
}
|
}
|
||||||
arsort($aLanguages);
|
|
||||||
}
|
}
|
||||||
if (empty($aLanguages) && CONST_Default_Language) {
|
if (empty($aLanguages) && CONST_Default_Language) {
|
||||||
$aLanguages[CONST_Default_Language] = 1;
|
$aLanguages[CONST_Default_Language] = 1;
|
||||||
@@ -3,70 +3,105 @@
|
|||||||
namespace Nominatim;
|
namespace Nominatim;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A word list creator based on simple splitting by space.
|
* Segment of a query string.
|
||||||
*
|
*
|
||||||
* Creates possible permutations of split phrases by finding all combination
|
* The parts of a query strings are usually separated by commas.
|
||||||
* of splitting the phrase on space boundaries.
|
|
||||||
*/
|
*/
|
||||||
class SimpleWordList
|
class Phrase
|
||||||
{
|
{
|
||||||
const MAX_WORDSET_LEN = 20;
|
const MAX_WORDSET_LEN = 20;
|
||||||
const MAX_WORDSETS = 100;
|
const MAX_WORDSETS = 100;
|
||||||
|
|
||||||
// The phrase as a list of simple terms (without spaces).
|
// Complete phrase as a string.
|
||||||
|
private $sPhrase;
|
||||||
|
// Element type for structured searches.
|
||||||
|
private $sPhraseType;
|
||||||
|
// Space-separated words of the phrase.
|
||||||
private $aWords;
|
private $aWords;
|
||||||
|
// Possible segmentations of the phrase.
|
||||||
|
private $aWordSets;
|
||||||
|
|
||||||
/**
|
public static function cmpByArraylen($aA, $aB)
|
||||||
* Create a new word list
|
|
||||||
*
|
|
||||||
* @param string sPhrase Phrase to create the word list from. The phrase is
|
|
||||||
* expected to be normalised, so that there are no
|
|
||||||
* subsequent spaces.
|
|
||||||
*/
|
|
||||||
public function __construct($sPhrase)
|
|
||||||
{
|
{
|
||||||
if (strlen($sPhrase) > 0) {
|
$iALen = count($aA);
|
||||||
$this->aWords = explode(' ', $sPhrase);
|
$iBLen = count($aB);
|
||||||
} else {
|
|
||||||
$this->aWords = array();
|
if ($iALen == $iBLen) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ($iALen < $iBLen) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($sPhrase, $sPhraseType)
|
||||||
|
{
|
||||||
|
$this->sPhrase = trim($sPhrase);
|
||||||
|
$this->sPhraseType = $sPhraseType;
|
||||||
|
$this->aWords = explode(' ', $this->sPhrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all possible tokens that are present in this word list.
|
* Return the element type of the phrase.
|
||||||
*
|
*
|
||||||
* @return array The list of string tokens in the word list.
|
* @return string Pharse type if the phrase comes from a structured query
|
||||||
|
* or empty string otherwise.
|
||||||
*/
|
*/
|
||||||
public function getTokens()
|
public function getPhraseType()
|
||||||
|
{
|
||||||
|
return $this->sPhraseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the array of possible segmentations of the phrase.
|
||||||
|
*
|
||||||
|
* @return string[][] Array of segmentations, each consisting of an
|
||||||
|
* array of terms.
|
||||||
|
*/
|
||||||
|
public function getWordSets()
|
||||||
|
{
|
||||||
|
return $this->aWordSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the tokens from this phrase to the given list of tokens.
|
||||||
|
*
|
||||||
|
* @param string[] $aTokens List of tokens to append.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addTokens(&$aTokens)
|
||||||
{
|
{
|
||||||
$aTokens = array();
|
|
||||||
$iNumWords = count($this->aWords);
|
$iNumWords = count($this->aWords);
|
||||||
|
|
||||||
for ($i = 0; $i < $iNumWords; $i++) {
|
for ($i = 0; $i < $iNumWords; $i++) {
|
||||||
$sPhrase = $this->aWords[$i];
|
$sPhrase = $this->aWords[$i];
|
||||||
|
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
|
||||||
$aTokens[$sPhrase] = $sPhrase;
|
$aTokens[$sPhrase] = $sPhrase;
|
||||||
|
|
||||||
for ($j = $i + 1; $j < $iNumWords; $j++) {
|
for ($j = $i + 1; $j < $iNumWords; $j++) {
|
||||||
$sPhrase .= ' '.$this->aWords[$j];
|
$sPhrase .= ' '.$this->aWords[$j];
|
||||||
|
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
|
||||||
$aTokens[$sPhrase] = $sPhrase;
|
$aTokens[$sPhrase] = $sPhrase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $aTokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute all possible permutations of phrase splits that result in
|
* Invert the set of possible segmentations.
|
||||||
* words which are in the token list.
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getWordSets($oTokens)
|
public function invertWordSets()
|
||||||
|
{
|
||||||
|
foreach ($this->aWordSets as $i => $aSet) {
|
||||||
|
$this->aWordSets[$i] = array_reverse($aSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function computeWordSets($oTokens)
|
||||||
{
|
{
|
||||||
$iNumWords = count($this->aWords);
|
$iNumWords = count($this->aWords);
|
||||||
|
|
||||||
if ($iNumWords == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caches the word set for the partial phrase up to word i.
|
// Caches the word set for the partial phrase up to word i.
|
||||||
$aSetCache = array_fill(0, $iNumWords, array());
|
$aSetCache = array_fill(0, $iNumWords, array());
|
||||||
|
|
||||||
@@ -82,19 +117,19 @@ class SimpleWordList
|
|||||||
if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
|
if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
|
||||||
$aPartial = array($sPartial);
|
$aPartial = array($sPartial);
|
||||||
foreach ($aSetCache[$j - 1] as $aSet) {
|
foreach ($aSetCache[$j - 1] as $aSet) {
|
||||||
if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) {
|
if (count($aSet) < Phrase::MAX_WORDSET_LEN) {
|
||||||
$aSetCache[$i][] = array_merge($aSet, $aPartial);
|
$aSetCache[$i][] = array_merge($aSet, $aPartial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) {
|
if (count($aSetCache[$i]) > 2 * Phrase::MAX_WORDSETS) {
|
||||||
usort(
|
usort(
|
||||||
$aSetCache[$i],
|
$aSetCache[$i],
|
||||||
array('\Nominatim\SimpleWordList', 'cmpByArraylen')
|
array('\Nominatim\Phrase', 'cmpByArraylen')
|
||||||
);
|
);
|
||||||
$aSetCache[$i] = array_slice(
|
$aSetCache[$i] = array_slice(
|
||||||
$aSetCache[$i],
|
$aSetCache[$i],
|
||||||
0,
|
0,
|
||||||
SimpleWordList::MAX_WORDSETS
|
Phrase::MAX_WORDSETS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,25 +142,19 @@ class SimpleWordList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$aWordSets = $aSetCache[$iNumWords - 1];
|
$this->aWordSets = $aSetCache[$iNumWords - 1];
|
||||||
usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen'));
|
usort($this->aWordSets, array('\Nominatim\Phrase', 'cmpByArraylen'));
|
||||||
return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
|
$this->aWordSets = array_slice($this->aWordSets, 0, Phrase::MAX_WORDSETS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cmpByArraylen($aA, $aB)
|
|
||||||
{
|
|
||||||
$iALen = count($aA);
|
|
||||||
$iBLen = count($aB);
|
|
||||||
|
|
||||||
if ($iALen == $iBLen) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($iALen < $iBLen) ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
public function debugInfo()
|
||||||
{
|
{
|
||||||
return $this->aWords;
|
return array(
|
||||||
|
'Type' => $this->sPhraseType,
|
||||||
|
'Phrase' => $this->sPhrase,
|
||||||
|
'Words' => $this->aWords,
|
||||||
|
'WordSets' => $this->aWordSets
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user