]> git.openstreetmap.org Git - nominatim.git/commitdiff
Merge remote-tracking branch 'upstream/master'
authorSarah Hoffmann <lonvia@denofr.de>
Sun, 29 Sep 2024 09:44:04 +0000 (11:44 +0200)
committerSarah Hoffmann <lonvia@denofr.de>
Sun, 29 Sep 2024 09:44:04 +0000 (11:44 +0200)
177 files changed:
.github/workflows/ci-tests.yml
.pylintrc
CMakeLists.txt
CONTRIBUTING.md
ChangeLog
SECURITY.md
cmake/paths-py-no-php.tmpl
cmake/paths-py.tmpl [deleted file]
docs/admin/Advanced-Installations.md
docs/admin/Deployment-PHP.md [deleted file]
docs/admin/Import.md
docs/admin/Installation.md
docs/admin/Migration.md
docs/api/Details.md
docs/api/Output.md
docs/api/Overview.md
docs/api/Reverse.md
docs/api/Search.md
docs/customize/Import-Styles.md
docs/customize/Settings.md
docs/customize/Tokenizers.md
docs/develop/Development-Environment.md
docs/develop/Testing.md
docs/develop/Tokenizers.md
docs/develop/overview.md
docs/extra.css
docs/library/Configuration.md
docs/library/Getting-Started.md
docs/library/Low-Level-DB-Access.md
lib-php/AddressDetails.php [deleted file]
lib-php/ClassTypes.php [deleted file]
lib-php/DB.php [deleted file]
lib-php/DatabaseError.php [deleted file]
lib-php/DebugHtml.php [deleted file]
lib-php/DebugNone.php [deleted file]
lib-php/Geocode.php [deleted file]
lib-php/ParameterParser.php [deleted file]
lib-php/Phrase.php [deleted file]
lib-php/PlaceLookup.php [deleted file]
lib-php/Result.php [deleted file]
lib-php/ReverseGeocode.php [deleted file]
lib-php/SearchContext.php [deleted file]
lib-php/SearchDescription.php [deleted file]
lib-php/SearchPosition.php [deleted file]
lib-php/Shell.php [deleted file]
lib-php/SimpleWordList.php [deleted file]
lib-php/SpecialSearchOperator.php [deleted file]
lib-php/Status.php [deleted file]
lib-php/TokenCountry.php [deleted file]
lib-php/TokenHousenumber.php [deleted file]
lib-php/TokenList.php [deleted file]
lib-php/TokenPartial.php [deleted file]
lib-php/TokenPostcode.php [deleted file]
lib-php/TokenSpecialTerm.php [deleted file]
lib-php/TokenWord.php [deleted file]
lib-php/cmd.php [deleted file]
lib-php/dotenv_loader.php [deleted file]
lib-php/init-cmd.php [deleted file]
lib-php/init-website.php [deleted file]
lib-php/init.php [deleted file]
lib-php/lib.php [deleted file]
lib-php/log.php [deleted file]
lib-php/output.php [deleted file]
lib-php/setup_functions.php [deleted file]
lib-php/template/address-geocodejson.php [deleted file]
lib-php/template/address-geojson.php [deleted file]
lib-php/template/address-json.php [deleted file]
lib-php/template/address-xml.php [deleted file]
lib-php/template/details-json.php [deleted file]
lib-php/template/error-json.php [deleted file]
lib-php/template/error-xml.php [deleted file]
lib-php/template/search-batch-json.php [deleted file]
lib-php/template/search-geocodejson.php [deleted file]
lib-php/template/search-geojson.php [deleted file]
lib-php/template/search-json.php [deleted file]
lib-php/template/search-xml.php [deleted file]
lib-php/tokenizer/icu_tokenizer.php [deleted file]
lib-php/tokenizer/legacy_tokenizer.php [deleted file]
lib-php/website/deletable.php [deleted file]
lib-php/website/details.php [deleted file]
lib-php/website/lookup.php [deleted file]
lib-php/website/polygons.php [deleted file]
lib-php/website/reverse-only-search.php [deleted file]
lib-php/website/reverse.php [deleted file]
lib-php/website/search.php [deleted file]
lib-php/website/status.php [deleted file]
lib-sql/tokenizer/legacy_tokenizer.sql [deleted file]
lib-sql/tokenizer/legacy_tokenizer_indices.sql [deleted file]
lib-sql/tokenizer/legacy_tokenizer_tables.sql [deleted file]
mkdocs.yml
module/CMakeLists.txt [deleted file]
module/Makefile [deleted file]
module/nominatim.c [deleted file]
module/tokenstringreplacements.inc [deleted file]
module/utfasciitable.h [deleted file]
packaging/nominatim-api/extra_src/paths.py
packaging/nominatim-db/extra_src/nominatim_db/paths.py
packaging/nominatim-db/scripts/nominatim
phpcs.xml [deleted file]
settings/env.defaults
src/nominatim_api/core.py
src/nominatim_api/search/db_search_builder.py
src/nominatim_api/search/icu_tokenizer.py
src/nominatim_api/search/legacy_tokenizer.py [deleted file]
src/nominatim_api/search/query.py
src/nominatim_api/version.py
src/nominatim_db/cli.py
src/nominatim_db/clicmd/refresh.py
src/nominatim_db/clicmd/setup.py
src/nominatim_db/config.py
src/nominatim_db/paths.py
src/nominatim_db/tokenizer/factory.py
src/nominatim_db/tokenizer/icu_tokenizer.py
src/nominatim_db/tokenizer/legacy_tokenizer.py [deleted file]
src/nominatim_db/tools/convert_sqlite.py
src/nominatim_db/tools/exec_utils.py
src/nominatim_db/tools/migration.py
src/nominatim_db/tools/refresh.py
src/nominatim_db/version.py
test/Makefile
test/bdd/api/details/simple.feature
test/bdd/api/reverse/layers.feature
test/bdd/api/reverse/v1_params.feature
test/bdd/api/search/params.feature
test/bdd/api/search/postcode.feature
test/bdd/api/search/queries.feature
test/bdd/db/import/parenting.feature
test/bdd/db/import/postcodes.feature
test/bdd/db/import/search_name.feature
test/bdd/db/query/housenumbers.feature
test/bdd/db/query/japanese.feature
test/bdd/db/query/postcodes.feature
test/bdd/db/query/reverse.feature
test/bdd/db/query/search_simple.feature
test/bdd/db/update/country.feature
test/bdd/environment.py
test/bdd/steps/nominatim_environment.py
test/bdd/steps/steps_api_queries.py
test/bdd/steps/steps_db_ops.py
test/php/Nominatim/AddressDetailsTest.php [deleted file]
test/php/Nominatim/ClassTypesTest.php [deleted file]
test/php/Nominatim/DBTest.php [deleted file]
test/php/Nominatim/DatabaseErrorTest.php [deleted file]
test/php/Nominatim/DebugTest.php [deleted file]
test/php/Nominatim/LibTest.php [deleted file]
test/php/Nominatim/ParameterParserTest.php [deleted file]
test/php/Nominatim/ResultTest.php [deleted file]
test/php/Nominatim/SearchContextTest.php [deleted file]
test/php/Nominatim/ShellTest.php [deleted file]
test/php/Nominatim/SimpleWordListTest.php [deleted file]
test/php/Nominatim/StatusTest.php [deleted file]
test/php/Nominatim/TokenListTest.php [deleted file]
test/php/Nominatim/tokenizer.php [deleted file]
test/php/bootstrap.php [deleted file]
test/php/fixtures/address_details_10_downing_street.json [deleted file]
test/php/phpunit.xml [deleted file]
test/python/api/conftest.py
test/python/api/search/test_api_search_query.py
test/python/api/search/test_db_search_builder.py
test/python/api/search/test_icu_query_analyzer.py
test/python/api/search/test_legacy_query_analyzer.py [deleted file]
test/python/api/search/test_token_assignment.py
test/python/api/test_api_status.py
test/python/api/test_export.py
test/python/api/test_server_glue_v1.py
test/python/api/test_warm.py
test/python/cli/conftest.py
test/python/cli/test_cli.py
test/python/cli/test_cmd_import.py
test/python/cli/test_cmd_refresh.py
test/python/config/test_config.py
test/python/conftest.py
test/python/mock_legacy_word_table.py [deleted file]
test/python/tokenizer/test_legacy.py [deleted file]
test/python/tools/test_migration.py
test/python/tools/test_refresh_setup_website.py [deleted file]
test/testdb/specialphrases_testdb.sql [deleted file]

index 1cfaf616818a8b3d3151d4f57199bf752c046c64..21d506ae81d2c1c8277d928575e38026341aae1a 100644 (file)
@@ -131,99 +131,6 @@ jobs:
               working-directory: Nominatim
               if: matrix.flavour != 'oldstuff'
 
-    legacy-test:
-        needs: create-archive
-        runs-on: ubuntu-20.04
-
-        strategy:
-            matrix:
-                postgresql: ["13", "16"]
-
-        steps:
-            - uses: actions/download-artifact@v4
-              with:
-                  name: full-source
-
-            - name: Unpack Nominatim
-              run: tar xf nominatim-src.tar.bz2
-
-            - name: Setup PHP
-              uses: shivammathur/setup-php@v2
-              with:
-                  php-version: '7.4'
-
-            - uses: ./Nominatim/.github/actions/setup-postgresql
-              with:
-                  postgresql-version: ${{ matrix.postgresql }}
-                  postgis-version: 3
-
-            - name: Install Postgresql server dev
-              run: sudo apt-get install postgresql-server-dev-$PGVER
-              env:
-                PGVER: ${{ matrix.postgresql }}
-
-            - uses: ./Nominatim/.github/actions/build-nominatim
-              with:
-                  cmake-args: -DBUILD_MODULE=on
-
-            - name: Install test prerequisites
-              run: sudo apt-get install -y -qq python3-behave
-
-            - name: BDD tests (legacy tokenizer)
-              run: |
-                  export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH
-                  python3 -m behave -DREMOVE_TEMPLATE=1 -DSERVER_MODULE_PATH=$GITHUB_WORKSPACE/build/module -DAPI_ENGINE=php -DTOKENIZER=legacy --format=progress3
-              working-directory: Nominatim/test/bdd
-
-
-    php-test:
-        needs: create-archive
-        runs-on: ubuntu-22.04
-
-        steps:
-            - uses: actions/download-artifact@v4
-              with:
-                  name: full-source
-
-            - name: Unpack Nominatim
-              run: tar xf nominatim-src.tar.bz2
-
-            - uses: ./Nominatim/.github/actions/setup-postgresql
-              with:
-                  postgresql-version: 15
-                  postgis-version: 3
-
-            - name: Setup PHP
-              uses: shivammathur/setup-php@v2
-              with:
-                  php-version: 8.1
-                  tools: phpunit:9, phpcs, composer
-                  ini-values: opcache.jit=disable
-              env:
-                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-            - name: PHP linting
-              run: phpcs --report-width=120 .
-              working-directory: Nominatim
-
-            - name: PHP unit tests
-              run: phpunit ./
-              working-directory: Nominatim/test/php
-
-            - uses: ./Nominatim/.github/actions/build-nominatim
-              with:
-                  flavour: 'ubuntu-22'
-
-            - name: Install test prerequisites
-              run: sudo apt-get install -y -qq python3-behave
-
-            - name: BDD tests (php)
-              run: |
-                  export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH
-                  python3 -m behave -DREMOVE_TEMPLATE=1 -DAPI_ENGINE=php --format=progress3
-              working-directory: Nominatim/test/bdd
-
-
     install:
         runs-on: ubuntu-latest
         needs: create-archive
index c591509607a077f6d35f371921092eef193437fe..e562055d2c99dcd587e99bdca4aeed05e3674056 100644 (file)
--- a/.pylintrc
+++ b/.pylintrc
@@ -13,10 +13,10 @@ ignored-classes=NominatimArgs,closing
 # 'too-many-ancestors' is triggered already by deriving from UserDict
 # 'not-context-manager' disabled because it causes false positives once
 #   typed Python is enabled. See also https://github.com/PyCQA/pylint/issues/5273
-disable=too-few-public-methods,duplicate-code,too-many-ancestors,bad-option-value,no-self-use,not-context-manager,use-dict-literal,chained-comparison,attribute-defined-outside-init,too-many-boolean-expressions,contextmanager-generator-missing-cleanup
+disable=too-few-public-methods,duplicate-code,too-many-ancestors,bad-option-value,no-self-use,not-context-manager,use-dict-literal,chained-comparison,attribute-defined-outside-init,too-many-boolean-expressions,contextmanager-generator-missing-cleanup,too-many-positional-arguments
 
 good-names=i,j,x,y,m,t,fd,db,cc,x1,x2,y1,y2,pt,k,v,nr
 
 [DESIGN]
 
-max-returns=7
\ No newline at end of file
+max-returns=7
index 4b4e3fc9d139b4f714afe8d315c7e233b385dc5e..e6d59520c10601b8660a855c8f5ad3dc8ca1c4c5 100644 (file)
@@ -19,7 +19,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
 project(nominatim)
 
 set(NOMINATIM_VERSION_MAJOR 4)
-set(NOMINATIM_VERSION_MINOR 4)
+set(NOMINATIM_VERSION_MINOR 5)
 set(NOMINATIM_VERSION_PATCH 0)
 
 set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
@@ -44,7 +44,6 @@ endif()
 
 set(BUILD_IMPORTER on CACHE BOOL "Build everything for importing/updating the database")
 set(BUILD_API on CACHE BOOL "Build everything for the API server")
-set(BUILD_MODULE off CACHE BOOL "Build PostgreSQL module for legacy tokenizer")
 set(BUILD_TESTS on CACHE BOOL "Build test suite")
 set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
 set(INSTALL_MUNIN_PLUGINS on CACHE BOOL "Install Munin plugins for supervising Nominatim")
@@ -74,25 +73,6 @@ if (BUILD_IMPORTER OR BUILD_API)
     find_package(PythonInterp 3.7 REQUIRED)
 endif()
 
-#-----------------------------------------------------------------------------
-# PHP
-#-----------------------------------------------------------------------------
-
-# Setting PHP binary variable as to command line (prevailing) or auto detect
-
-if (BUILD_API)
-    if (NOT PHP_BIN)
-         find_program (PHP_BIN php)
-    endif()
-    # sanity check if PHP binary exists
-    if (NOT EXISTS ${PHP_BIN})
-        message(WARNING "PHP binary not found. Only Python frontend can be used.")
-        set(PHP_BIN "")
-    else()
-        message (STATUS "Using PHP binary " ${PHP_BIN})
-    endif()
-endif()
-
 #-----------------------------------------------------------------------------
 # import scripts and utilities (importer only)
 #-----------------------------------------------------------------------------
@@ -146,8 +126,6 @@ if (BUILD_TESTS)
     find_program(PYTHON_BEHAVE behave)
     find_program(PYLINT NAMES pylint3 pylint)
     find_program(PYTEST NAMES pytest py.test-3 py.test)
-    find_program(PHPCS phpcs)
-    find_program(PHPUNIT phpunit)
 
     if (PYTHON_BEHAVE)
         message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}")
@@ -162,24 +140,6 @@ if (BUILD_TESTS)
         message(WARNING "behave not found. BDD tests disabled." )
     endif()
 
-    if (PHPUNIT)
-        message(STATUS "Using phpunit binary ${PHPUNIT}")
-        add_test(NAME php
-                 COMMAND ${PHPUNIT} ./
-                 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
-    else()
-        message(WARNING "phpunit not found. PHP unit tests disabled." )
-    endif()
-
-    if (PHPCS)
-        message(STATUS "Using phpcs binary ${PHPCS}")
-        add_test(NAME phpcs
-                 COMMAND ${PHPCS} --report-width=120 --colors lib-php
-                 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
-    else()
-        message(WARNING "phpcs not found. PHP linting tests disabled." )
-    endif()
-
     if (PYLINT)
         message(STATUS "Using pylint binary ${PYLINT}")
         add_test(NAME pylint
@@ -199,14 +159,6 @@ if (BUILD_TESTS)
     endif()
 endif()
 
-#-----------------------------------------------------------------------------
-# Postgres module
-#-----------------------------------------------------------------------------
-
-if (BUILD_MODULE)
-    add_subdirectory(module)
-endif()
-
 #-----------------------------------------------------------------------------
 # Installation
 #-----------------------------------------------------------------------------
@@ -224,11 +176,7 @@ if (BUILD_IMPORTER)
             DESTINATION ${CMAKE_INSTALL_BINDIR}
             RENAME nominatim)
 
-    if (EXISTS ${PHP_BIN})
-        configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed)
-    else()
-        configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
-    endif()
+    configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
 
     foreach (submodule nominatim_db nominatim_api)
         install(DIRECTORY src/${submodule}
@@ -259,15 +207,6 @@ if (BUILD_OSM2PGSQL)
     endif()
 endif()
 
-if (BUILD_MODULE)
-    install(PROGRAMS ${PROJECT_BINARY_DIR}/module/nominatim.so
-            DESTINATION ${NOMINATIM_LIBDIR}/module)
-endif()
-
-if (BUILD_API AND EXISTS ${PHP_BIN})
-    install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
-endif()
-
 install(FILES settings/env.defaults
               settings/address-levels.json
               settings/phrase-settings.json
index 1df644e750e3c66fbee24102010e014e1faaffa3..a78bbfb3e8a8f9fc641da3bcd5be65d762e85570 100644 (file)
@@ -61,8 +61,7 @@ pylint3 --extension-pkg-whitelist=osmium nominatim
 Before submitting a pull request make sure that the tests pass:
 
 ```
-  cd build
-  make test
+  make tests
 ```
 
 ## Releases
@@ -75,7 +74,10 @@ relevant changes are cherry-picked from the master branch.
 
 Checklist for releases:
 
-* [ ] increase version in `nominatim/version.py` and CMakeLists.txt
+* [ ] increase versions in
+  * `src/nominatim_api/version.py`
+  * `src/nominatim_db/version.py`
+  * CMakeLists.txt
 * [ ] update `ChangeLog` (copy information from patch releases from release branch)
 * [ ] complete `docs/admin/Migration.md`
 * [ ] update EOL dates in `SECURITY.md`
@@ -100,3 +102,4 @@ Checklist for releases:
   * compile and import Nominatim
   * run `nominatim --version` to confirm correct version
 * [ ] tag new release and add a release on github.com
+* [ ] build pip packages and upload to pypi
index 2f5d51d5f24c4442f3ba32ed61a433df31241e7c..b7609255e4e4b3e26f5298ef33cf630b11e8eb50 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+4.5.0
+ * allow building Nominatim as a pip package
+ * make osm2pgsql building optional
+ * switch importer to psycopg3
+ * allow output format of web search to be customized in self-installations
+ * look up potential postcode areas for postcode results
+ * add word usage statistics for address terms
+ * implement more light-weight CSV format for wiki importance tables
+ * rewrite SQL for place search to use window functions
+ * increase search radius when filtering by postcode
+ * prefer POI points over POI areas
+ * reintroduce full terms for address terms in search_name table
+ * reindex postcodes when their parent is deleted
+ * indexing: precompute counts of affected rows
+ * ensure consistent country assignments for overlapping countries
+ * make Nominatim[Async]API context manager to ensure proper calling of
+   close()
+ * make usage of project dir optional for library
+ * drop interpolations when no parent can be found
+ * style tweaks to reflect OSM usage (man_made, highway and others)
+ * deprecation of: bundled osm2pgsql, legacy tokenizer, PHP frontend
+ * make documentation buildable without CMake
+ * various fixes and improvements to documentation
+
+4.4.1
+ * fix geocodejson output: admin level output should only print boundaries
+ * updating: restrict invalidation of child objects on large street features
+ * restrict valid interpolation house numbers to 0-999999
+ * fix import error when SQLAlchemy 1.4 and psycopg3 are installed
+ * various typo fixes in the documentation
+
 4.4.0
  * add export to SQLite database and SQLite support for the frontend
  * switch to Python frontend as the default frontend
@@ -8,7 +39,7 @@
  * fix regression in search with categories where it was confused with near
    search
  * partially roll back use of SQLAlchemy lambda statements due to bugs
-   in SQLAchemy
+   in SQLAlchemy
  * fix handling of timezones for timestamps from the database
  * fix handling of full address searches in connection with a viewbox
  * fix postcode computation of highway areas
index a14eba13169129ed498c3fcf8e2487ce5753e326..3ec22cbde8c54fc6409acd593d4540b0b6582d60 100644 (file)
@@ -9,10 +9,10 @@ versions.
 
 | Version | End of support for security updates |
 | ------- | ----------------------------------- |
+| 4.5.x   | 2026-09-12                          |
 | 4.4.x   | 2026-03-07                          |
 | 4.3.x   | 2025-09-07                          |
 | 4.2.x   | 2024-11-24                          |
-| 4.1.x   | 2024-08-05                          |
 
 ## Reporting a Vulnerability
 
index 36856bf3c37c4330ab9c012a5830d00c06f0a632..a95cb6645b5ebdca2210530b39bc9607d6dd3835 100644 (file)
@@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim (installed version).
 """
 from pathlib import Path
 
-PHPLIB_DIR = None
 SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
 DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
 CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
diff --git a/cmake/paths-py.tmpl b/cmake/paths-py.tmpl
deleted file mode 100644 (file)
index 372a454..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2022 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Path settings for extra data used by Nominatim (installed version).
-"""
-from pathlib import Path
-
-PHPLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-php').resolve()
-SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
-DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
-CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
index f8232fb29cea6189b36a2ae7d47a8617531813fb..de3c5876984ce85e5ea48208b337cf4fa70898c4 100644 (file)
@@ -131,76 +131,13 @@ script ([Geofabrik](https://download.geofabrik.de)) provides daily updates.
 
 ## Using an external PostgreSQL database
 
-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
-uses a custom normalization library that needs to be made accessible to the
-PostgreSQL server. This section explains how to set up the normalization
-library.
-
-!!! note
-    The external module is only needed when using the legacy tokenizer.
-    If you have chosen the ICU tokenizer, then you can ignore this section
-    and follow the standard import documentation.
-
-### 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
-server. From the prerequisites you need at least cmake, gcc and the
-PostgreSQL server package.
-
-Clone or unpack the Nominatim source code, enter the source directory and
-create and enter a build directory.
-
-```sh
-cd Nominatim
-mkdir build
-cd build
-```
-
-Now configure cmake to only build the PostgreSQL module and build it:
-
-```
-cmake -DBUILD_IMPORTER=off -DBUILD_API=off -DBUILD_TESTS=off -DBUILD_DOCS=off -DBUILD_OSM2PGSQL=off ..
-make
-```
-
-When done, you find the normalization library in `build/module/nominatim.so`.
-Copy it to a place where it is readable and executable by the PostgreSQL server
-process.
-
-### Option 2: Compiling the library on the import machine
-
-You can also compile the normalization library on the machine from where you
-run the import.
-
-!!! important
-    You can only do this when the database server and the import machine have
-    the same architecture and run the same version of Linux. Otherwise there is
-    no guarantee that the compiled library is compatible with the PostgreSQL
-    server running on the database server.
-
-Make sure that the PostgreSQL server package is installed on the machine
-**with the same version as on the database server**. You do not need to install
-the PostgreSQL server itself.
-
-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 database server at a location where it is readable and executable by the
-PostgreSQL server process.
-
-### Running the import
-
-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
-line to your your `.env` file:
-
-```php
-NOMINATIM_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
-to follow the [standard instructions for importing](Import.md).
+You can install Nominatim using a database that runs on a different server.
+Simply point the configuration variable `NOMINATIM_DATABASE_DSN` to the
+server and follow the standard import documentation.
 
+The import will be faster, if the import is run directly from the database
+machine. You can easily switch to a different machine for the query frontend
+after the import.
 
 ## Moving the database to another machine
 
@@ -225,20 +162,9 @@ target machine.
     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.
+Next install nominatim-api on the target machine by following the standard
+installation instructions. Again, make sure to use the same version as the
+source machine.
 
 Create a project directory on your destination machine and set up the `.env`
-file to match the configuration on the source machine. 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 rerun
-
-    nominatim refresh --functions
-
-on the target machine to update the the location of the module.
+file to match the configuration on the source machine. That's all.
diff --git a/docs/admin/Deployment-PHP.md b/docs/admin/Deployment-PHP.md
deleted file mode 100644 (file)
index 9416c53..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-# Deploying Nominatim using the PHP frontend
-
-!!! danger
-    The PHP frontend is deprecated and will be removed in Nominatim 5.0.
-
-The Nominatim API is implemented as a PHP application. The `website/` directory
-in the project directory contains the configured website. You can serve this
-in a production environment with any web server that is capable to run
-PHP scripts.
-
-This section gives a quick overview on how to configure Apache and Nginx to
-serve Nominatim. It is not meant as a full system administration guide on how
-to run a web service. Please refer to the documentation of
-[Apache](https://httpd.apache.org/docs/current/) and
-[Nginx](https://nginx.org/en/docs/)
-for background information on configuring the services.
-
-!!! Note
-    Throughout this page, we assume your Nominatim project directory is
-    located in `/srv/nominatim-project` and you have installed Nominatim
-    using the default installation prefix `/usr/local`. If you have put it
-    somewhere else, you need to adjust the commands and configuration
-    accordingly.
-
-    We further assume that your web server runs as user `www-data`. Older
-    versions of CentOS may still use the user name `apache`. You also need
-    to adapt the instructions in this case.
-
-## Making the website directory accessible
-
-You need to make sure that the `website` directory is accessible for the
-web server user. You can check that the permissions are correct by accessing
-on of the php files as the web server user:
-
-``` sh
-sudo -u www-data head -n 1 /srv/nominatim-project/website/search.php
-```
-
-If this shows a permission error, then you need to adapt the permissions of
-each directory in the path so that it is executable for `www-data`.
-
-If you have SELinux enabled, further adjustments may be necessary to give the
-web server access. At a minimum the following SELinux labelling should be done
-for Nominatim:
-
-``` sh
-sudo semanage fcontext -a -t httpd_sys_content_t "/usr/local/nominatim/lib/lib-php(/.*)?"
-sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim-project/website(/.*)?"
-sudo semanage fcontext -a -t lib_t "/srv/nominatim-project/module/nominatim.so"
-sudo restorecon -R -v /usr/local/lib/nominatim
-sudo restorecon -R -v /srv/nominatim-project
-```
-
-## Nominatim with Apache
-
-### Installing the required packages
-
-With Apache you can use the PHP module to run Nominatim.
-
-Under Ubuntu/Debian install them with:
-
-``` sh
-sudo apt install apache2 libapache2-mod-php
-```
-
-### Configuring Apache
-
-Make sure your Apache configuration contains the required permissions for the
-directory and create an alias:
-
-``` apache
-<Directory "/srv/nominatim-project/website">
-  Options FollowSymLinks MultiViews
-  AddType text/html   .php
-  DirectoryIndex search.php
-  Require all granted
-</Directory>
-Alias /nominatim /srv/nominatim-project/website
-```
-
-After making changes in the apache config you need to restart apache.
-The website should now be available on `http://localhost/nominatim`.
-
-## Nominatim with Nginx
-
-### Installing the required packages
-
-Nginx has no built-in PHP interpreter. You need to use php-fpm as a daemon for
-serving PHP cgi.
-
-On Ubuntu/Debian install nginx and php-fpm with:
-
-``` sh
-sudo apt install nginx php-fpm
-```
-
-### Configure php-fpm and Nginx
-
-By default php-fpm listens on a network socket. If you want it to listen to a
-Unix socket instead, change the pool configuration
-(`/etc/php/<php version>/fpm/pool.d/www.conf`) as follows:
-
-``` ini
-; Replace the tcp listener and add the unix socket
-listen = /var/run/php-fpm-nominatim.sock
-
-; Ensure that the daemon runs as the correct user
-listen.owner = www-data
-listen.group = www-data
-listen.mode = 0666
-```
-
-Tell nginx that php files are special and to fastcgi_pass to the php-fpm
-unix socket by adding the location definition to the default configuration.
-
-``` nginx
-root /srv/nominatim-project/website;
-index search.php;
-location / {
-    try_files $uri $uri/ @php;
-}
-
-location @php {
-    fastcgi_param SCRIPT_FILENAME "$document_root$uri.php";
-    fastcgi_param PATH_TRANSLATED "$document_root$uri.php";
-    fastcgi_param QUERY_STRING    $args;
-    fastcgi_pass unix:/var/run/php-fpm-nominatim.sock;
-    fastcgi_index index.php;
-    include fastcgi_params;
-}
-
-location ~ [^/]\.php(/|$) {
-    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
-    if (!-f $document_root$fastcgi_script_name) {
-        return 404;
-    }
-    fastcgi_pass unix:/var/run/php-fpm-nominatim.sock;
-    fastcgi_index search.php;
-    include fastcgi.conf;
-}
-```
-
-Restart the nginx and php-fpm services and the website should now be available
-at `http://localhost/`.
-
-## Nominatim with other webservers
-
-Users have created instructions for other webservers:
-
-* [Caddy](https://github.com/osm-search/Nominatim/discussions/2580)
-
index 5a365b236b5897adcae2e6423369b5b2426174db..3f248b0e3eeb359a3a6dbea103685cc0ebf4ea54 100644 (file)
@@ -257,8 +257,8 @@ successfully.
 nominatim admin --check-database
 ```
 
-Now you can try out your installation by executing a simple query on the
-command line:
+If you have installed the `nominatim-api` package, then you can try out
+your installation by executing a simple query on the command line:
 
 ``` sh
 nominatim search --query Berlin
@@ -270,10 +270,8 @@ or, when you have a reverse-only installation:
 nominatim reverse --lat 51 --lon 45
 ```
 
-If you want to run Nominatim as a service, you need to make a choice between
-running the modern Python frontend and the legacy PHP frontend.
-Make sure you have installed the right packages as per
-[Installation](Installation.md#software).
+If you want to run Nominatim as a service, make sure you have installed
+the right packages as per [Installation](Installation.md#software).
 
 #### Testing the Python frontend
 
@@ -291,36 +289,15 @@ or, if you prefer to use Starlette instead of Falcon as webserver,
 nominatim serve --engine starlette
 ```
 
-Go to `http://localhost:8088/status.php` and you should see the message `OK`.
-You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`
+Go to `http://localhost:8088/status` and you should see the message `OK`.
+You can also run a search query, e.g. `http://localhost:8088/search?q=Berlin`
 or, for reverse-only installations a reverse query,
-e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`.
+e.g. `http://localhost:8088/reverse?lat=27.1750090510034&lon=78.04209025`.
 
 Do not use this test server in production.
 To run Nominatim via webservers like Apache or nginx, please continue reading
 [Deploy the Python frontend](Deployment-Python.md).
 
-#### Testing the PHP frontend
-
-!!! danger
-    The PHP fronted is deprecated and will be removed in Nominatim 5.0.
-
-You can run a small test server with the PHP frontend like this:
-
-```sh
-nominatim serve --engine php
-```
-
-Go to `http://localhost:8088/status.php` and you should see the message `OK`.
-You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`
-or, for reverse-only installations a reverse query,
-e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`.
-
-Do not use this test server in production.
-To run Nominatim via webservers like Apache or nginx, please continue reading
-[Deploy the PHP frontend](Deployment-PHP.md).
-
-
 
 ## Enabling search by category phrases
 
index e67371bd9de4424fdce0f9b24838657493a4fa18..78062908c9ca230179023a960008a6f04fd9deb1 100644 (file)
@@ -72,13 +72,6 @@ For running the Python frontend:
     * [starlette](https://www.starlette.io/)
   * [uvicorn](https://www.uvicorn.org/)
 
-For running the legacy PHP frontend (deprecated, will be removed in Nominatim 5.0):
-
-  * [PHP](https://php.net) (7.3+)
-  * PHP-pgsql
-  * PHP-intl (bundled with PHP)
-
-
 For dependencies for running tests and building documentation, see
 the [Development section](../develop/Development-Environment.md).
 
@@ -185,18 +178,6 @@ make
 sudo make install
 ```
 
-!!! warning
-    The default installation no longer compiles the PostgreSQL module that
-    is needed for the legacy tokenizer from older Nominatim versions. If you
-    are upgrading an older database or want to run the
-    [legacy tokenizer](../customize/Tokenizers.md#legacy-tokenizer) for
-    some other reason, you need to enable the PostgreSQL module via
-    cmake: `cmake -DBUILD_MODULE=on ../Nominatim`. To compile the module
-    you need to have the server development headers for PostgreSQL installed.
-    On Ubuntu/Debian run: `sudo apt install postgresql-server-dev-<postgresql version>`
-    The legacy tokenizer is deprecated and will be removed in Nominatim 5.0
-
-
 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
index 75f891412e8cf48f8644adab9e1c39d78377aae4..13e6d7f5e5ce64629a37f58e5344cc774731bfea 100644 (file)
@@ -1,6 +1,6 @@
 # Database Migrations
 
-Nominatim offers automatic migrations since version 3.7. Please follow
+Nominatim offers automatic migrations for versions 4.3+. Please follow
 the following steps:
 
 * Stop any updates that are potentially running
@@ -17,10 +17,23 @@ 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.
+    If you are migrating from a version <4.3, you need to install 4.3
+    first and migrate to 4.3 first. Then you can migrate to the current
+    version. It is strongly recommended to do a reimport instead.
 
-## 4.4.0 -> master
+## 4.5.0 -> master
+
+### PHP frontend removed
+
+The PHP frontend has been completely removed. Please switch to the Python
+frontend.
+
+Without the PHP code, the `nominatim refresh --website` command is no longer
+needed. It currently omits a warning and does otherwise nothing. It will be
+removed in later versions of Nominatim. So make sure you remove it from your
+scripts.
+
+## 4.4.0 -> 4.5.0
 
 ### New structure for Python packages
 
index c50378c5a6275cf0be3f0f0da1fd424c32ac1b84..b836efd30eaed27a77bee37e09014ebdf3dea219 100644 (file)
@@ -59,13 +59,6 @@ When set, then JSON output will be wrapped in a callback function with
 the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more
 information.
 
-| Parameter | Value | Default |
-|-----------| ----- | ------- |
-| pretty    | 0 or 1 | 0 |
-
-`[PHP-only]` Add indentation to the output to make it more human-readable.
-
-
 ### Output details
 
 | Parameter | Value | Default |
@@ -95,10 +88,8 @@ members.
 |-----------| ----- | ------- |
 | hierarchy  | 0 or 1 | 0 |
 
-Include details of places lower in the address hierarchy.
-
-`[Python-only]` will only return properly parented places. These are address
-or POI-like places that reuse the address of their parent street or place.
+Include details of POIs and address that depend on the place. Only POIs
+that use this place to determine their address will be returned.
 
 | Parameter | Value | Default |
 |-----------| ----- | ------- |
@@ -129,7 +120,7 @@ as the ["Accept-Language" HTTP header](https://developer.mozilla.org/en-US/docs/
 
 ##### JSON
 
-[https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json)
+[https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json)
 
 
 ```json
index 029f78bc5ea2d32d64d5b9722d7238cc842ed823..75220cf510b1595abbaccd084ab388a0304b813f 100644 (file)
@@ -168,7 +168,7 @@ Additional information requested with `addressdetails=1`, `extratags=1` and
 <searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
                attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
                querystring="london" polygon="false" exclude_place_ids="100149"
-               more_url="https://nominatim.openstreetmap.org/search.php?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
+               more_url="https://nominatim.openstreetmap.org/search?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
   <place place_id="100149" osm_type="node" osm_id="107775" place_rank="15" address_rank="15"
          boundingbox="51.3473219,51.6673219,-0.2876474,0.0323526" lat="51.5073219" lon="-0.1276474"
          display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
index 383eef53650db44f68fb66272b0914319e232f98..30e68cff2e614da67e10afe8a32f63002ad0a1ac 100644 (file)
@@ -1,14 +1,3 @@
-### Nominatim API
-
-!!! Attention
-    The current version of Nominatim implements two different search frontends:
-    the old PHP frontend and the new Python frontend. They have a very similar
-    API but differ in some implementation details. These are marked in the
-    documentation as `[Python-only]` or `[PHP-only]`.
-
-    `https://nominatim.openstreetmap.org` implements the **Python frontend**.
-    So users should refer to the **`[Python-only]`** comments.
-
 This section describes the API V1 of the Nominatim web service. The
 service offers the following endpoints:
 
index 67306fa83bc1c3d9cfa57ea78b3a00a88cbbb174..e646818f0e4fd0614451cb9989ddcde5d728d760 100644 (file)
@@ -32,10 +32,9 @@ projection. The API returns exactly one result or an error when the coordinate
 is in an area with no OSM data coverage.
 
 
-!!! danger "Deprecation warning"
-    The reverse API used to allow address lookup for a single OSM object by
-    its OSM id for `[PHP-only]`. The use is considered deprecated.
-    Use the [Address Lookup API](Lookup.md) instead.
+!!! tip
+    The reverse API allows a lookup of object by coordinate. If you want
+    to look up an object by ID, use the [Address Lookup API](Lookup.md) instead.
 
 !!! danger "Deprecation warning"
     The API can also be used with the URL
@@ -149,8 +148,6 @@ In terms of address details the zoom levels are as follows:
 |-----------| ----- | ------- |
 | layer     | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
 
-**`[Python-only]`**
-
 The layer filter allows to select places by themes.
 
 The `address` layer contains all places that make up an address:
index f1b4b8a09c0458b18e2d2bde13acb6f78ff67062..1c2691689273333e38a946178ea15f4833e89c09 100644 (file)
@@ -187,8 +187,6 @@ also excluded when the filter is set.
 |-----------| ----- | ------- |
 | layer     | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
 
-**`[Python-only]`**
-
 The layer filter allows to select places by themes.
 
 The `address` layer contains all places that make up an address:
index eb548e10c460560ea3d9c29ef4870f625a9dc2e4..003e56e383034da603b702a499bf2a6e875ccbe8 100644 (file)
@@ -191,7 +191,7 @@ They take _key match lists_ for main and extra names respectively.
 Address tags will be used to build up the address of an object.
 
 `set_address_tags()` takes a table with arbitrary fields pointing to
-_key match lists_. To fields have a special meaning:
+_key match lists_. Two fields have a special meaning:
 
 * __main__: defines
 the tags that make a full address object out of the OSM object. This
@@ -218,7 +218,7 @@ are accepted, all other values are discarded.
         main = {'addr:housenumber'},
         extra = {'addr:*'},
         postcode = {'postal_code', 'postcode', 'addr:postcode'},
-        country = {'country-code', 'ISO3166-1'}
+        country = {'country_code', 'ISO3166-1'}
     }
     ```
 
@@ -227,7 +227,7 @@ are accepted, all other values are discarded.
     object will fall back to be entered as a `place=house` in the database
     unless there is another interested main tag to be found.
 
-    Tags with keys `country-code` and `ISO3166-1` are saved with their
+    Tags with keys `country_code` and `ISO3166-1` are saved with their
     value under `country` in the address tag list. The same thing happens
     to postcodes, they will always be saved under the key `postcode` thus
     normalizing the multitude of keys that are used in the OSM database.
index 8245e309f1a476f0fba05d1df857002ddd6e06c3..b00d04cf6386bb3aa41b2d6f0409e0e043128239 100644 (file)
@@ -57,34 +57,13 @@ parameter that is understood by libpq. See the [Postgres documentation](https://
 | **After Changes:** | cannot be changed after import |
 
 Defines the name of the database user that will run search queries. Usually
-this is the user under which the webserver is executed. When running Nominatim
-via php-fpm, you can also define a separate query user. The Postgres user
+this is the user under which the webserver is executed. The Postgres user
 needs to be set up before starting the import.
 
 Nominatim grants minimal rights to this user to all tables that are needed
 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            |                                                     |
@@ -115,20 +94,6 @@ 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
 
@@ -163,25 +128,6 @@ 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            |                                                     |
@@ -544,38 +490,6 @@ 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` |
-| **Comment:**       | PHP frontend only |
-
-
-This feature is currently undocumented and potentially broken.
-
-
-#### NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD
-
-| Summary            |                                                     |
-| --------------     | --------------------------------------------------- |
-| **Description:**   | Threshold for switching the search index lookup strategy |
-| **Format:**        | integer |
-| **Default:**       | 500 |
-| **After Changes:** | run `nominatim refresh --website` |
-| **Comment:**       | PHP frontend only |
-
-This setting defines the threshold over which a name is no longer considered
-as rare. When searching for places with rare names, only the name is used
-for place lookups. Otherwise the name and any address information is used.
-
-This setting only has an effect after `nominatim refresh --word-counts` has
-been called to compute the word frequencies.
-
-
 #### NOMINATIM_LOOKUP_MAX_COUNT
 
 | Summary            |                                                     |
@@ -616,7 +530,6 @@ Setting this parameter to 0 disables polygon output completely.
 | **Format:**        | boolean |
 | **Default:**       | no |
 | **After Changes:** | run `nominatim refresh --website` |
-| **Comment:**       | PHP frontend only |
 
 Enable to search elements just within countries.
 
@@ -728,7 +641,8 @@ The entries in the log file have the following format:
     <request time> <execution time in s> <number of results> <type> "<query string>"
 
 Request time is the time when the request was started. The execution time is
-given in seconds and corresponds to the time the query took executing in PHP.
+given in seconds and includes the entire time the query was queued and executed
+in the frontend.
 type contains the name of the endpoint used.
 
 Can be used as the same time as NOMINATIM_LOG_DB.
index 49e86a5009289cea7f12aea36202abbda1548737..30be170edea91babfa271d47364d123d84f9fdf0 100644 (file)
@@ -15,53 +15,6 @@ they can be configured.
     chosen tokenizer is very limited as well. See the comments in each tokenizer
     section.
 
-## Legacy tokenizer
-
-!!! danger
-    The Legacy tokenizer is deprecated and will be removed in Nominatim 5.0.
-    If you still use a database with the legacy tokenizer, you must reimport
-    it using the ICU tokenizer below.
-
-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 automatically installed and used when upgrading an older
-database. It should not be used for new installations anymore.
-
-### Compiling the PostgreSQL module
-
-The tokeinzer needs a special C module for PostgreSQL which is not compiled
-by default. If you need the legacy tokenizer, compile Nominatim as follows:
-
-```
-mkdir build
-cd build
-cmake -DBUILD_MODULE=on
-make
-```
-
-### Enabling the tokenizer
-
-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#using-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
index fd7820c6abef5a0be686c6477572a51db22b7ec5..441556ff206db92d69f38e929f94ad1555d2f193 100644 (file)
@@ -26,12 +26,9 @@ following packages should get you started:
 ## Prerequisites for testing and documentation
 
 The Nominatim test suite consists of behavioural tests (using behave) and
-unit tests (using PHPUnit for PHP code and pytest for Python code).
-It has the following additional requirements:
+unit tests (using pytest). It has the following additional requirements:
 
 * [behave test framework](https://behave.readthedocs.io) >= 1.2.6
-* [phpunit](https://phpunit.de) (9.5 is known to work)
-* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)
 * [Pylint](https://pylint.org/) (CI always runs the latest version from pip)
 * [mypy](http://mypy-lang.org/) (plus typing information for external libs)
 * [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9)
@@ -63,7 +60,7 @@ The easiest way, to handle these Python dependencies is to run your
 development from within a virtual environment.
 
 ```sh
-sudo apt install libsqlite3-mod-spatialite php-cli
+sudo apt install libsqlite3-mod-spatialite
 ```
 
 To set up the virtual environment with all necessary packages run:
@@ -86,28 +83,6 @@ Now enter the virtual environment whenever you want to develop:
 . ~/nominatim-dev-venv/bin/activate
 ```
 
-For installing the PHP development tools, run:
-
-```sh
-sudo apt install php-cgi phpunit php-codesniffer
-```
-
-If your distribution does not have PHPUnit 7.3+, you can install it (as well
-as CodeSniffer) via composer:
-
-```
-sudo apt-get install composer
-composer global require "squizlabs/php_codesniffer=*"
-composer global require "phpunit/phpunit=8.*"
-```
-
-The binaries are found in `.config/composer/vendor/bin`. You need to add this
-to your PATH:
-
-```
-echo 'export PATH=~/.config/composer/vendor/bin:$PATH' > ~/.profile
-```
-
 ### Running Nominatim during development
 
 The source code for Nominatim can be found in the `src` directory and can
index c220f4e4e1f80f9a3ee6cbb5473ff004b1a18384..12673d403aa5f48297ce6999741221dca1bd6d3e 100644 (file)
@@ -8,7 +8,7 @@ the tests, see the [Development setup chapter](Development-Environment.md).
 
 There are two kind of tests in this test suite. There are functional tests
 which test the API interface using a BDD test framework and there are unit
-tests for specific PHP functions.
+tests for the Python code.
 
 This test directory is structured as follows:
 
@@ -20,28 +20,11 @@ This test directory is structured as follows:
   | +-  db         Tests for internal data processing on import and update
   | +-  api        Tests for API endpoints (search, reverse, etc.)
   |
-  +-   php         PHP unit tests
   +-   python      Python unit tests
   +-   testdb      Base data for generating API test database
   +-   testdata    Additional test data used by unit tests
 ```
 
-## PHP Unit Tests (`test/php`)
-
-Unit tests for PHP code can be found in the `php/` directory. They test selected
-PHP functions. Very low coverage.
-
-To execute the test suite run
-
-    cd test/php
-    UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../
-
-It will read phpunit.xml which points to the library, test path, bootstrap
-strip and sets other parameters.
-
-It will use (and destroy) a local database 'nominatim_unit_tests'. You can set
-a different connection string with e.g. UNIT_TEST_DSN='pgsql:dbname=foo_unit_tests'.
-
 ## Python Unit Tests (`test/python`)
 
 Unit tests for Python code can be found in the `python/` directory. The goal is
@@ -89,8 +72,6 @@ The tests can be configured with a set of environment variables (`behave -D key=
  * `DB_PORT` - (optional) port of database on host
  * `DB_USER` - (optional) username of database login
  * `DB_PASS` - (optional) password for database login
- * `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
-                          module shared library file (only needed for legacy tokenizer)
  * `REMOVE_TEMPLATE` - if true, the template and API database will not be reused
                        during the next run. Reusing the base templates speeds
                        up tests considerably but might lead to outdated errors
@@ -118,7 +99,7 @@ and compromises the following data:
  * extract of Autauga country, Alabama, US (for tests against Tiger data)
  * additional data from `test/testdb/additional_api_test.data.osm`
 
-API tests should only be testing the functionality of the website PHP code.
+API tests should only be testing the functionality of the website frontend code.
 Most tests should be formulated as BDD DB creation tests (see below) instead.
 
 ### DB Creation Tests (`test/bdd/db`)
index 03988ce0689ad219017d0d746a3b8ec1dc742b4b..f4a55adcae1252cd72d0be3509583690b9ad446d 100644 (file)
@@ -91,14 +91,9 @@ for a custom tokenizer implementation.
 
 ### Directory Structure
 
-Nominatim expects two files for a tokenizer:
-
-* `nominatim/tokenizer/<NAME>_tokenizer.py` containing the Python part of the
-  implementation
-* `lib-php/tokenizer/<NAME>_tokenizer.php` with the PHP part of the
-  implementation
-
-where `<NAME>` is a unique name for the tokenizer consisting of only lower-case
+Nominatim expects a single file `src/nominatim_db/tokenizer/<NAME>_tokenizer.py`
+containing the Python part of the implementation.
+`<NAME>` is a unique name for the tokenizer consisting of only lower-case
 letters, digits and underscore. A tokenizer also needs to install some SQL
 functions. By convention, these should be placed in `lib-sql/tokenizer`.
 
@@ -282,73 +277,3 @@ permanently. The indexer calls this function when all processing is done and
 replaces the content of the `token_info` column with the returned value before
 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.
-
index b5625a883ceec1ca19ca3fc4376e859089306258..aedce9908c6cf73fdc635719572008c1f0356b14 100644 (file)
@@ -20,5 +20,5 @@ and can be found in the files in the `sql/functions/` directory.
 
 The __search frontend__ implements the actual API. It takes search
 and reverse geocoding queries from the user, looks up the data and
-returns the results in the requested format. This part is written in PHP
-and can be found in the `lib/` and `website/` directories.
+returns the results in the requested format. This part is located in the
+`nominatim-api` package. The source code can be found in `src/nominatim_api`.
index 155fa1aa71fcaf91bbbd66792a87110b206569a9..1decc4789e7d72f31ad9d445e72a488e4516c96b 100644 (file)
@@ -34,3 +34,8 @@ th {
 .md-footer__inner {
     display: none;
 }
+
+.headerlink {
+  filter: grayscale(100%);
+  font-size: 80%;
+}
index e13470e9fceb5a0d5a45f26cd46d2c8d46e55b39..713d1c53a0fdbbfa3cd5ffcbd980ee601651f0e9 100644 (file)
@@ -1,11 +1,13 @@
 # Configuration
 
 When using Nominatim through the library, it can be configured in exactly
-the same way as when running as a service. This means that you should have
-created a [project directory](../admin/Import.md#creating-the-project-directory)
-which contains all files belonging to the Nominatim instance. It can also contain
-an `.env` file with configuration options. Setting configuration parameters
-via environment variables works as well.
+the same way as when running as a service. You may instantiate the library
+against the [project directory](../admin/Import.md#creating-the-project-directory)
+of your Nominatim installation. It contains all files belonging to the
+Nominatim instance. This may include an `.env` file with configuration options.
+Setting configuration parameters via environment variables works as well.
+Alternatively to using the operating system's environment, a set of
+configuration parameters may also be passed to the Nomiantim API object.
 
 Configuration options are resolved in the following order:
 
index 1f5b2baa53fd488f0932c865aa3a8d90d79e9935..9f81724a155888b4e8174935fce372112f8d193f 100644 (file)
@@ -1,16 +1,21 @@
 # Getting Started
 
-The Nominatim search frontend can directly be used as a Python library in
-scripts and applications. When you have imported your own Nominatim database,
-then it is no longer necessary to run a full web service for it and access
-the database through http requests. There are
-also less constraints on the kinds of data that can be accessed. The library
-allows to get access to more detailed information about the objects saved
-in the database.
-
-!!! danger
-    The library interface is currently in an experimental stage. There might
-    be some smaller adjustments to the public interface until the next version.
+The Nominatim search frontend is implemented as a Python library and can as
+such directly be used in Python scripts and applications. You don't need to
+set up a web frontend and access it through HTTP calls. The library gives
+direct access to the Nominatim database through similar search functions as
+offered by the web API. In addition, it will give you a more complete and
+detailed view on the search objects stored in the database.
+
+!!! warning
+
+    The Nominatim library is used for accessing a local Nominatim database.
+    It is not meant to be used against web services of Nominatim like the
+    one on https://nominatim.openstreetmap.org. If you need a Python library
+    to access these web services, have a look at
+    [GeoPy](https://geopy.readthedocs.io). Don't forget to consult the
+    usage policy of the service you want to use before accessing such
+    a web service.
 
 ## Installation
 
@@ -19,13 +24,17 @@ Follow the [installation](../admin/Installation.md) and
 [import](../admin/Import.md) instructions to set up your database.
 
 The Nominatim frontend library is contained in the Python package `nominatim-api`.
+You can install the latest released version directly from pip:
+
+    pip install nominatim-api
+
 To install the package from the source tree directly, run:
 
     pip install packaging/nominatim-api
 
 Usually you would want to run this in a virtual environment.
 
-### A simple search example
+## A simple search example
 
 To query the Nominatim database you need to first set up a connection. This
 is done by creating an Nominatim API object. This object exposes all the
@@ -36,15 +45,13 @@ This code snippet implements a simple search for the town of 'Brugge':
 !!! example
     === "NominatimAPIAsync"
         ``` python
-        from pathlib import Path
         import asyncio
 
         import nominatim_api as napi
 
         async def search(query):
-            api = napi.NominatimAPIAsync(Path('.'))
-
-            return await api.search(query)
+            async with napi.NominatimAPIAsync() as api:
+                return await api.search(query)
 
         results = asyncio.run(search('Brugge'))
         if not results:
@@ -55,13 +62,10 @@ This code snippet implements a simple search for the town of 'Brugge':
 
     === "NominatimAPI"
         ``` python
-        from pathlib import Path
-
         import nominatim_api as napi
 
-        api = napi.NominatimAPI(Path('.'))
-
-        results = api.search('Brugge')
+        with napi.NominatimAPI() as api:
+            results = api.search('Brugge')
 
         if not results:
             print('Cannot find Brugge')
@@ -84,7 +88,7 @@ implementations. The documentation itself will usually refer only to
 available only for the synchronous or asynchronous version, this will be
 explicitly mentioned.
 
-### Defining which database to use
+## Defining which database to use
 
 The [Configuration](../admin/Import.md#configuration-setup-in-env)
 section explains how Nominatim is configured using the
@@ -93,25 +97,65 @@ The same configuration mechanism is used with the
 Nominatim API library. You should therefore be sure you are familiar with
 the section.
 
-The constructor of the 'Nominatim API class' takes one mandatory parameter:
-the path to the [project directory](../admin/Import.md#creating-the-project-directory).
-You should have set up this directory as part of the Nominatim import.
-Any configuration found in the `.env` file in this directory will automatically
-used.
+There are three different ways, how configuration options can be set for
+a 'Nominatim API class'. When you have set up your Nominatim database, you
+have normally created a [project directory](../admin/Import.md#creating-the-project-directory)
+which stores the various configuration and customization files that Nominatim
+needs. You may pass the location of the project directory to your
+'Nominatim API class' constructor and it will read the .env file in the
+directory and set the configuration accordingly. Here is the simple search
+example, using the configuration from a pre-defined project directory in
+`/srv/nominatim-project`:
+
+!!! example
+    === "NominatimAPIAsync"
+        ``` python
+        import asyncio
+
+        import nominatim_api as napi
+
+        async def search(query):
+            async with napi.NominatimAPIAsync('/srv/nominatim-project') as api:
+                return await api.search(query)
+
+        results = asyncio.run(search('Brugge'))
+        if not results:
+            print('Cannot find Brugge')
+        else:
+            print(f'Found a place at {results[0].centroid.x},{results[0].centroid.y}')
+        ```
+
+    === "NominatimAPI"
+        ``` python
+        import nominatim_api as napi
+
+        with napi.NominatimAPI('/srv/nominatim-project') as api:
+            results = api.search('Brugge')
+
+        if not results:
+            print('Cannot find Brugge')
+        else:
+            print(f'Found a place at {results[0].centroid.x},{results[0].centroid.y}')
+        ```
+
 
 You may also configure Nominatim by setting environment variables.
-Normally, Nominatim will check the operating system environment. This can be
-overwritten by giving the constructor a dictionary of configuration parameters.
+Normally Nominatim will check the operating system environment. Lets
+say you want to look up 'Brugge' in the special database named 'belgium' instead of the
+standard 'nominatim' database. You can run the example script above like this:
+
+```
+NOMINATIM_DATABASE_DSN=pgsql:dbname=belgium python3 example.py
+```
 
-Let us look up 'Brugge' in the special database named 'belgium' instead of the
-standard 'nominatim' database:
+The third option to configure the library is to hand in the configuration
+parameters into the 'Nominatim API class'. Changing the database would look
+like this:
 
 !!! example
     === "NominatimAPIAsync"
         ``` python
-        from pathlib import Path
         import asyncio
-
         import nominatim_api as napi
 
         config_params = {
@@ -119,50 +163,54 @@ standard 'nominatim' database:
         }
 
         async def search(query):
-            api = napi.NominatimAPIAsync(Path('.'), environ=config_params)
-
-            return await api.search(query)
+            async with napi.NominatimAPIAsync(environ=config_params) as api:
+                return await api.search(query)
 
         results = asyncio.run(search('Brugge'))
         ```
 
     === "NominatimAPI"
         ``` python
-        from pathlib import Path
-
         import nominatim_api as napi
 
         config_params = {
             'NOMINATIM_DATABASE_DSN': 'pgsql:dbname=belgium'
         }
 
-        api = napi.NominatimAPI(Path('.'), environ=config_params)
-
-        results = api.search('Brugge')
+        with napi.NominatimAPI(environ=config_params) as api:
+            results = api.search('Brugge')
         ```
 
-### Presenting results to humans
+When the `environ` parameter is given, then only configuration variables
+from this dictionary will be used. The operating system's environment
+variables will be ignored.
 
-All search functions return the raw results from the database. There is no
-full human-readable label. To create such a label, you need two things:
+## Presenting results to humans
+
+All search functions return full result objects from the database. Such a
+result object contains lots of details: names, address information, OSM tags etc.
+This gives you lots of flexibility what to do with the results.
+
+One of the most common things to get is some kind of human-readable label
+that describes the result in a compact form. Usually this would be the name
+of the object and some parts of the address to explain where in the world
+it is. To create such a label, you need two things:
 
 * the address details of the place
-* adapt the result to the language you wish to use for display
+* all names for the label adapted to the language you wish to use for display
 
 Again searching for 'Brugge', this time with a nicely formatted result:
 
 !!! example
     === "NominatimAPIAsync"
         ``` python
-        from pathlib import Path
         import asyncio
 
         import nominatim_api as napi
 
         async def search(query):
-            api = napi.NominatimAPIAsync(Path('.'))
-
-            return await api.search(query, address_details=True)
+            async with napi.NominatimAPIAsync() as api:
+                return await api.search(query, address_details=True)
 
         results = asyncio.run(search('Brugge'))
 
@@ -174,13 +222,10 @@ Again searching for 'Brugge', this time with a nicely formatted result:
 
     === "NominatimAPI"
         ``` python
-        from pathlib import Path
-
         import nominatim_api as napi
 
-        api = napi.NominatimAPI(Path('.'))
-
-        results = api.search('Brugge', address_details=True)
+        with napi.NominatimAPI() as api:
+            results = api.search('Brugge', address_details=True)
 
         locale = napi.Locales(['fr', 'en'])
         for i, result in enumerate(results):
@@ -236,7 +281,7 @@ Bruges, Flandre-Occidentale, Flandre, Belgique
 
 This is a fairly simple way to create a human-readable description. The
 place information in `address_rows` contains further information about each
-place. For example, which OSM `adlin_level` was used, what category the place
+place. For example, which OSM `admin_level` was used, what category the place
 belongs to or what rank Nominatim has assigned. Use this to adapt the output
 to local address formats.
 
index 84a40b9b04a963f9f1a64a21a83b7ca219d5f047..9669906122f31fcbfae3b925dcdd0673cbcbf7bb 100644 (file)
@@ -24,12 +24,11 @@ the placex table:
 
 ```
 import asyncio
-from pathlib import Path
 import sqlalchemy as sa
 from nominatim_api import NominatimAPIAsync
 
 async def print_table_size():
-    api = NominatimAPIAsync(Path('.'))
+    api = NominatimAPIAsync()
 
     async with api.begin() as conn:
         cnt = await conn.scalar(sa.select(sa.func.count()).select_from(conn.t.placex))
diff --git a/lib-php/AddressDetails.php b/lib-php/AddressDetails.php
deleted file mode 100644 (file)
index cfdd041..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-/**
- * Detailed list of address parts for a single result
- */
-class AddressDetails
-{
-    private $iPlaceID;
-    private $aAddressLines;
-
-    public function __construct(&$oDB, $iPlaceID, $sHousenumber, $mLangPref)
-    {
-        $this->iPlaceID = $iPlaceID;
-
-        if (is_array($mLangPref)) {
-            $mLangPref = $oDB->getArraySQL($oDB->getDBQuotedList($mLangPref));
-        }
-
-        if (!isset($sHousenumber)) {
-            $sHousenumber = -1;
-        }
-
-        $sSQL = 'SELECT *,';
-        $sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname';
-        $sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')';
-        $sSQL .= ' ORDER BY rank_address DESC, isaddress DESC';
-
-        $this->aAddressLines = $oDB->getAll($sSQL);
-    }
-
-    private static function isAddress($aLine)
-    {
-        return $aLine['isaddress'] || $aLine['type'] == 'country_code';
-    }
-
-    public function getAddressDetails($bAll = false)
-    {
-        if ($bAll) {
-            return $this->aAddressLines;
-        }
-
-        return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress'));
-    }
-
-    public function getLocaleAddress()
-    {
-        $aParts = array();
-        $sPrevResult = '';
-
-        foreach ($this->aAddressLines as $aLine) {
-            if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) {
-                $sPrevResult = $aLine['localname'];
-                $aParts[] = $sPrevResult;
-            }
-        }
-
-        return join(', ', $aParts);
-    }
-
-    public function getAddressNames()
-    {
-        $aAddress = array();
-
-        foreach ($this->aAddressLines as $aLine) {
-            if (!self::isAddress($aLine)) {
-                continue;
-            }
-
-            $sTypeLabel = ClassTypes\getLabelTag($aLine);
-
-            $sName = null;
-            if (isset($aLine['localname']) && $aLine['localname']!=='') {
-                $sName = $aLine['localname'];
-            } elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') {
-                $sName = $aLine['housenumber'];
-            }
-
-            if (isset($sName)
-                && (!isset($aAddress[$sTypeLabel])
-                    || $aLine['class'] == 'place')
-            ) {
-                $aAddress[$sTypeLabel] = $sName;
-
-                if (!empty($aLine['name'])) {
-                    $this->addSubdivisionCode($aAddress, $aLine['admin_level'], $aLine['name']);
-                }
-            }
-        }
-
-        return $aAddress;
-    }
-
-    /**
-     * Annotates the given json with geocodejson address information fields.
-     *
-     * @param array  $aJson  Json hash to add the fields to.
-     *
-     * Geocodejson has the following fields:
-     *  street, locality, postcode, city, district,
-     *  county, state, country
-     *
-     * Postcode and housenumber are added by type, district is not used.
-     * All other fields are set according to address rank.
-     */
-    public function addGeocodeJsonAddressParts(&$aJson)
-    {
-        foreach (array_reverse($this->aAddressLines) as $aLine) {
-            if (!$aLine['isaddress']) {
-                continue;
-            }
-
-            if (!isset($aLine['localname']) || $aLine['localname'] == '') {
-                continue;
-            }
-
-            if ($aLine['type'] == 'postcode' || $aLine['type'] == 'postal_code') {
-                $aJson['postcode'] = $aLine['localname'];
-                continue;
-            }
-
-            if ($aLine['type'] == 'house_number') {
-                $aJson['housenumber'] = $aLine['localname'];
-                continue;
-            }
-
-            if ($this->iPlaceID == $aLine['place_id']) {
-                continue;
-            }
-
-            $iRank = (int)$aLine['rank_address'];
-
-            if ($iRank > 25 && $iRank < 28) {
-                $aJson['street'] = $aLine['localname'];
-            } elseif ($iRank >= 22 && $iRank <= 25) {
-                $aJson['locality'] = $aLine['localname'];
-            } elseif ($iRank >= 17 && $iRank <= 21) {
-                $aJson['district'] = $aLine['localname'];
-            } elseif ($iRank >= 13 && $iRank <= 16) {
-                $aJson['city'] = $aLine['localname'];
-            } elseif ($iRank >= 10 && $iRank <= 12) {
-                $aJson['county'] = $aLine['localname'];
-            } elseif ($iRank >= 5 && $iRank <= 9) {
-                $aJson['state'] = $aLine['localname'];
-            } elseif ($iRank == 4) {
-                $aJson['country'] = $aLine['localname'];
-            }
-        }
-    }
-
-    public function getAdminLevels()
-    {
-        $aAddress = array();
-        foreach (array_reverse($this->aAddressLines) as $aLine) {
-            if (self::isAddress($aLine)
-                && isset($aLine['admin_level'])
-                && $aLine['admin_level'] < 15
-                && !isset($aAddress['level'.$aLine['admin_level']])
-            ) {
-                $aAddress['level'.$aLine['admin_level']] = $aLine['localname'];
-            }
-        }
-        return $aAddress;
-    }
-
-    public function debugInfo()
-    {
-        return $this->aAddressLines;
-    }
-
-    private function addSubdivisionCode(&$aAddress, $iAdminLevel, $nameDetails)
-    {
-        if (is_string($nameDetails)) {
-            $nameDetails = json_decode('{' . str_replace('"=>"', '":"', $nameDetails) . '}', true);
-        }
-        if (!empty($nameDetails['ISO3166-2'])) {
-            $aAddress["ISO3166-2-lvl$iAdminLevel"] = $nameDetails['ISO3166-2'];
-        }
-    }
-}
diff --git a/lib-php/ClassTypes.php b/lib-php/ClassTypes.php
deleted file mode 100644 (file)
index 0561f48..0000000
+++ /dev/null
@@ -1,576 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\ClassTypes;
-
-/**
- * Create a label tag for the given place that can be used as an XML name.
- *
- * @param array[] $aPlace  Information about the place to label.
- *
- * A label tag groups various object types together under a common
- * label. The returned value is lower case and has no spaces
- */
-function getLabelTag($aPlace, $sCountry = null)
-{
-    $iRank = (int) ($aPlace['rank_address'] ?? 30);
-    $sLabel;
-    if (isset($aPlace['place_type'])) {
-        $sLabel = $aPlace['place_type'];
-    } elseif ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
-        $sLabel = getBoundaryLabel($iRank/2, $sCountry);
-    } elseif ($aPlace['type'] == 'postal_code') {
-        $sLabel = 'postcode';
-    } elseif ($iRank < 26) {
-        $sLabel = $aPlace['type'];
-    } elseif ($iRank < 28) {
-        $sLabel = 'road';
-    } elseif ($aPlace['class'] == 'place'
-            && ($aPlace['type'] == 'house_number' ||
-                $aPlace['type'] == 'house_name' ||
-                $aPlace['type'] == 'country_code')
-    ) {
-        $sLabel = $aPlace['type'];
-    } else {
-        $sLabel = $aPlace['class'];
-    }
-
-    return strtolower(str_replace(' ', '_', $sLabel));
-}
-
-/**
- * Create a label for the given place.
- *
- * @param array[] $aPlace  Information about the place to label.
- */
-function getLabel($aPlace, $sCountry = null)
-{
-    if (isset($aPlace['place_type'])) {
-        return ucwords(str_replace('_', ' ', $aPlace['place_type']));
-    }
-
-    if ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
-        return getBoundaryLabel(($aPlace['rank_address'] ?? 30)/2, $sCountry ?? null);
-    }
-
-    // Return a label only for 'important' class/type combinations
-    if (getImportance($aPlace) !== null) {
-        return ucwords(str_replace('_', ' ', $aPlace['type']));
-    }
-
-    return null;
-}
-
-
-/**
- * Return a simple label for an administrative boundary for the given country.
- *
- * @param int $iAdminLevel   Content of admin_level tag.
- * @param string $sCountry   Country code of the country where the object is
- *                           in. May be null, in which case a world-wide
- *                           fallback is used.
- * @param string $sFallback  String to return if no explicit string is listed.
- *
- * @return string
- */
-function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative')
-{
-    static $aBoundaryList = array (
-                             'default' => array (
-                                           1 => 'Continent',
-                                           2 => 'Country',
-                                           3 => 'Region',
-                                           4 => 'State',
-                                           5 => 'State District',
-                                           6 => 'County',
-                                           7 => 'Municipality',
-                                           8 => 'City',
-                                           9 => 'City District',
-                                           10 => 'Suburb',
-                                           11 => 'Neighbourhood',
-                                           12 => 'City Block'
-                                          ),
-                             'no' => array (
-                                      3 => 'State',
-                                      4 => 'County'
-                                     ),
-                             'se' => array (
-                                      3 => 'State',
-                                      4 => 'County'
-                                     )
-            );
-
-    if (isset($aBoundaryList[$sCountry])
-        && isset($aBoundaryList[$sCountry][$iAdminLevel])
-    ) {
-        return $aBoundaryList[$sCountry][$iAdminLevel];
-    }
-
-    return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback;
-}
-
-/**
- * Return an estimated radius of how far the object node extends.
- *
- * @param array[] $aPlace  Information about the place. This must be a node
- *                         feature.
- *
- * @return float  The radius around the feature in degrees.
- */
-function getDefRadius($aPlace)
-{
-    $aSpecialRadius = array(
-                       'place:continent' => 25,
-                       'place:country' => 7,
-                       'place:state' => 2.6,
-                       'place:province' => 2.6,
-                       'place:region' => 1.0,
-                       'place:county' => 0.7,
-                       'place:city' => 0.16,
-                       'place:municipality' => 0.16,
-                       'place:island' => 0.32,
-                       'place:postcode' => 0.16,
-                       'place:town' => 0.04,
-                       'place:village' => 0.02,
-                       'place:hamlet' => 0.02,
-                       'place:district' => 0.02,
-                       'place:borough' => 0.02,
-                       'place:suburb' => 0.02,
-                       'place:locality' => 0.01,
-                       'place:neighbourhood'=> 0.01,
-                       'place:quarter' => 0.01,
-                       'place:city_block' => 0.01,
-                       'landuse:farm' => 0.01,
-                       'place:farm' => 0.01,
-                       'place:airport' => 0.015,
-                       'aeroway:aerodrome' => 0.015,
-                       'railway:station' => 0.005
-           );
-
-    $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
-    return $aSpecialRadius[$sClassPlace] ?? 0.00005;
-}
-
-/**
- * Get the icon to use with the given object.
- */
-function getIcon($aPlace)
-{
-    $aIcons = array(
-               'boundary:administrative' => 'poi_boundary_administrative',
-               'place:city' => 'poi_place_city',
-               'place:town' => 'poi_place_town',
-               'place:village' => 'poi_place_village',
-               'place:hamlet' => 'poi_place_village',
-               'place:suburb' => 'poi_place_village',
-               'place:locality' => 'poi_place_village',
-               'place:airport' => 'transport_airport2',
-               'aeroway:aerodrome' => 'transport_airport2',
-               'railway:station' => 'transport_train_station2',
-               'amenity:place_of_worship' => 'place_of_worship_unknown3',
-               'amenity:pub' => 'food_pub',
-               'amenity:bar' => 'food_bar',
-               'amenity:university' => 'education_university',
-               'tourism:museum' => 'tourist_museum',
-               'amenity:arts_centre' => 'tourist_art_gallery2',
-               'tourism:zoo' => 'tourist_zoo',
-               'tourism:theme_park' => 'poi_point_of_interest',
-               'tourism:attraction' => 'poi_point_of_interest',
-               'leisure:golf_course' => 'sport_golf',
-               'historic:castle' => 'tourist_castle',
-               'amenity:hospital' => 'health_hospital',
-               'amenity:school' => 'education_school',
-               'amenity:theatre' => 'tourist_theatre',
-               'amenity:library' => 'amenity_library',
-               'amenity:fire_station' => 'amenity_firestation3',
-               'amenity:police' => 'amenity_police2',
-               'amenity:bank' => 'money_bank2',
-               'amenity:post_office' => 'amenity_post_office',
-               'tourism:hotel' => 'accommodation_hotel2',
-               'amenity:cinema' => 'tourist_cinema',
-               'tourism:artwork' => 'tourist_art_gallery2',
-               'historic:archaeological_site' => 'tourist_archaeological2',
-               'amenity:doctors' => 'health_doctors',
-               'leisure:sports_centre' => 'sport_leisure_centre',
-               'leisure:swimming_pool' => 'sport_swimming_outdoor',
-               'shop:supermarket' => 'shopping_supermarket',
-               'shop:convenience' => 'shopping_convenience',
-               'amenity:restaurant' => 'food_restaurant',
-               'amenity:fast_food' => 'food_fastfood',
-               'amenity:cafe' => 'food_cafe',
-               'tourism:guest_house' => 'accommodation_bed_and_breakfast',
-               'amenity:pharmacy' => 'health_pharmacy_dispensing',
-               'amenity:fuel' => 'transport_fuel',
-               'natural:peak' => 'poi_peak',
-               'natural:wood' => 'landuse_coniferous_and_deciduous',
-               'shop:bicycle' => 'shopping_bicycle',
-               'shop:clothes' => 'shopping_clothes',
-               'shop:hairdresser' => 'shopping_hairdresser',
-               'shop:doityourself' => 'shopping_diy',
-               'shop:estate_agent' => 'shopping_estateagent2',
-               'shop:car' => 'shopping_car',
-               'shop:garden_centre' => 'shopping_garden_centre',
-               'shop:car_repair' => 'shopping_car_repair',
-               'shop:bakery' => 'shopping_bakery',
-               'shop:butcher' => 'shopping_butcher',
-               'shop:apparel' => 'shopping_clothes',
-               'shop:laundry' => 'shopping_laundrette',
-               'shop:beverages' => 'shopping_alcohol',
-               'shop:alcohol' => 'shopping_alcohol',
-               'shop:optician' => 'health_opticians',
-               'shop:chemist' => 'health_pharmacy',
-               'shop:gallery' => 'tourist_art_gallery2',
-               'shop:jewelry' => 'shopping_jewelry',
-               'tourism:information' => 'amenity_information',
-               'historic:ruins' => 'tourist_ruin',
-               'amenity:college' => 'education_school',
-               'historic:monument' => 'tourist_monument',
-               'historic:memorial' => 'tourist_monument',
-               'historic:mine' => 'poi_mine',
-               'tourism:caravan_site' => 'accommodation_caravan_park',
-               'amenity:bus_station' => 'transport_bus_station',
-               'amenity:atm' => 'money_atm2',
-               'tourism:viewpoint' => 'tourist_view_point',
-               'tourism:guesthouse' => 'accommodation_bed_and_breakfast',
-               'railway:tram' => 'transport_tram_stop',
-               'amenity:courthouse' => 'amenity_court',
-               'amenity:recycling' => 'amenity_recycling',
-               'amenity:dentist' => 'health_dentist',
-               'natural:beach' => 'tourist_beach',
-               'railway:tram_stop' => 'transport_tram_stop',
-               'amenity:prison' => 'amenity_prison',
-               'highway:bus_stop' => 'transport_bus_stop2'
-    );
-
-    $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
-    return $aIcons[$sClassPlace] ?? null;
-}
-
-/**
- * Get an icon for the given object with its full URL.
- */
-function getIconFile($aPlace)
-{
-    if (CONST_MapIcon_URL === false) {
-        return null;
-    }
-
-    $sIcon = getIcon($aPlace);
-
-    if (!isset($sIcon)) {
-        return null;
-    }
-
-    return CONST_MapIcon_URL.'/'.$sIcon.'.p.20.png';
-}
-
-/**
- * Return a class importance value for the given place.
- *
- * @param array[] $aPlace  Information about the place.
- *
- * @return int  An importance value. The lower the value, the more
- *              important the class.
- */
-function getImportance($aPlace)
-{
-    static $aWithImportance = null;
-
-    if ($aWithImportance === null) {
-        $aWithImportance = array_flip(array(
-                                           'boundary:administrative',
-                                           'place:country',
-                                           'place:state',
-                                           'place:province',
-                                           'place:county',
-                                           'place:city',
-                                           'place:region',
-                                           'place:island',
-                                           'place:town',
-                                           'place:village',
-                                           'place:hamlet',
-                                           'place:suburb',
-                                           'place:locality',
-                                           'landuse:farm',
-                                           'place:farm',
-                                           'highway:motorway_junction',
-                                           'highway:motorway',
-                                           'highway:trunk',
-                                           'highway:primary',
-                                           'highway:secondary',
-                                           'highway:tertiary',
-                                           'highway:residential',
-                                           'highway:unclassified',
-                                           'highway:living_street',
-                                           'highway:service',
-                                           'highway:track',
-                                           'highway:road',
-                                           'highway:byway',
-                                           'highway:bridleway',
-                                           'highway:cycleway',
-                                           'highway:pedestrian',
-                                           'highway:footway',
-                                           'highway:steps',
-                                           'highway:motorway_link',
-                                           'highway:trunk_link',
-                                           'highway:primary_link',
-                                           'landuse:industrial',
-                                           'landuse:residential',
-                                           'landuse:retail',
-                                           'landuse:commercial',
-                                           'place:airport',
-                                           'aeroway:aerodrome',
-                                           'railway:station',
-                                           'amenity:place_of_worship',
-                                           'amenity:pub',
-                                           'amenity:bar',
-                                           'amenity:university',
-                                           'tourism:museum',
-                                           'amenity:arts_centre',
-                                           'tourism:zoo',
-                                           'tourism:theme_park',
-                                           'tourism:attraction',
-                                           'leisure:golf_course',
-                                           'historic:castle',
-                                           'amenity:hospital',
-                                           'amenity:school',
-                                           'amenity:theatre',
-                                           'amenity:public_building',
-                                           'amenity:library',
-                                           'amenity:townhall',
-                                           'amenity:community_centre',
-                                           'amenity:fire_station',
-                                           'amenity:police',
-                                           'amenity:bank',
-                                           'amenity:post_office',
-                                           'leisure:park',
-                                           'amenity:park',
-                                           'landuse:park',
-                                           'landuse:recreation_ground',
-                                           'tourism:hotel',
-                                           'tourism:motel',
-                                           'amenity:cinema',
-                                           'tourism:artwork',
-                                           'historic:archaeological_site',
-                                           'amenity:doctors',
-                                           'leisure:sports_centre',
-                                           'leisure:swimming_pool',
-                                           'shop:supermarket',
-                                           'shop:convenience',
-                                           'amenity:restaurant',
-                                           'amenity:fast_food',
-                                           'amenity:cafe',
-                                           'tourism:guest_house',
-                                           'amenity:pharmacy',
-                                           'amenity:fuel',
-                                           'natural:peak',
-                                           'waterway:waterfall',
-                                           'natural:wood',
-                                           'natural:water',
-                                           'landuse:forest',
-                                           'landuse:cemetery',
-                                           'landuse:allotments',
-                                           'landuse:farmyard',
-                                           'railway:rail',
-                                           'waterway:canal',
-                                           'waterway:river',
-                                           'waterway:stream',
-                                           'shop:bicycle',
-                                           'shop:clothes',
-                                           'shop:hairdresser',
-                                           'shop:doityourself',
-                                           'shop:estate_agent',
-                                           'shop:car',
-                                           'shop:garden_centre',
-                                           'shop:car_repair',
-                                           'shop:newsagent',
-                                           'shop:bakery',
-                                           'shop:furniture',
-                                           'shop:butcher',
-                                           'shop:apparel',
-                                           'shop:electronics',
-                                           'shop:department_store',
-                                           'shop:books',
-                                           'shop:yes',
-                                           'shop:outdoor',
-                                           'shop:mall',
-                                           'shop:florist',
-                                           'shop:charity',
-                                           'shop:hardware',
-                                           'shop:laundry',
-                                           'shop:shoes',
-                                           'shop:beverages',
-                                           'shop:dry_cleaning',
-                                           'shop:carpet',
-                                           'shop:computer',
-                                           'shop:alcohol',
-                                           'shop:optician',
-                                           'shop:chemist',
-                                           'shop:gallery',
-                                           'shop:mobile_phone',
-                                           'shop:sports',
-                                           'shop:jewelry',
-                                           'shop:pet',
-                                           'shop:beauty',
-                                           'shop:stationery',
-                                           'shop:shopping_centre',
-                                           'shop:general',
-                                           'shop:electrical',
-                                           'shop:toys',
-                                           'shop:jeweller',
-                                           'shop:betting',
-                                           'shop:household',
-                                           'shop:travel_agency',
-                                           'shop:hifi',
-                                           'amenity:shop',
-                                           'tourism:information',
-                                           'place:house',
-                                           'place:house_name',
-                                           'place:house_number',
-                                           'place:country_code',
-                                           'leisure:pitch',
-                                           'highway:unsurfaced',
-                                           'historic:ruins',
-                                           'amenity:college',
-                                           'historic:monument',
-                                           'railway:subway',
-                                           'historic:memorial',
-                                           'leisure:nature_reserve',
-                                           'leisure:common',
-                                           'waterway:lock_gate',
-                                           'natural:fell',
-                                           'amenity:nightclub',
-                                           'highway:path',
-                                           'leisure:garden',
-                                           'landuse:reservoir',
-                                           'leisure:playground',
-                                           'leisure:stadium',
-                                           'historic:mine',
-                                           'natural:cliff',
-                                           'tourism:caravan_site',
-                                           'amenity:bus_station',
-                                           'amenity:kindergarten',
-                                           'highway:construction',
-                                           'amenity:atm',
-                                           'amenity:emergency_phone',
-                                           'waterway:lock',
-                                           'waterway:riverbank',
-                                           'natural:coastline',
-                                           'tourism:viewpoint',
-                                           'tourism:hostel',
-                                           'tourism:bed_and_breakfast',
-                                           'railway:halt',
-                                           'railway:platform',
-                                           'railway:tram',
-                                           'amenity:courthouse',
-                                           'amenity:recycling',
-                                           'amenity:dentist',
-                                           'natural:beach',
-                                           'place:moor',
-                                           'amenity:grave_yard',
-                                           'waterway:drain',
-                                           'landuse:grass',
-                                           'landuse:village_green',
-                                           'natural:bay',
-                                           'railway:tram_stop',
-                                           'leisure:marina',
-                                           'highway:stile',
-                                           'natural:moor',
-                                           'railway:light_rail',
-                                           'railway:narrow_gauge',
-                                           'natural:land',
-                                           'amenity:village_hall',
-                                           'waterway:dock',
-                                           'amenity:veterinary',
-                                           'landuse:brownfield',
-                                           'leisure:track',
-                                           'railway:historic_station',
-                                           'landuse:construction',
-                                           'amenity:prison',
-                                           'landuse:quarry',
-                                           'amenity:telephone',
-                                           'highway:traffic_signals',
-                                           'natural:heath',
-                                           'historic:house',
-                                           'amenity:social_club',
-                                           'landuse:military',
-                                           'amenity:health_centre',
-                                           'historic:building',
-                                           'amenity:clinic',
-                                           'highway:services',
-                                           'amenity:ferry_terminal',
-                                           'natural:marsh',
-                                           'natural:hill',
-                                           'highway:raceway',
-                                           'amenity:taxi',
-                                           'amenity:take_away',
-                                           'amenity:car_rental',
-                                           'place:islet',
-                                           'amenity:nursery',
-                                           'amenity:nursing_home',
-                                           'amenity:toilets',
-                                           'amenity:hall',
-                                           'waterway:boatyard',
-                                           'highway:mini_roundabout',
-                                           'historic:manor',
-                                           'tourism:chalet',
-                                           'amenity:bicycle_parking',
-                                           'amenity:hotel',
-                                           'waterway:weir',
-                                           'natural:wetland',
-                                           'natural:cave_entrance',
-                                           'amenity:crematorium',
-                                           'tourism:picnic_site',
-                                           'landuse:wood',
-                                           'landuse:basin',
-                                           'natural:tree',
-                                           'leisure:slipway',
-                                           'landuse:meadow',
-                                           'landuse:piste',
-                                           'amenity:care_home',
-                                           'amenity:club',
-                                           'amenity:medical_centre',
-                                           'historic:roman_road',
-                                           'historic:fort',
-                                           'railway:subway_entrance',
-                                           'historic:yes',
-                                           'highway:gate',
-                                           'leisure:fishing',
-                                           'historic:museum',
-                                           'amenity:car_wash',
-                                           'railway:level_crossing',
-                                           'leisure:bird_hide',
-                                           'natural:headland',
-                                           'tourism:apartments',
-                                           'amenity:shopping',
-                                           'natural:scrub',
-                                           'natural:fen',
-                                           'building:yes',
-                                           'mountain_pass:yes',
-                                           'amenity:parking',
-                                           'highway:bus_stop',
-                                           'place:postcode',
-                                           'amenity:post_box',
-                                           'place:houses',
-                                           'railway:preserved',
-                                           'waterway:derelict_canal',
-                                           'amenity:dead_pub',
-                                           'railway:disused_station',
-                                           'railway:abandoned',
-                                           'railway:disused'
-                ));
-    }
-
-    $sClassPlace = $aPlace['class'].':'.$aPlace['type'];
-
-    return $aWithImportance[$sClassPlace] ?? null;
-}
diff --git a/lib-php/DB.php b/lib-php/DB.php
deleted file mode 100644 (file)
index 553d945..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/DatabaseError.php');
-
-/**
- * Uses PDO to access the database specified in the CONST_Database_DSN
- * setting.
- */
-class DB
-{
-    protected $connection;
-
-    public function __construct($sDSN = null)
-    {
-        $this->sDSN = $sDSN ?? getSetting('DATABASE_DSN');
-    }
-
-    public function connect($bNew = false, $bPersistent = true)
-    {
-        if (isset($this->connection) && !$bNew) {
-            return true;
-        }
-        $aConnOptions = array(
-                         \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
-                         \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
-                         \PDO::ATTR_PERSISTENT         => $bPersistent
-        );
-
-        // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
-        try {
-            $this->connection = new \PDO($this->sDSN, null, null, $aConnOptions);
-        } catch (\PDOException $e) {
-            $sMsg = 'Failed to establish database connection:' . $e->getMessage();
-            throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage());
-        }
-
-        $this->connection->exec("SET DateStyle TO 'sql,european'");
-        $this->connection->exec("SET client_encoding TO 'utf-8'");
-        // Disable JIT and parallel workers. They interfere badly with search SQL.
-        $this->connection->exec('SET max_parallel_workers_per_gather TO 0');
-        if ($this->getPostgresVersion() >= 11) {
-            $this->connection->exec('SET jit_above_cost TO -1');
-        }
-        
-        $iMaxExecution = ini_get('max_execution_time');
-        if ($iMaxExecution > 0) {
-            $this->connection->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
-        }
-
-        return true;
-    }
-
-    // returns the number of rows that were modified or deleted by the SQL
-    // statement. If no rows were affected returns 0.
-    public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        $val = null;
-        try {
-            if (isset($aInputVars)) {
-                $stmt = $this->connection->prepare($sSQL);
-                $stmt->execute($aInputVars);
-            } else {
-                $val = $this->connection->exec($sSQL);
-            }
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $val;
-    }
-
-    /**
-     * Executes query. Returns first row as array.
-     * Returns false if no result found.
-     *
-     * @param string  $sSQL
-     *
-     * @return array[]
-     */
-    public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        try {
-            $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-            $row = $stmt->fetch();
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $row;
-    }
-
-    /**
-     * Executes query. Returns first value of first result.
-     * Returns false if no results found.
-     *
-     * @param string  $sSQL
-     *
-     * @return array[]
-     */
-    public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        try {
-            $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-            $row = $stmt->fetch(\PDO::FETCH_NUM);
-            if ($row === false) {
-                return false;
-            }
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $row[0];
-    }
-
-    /**
-     * Executes query. Returns array of results (arrays).
-     * Returns empty array if no results found.
-     *
-     * @param string  $sSQL
-     *
-     * @return array[]
-     */
-    public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        try {
-            $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-            $rows = $stmt->fetchAll();
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $rows;
-    }
-
-    /**
-     * Executes query. Returns array of the first value of each result.
-     * Returns empty array if no results found.
-     *
-     * @param string  $sSQL
-     *
-     * @return array[]
-     */
-    public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        $aVals = array();
-        try {
-            $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-
-            while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
-                $aVals[] = $val;
-            }
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $aVals;
-    }
-
-    /**
-     * Executes query. Returns associate array mapping first value to second value of each result.
-     * Returns empty array if no results found.
-     *
-     * @param string  $sSQL
-     *
-     * @return array[]
-     */
-    public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        try {
-            $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
-
-            $aList = array();
-            while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
-                $aList[$aRow[0]] = $aRow[1];
-            }
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $aList;
-    }
-
-    /**
-     * Executes query. Returns a PDO statement to iterate over.
-     *
-     * @param string  $sSQL
-     *
-     * @return PDOStatement
-     */
-    public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
-    {
-        try {
-            if (isset($aInputVars)) {
-                $stmt = $this->connection->prepare($sSQL);
-                $stmt->execute($aInputVars);
-            } else {
-                $stmt = $this->connection->query($sSQL);
-            }
-        } catch (\PDOException $e) {
-            throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
-        }
-        return $stmt;
-    }
-
-    /**
-     * St. John's Way => 'St. John\'s Way'
-     *
-     * @param string  $sVal  Text to be quoted.
-     *
-     * @return string
-     */
-    public function getDBQuoted($sVal)
-    {
-        return $this->connection->quote($sVal);
-    }
-
-    /**
-     * Like getDBQuoted, but takes an array.
-     *
-     * @param array  $aVals  List of text to be quoted.
-     *
-     * @return array[]
-     */
-    public function getDBQuotedList($aVals)
-    {
-        return array_map(function ($sVal) {
-            return $this->getDBQuoted($sVal);
-        }, $aVals);
-    }
-
-    /**
-     * [1,2,'b'] => 'ARRAY[1,2,'b']''
-     *
-     * @param array  $aVals  List of text to be quoted.
-     *
-     * @return string
-     */
-    public function getArraySQL($a)
-    {
-        return 'ARRAY['.join(',', $a).']';
-    }
-
-    /**
-     * Check if a table exists in the database. Returns true if it does.
-     *
-     * @param string  $sTableName
-     *
-     * @return boolean
-     */
-    public function tableExists($sTableName)
-    {
-        $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
-        return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
-    }
-
-    /**
-     * Deletes a table. Returns true if deleted or didn't exist.
-     *
-     * @param string  $sTableName
-     *
-     * @return boolean
-     */
-    public function deleteTable($sTableName)
-    {
-        return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
-    }
-
-    /**
-     * Tries to connect to the database but on failure doesn't throw an exception.
-     *
-     * @return boolean
-     */
-    public function checkConnection()
-    {
-        $bExists = true;
-        try {
-            $this->connect(true);
-        } catch (\Nominatim\DatabaseError $e) {
-            $bExists = false;
-        }
-        return $bExists;
-    }
-
-    /**
-     * e.g. 9.6, 10, 11.2
-     *
-     * @return float
-     */
-    public function getPostgresVersion()
-    {
-        $sVersionString = $this->getOne('SHOW server_version_num');
-        preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
-        return (float) ($aMatches[1].'.'.$aMatches[2]);
-    }
-
-    /**
-     * e.g. 2, 2.2
-     *
-     * @return float
-     */
-    public function getPostgisVersion()
-    {
-        $sVersionString = $this->getOne('select postgis_lib_version()');
-        preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
-        return (float) ($aMatches[1].'.'.$aMatches[2]);
-    }
-
-    /**
-     * Returns an associate array of postgresql database connection settings. Keys can
-     * be 'database', 'hostspec', 'port', 'username', 'password'.
-     * Returns empty array on failure, thus check if at least 'database' is set.
-     *
-     * @return array[]
-     */
-    public static function parseDSN($sDSN)
-    {
-        // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
-        $aInfo = array();
-        if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
-            foreach (explode(';', $aMatches[1]) as $sKeyVal) {
-                list($sKey, $sVal) = explode('=', $sKeyVal, 2);
-                if ($sKey == 'host') {
-                    $sKey = 'hostspec';
-                } elseif ($sKey == 'dbname') {
-                    $sKey = 'database';
-                } elseif ($sKey == 'user') {
-                    $sKey = 'username';
-                }
-                $aInfo[$sKey] = $sVal;
-            }
-        }
-        return $aInfo;
-    }
-
-    /**
-     * Takes an array of settings and return the DNS string. Key names can be
-     * 'database', 'hostspec', 'port', 'username', 'password' but aliases
-     * 'dbname', 'host' and 'user' are also supported.
-     *
-     * @return string
-     *
-     */
-    public static function generateDSN($aInfo)
-    {
-        $sDSN = sprintf(
-            'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
-            $aInfo['host'] ?? $aInfo['hostspec'] ?? '',
-            $aInfo['port'] ?? '',
-            $aInfo['dbname'] ?? $aInfo['database'] ?? '',
-            $aInfo['user'] ?? '',
-            $aInfo['password'] ?? ''
-        );
-        $sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
-        $sDSN = preg_replace('/;\Z/', '', $sDSN);
-
-        return $sDSN;
-    }
-}
diff --git a/lib-php/DatabaseError.php b/lib-php/DatabaseError.php
deleted file mode 100644 (file)
index 68f1efe..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class DatabaseError extends \Exception
-{
-
-    public function __construct($message, $code, $previous, $oPDOErr, $sSql = null)
-    {
-        parent::__construct($message, $code, $previous);
-        // https://secure.php.net/manual/en/class.pdoexception.php
-        $this->oPDOErr = $oPDOErr;
-        $this->sSql = $sSql;
-    }
-
-    public function __toString()
-    {
-        return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
-    }
-
-    public function getSqlError()
-    {
-        return $this->oPDOErr->getMessage();
-    }
-
-    public function getSqlDebugDump()
-    {
-        if (CONST_Debug) {
-            return var_export($this->oPDOErr, true);
-        } else {
-            return $this->sSql;
-        }
-    }
-}
diff --git a/lib-php/DebugHtml.php b/lib-php/DebugHtml.php
deleted file mode 100644 (file)
index 7b0cba2..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Debug
-{
-    public static function newFunction($sHeading)
-    {
-        echo "<pre><h2>Debug output for $sHeading</h2></pre>\n";
-    }
-
-    public static function newSection($sHeading)
-    {
-        echo "<hr><pre><h3>$sHeading</h3></pre>\n";
-    }
-
-    public static function printVar($sHeading, $mVar)
-    {
-        echo '<pre><b>'.$sHeading. ':</b>  ';
-        Debug::outputVar($mVar, str_repeat(' ', strlen($sHeading) + 3));
-        echo "</pre>\n";
-    }
-
-    public static function fmtArrayVals($aArr)
-    {
-        return array('__debug_format' => 'array_vals', 'data' => $aArr);
-    }
-
-    public static function printDebugArray($sHeading, $oVar)
-    {
-
-        if ($oVar === null) {
-            Debug::printVar($sHeading, 'null');
-        } else {
-            Debug::printVar($sHeading, $oVar->debugInfo());
-        }
-    }
-
-    public static function printDebugTable($sHeading, $aVar)
-    {
-        echo '<b>'.$sHeading.":</b>\n";
-        echo "<table border='1'>\n";
-        if (!empty($aVar)) {
-            echo "  <tr>\n";
-            $aKeys = array();
-            $aInfo = reset($aVar);
-            if (!is_array($aInfo)) {
-                $aInfo = $aInfo->debugInfo();
-            }
-            foreach ($aInfo as $sKey => $mVal) {
-                echo '    <th><small>'.$sKey.'</small></th>'."\n";
-                $aKeys[] = $sKey;
-            }
-            echo "  </tr>\n";
-            foreach ($aVar as $oRow) {
-                $aInfo = $oRow;
-                if (!is_array($oRow)) {
-                    $aInfo = $oRow->debugInfo();
-                }
-                echo "  <tr>\n";
-                foreach ($aKeys as $sKey) {
-                    echo '    <td><pre>';
-                    if (isset($aInfo[$sKey])) {
-                        Debug::outputVar($aInfo[$sKey], '');
-                    }
-                    echo '</pre></td>'."\n";
-                }
-                echo "  </tr>\n";
-            }
-        }
-        echo "</table>\n";
-    }
-
-    public static function printGroupedSearch($aSearches, $aWordsIDs)
-    {
-        echo '<table border="1">';
-        echo '<tr><th>rank</th><th>Name Tokens</th><th>Name Not</th>';
-        echo '<th>Address Tokens</th><th>Address Not</th>';
-        echo '<th>country</th><th>operator</th>';
-        echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
-        foreach ($aSearches as $aRankedSet) {
-            foreach ($aRankedSet as $aRow) {
-                $aRow->dumpAsHtmlTableRow($aWordsIDs);
-            }
-        }
-        echo '</table>';
-    }
-
-    public static function printGroupTable($sHeading, $aVar)
-    {
-        echo '<b>'.$sHeading.":</b>\n";
-        echo "<table border='1'>\n";
-        if (!empty($aVar)) {
-            echo "  <tr>\n";
-            echo '    <th><small>Group</small></th>'."\n";
-            $aKeys = array();
-            $aInfo = reset($aVar)[0];
-            if (!is_array($aInfo)) {
-                $aInfo = $aInfo->debugInfo();
-            }
-            foreach ($aInfo as $sKey => $mVal) {
-                echo '    <th><small>'.$sKey.'</small></th>'."\n";
-                $aKeys[] = $sKey;
-            }
-            echo "  </tr>\n";
-            foreach ($aVar as $sGrpKey => $aGroup) {
-                foreach ($aGroup as $oRow) {
-                    $aInfo = $oRow;
-                    if (!is_array($oRow)) {
-                        $aInfo = $oRow->debugInfo();
-                    }
-                    echo "  <tr>\n";
-                    echo '    <td><pre>'.$sGrpKey.'</pre></td>'."\n";
-                    foreach ($aKeys as $sKey) {
-                        echo '    <td><pre>';
-                        if (!empty($aInfo[$sKey])) {
-                            Debug::outputVar($aInfo[$sKey], '');
-                        }
-                        echo '</pre></td>'."\n";
-                    }
-                    echo "  </tr>\n";
-                }
-            }
-        }
-        echo "</table>\n";
-    }
-
-    public static function printSQL($sSQL)
-    {
-        echo '<p><tt><b>'.date('c').'</b> <font color="#aaa">'.htmlspecialchars($sSQL, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401).'</font></tt></p>'."\n";
-    }
-
-    private static function outputVar($mVar, $sPreNL)
-    {
-        if (is_array($mVar) && !isset($mVar['__debug_format'])) {
-            $sPre = '';
-            foreach ($mVar as $mKey => $aValue) {
-                echo $sPre;
-                $iKeyLen = Debug::outputSimpleVar($mKey);
-                echo ' => ';
-                Debug::outputVar(
-                    $aValue,
-                    $sPreNL.str_repeat(' ', $iKeyLen + 4)
-                );
-                $sPre = "\n".$sPreNL;
-            }
-        } elseif (is_array($mVar) && isset($mVar['__debug_format'])) {
-            if (!empty($mVar['data'])) {
-                $sPre = '';
-                foreach ($mVar['data'] as $mValue) {
-                    echo $sPre;
-                    Debug::outputSimpleVar($mValue);
-                    $sPre = ', ';
-                }
-            }
-        } elseif (is_object($mVar) && method_exists($mVar, 'debugInfo')) {
-            Debug::outputVar($mVar->debugInfo(), $sPreNL);
-        } elseif (is_a($mVar, 'stdClass')) {
-            Debug::outputVar(json_decode(json_encode($mVar), true), $sPreNL);
-        } else {
-            Debug::outputSimpleVar($mVar);
-        }
-    }
-
-    private static function outputSimpleVar($mVar)
-    {
-        if (is_bool($mVar)) {
-            echo '<i>'.($mVar ? 'True' : 'False').'</i>';
-            return $mVar ? 4 : 5;
-        }
-
-        if (is_string($mVar)) {
-            $sOut = "'$mVar'";
-        } else {
-            $sOut = (string)$mVar;
-        }
-
-        echo htmlspecialchars($sOut, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
-        return strlen($sOut);
-    }
-}
diff --git a/lib-php/DebugNone.php b/lib-php/DebugNone.php
deleted file mode 100644 (file)
index 818cc08..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Debug
-{
-    public static function __callStatic($name, $arguments)
-    {
-        // nothing
-    }
-}
diff --git a/lib-php/Geocode.php b/lib-php/Geocode.php
deleted file mode 100644 (file)
index 3529d83..0000000
+++ /dev/null
@@ -1,942 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/Phrase.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/SearchDescription.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/SearchPosition.php');
-require_once(CONST_LibDir.'/TokenList.php');
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-class Geocode
-{
-    protected $oDB;
-
-    protected $oPlaceLookup;
-    protected $oTokenizer;
-
-    protected $aLangPrefOrder = array();
-
-    protected $aExcludePlaceIDs = array();
-
-    protected $iLimit = 20;
-    protected $iFinalLimit = 10;
-    protected $iOffset = 0;
-    protected $bFallback = false;
-
-    protected $aCountryCodes = false;
-
-    protected $bBoundedSearch = false;
-    protected $aViewBox = false;
-    protected $aRoutePoints = false;
-    protected $aRouteWidth = false;
-
-    protected $iMaxRank = 20;
-    protected $iMinAddressRank = 0;
-    protected $iMaxAddressRank = 30;
-    protected $aAddressRankList = array();
-
-    protected $sAllowedTypesSQLList = false;
-
-    protected $sQuery = false;
-    protected $aStructuredQuery = false;
-
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-        $this->oPlaceLookup = new PlaceLookup($this->oDB);
-        $this->oTokenizer = new \Nominatim\Tokenizer($this->oDB);
-    }
-
-    public function setLanguagePreference($aLangPref)
-    {
-        $this->aLangPrefOrder = $aLangPref;
-    }
-
-    public function getMoreUrlParams()
-    {
-        if ($this->aStructuredQuery) {
-            $aParams = $this->aStructuredQuery;
-        } else {
-            $aParams = array('q' => $this->sQuery);
-        }
-
-        $aParams = array_merge($aParams, $this->oPlaceLookup->getMoreUrlParams());
-
-        if ($this->aExcludePlaceIDs) {
-            $aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
-        }
-
-        if ($this->bBoundedSearch) {
-            $aParams['bounded'] = '1';
-        }
-
-        if ($this->aCountryCodes) {
-            $aParams['countrycodes'] = implode(',', $this->aCountryCodes);
-        }
-
-        if ($this->aViewBox) {
-            $aParams['viewbox'] = join(',', $this->aViewBox);
-        }
-
-        return $aParams;
-    }
-
-    public function setLimit($iLimit = 10)
-    {
-        if ($iLimit > 50) {
-            $iLimit = 50;
-        } elseif ($iLimit < 1) {
-            $iLimit = 1;
-        }
-
-        $this->iFinalLimit = $iLimit;
-        $this->iLimit = $iLimit + max($iLimit, 10);
-    }
-
-    public function setFeatureType($sFeatureType)
-    {
-        switch ($sFeatureType) {
-            case 'country':
-                $this->setRankRange(4, 4);
-                break;
-            case 'state':
-                $this->setRankRange(8, 8);
-                break;
-            case 'city':
-                $this->setRankRange(14, 16);
-                break;
-            case 'settlement':
-                $this->setRankRange(8, 20);
-                break;
-        }
-    }
-
-    public function setRankRange($iMin, $iMax)
-    {
-        $this->iMinAddressRank = $iMin;
-        $this->iMaxAddressRank = $iMax;
-    }
-
-    public function setViewbox($aViewbox)
-    {
-        $aBox = array_map('floatval', $aViewbox);
-
-        $this->aViewBox[0] = max(-180.0, min($aBox[0], $aBox[2]));
-        $this->aViewBox[1] = max(-90.0, min($aBox[1], $aBox[3]));
-        $this->aViewBox[2] = min(180.0, max($aBox[0], $aBox[2]));
-        $this->aViewBox[3] = min(90.0, max($aBox[1], $aBox[3]));
-
-        if ($this->aViewBox[2] - $this->aViewBox[0] < 0.000000001
-            || $this->aViewBox[3] - $this->aViewBox[1] < 0.000000001
-        ) {
-            userError("Bad parameter 'viewbox'. Not a box.");
-        }
-    }
-
-    private function viewboxImportanceFactor($fX, $fY)
-    {
-        if (!$this->aViewBox) {
-            return 1;
-        }
-
-        $fWidth = ($this->aViewBox[2] - $this->aViewBox[0])/2;
-        $fHeight = ($this->aViewBox[3] - $this->aViewBox[1])/2;
-
-        $fXDist = abs($fX - ($this->aViewBox[0] + $this->aViewBox[2])/2);
-        $fYDist = abs($fY - ($this->aViewBox[1] + $this->aViewBox[3])/2);
-
-        if ($fXDist <= $fWidth && $fYDist <= $fHeight) {
-            return 1;
-        }
-
-        if ($fXDist <= $fWidth * 3 && $fYDist <= 3 * $fHeight) {
-            return 0.5;
-        }
-
-        return 0.25;
-    }
-
-    public function setQuery($sQueryString)
-    {
-        $this->sQuery = $sQueryString;
-        $this->aStructuredQuery = false;
-    }
-
-    public function getQueryString()
-    {
-        return $this->sQuery;
-    }
-
-
-    public function loadParamArray($oParams, $sForceGeometryType = null)
-    {
-        $this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch);
-
-        $this->setLimit($oParams->getInt('limit', $this->iFinalLimit));
-        $this->iOffset = $oParams->getInt('offset', $this->iOffset);
-
-        $this->bFallback = $oParams->getBool('fallback', $this->bFallback);
-
-        // List of excluded Place IDs - used for more accurate pageing
-        $sExcluded = $oParams->getStringList('exclude_place_ids');
-        if ($sExcluded) {
-            foreach ($sExcluded as $iExcludedPlaceID) {
-                $iExcludedPlaceID = (int)$iExcludedPlaceID;
-                if ($iExcludedPlaceID) {
-                    $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
-                }
-            }
-
-            if (isset($aExcludePlaceIDs)) {
-                $this->aExcludePlaceIDs = $aExcludePlaceIDs;
-            }
-        }
-
-        // Only certain ranks of feature
-        $sFeatureType = $oParams->getString('featureType');
-        if (!$sFeatureType) {
-            $sFeatureType = $oParams->getString('featuretype');
-        }
-        if ($sFeatureType) {
-            $this->setFeatureType($sFeatureType);
-        }
-
-        // Country code list
-        $sCountries = $oParams->getStringList('countrycodes');
-        if ($sCountries) {
-            foreach ($sCountries as $sCountryCode) {
-                if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) {
-                    $aCountries[] = strtolower($sCountryCode);
-                }
-            }
-            if (isset($aCountries)) {
-                $this->aCountryCodes = $aCountries;
-            }
-        }
-
-        $aViewbox = $oParams->getStringList('viewboxlbrt');
-        if ($aViewbox) {
-            if (count($aViewbox) != 4) {
-                userError("Bad parameter 'viewboxlbrt'. Expected 4 coordinates.");
-            }
-            $this->setViewbox($aViewbox);
-        } else {
-            $aViewbox = $oParams->getStringList('viewbox');
-            if ($aViewbox) {
-                if (count($aViewbox) != 4) {
-                    userError("Bad parameter 'viewbox'. Expected 4 coordinates.");
-                }
-                $this->setViewBox($aViewbox);
-            } else {
-                $aRoute = $oParams->getStringList('route');
-                $fRouteWidth = $oParams->getFloat('routewidth');
-                if ($aRoute && $fRouteWidth) {
-                    $this->aRoutePoints = $aRoute;
-                    $this->aRouteWidth = $fRouteWidth;
-                }
-            }
-        }
-
-        $this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
-        $this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
-    }
-
-    public function setQueryFromParams($oParams)
-    {
-        // Search query
-        $this->setStructuredQuery(
-            $oParams->getString('amenity'),
-            $oParams->getString('street'),
-            $oParams->getString('city'),
-            $oParams->getString('county'),
-            $oParams->getString('state'),
-            $oParams->getString('country'),
-            $oParams->getString('postalcode')
-        );
-        if (!$this->sQuery) {
-            $sQuery = $oParams->getString('q');
-
-            if ($sQuery) {
-                $this->setQuery($sQuery);
-            }
-        }
-    }
-
-    public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
-    {
-        $sValue = trim($sValue);
-        if (!$sValue) {
-            return false;
-        }
-        $this->aStructuredQuery[$sKey] = $sValue;
-        if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
-            $this->iMinAddressRank = $iNewMinAddressRank;
-            $this->iMaxAddressRank = $iNewMaxAddressRank;
-        }
-        if ($aItemListValues) {
-            $this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
-        }
-        return true;
-    }
-
-    public function setStructuredQuery($sAmenity = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false)
-    {
-        $this->sQuery = false;
-
-        if ($sAmenity || $sStreet || $sCity || $sCounty || $sState || $sCountry || $sPostalCode) {
-            // Reset
-            $this->iMinAddressRank = 0;
-            $this->iMaxAddressRank = 30;
-            $this->aAddressRankList = array();
-
-            $this->aStructuredQuery = array();
-            $this->sAllowedTypesSQLList = false;
-
-            $this->loadStructuredAddressElement($sAmenity, 'amenity', 26, 30, false);
-            $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false);
-            $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false);
-            $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false);
-            $this->loadStructuredAddressElement($sState, 'state', 8, 8, false);
-            $this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11));
-            $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false);
-
-            if (!empty($this->aStructuredQuery)) {
-                $this->sQuery = join(', ', $this->aStructuredQuery);
-                if ($this->iMaxAddressRank < 30) {
-                    $this->sAllowedTypesSQLList = '(\'place\',\'boundary\')';
-                }
-            }
-        }
-    }
-
-    public function fallbackStructuredQuery()
-    {
-        $aParams = $this->aStructuredQuery;
-
-        if (!$aParams || count($aParams) == 1) {
-            return false;
-        }
-
-        $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
-
-        foreach ($aOrderToFallback as $sType) {
-            if (isset($aParams[$sType])) {
-                unset($aParams[$sType]);
-                $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']);
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
-    {
-        /*
-             Calculate all searches using oValidTokens i.e.
-             'Wodsworth Road, Sheffield' =>
-
-             Phrase Wordset
-             0      0       (wodsworth road)
-             0      1       (wodsworth)(road)
-             1      0       (sheffield)
-
-             Score how good the search is so they can be ordered
-         */
-        foreach ($aPhrases as $iPhrase => $oPhrase) {
-            $aNewPhraseSearches = array();
-            $oPosition = new SearchPosition(
-                $oPhrase->getPhraseType(),
-                $iPhrase,
-                count($aPhrases)
-            );
-
-            foreach ($oPhrase->getWordSets() as $aWordset) {
-                $aWordsetSearches = $aSearches;
-
-                // Add all words from this wordset
-                foreach ($aWordset as $iToken => $sToken) {
-                    $aNewWordsetSearches = array();
-                    $oPosition->setTokenPosition($iToken, count($aWordset));
-
-                    foreach ($aWordsetSearches as $oCurrentSearch) {
-                        foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
-                            if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) {
-                                $aNewSearches = $oSearchTerm->extendSearch(
-                                    $oCurrentSearch,
-                                    $oPosition
-                                );
-
-                                foreach ($aNewSearches as $oSearch) {
-                                    if ($oSearch->getRank() < $this->iMaxRank) {
-                                        $aNewWordsetSearches[] = $oSearch;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    // Sort and cut
-                    usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
-                    $aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
-                }
-
-                $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
-                usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
-
-                $aSearchHash = array();
-                foreach ($aNewPhraseSearches as $iSearch => $aSearch) {
-                    $sHash = serialize($aSearch);
-                    if (isset($aSearchHash[$sHash])) {
-                        unset($aNewPhraseSearches[$iSearch]);
-                    } else {
-                        $aSearchHash[$sHash] = 1;
-                    }
-                }
-
-                $aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
-            }
-
-            // Re-group the searches by their score, junk anything over 20 as just not worth trying
-            $aGroupedSearches = array();
-            foreach ($aNewPhraseSearches as $aSearch) {
-                $iRank = $aSearch->getRank();
-                if ($iRank < $this->iMaxRank) {
-                    if (!isset($aGroupedSearches[$iRank])) {
-                        $aGroupedSearches[$iRank] = array();
-                    }
-                    $aGroupedSearches[$iRank][] = $aSearch;
-                }
-            }
-            ksort($aGroupedSearches);
-
-            $iSearchCount = 0;
-            $aSearches = array();
-            foreach ($aGroupedSearches as $aNewSearches) {
-                $iSearchCount += count($aNewSearches);
-                $aSearches = array_merge($aSearches, $aNewSearches);
-                if ($iSearchCount > 50) {
-                    break;
-                }
-            }
-        }
-
-        // Revisit searches, drop bad searches and give penalty to unlikely combinations.
-        $aGroupedSearches = array();
-        foreach ($aSearches as $oSearch) {
-            if (!$oSearch->isValidSearch()) {
-                continue;
-            }
-
-            $iRank = $oSearch->getRank();
-            if (!isset($aGroupedSearches[$iRank])) {
-                $aGroupedSearches[$iRank] = array();
-            }
-            $aGroupedSearches[$iRank][] = $oSearch;
-        }
-        ksort($aGroupedSearches);
-
-        return $aGroupedSearches;
-    }
-
-    /* Perform the actual query lookup.
-
-        Returns an ordered list of results, each with the following fields:
-            osm_type: type of corresponding OSM object
-                        N - node
-                        W - way
-                        R - relation
-                        P - postcode (internally computed)
-            osm_id: id of corresponding OSM object
-            class: general object class (corresponds to tag key of primary OSM tag)
-            type: subclass of object (corresponds to tag value of primary OSM tag)
-            admin_level: see https://wiki.openstreetmap.org/wiki/Admin_level
-            rank_search: rank in search hierarchy
-                        (see also https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level)
-            rank_address: rank in address hierarchy (determines orer in address)
-            place_id: internal key (may differ between different instances)
-            country_code: ISO country code
-            langaddress: localized full address
-            placename: localized name of object
-            ref: content of ref tag (if available)
-            lon: longitude
-            lat: latitude
-            importance: importance of place based on Wikipedia link count
-            addressimportance: cumulated importance of address elements
-            extra_place: type of place (for admin boundaries, if there is a place tag)
-            aBoundingBox: bounding Box
-            label: short description of the object class/type (English only)
-            name: full name (currently the same as langaddress)
-            foundorder: secondary ordering for places with same importance
-    */
-
-
-    public function lookup()
-    {
-        Debug::newFunction('Geocode::lookup');
-        if (!$this->sQuery && !$this->aStructuredQuery) {
-            return array();
-        }
-
-        Debug::printDebugArray('Geocode', $this);
-
-        $oCtx = new SearchContext();
-
-        if ($this->aRoutePoints) {
-            $oCtx->setViewboxFromRoute(
-                $this->oDB,
-                $this->aRoutePoints,
-                $this->aRouteWidth,
-                $this->bBoundedSearch
-            );
-        } elseif ($this->aViewBox) {
-            $oCtx->setViewboxFromBox($this->aViewBox, $this->bBoundedSearch);
-        }
-        if ($this->aExcludePlaceIDs) {
-            $oCtx->setExcludeList($this->aExcludePlaceIDs);
-        }
-        if ($this->aCountryCodes) {
-            $oCtx->setCountryList($this->aCountryCodes);
-        }
-
-        Debug::newSection('Query Preprocessing');
-
-        $sQuery = $this->sQuery;
-        if (!preg_match('//u', $sQuery)) {
-            userError('Query string is not UTF-8 encoded.');
-        }
-
-        // Do we have anything that looks like a lat/lon pair?
-        $sQuery = $oCtx->setNearPointFromQuery($sQuery);
-
-        if ($sQuery || $this->aStructuredQuery) {
-            // Start with a single blank search
-            $aSearches = array(new SearchDescription($oCtx));
-
-            if ($sQuery) {
-                $sQuery = $aSearches[0]->extractKeyValuePairs($sQuery);
-            }
-
-            $sSpecialTerm = '';
-            if ($sQuery) {
-                preg_match_all(
-                    '/\\[([\\w ]*)\\]/u',
-                    $sQuery,
-                    $aSpecialTermsRaw,
-                    PREG_SET_ORDER
-                );
-                if (!empty($aSpecialTermsRaw)) {
-                    Debug::printVar('Special terms', $aSpecialTermsRaw);
-                }
-
-                foreach ($aSpecialTermsRaw as $aSpecialTerm) {
-                    $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
-                    if (!$sSpecialTerm) {
-                        $sSpecialTerm = $aSpecialTerm[1];
-                    }
-                }
-            }
-            if (!$sSpecialTerm && $this->aStructuredQuery
-                && isset($this->aStructuredQuery['amenity'])) {
-                $sSpecialTerm = $this->aStructuredQuery['amenity'];
-                unset($this->aStructuredQuery['amenity']);
-            }
-
-            if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
-                $aTokens = $this->oTokenizer->tokensForSpecialTerm($sSpecialTerm);
-
-                if (!empty($aTokens)) {
-                    $aNewSearches = array();
-                    $oPosition = new SearchPosition('', 0, 1);
-                    $oPosition->setTokenPosition(0, 1);
-
-                    foreach ($aSearches as $oSearch) {
-                        foreach ($aTokens as $oToken) {
-                            $aNewSearches = array_merge(
-                                $aNewSearches,
-                                $oToken->extendSearch($oSearch, $oPosition)
-                            );
-                        }
-                    }
-                    $aSearches = $aNewSearches;
-                }
-            }
-
-            // Split query into phrases
-            // Commas are used to reduce the search space by indicating where phrases split
-            $aPhrases = array();
-            if ($this->aStructuredQuery) {
-                foreach ($this->aStructuredQuery as $iPhrase => $sPhrase) {
-                    $aPhrases[] = new Phrase($sPhrase, $iPhrase);
-                }
-            } else {
-                foreach (explode(',', $sQuery) as $sPhrase) {
-                    $aPhrases[] = new Phrase($sPhrase, '');
-                }
-            }
-
-            Debug::printDebugArray('Search context', $oCtx);
-            Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]);
-
-            Debug::newSection('Tokenization');
-            $oValidTokens = $this->oTokenizer->extractTokensFromPhrases($aPhrases);
-
-            if ($oValidTokens->count() > 0) {
-                $oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
-
-                $aPhrases = array_filter($aPhrases, function ($oPhrase) {
-                    return $oPhrase->getWordSets() !== null;
-                });
-
-                // Any words that have failed completely?
-                // TODO: suggestions
-
-                Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
-                Debug::printDebugTable('Phrases', $aPhrases);
-
-                Debug::newSection('Search candidates');
-
-                $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
-
-                if (!$this->aStructuredQuery) {
-                    // Reverse phrase array and also reverse the order of the wordsets in
-                    // the first and final phrase. Don't bother about phrases in the middle
-                    // because order in the address doesn't matter.
-                    $aPhrases = array_reverse($aPhrases);
-                    $aPhrases[0]->invertWordSets();
-                    if (count($aPhrases) > 1) {
-                        $aPhrases[count($aPhrases)-1]->invertWordSets();
-                    }
-                    $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
-
-                    foreach ($aReverseGroupedSearches as $aSearches) {
-                        foreach ($aSearches as $aSearch) {
-                            if (!isset($aGroupedSearches[$aSearch->getRank()])) {
-                                $aGroupedSearches[$aSearch->getRank()] = array();
-                            }
-                            $aGroupedSearches[$aSearch->getRank()][] = $aSearch;
-                        }
-                    }
-
-                    ksort($aGroupedSearches);
-                }
-            } else {
-                // Re-group the searches by their score, junk anything over 20 as just not worth trying
-                $aGroupedSearches = array();
-                foreach ($aSearches as $aSearch) {
-                    if ($aSearch->getRank() < $this->iMaxRank) {
-                        if (!isset($aGroupedSearches[$aSearch->getRank()])) {
-                            $aGroupedSearches[$aSearch->getRank()] = array();
-                        }
-                        $aGroupedSearches[$aSearch->getRank()][] = $aSearch;
-                    }
-                }
-                ksort($aGroupedSearches);
-            }
-
-            // Filter out duplicate searches
-            $aSearchHash = array();
-            foreach ($aGroupedSearches as $iGroup => $aSearches) {
-                foreach ($aSearches as $iSearch => $aSearch) {
-                    $sHash = serialize($aSearch);
-                    if (isset($aSearchHash[$sHash])) {
-                        unset($aGroupedSearches[$iGroup][$iSearch]);
-                        if (empty($aGroupedSearches[$iGroup])) {
-                            unset($aGroupedSearches[$iGroup]);
-                        }
-                    } else {
-                        $aSearchHash[$sHash] = 1;
-                    }
-                }
-            }
-
-            Debug::printGroupedSearch(
-                $aGroupedSearches,
-                $oValidTokens->debugTokenByWordIdList()
-            );
-
-            // Start the search process
-            $iGroupLoop = 0;
-            $iQueryLoop = 0;
-            $aNextResults = array();
-            foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
-                $iGroupLoop++;
-                $aResults = $aNextResults;
-                foreach ($aSearches as $oSearch) {
-                    $iQueryLoop++;
-
-                    Debug::newSection("Search Loop, group $iGroupLoop, loop $iQueryLoop");
-                    Debug::printGroupedSearch(
-                        array($iGroupedRank => array($oSearch)),
-                        $oValidTokens->debugTokenByWordIdList()
-                    );
-
-                    $aNewResults = $oSearch->query(
-                        $this->oDB,
-                        $this->iMinAddressRank,
-                        $this->iMaxAddressRank,
-                        $this->iLimit
-                    );
-
-                    // The same result may appear in different rounds, only
-                    // use the one with minimal rank.
-                    foreach ($aNewResults as $iPlace => $oRes) {
-                        if (!isset($aResults[$iPlace])
-                            || $aResults[$iPlace]->iResultRank > $oRes->iResultRank) {
-                            $aResults[$iPlace] = $oRes;
-                        }
-                    }
-
-                    if ($iQueryLoop > 30) {
-                        break;
-                    }
-                }
-
-                if (!empty($aResults)) {
-                    $aSplitResults = Result::splitResults($aResults);
-                    Debug::printVar('Split results', $aSplitResults);
-                    if ($iGroupLoop <= 4
-                        && reset($aSplitResults['head'])->iResultRank > 0
-                        && $iGroupedRank !== array_key_last($aGroupedSearches)) {
-                        // Haven't found an exact match for the query yet.
-                        // Therefore add result from the next group level.
-                        $aNextResults = $aSplitResults['head'];
-                        foreach ($aNextResults as $oRes) {
-                            $oRes->iResultRank--;
-                        }
-                        foreach ($aSplitResults['tail'] as $oRes) {
-                            $oRes->iResultRank--;
-                            $aNextResults[$oRes->iId] = $oRes;
-                        }
-                        $aResults = array();
-                    } else {
-                        $aResults = $aSplitResults['head'];
-                    }
-                }
-
-                if (!empty($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
-                    // Need to verify passes rank limits before dropping out of the loop (yuk!)
-                    // reduces the number of place ids, like a filter
-                    // rank_address is 30 for interpolated housenumbers
-                    $aFilterSql = array();
-                    $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
-                    if ($sPlaceIds) {
-                        $sSQL = 'SELECT place_id FROM placex ';
-                        $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
-                        $sSQL .= '  AND (';
-                        $sSQL .= "         placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                        $sSQL .= "         OR placex.rank_search between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                        if ($this->aAddressRankList) {
-                            $sSQL .= '     OR placex.rank_address in ('.join(',', $this->aAddressRankList).')';
-                        }
-                        $sSQL .= ')';
-                        $aFilterSql[] = $sSQL;
-                    }
-                    $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
-                    if ($sPlaceIds) {
-                        $sSQL = ' SELECT place_id FROM location_postcode lp ';
-                        $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
-                        $sSQL .= "  AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                        if ($this->aAddressRankList) {
-                            $sSQL .= '     OR lp.rank_address in ('.join(',', $this->aAddressRankList).')';
-                        }
-                        $sSQL .= ') ';
-                        $aFilterSql[] = $sSQL;
-                    }
-
-                    $aFilteredIDs = array();
-                    if ($aFilterSql) {
-                        $sSQL = join(' UNION ', $aFilterSql);
-                        Debug::printSQL($sSQL);
-                        $aFilteredIDs = $this->oDB->getCol($sSQL);
-                    }
-
-                    $tempIDs = array();
-                    foreach ($aResults as $oResult) {
-                        if (($this->iMaxAddressRank == 30 &&
-                             ($oResult->iTable == Result::TABLE_OSMLINE
-                              || $oResult->iTable == Result::TABLE_TIGER))
-                            || in_array($oResult->iId, $aFilteredIDs)
-                        ) {
-                            $tempIDs[$oResult->iId] = $oResult;
-                        }
-                    }
-                    $aResults = $tempIDs;
-                }
-
-                if (!empty($aResults) || $iGroupLoop > 6 || $iQueryLoop > 40) {
-                    break;
-                }
-            }
-        } else {
-            // Just interpret as a reverse geocode
-            $oReverse = new ReverseGeocode($this->oDB);
-            $oReverse->setZoom(18);
-
-            $oLookup = $oReverse->lookupPoint($oCtx->sqlNear, false);
-
-            Debug::printVar('Reverse search', $oLookup);
-
-            if ($oLookup) {
-                $aResults = array($oLookup->iId => $oLookup);
-            }
-        }
-
-        // No results? Done
-        if (empty($aResults)) {
-            if ($this->bFallback && $this->fallbackStructuredQuery()) {
-                return $this->lookup();
-            }
-
-            return array();
-        }
-
-        if ($this->aAddressRankList) {
-            $this->oPlaceLookup->setAddressRankList($this->aAddressRankList);
-        }
-        $this->oPlaceLookup->setAllowedTypesSQLList($this->sAllowedTypesSQLList);
-        $this->oPlaceLookup->setLanguagePreference($this->aLangPrefOrder);
-        if ($oCtx->hasNearPoint()) {
-            $this->oPlaceLookup->setAnchorSql($oCtx->sqlNear);
-        }
-
-        $aSearchResults = $this->oPlaceLookup->lookup($aResults);
-
-        $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
-        foreach ($aRecheckWords as $i => $sWord) {
-            if (!preg_match('/[\pL\pN]/', $sWord)) {
-                unset($aRecheckWords[$i]);
-            }
-        }
-
-        Debug::printVar('Recheck words', $aRecheckWords);
-
-        foreach ($aSearchResults as $iIdx => $aResult) {
-            $fRadius = ClassTypes\getDefRadius($aResult);
-
-            $aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius);
-            if ($aOutlineResult) {
-                $aResult = array_merge($aResult, $aOutlineResult);
-            }
-
-            // Is there an icon set for this type of result?
-            $sIcon = ClassTypes\getIconFile($aResult);
-            if (isset($sIcon)) {
-                $aResult['icon'] = $sIcon;
-            }
-
-            $sLabel = ClassTypes\getLabel($aResult);
-            if (isset($sLabel)) {
-                $aResult['label'] = $sLabel;
-            }
-            $aResult['name'] = $aResult['langaddress'];
-
-            if ($oCtx->hasNearPoint()) {
-                $aResult['importance'] = 0.001;
-                $aResult['foundorder'] = $aResult['addressimportance'];
-            } else {
-                if ($aResult['importance'] == 0) {
-                    $aResult['importance'] = 0.0001;
-                }
-                $aResult['importance'] *= $this->viewboxImportanceFactor(
-                    $aResult['lon'],
-                    $aResult['lat']
-                );
-
-                // secondary ordering (for results with same importance (the smaller the better):
-                // - approximate importance of address parts
-                if (isset($aResult['addressimportance']) && $aResult['addressimportance']) {
-                    $aResult['foundorder'] = -$aResult['addressimportance']/10;
-                } else {
-                    $aResult['foundorder'] = -$aResult['importance'];
-                }
-                // - number of exact matches from the query
-                $aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
-                // - importance of the class/type
-                $iClassImportance = ClassTypes\getImportance($aResult);
-                if (isset($iClassImportance)) {
-                    $aResult['foundorder'] += 0.0001 * $iClassImportance;
-                } else {
-                    $aResult['foundorder'] += 0.01;
-                }
-                // - rank
-                $aResult['foundorder'] -= 0.00001 * (30 - $aResult['rank_search']);
-
-                // Adjust importance for the number of exact string matches in the result
-                $iCountWords = 0;
-                $sAddress = $aResult['langaddress'];
-                foreach ($aRecheckWords as $i => $sWord) {
-                    if (grapheme_stripos($sAddress, $sWord)!==false) {
-                        $iCountWords++;
-                        if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) {
-                            $iCountWords += 0.1;
-                        }
-                    }
-                }
-
-                // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
-                $aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1);
-            }
-            $aSearchResults[$iIdx] = $aResult;
-        }
-        uasort($aSearchResults, 'byImportance');
-        Debug::printVar('Pre-filter results', $aSearchResults);
-
-        $aOSMIDDone = array();
-        $aClassTypeNameDone = array();
-        $aToFilter = $aSearchResults;
-        $aSearchResults = array();
-
-        foreach ($aToFilter as $aResult) {
-            $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
-            if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
-                && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
-            ) {
-                $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
-                $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
-                $aSearchResults[] = $aResult;
-            }
-
-            // Absolute limit on number of results
-            if (count($aSearchResults) >= $this->iFinalLimit) {
-                break;
-            }
-        }
-
-        Debug::printVar('Post-filter results', $aSearchResults);
-        return $aSearchResults;
-    } // end lookup()
-
-    public function debugInfo()
-    {
-        return array(
-                'Query' => $this->sQuery,
-                'Structured query' => $this->aStructuredQuery,
-                'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
-                'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
-                'Limit (for searches)' => $this->iLimit,
-                'Limit (for results)'=> $this->iFinalLimit,
-                'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
-                'Bounded search' => $this->bBoundedSearch,
-                'Viewbox' => Debug::fmtArrayVals($this->aViewBox),
-                'Route points' => Debug::fmtArrayVals($this->aRoutePoints),
-                'Route width' => $this->aRouteWidth,
-                'Max rank' => $this->iMaxRank,
-                'Min address rank' => $this->iMinAddressRank,
-                'Max address rank' => $this->iMaxAddressRank,
-                'Address rank list' => Debug::fmtArrayVals($this->aAddressRankList)
-               );
-    }
-} // end class
diff --git a/lib-php/ParameterParser.php b/lib-php/ParameterParser.php
deleted file mode 100644 (file)
index a4936d3..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class ParameterParser
-{
-    private $aParams;
-
-
-    public function __construct($aParams = null)
-    {
-        $this->aParams = ($aParams === null) ? $_GET : $aParams;
-    }
-
-    public function getBool($sName, $bDefault = false)
-    {
-        if (!isset($this->aParams[$sName])
-            || !is_string($this->aParams[$sName])
-            || strlen($this->aParams[$sName]) == 0
-        ) {
-            return $bDefault;
-        }
-
-        return (bool) $this->aParams[$sName];
-    }
-
-    public function getInt($sName, $bDefault = false)
-    {
-        if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
-            return $bDefault;
-        }
-
-        if (!preg_match('/^[+-]?[0-9]+$/', $this->aParams[$sName])) {
-            userError("Integer number expected for parameter '$sName'");
-        }
-
-        return (int) $this->aParams[$sName];
-    }
-
-    public function getFloat($sName, $bDefault = false)
-    {
-        if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
-            return $bDefault;
-        }
-
-        if (!preg_match('/^[+-]?[0-9]*\.?[0-9]+$/', $this->aParams[$sName])) {
-            userError("Floating-point number expected for parameter '$sName'");
-        }
-
-        return (float) $this->aParams[$sName];
-    }
-
-    public function getString($sName, $bDefault = false)
-    {
-        if (!isset($this->aParams[$sName])
-            || !is_string($this->aParams[$sName])
-            || strlen($this->aParams[$sName]) == 0
-        ) {
-            return $bDefault;
-        }
-
-        return $this->aParams[$sName];
-    }
-
-    public function getSet($sName, $aValues, $sDefault = false)
-    {
-        if (!isset($this->aParams[$sName])
-            || !is_string($this->aParams[$sName])
-            || strlen($this->aParams[$sName]) == 0
-        ) {
-            return $sDefault;
-        }
-
-        if (!in_array($this->aParams[$sName], $aValues, true)) {
-            userError("Parameter '$sName' must be one of: ".join(', ', $aValues));
-        }
-
-        return $this->aParams[$sName];
-    }
-
-    public function getStringList($sName, $aDefault = false)
-    {
-        $sValue = $this->getString($sName);
-
-        if ($sValue) {
-            // removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
-            return array_values(array_filter(explode(',', $sValue), 'strlen'));
-        }
-
-        return $aDefault;
-    }
-
-    public function getPreferredLanguages($sFallback = null)
-    {
-        if ($sFallback === null && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-            $sFallback = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
-        }
-
-        $aLanguages = array();
-        $sLangString = $this->getString('accept-language', $sFallback);
-
-        if ($sLangString
-            && preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)
-        ) {
-            foreach ($aLanguagesParse as $iLang => $aLanguage) {
-                $aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
-                if (!isset($aLanguages[$aLanguage[2]])) {
-                    $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
-                }
-            }
-            arsort($aLanguages);
-        }
-        if (empty($aLanguages) && CONST_Default_Language) {
-            $aLanguages[CONST_Default_Language] = 1;
-        }
-
-        foreach ($aLanguages as $sLanguage => $fLanguagePref) {
-            $this->addNameTag($aLangPrefOrder, 'name:'.$sLanguage);
-        }
-        $this->addNameTag($aLangPrefOrder, 'name');
-        $this->addNameTag($aLangPrefOrder, 'brand');
-        foreach ($aLanguages as $sLanguage => $fLanguagePref) {
-            $this->addNameTag($aLangPrefOrder, 'official_name:'.$sLanguage);
-            $this->addNameTag($aLangPrefOrder, 'short_name:'.$sLanguage);
-        }
-        $this->addNameTag($aLangPrefOrder, 'official_name');
-        $this->addNameTag($aLangPrefOrder, 'short_name');
-        $this->addNameTag($aLangPrefOrder, 'ref');
-        $this->addNameTag($aLangPrefOrder, 'type');
-        return $aLangPrefOrder;
-    }
-
-    private function addNameTag(&$aLangPrefOrder, $sTag)
-    {
-        $aLangPrefOrder[$sTag] = $sTag;
-        $aLangPrefOrder['_place_'.$sTag] = '_place_'.$sTag;
-    }
-
-    public function hasSetAny($aParamNames)
-    {
-        foreach ($aParamNames as $sName) {
-            if ($this->getBool($sName)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
diff --git a/lib-php/Phrase.php b/lib-php/Phrase.php
deleted file mode 100644 (file)
index 4ed4d40..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Segment of a query string.
- *
- * The parts of a query strings are usually separated by commas.
- */
-class Phrase
-{
-    // Complete phrase as a string (guaranteed to have no leading or trailing
-    // spaces).
-    private $sPhrase;
-    // Element type for structured searches.
-    private $sPhraseType;
-    // Possible segmentations of the phrase.
-    private $aWordSets;
-
-    public function __construct($sPhrase, $sPhraseType)
-    {
-        $this->sPhrase = trim($sPhrase);
-        $this->sPhraseType = $sPhraseType;
-    }
-
-    /**
-     * Get the original phrase of the string.
-     */
-    public function getPhrase()
-    {
-        return $this->sPhrase;
-    }
-
-    /**
-     * Return the element type of the phrase.
-     *
-     * @return string Pharse type if the phrase comes from a structured query
-     *                or empty string otherwise.
-     */
-    public function getPhraseType()
-    {
-        return $this->sPhraseType;
-    }
-
-    public function setWordSets($aWordSets)
-    {
-        $this->aWordSets = $aWordSets;
-    }
-
-    /**
-     * Return the array of possible segmentations of the phrase.
-     *
-     * @return string[][] Array of segmentations, each consisting of an
-     *                    array of terms.
-     */
-    public function getWordSets()
-    {
-        return $this->aWordSets;
-    }
-
-    /**
-     * Invert the set of possible segmentations.
-     *
-     * @return void
-     */
-    public function invertWordSets()
-    {
-        foreach ($this->aWordSets as $i => $aSet) {
-            $this->aWordSets[$i] = array_reverse($aSet);
-        }
-    }
-
-    public function debugInfo()
-    {
-        return array(
-                'Type' => $this->sPhraseType,
-                'Phrase' => $this->sPhrase,
-                'WordSets' => $this->aWordSets
-               );
-    }
-}
diff --git a/lib-php/PlaceLookup.php b/lib-php/PlaceLookup.php
deleted file mode 100644 (file)
index 895a30d..0000000
+++ /dev/null
@@ -1,615 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/AddressDetails.php');
-require_once(CONST_LibDir.'/Result.php');
-
-class PlaceLookup
-{
-    protected $oDB;
-
-    protected $aLangPrefOrderSql = "''";
-
-    protected $bAddressDetails = false;
-    protected $bExtraTags = false;
-    protected $bNameDetails = false;
-
-    protected $bIncludePolygonAsText = false;
-    protected $bIncludePolygonAsGeoJSON = false;
-    protected $bIncludePolygonAsKML = false;
-    protected $bIncludePolygonAsSVG = false;
-    protected $fPolygonSimplificationThreshold = 0.0;
-
-    protected $sAnchorSql = null;
-    protected $sAddressRankListSql = null;
-    protected $sAllowedTypesSQLList = null;
-    protected $bDeDupe = true;
-
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-    }
-
-    public function doDeDupe()
-    {
-        return $this->bDeDupe;
-    }
-
-    public function setIncludeAddressDetails($b)
-    {
-        $this->bAddressDetails = $b;
-    }
-
-    public function loadParamArray($oParams, $sGeomType = null)
-    {
-        $aLangs = $oParams->getPreferredLanguages();
-        $this->aLangPrefOrderSql =
-            'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']';
-
-        $this->bExtraTags = $oParams->getBool('extratags', false);
-        $this->bNameDetails = $oParams->getBool('namedetails', false);
-
-        $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe);
-
-        if ($sGeomType === null || $sGeomType == 'geojson') {
-            $this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
-        }
-
-        if ($oParams->getString('format', '') !== 'geojson') {
-            if ($sGeomType === null || $sGeomType == 'text') {
-                $this->bIncludePolygonAsText = $oParams->getBool('polygon_text');
-            }
-            if ($sGeomType === null || $sGeomType == 'kml') {
-                $this->bIncludePolygonAsKML = $oParams->getBool('polygon_kml');
-            }
-            if ($sGeomType === null || $sGeomType == 'svg') {
-                $this->bIncludePolygonAsSVG = $oParams->getBool('polygon_svg');
-            }
-        }
-        $this->fPolygonSimplificationThreshold
-            = $oParams->getFloat('polygon_threshold', 0.0);
-
-        $iWantedTypes =
-            ($this->bIncludePolygonAsText ? 1 : 0) +
-            ($this->bIncludePolygonAsGeoJSON ? 1 : 0) +
-            ($this->bIncludePolygonAsKML ? 1 : 0) +
-            ($this->bIncludePolygonAsSVG ? 1 : 0);
-        if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
-            if (CONST_PolygonOutput_MaximumTypes) {
-                userError('Select only '.CONST_PolygonOutput_MaximumTypes.' polygon output option');
-            } else {
-                userError('Polygon output is disabled');
-            }
-        }
-    }
-
-    public function getMoreUrlParams()
-    {
-        $aParams = array();
-
-        if ($this->bAddressDetails) {
-            $aParams['addressdetails'] = '1';
-        }
-        if ($this->bExtraTags) {
-            $aParams['extratags'] = '1';
-        }
-        if ($this->bNameDetails) {
-            $aParams['namedetails'] = '1';
-        }
-
-        if ($this->bIncludePolygonAsText) {
-            $aParams['polygon_text'] = '1';
-        }
-        if ($this->bIncludePolygonAsGeoJSON) {
-            $aParams['polygon_geojson'] = '1';
-        }
-        if ($this->bIncludePolygonAsKML) {
-            $aParams['polygon_kml'] = '1';
-        }
-        if ($this->bIncludePolygonAsSVG) {
-            $aParams['polygon_svg'] = '1';
-        }
-
-        if ($this->fPolygonSimplificationThreshold > 0.0) {
-            $aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold;
-        }
-
-        if (!$this->bDeDupe) {
-            $aParams['dedupe'] = '0';
-        }
-
-        return $aParams;
-    }
-
-    public function setAnchorSql($sPoint)
-    {
-        $this->sAnchorSql = $sPoint;
-    }
-
-    public function setAddressRankList($aList)
-    {
-        $this->sAddressRankListSql = '('.join(',', $aList).')';
-    }
-
-    public function setAllowedTypesSQLList($sSql)
-    {
-        $this->sAllowedTypesSQLList = $sSql;
-    }
-
-    public function setLanguagePreference($aLangPrefOrder)
-    {
-        $this->aLangPrefOrderSql = $this->oDB->getArraySQL(
-            $this->oDB->getDBQuotedList($aLangPrefOrder)
-        );
-    }
-
-    private function addressImportanceSql($sGeometry, $sPlaceId)
-    {
-        if ($this->sAnchorSql) {
-            $sSQL = 'ST_Distance('.$this->sAnchorSql.','.$sGeometry.')';
-        } else {
-            $sSQL = '(SELECT max(ai_p.importance * (ai_p.rank_address + 2))';
-            $sSQL .= '   FROM place_addressline ai_s, placex ai_p';
-            $sSQL .= '   WHERE ai_s.place_id = '.$sPlaceId;
-            $sSQL .= '     AND ai_p.place_id = ai_s.address_place_id ';
-            $sSQL .= '     AND ai_s.isaddress ';
-            $sSQL .= '     AND ai_p.importance is not null)';
-        }
-
-        return $sSQL.' AS addressimportance,';
-    }
-
-    private function langAddressSql($sHousenumber)
-    {
-        if ($this->bAddressDetails) {
-            return ''; // langaddress will be computed from address details
-        }
-
-        return 'get_address_by_language(place_id,'.$sHousenumber.','.$this->aLangPrefOrderSql.') AS langaddress,';
-    }
-
-    public function lookupOSMID($sType, $iID)
-    {
-        $sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id';
-        $iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID));
-
-        if (!$iPlaceID) {
-            return null;
-        }
-
-        $aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)), 0, 30, true);
-
-        return empty($aResults) ? null : reset($aResults);
-    }
-
-    public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30, $bAllowLinked = false)
-    {
-        Debug::newFunction('Place lookup');
-
-        if (empty($aResults)) {
-            return array();
-        }
-        $aSubSelects = array();
-
-        $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
-        if ($sPlaceIDs) {
-            Debug::printVar('Ids from placex', $sPlaceIDs);
-            $sSQL  = 'SELECT ';
-            $sSQL .= '    osm_type,';
-            $sSQL .= '    osm_id,';
-            $sSQL .= '    class,';
-            $sSQL .= '    type,';
-            $sSQL .= '    admin_level,';
-            $sSQL .= '    rank_search,';
-            $sSQL .= '    rank_address,';
-            $sSQL .= '    min(place_id) AS place_id,';
-            $sSQL .= '    min(parent_place_id) AS parent_place_id,';
-            $sSQL .= '    -1 as housenumber,';
-            $sSQL .= '    country_code,';
-            $sSQL .= $this->langAddressSql('-1');
-            $sSQL .= '    get_name_by_language(name,'.$this->aLangPrefOrderSql.') AS placename,';
-            $sSQL .= "    get_name_by_language(name, ARRAY['ref']) AS ref,";
-            if ($this->bExtraTags) {
-                $sSQL .= 'hstore_to_json(extratags)::text AS extra,';
-            }
-            if ($this->bNameDetails) {
-                $sSQL .= 'hstore_to_json(name)::text AS names,';
-            }
-            $sSQL .= '    avg(ST_X(centroid)) AS lon, ';
-            $sSQL .= '    avg(ST_Y(centroid)) AS lat, ';
-            $sSQL .= '    COALESCE(importance,0.75-(rank_search::float/40)) AS importance, ';
-            $sSQL .= $this->addressImportanceSql(
-                'ST_Collect(centroid)',
-                'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)'
-            );
-            $sSQL .= "    COALESCE(extratags->'place', extratags->'linked_place') AS extra_place ";
-            $sSQL .= ' FROM placex';
-            $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
-            $sSQL .= '   AND (';
-            $sSQL .= "        placex.rank_address between $iMinRank and $iMaxRank ";
-            if (14 >= $iMinRank && 14 <= $iMaxRank) {
-                $sSQL .= "    OR (extratags->'place') = 'city'";
-            }
-            if ($this->sAddressRankListSql) {
-                $sSQL .= '    OR placex.rank_address in '.$this->sAddressRankListSql;
-            }
-            $sSQL .= '       ) ';
-            if ($this->sAllowedTypesSQLList) {
-                $sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList;
-            }
-            if (!$bAllowLinked) {
-                $sSQL .= '    AND linked_place_id is null ';
-            }
-            $sSQL .= ' GROUP BY ';
-            $sSQL .= '     osm_type, ';
-            $sSQL .= '     osm_id, ';
-            $sSQL .= '     class, ';
-            $sSQL .= '     type, ';
-            $sSQL .= '     admin_level, ';
-            $sSQL .= '     rank_search, ';
-            $sSQL .= '     rank_address, ';
-            $sSQL .= '     housenumber,';
-            $sSQL .= '     country_code, ';
-            $sSQL .= '     importance, ';
-            if (!$this->bDeDupe) {
-                $sSQL .= 'place_id,';
-            }
-            if (!$this->bAddressDetails) {
-                $sSQL .= 'langaddress, ';
-            }
-            $sSQL .= '     placename, ';
-            $sSQL .= '     ref, ';
-            if ($this->bExtraTags) {
-                $sSQL .= 'extratags, ';
-            }
-            if ($this->bNameDetails) {
-                $sSQL .= 'name, ';
-            }
-            $sSQL .= '     extra_place ';
-
-            $aSubSelects[] = $sSQL;
-        }
-
-        // postcode table
-        $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
-        if ($sPlaceIDs) {
-            Debug::printVar('Ids from location_postcode', $sPlaceIDs);
-            $sSQL = 'SELECT';
-            $sSQL .= "  'P' as osm_type,";
-            $sSQL .= '  (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,';
-            $sSQL .= "  'place' as class, 'postcode' as type,";
-            $sSQL .= '  null::smallint as admin_level, rank_search, rank_address,';
-            $sSQL .= '  place_id, parent_place_id,';
-            $sSQL .= '  -1 as housenumber,';
-            $sSQL .= '  country_code,';
-            $sSQL .= $this->langAddressSql('-1');
-            $sSQL .= '  postcode as placename,';
-            $sSQL .= '  postcode as ref,';
-            if ($this->bExtraTags) {
-                $sSQL .= 'null::text AS extra,';
-            }
-            if ($this->bNameDetails) {
-                $sSQL .= 'null::text AS names,';
-            }
-            $sSQL .= '  ST_x(geometry) AS lon, ST_y(geometry) AS lat,';
-            $sSQL .= '  (0.75-(rank_search::float/40)) AS importance, ';
-            $sSQL .= $this->addressImportanceSql('geometry', 'lp.parent_place_id');
-            $sSQL .= '  null::text AS extra_place ';
-            $sSQL .= 'FROM location_postcode lp';
-            $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
-            $sSQL .= "   AND lp.rank_address between $iMinRank and $iMaxRank";
-
-            $aSubSelects[] = $sSQL;
-        }
-
-        // All other tables are rank 30 only.
-        if ($iMaxRank == 30) {
-            // TIGER table
-            if (CONST_Use_US_Tiger_Data) {
-                $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_TIGER);
-                if ($sPlaceIDs) {
-                    Debug::printVar('Ids from Tiger table', $sPlaceIDs);
-                    $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_TIGER);
-                    // Tiger search only if a housenumber was searched and if it was found
-                    // (realized through a join)
-                    $sSQL = ' SELECT ';
-                    $sSQL .= "     'T' AS osm_type, ";
-                    $sSQL .= '     (SELECT osm_id from placex p WHERE p.place_id=blub.parent_place_id) as osm_id, ';
-                    $sSQL .= "     'place' AS class, ";
-                    $sSQL .= "     'house' AS type, ";
-                    $sSQL .= '     null::smallint AS admin_level, ';
-                    $sSQL .= '     30 AS rank_search, ';
-                    $sSQL .= '     30 AS rank_address, ';
-                    $sSQL .= '     place_id, ';
-                    $sSQL .= '     parent_place_id, ';
-                    $sSQL .= '     housenumber_for_place as housenumber,';
-                    $sSQL .= "     'us' AS country_code, ";
-                    $sSQL .= $this->langAddressSql('housenumber_for_place');
-                    $sSQL .= '     null::text AS placename, ';
-                    $sSQL .= '     null::text AS ref, ';
-                    if ($this->bExtraTags) {
-                        $sSQL .= 'null::text AS extra,';
-                    }
-                    if ($this->bNameDetails) {
-                        $sSQL .= 'null::text AS names,';
-                    }
-                    $sSQL .= '     st_x(centroid) AS lon, ';
-                    $sSQL .= '     st_y(centroid) AS lat,';
-                    $sSQL .= '     -1.15 AS importance, ';
-                    $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
-                    $sSQL .= '     null::text AS extra_place ';
-                    $sSQL .= ' FROM (';
-                    $sSQL .= '     SELECT place_id, ';    // interpolate the Tiger housenumbers here
-                    $sSQL .= '         CASE WHEN startnumber != endnumber';
-                    $sSQL .= '              THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float)';
-                    $sSQL .= '              ELSE ST_LineInterpolatePoint(linegeo, 0.5) END AS centroid, ';
-                    $sSQL .= '         parent_place_id, ';
-                    $sSQL .= '         housenumber_for_place';
-                    $sSQL .= '     FROM (';
-                    $sSQL .= '            location_property_tiger ';
-                    $sSQL .= '            JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)) ';
-                    $sSQL .= '     WHERE ';
-                    $sSQL .= '         housenumber_for_place >= startnumber';
-                    $sSQL .= '         AND housenumber_for_place <= endnumber';
-                    $sSQL .= ' ) AS blub'; //postgres wants an alias here
-
-                    $aSubSelects[] = $sSQL;
-                }
-            }
-
-            // osmline - interpolated housenumbers
-            $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_OSMLINE);
-            if ($sPlaceIDs) {
-                Debug::printVar('Ids from interpolation', $sPlaceIDs);
-                $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_OSMLINE);
-                // interpolation line search only if a housenumber was searched
-                // (realized through a join)
-                $sSQL = 'SELECT ';
-                $sSQL .= "  'W' AS osm_type, ";
-                $sSQL .= '  osm_id, ';
-                $sSQL .= "  'place' AS class, ";
-                $sSQL .= "  'house' AS type, ";
-                $sSQL .= '  null::smallint AS admin_level, ';
-                $sSQL .= '  30 AS rank_search, ';
-                $sSQL .= '  30 AS rank_address, ';
-                $sSQL .= '  place_id, ';
-                $sSQL .= '  parent_place_id, ';
-                $sSQL .= '  housenumber_for_place as housenumber,';
-                $sSQL .= '  country_code, ';
-                $sSQL .= $this->langAddressSql('housenumber_for_place');
-                $sSQL .= '  null::text AS placename, ';
-                $sSQL .= '  null::text AS ref, ';
-                if ($this->bExtraTags) {
-                    $sSQL .= 'null::text AS extra, ';
-                }
-                if ($this->bNameDetails) {
-                    $sSQL .= 'null::text AS names, ';
-                }
-                $sSQL .= '  st_x(centroid) AS lon, ';
-                $sSQL .= '  st_y(centroid) AS lat, ';
-                // slightly smaller than the importance for normal houses
-                $sSQL .= '  -0.1 AS importance, ';
-                $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
-                $sSQL .= '  null::text AS extra_place ';
-                $sSQL .= '  FROM (';
-                $sSQL .= '     SELECT ';
-                $sSQL .= '         osm_id, ';
-                $sSQL .= '         place_id, ';
-                $sSQL .= '         country_code, ';
-                $sSQL .= '         CASE ';             // interpolate the housenumbers here
-                $sSQL .= '           WHEN startnumber != endnumber ';
-                $sSQL .= '           THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) ';
-                $sSQL .= '           ELSE linegeo ';
-                $sSQL .= '         END as centroid, ';
-                $sSQL .= '         parent_place_id, ';
-                $sSQL .= '         housenumber_for_place ';
-                $sSQL .= '     FROM (';
-                $sSQL .= '            location_property_osmline ';
-                $sSQL .= '            JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)';
-                $sSQL .= '          ) ';
-                $sSQL .= '     WHERE housenumber_for_place >= 0 ';
-                $sSQL .= '  ) as blub'; //postgres wants an alias here
-
-                $aSubSelects[] = $sSQL;
-            }
-        }
-
-        if (empty($aSubSelects)) {
-            return array();
-        }
-
-        $sSQL = join(' UNION ', $aSubSelects);
-        Debug::printSQL($sSQL);
-        $aPlaces = $this->oDB->getAll($sSQL, null, 'Could not lookup place');
-
-        foreach ($aPlaces as &$aPlace) {
-            $aPlace['importance'] = (float) $aPlace['importance'];
-            if ($this->bAddressDetails) {
-                // to get addressdetails for tiger data, the housenumber is needed
-                $aPlace['address'] = new AddressDetails(
-                    $this->oDB,
-                    $aPlace['place_id'],
-                    $aPlace['housenumber'],
-                    $this->aLangPrefOrderSql
-                );
-                $aPlace['langaddress'] = $aPlace['address']->getLocaleAddress();
-            }
-
-            if ($this->bExtraTags) {
-                if ($aPlace['extra']) {
-                    $aPlace['sExtraTags'] = json_decode($aPlace['extra'], true);
-                } else {
-                    $aPlace['sExtraTags'] = (object) array();
-                }
-            }
-
-            if ($this->bNameDetails) {
-                $aPlace['sNameDetails'] = $this->extractNames($aPlace['names']);
-            }
-
-            $aPlace['addresstype'] = ClassTypes\getLabelTag(
-                $aPlace,
-                $aPlace['country_code']
-            );
-
-            $aResults[$aPlace['place_id']] = $aPlace;
-        }
-
-        $aResults = array_filter(
-            $aResults,
-            function ($v) {
-                return !($v instanceof Result);
-            }
-        );
-
-        Debug::printVar('Places', $aResults);
-
-        return $aResults;
-    }
-
-
-    private function extractNames($sNames)
-    {
-        if (!$sNames) {
-            return (object) array();
-        }
-
-        $aFullNames = json_decode($sNames, true);
-        $aNames = array();
-
-        foreach ($aFullNames as $sKey => $sValue) {
-            if (strpos($sKey, '_place_') === 0) {
-                $sSubKey = substr($sKey, 7);
-                if (array_key_exists($sSubKey, $aFullNames)) {
-                    $aNames[$sKey] = $sValue;
-                } else {
-                    $aNames[$sSubKey] = $sValue;
-                }
-            } else {
-                $aNames[$sKey] = $sValue;
-            }
-        }
-
-        return $aNames;
-    }
-
-
-    /* returns an array which will contain the keys
-     *   aBoundingBox
-     * and may also contain one or more of the keys
-     *   asgeojson
-     *   askml
-     *   assvg
-     *   astext
-     *   lat
-     *   lon
-     */
-    public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null, $fLonReverse = null, $fLatReverse = null)
-    {
-
-        $aOutlineResult = array();
-        if (!$iPlaceID) {
-            return $aOutlineResult;
-        }
-
-        // Get the bounding box and outline polygon
-        $sSQL = 'select place_id,0 as numfeatures,st_area(geometry) as area,';
-        $sSQL .= ' ST_Y(centroid) as centrelat, ST_X(centroid) as centrelon,';
-        $sSQL .= ' ST_YMin(geometry) as minlat,ST_YMax(geometry) as maxlat,';
-        $sSQL .= ' ST_XMin(geometry) as minlon,ST_XMax(geometry) as maxlon';
-        if ($this->bIncludePolygonAsGeoJSON) {
-            $sSQL .= ',ST_AsGeoJSON(geometry) as asgeojson';
-        }
-        if ($this->bIncludePolygonAsKML) {
-            $sSQL .= ',ST_AsKML(geometry) as askml';
-        }
-        if ($this->bIncludePolygonAsSVG) {
-            $sSQL .= ',ST_AsSVG(geometry) as assvg';
-        }
-        if ($this->bIncludePolygonAsText) {
-            $sSQL .= ',ST_AsText(geometry) as astext';
-        }
-
-        $sSQL .= ' FROM (SELECT place_id';
-        if ($fLonReverse != null && $fLatReverse != null) {
-            $sSQL .= ',CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
-            $sSQL .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
-            $sSQL .=' ELSE centroid END AS centroid';
-        } else {
-            $sSQL .= ',centroid';
-        }
-        if ($this->fPolygonSimplificationThreshold > 0) {
-            $sSQL .= ',ST_SimplifyPreserveTopology(geometry,'.$this->fPolygonSimplificationThreshold.') as geometry';
-        } else {
-            $sSQL .= ',geometry';
-        }
-        $sSQL .= ' FROM placex where place_id = '.$iPlaceID.') as plx';
-
-        $aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline');
-
-        if ($aPointPolygon && $aPointPolygon['place_id']) {
-            if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
-                $aOutlineResult['lat'] = $aPointPolygon['centrelat'];
-                $aOutlineResult['lon'] = $aPointPolygon['centrelon'];
-            }
-
-            if ($this->bIncludePolygonAsGeoJSON) {
-                $aOutlineResult['asgeojson'] = $aPointPolygon['asgeojson'];
-            }
-            if ($this->bIncludePolygonAsKML) {
-                $aOutlineResult['askml'] = $aPointPolygon['askml'];
-            }
-            if ($this->bIncludePolygonAsSVG) {
-                $aOutlineResult['assvg'] = $aPointPolygon['assvg'];
-            }
-            if ($this->bIncludePolygonAsText) {
-                $aOutlineResult['astext'] = $aPointPolygon['astext'];
-            }
-
-            if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
-                $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
-                $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
-            }
-
-            if (abs($aPointPolygon['minlon'] - $aPointPolygon['maxlon']) < 0.0000001) {
-                $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
-                $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
-            }
-
-            $aOutlineResult['aBoundingBox'] = array(
-                                               (string)$aPointPolygon['minlat'],
-                                               (string)$aPointPolygon['maxlat'],
-                                               (string)$aPointPolygon['minlon'],
-                                               (string)$aPointPolygon['maxlon']
-                                              );
-        }
-
-        // as a fallback we generate a bounding box without knowing the size of the geometry
-        if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) {
-            $aBounds = array(
-                        'minlat' => $fLat - $fRadius,
-                        'maxlat' => $fLat + $fRadius,
-                        'minlon' => $fLon - $fRadius,
-                        'maxlon' => $fLon + $fRadius
-                       );
-
-            $aOutlineResult['aBoundingBox'] = array(
-                                               (string)$aBounds['minlat'],
-                                               (string)$aBounds['maxlat'],
-                                               (string)$aBounds['minlon'],
-                                               (string)$aBounds['maxlon']
-                                              );
-        }
-        return $aOutlineResult;
-    }
-}
diff --git a/lib-php/Result.php b/lib-php/Result.php
deleted file mode 100644 (file)
index 4b244d1..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * A single result of a search operation or a reverse lookup.
- *
- * This object only contains the id of the result. It does not yet
- * have any details needed to format the output document.
- */
-class Result
-{
-    const TABLE_PLACEX = 0;
-    const TABLE_POSTCODE = 1;
-    const TABLE_OSMLINE = 2;
-    const TABLE_TIGER = 3;
-
-    /// Database table that contains the result.
-    public $iTable;
-    /// Id of the result.
-    public $iId;
-    /// House number (only for interpolation results).
-    public $iHouseNumber = -1;
-    /// Number of exact matches in address (address searches only).
-    public $iExactMatches = 0;
-    /// Subranking within the results (the higher the worse).
-    public $iResultRank = 0;
-    /// Address rank of the result.
-    public $iAddressRank;
-
-    public function debugInfo()
-    {
-        return array(
-                'Table' => $this->iTable,
-                'ID' => $this->iId,
-                'House number' => $this->iHouseNumber,
-                'Exact Matches' => $this->iExactMatches,
-                'Result rank' => $this->iResultRank
-               );
-    }
-
-
-    public function __construct($sId, $iTable = Result::TABLE_PLACEX)
-    {
-        $this->iTable = $iTable;
-        $this->iId = (int) $sId;
-    }
-
-    public static function joinIdsByTable($aResults, $iTable)
-    {
-        return join(',', array_keys(array_filter(
-            $aResults,
-            function ($aValue) use ($iTable) {
-                return $aValue->iTable == $iTable;
-            }
-        )));
-    }
-
-    public static function joinIdsByTableMinRank($aResults, $iTable, $iMinAddressRank)
-    {
-        return join(',', array_keys(array_filter(
-            $aResults,
-            function ($aValue) use ($iTable, $iMinAddressRank) {
-                return $aValue->iTable == $iTable && $aValue->iAddressRank >= $iMinAddressRank;
-            }
-        )));
-    }
-
-    public static function joinIdsByTableMaxRank($aResults, $iTable, $iMaxAddressRank)
-    {
-        return join(',', array_keys(array_filter(
-            $aResults,
-            function ($aValue) use ($iTable, $iMaxAddressRank) {
-                return $aValue->iTable == $iTable && $aValue->iAddressRank <= $iMaxAddressRank;
-            }
-        )));
-    }
-
-    public static function sqlHouseNumberTable($aResults, $iTable)
-    {
-        $sHousenumbers = '';
-        $sSep = '';
-        foreach ($aResults as $oResult) {
-            if ($oResult->iTable == $iTable) {
-                $sHousenumbers .= $sSep.'('.$oResult->iId.',';
-                $sHousenumbers .= $oResult->iHouseNumber.')';
-                $sSep = ',';
-            }
-        }
-
-        return $sHousenumbers;
-    }
-
-    /**
-     * Split a result array into highest ranked result and the rest
-     *
-     * @param object[] $aResults List of results to split.
-     *
-     * @return array[]
-     */
-    public static function splitResults($aResults)
-    {
-        $aHead = array();
-        $aTail = array();
-        $iMinRank = 10000;
-
-        foreach ($aResults as $oRes) {
-            if ($oRes->iResultRank < $iMinRank) {
-                $aTail += $aHead;
-                $aHead = array($oRes->iId => $oRes);
-                $iMinRank = $oRes->iResultRank;
-            } elseif ($oRes->iResultRank == $iMinRank) {
-                $aHead[$oRes->iId] = $oRes;
-            } else {
-                $aTail[$oRes->iId] = $oRes;
-            }
-        }
-
-        return array('head' => $aHead, 'tail' => $aTail);
-    }
-}
diff --git a/lib-php/ReverseGeocode.php b/lib-php/ReverseGeocode.php
deleted file mode 100644 (file)
index f6ea590..0000000
+++ /dev/null
@@ -1,401 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Result.php');
-
-class ReverseGeocode
-{
-    protected $oDB;
-    protected $iMaxRank = 28;
-
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-    }
-
-
-    public function setZoom($iZoom)
-    {
-        // Zoom to rank, this could probably be calculated but a lookup gives fine control
-        $aZoomRank = array(
-                      0 => 2, // Continent / Sea
-                      1 => 2,
-                      2 => 2,
-                      3 => 4, // Country
-                      4 => 4,
-                      5 => 8, // State
-                      6 => 10, // Region
-                      7 => 10,
-                      8 => 12, // County
-                      9 => 12,
-                      10 => 17, // City
-                      11 => 17,
-                      12 => 18, // Town
-                      13 => 19, // Village
-                      14 => 22, // Neighbourhood
-                      15 => 25, // Locality
-                      16 => 26, // major street
-                      17 => 27, // minor street
-                      18 => 30, // or >, Building
-                      19 => 30, // or >, Building
-                     );
-        $this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
-    }
-
-    /**
-     * Find the closest interpolation with the given search diameter.
-     *
-     * @param string $sPointSQL   Reverse geocoding point as SQL
-     * @param float  $fSearchDiam Search diameter
-     *
-     * @return Record of the interpolation or null.
-     */
-    protected function lookupInterpolation($sPointSQL, $fSearchDiam)
-    {
-        Debug::newFunction('lookupInterpolation');
-        $sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,';
-        $sSQL .= '  (CASE WHEN endnumber != startnumber';
-        $sSQL .= '        THEN (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.')';
-        $sSQL .= '        ELSE startnumber END) as fhnr,';
-        $sSQL .= '  startnumber, endnumber, step,';
-        $sSQL .= '  ST_Distance(linegeo,'.$sPointSQL.') as distance';
-        $sSQL .= ' FROM location_property_osmline';
-        $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
-        $sSQL .= '       and indexed_status = 0 and startnumber is not NULL ';
-        $sSQL .= '       and parent_place_id != 0';
-        $sSQL .= ' ORDER BY distance ASC limit 1';
-        Debug::printSQL($sSQL);
-
-        return $this->oDB->getRow(
-            $sSQL,
-            null,
-            'Could not determine closest housenumber on an osm interpolation line.'
-        );
-    }
-
-    protected function lookupLargeArea($sPointSQL, $iMaxRank)
-    {
-        $sCountryCode = $this->getCountryCode($sPointSQL);
-        if (CONST_Search_WithinCountries and $sCountryCode == null) {
-            return  null;
-        }
-
-        if ($iMaxRank > 4) {
-            $aPlace = $this->lookupPolygon($sPointSQL, $iMaxRank);
-            if ($aPlace) {
-                return new Result($aPlace['place_id']);
-            }
-        }
-
-        // If no polygon which contains the searchpoint is found,
-        // searches in the country_osm_grid table for a polygon.
-        return  $this->lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode);
-    }
-
-    protected function getCountryCode($sPointSQL)
-    {
-        Debug::newFunction('getCountryCode');
-        // searches for polygon in table country_osm_grid which contains the searchpoint
-        // and searches for the nearest place node to the searchpoint in this polygon
-        $sSQL = 'SELECT country_code FROM country_osm_grid';
-        $sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1';
-        Debug::printSQL($sSQL);
-
-        $sCountryCode = $this->oDB->getOne(
-            $sSQL,
-            null,
-            'Could not determine country polygon containing the point.'
-        );
-        return $sCountryCode;
-    }
-
-    protected function lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode)
-    {
-        Debug::newFunction('lookupInCountry');
-        if ($sCountryCode) {
-            if ($iMaxRank > 4) {
-                // look for place nodes with the given country code
-                $sSQL = 'SELECT place_id FROM';
-                $sSQL .= ' (SELECT place_id, rank_search,';
-                $sSQL .= '         ST_distance('.$sPointSQL.', geometry) as distance';
-                $sSQL .= ' FROM placex';
-                $sSQL .= ' WHERE osm_type = \'N\'';
-                $sSQL .= ' AND country_code = \''.$sCountryCode.'\'';
-                $sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index
-                $sSQL .= ' AND rank_search between 5 and ' .min(25, $iMaxRank);
-                $sSQL .= ' AND type != \'postcode\'';
-                $sSQL .= ' AND name IS NOT NULL ';
-                $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
-                $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
-                $sSQL .= ') as a ';
-                $sSQL .= 'WHERE distance <= reverse_place_diameter(rank_search)';
-                $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
-                $sSQL .= ' LIMIT 1';
-                Debug::printSQL($sSQL);
-
-                $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
-                Debug::printVar('Country node', $aPlace);
-
-                if ($aPlace) {
-                    return new Result($aPlace['place_id']);
-                }
-            }
-
-            // still nothing, then return the country object
-            $sSQL = 'SELECT place_id, ST_distance('.$sPointSQL.', centroid) as distance';
-            $sSQL .= ' FROM placex';
-            $sSQL .= ' WHERE country_code = \''.$sCountryCode.'\'';
-            $sSQL .= ' AND rank_search = 4 AND rank_address = 4';
-            $sSQL .= ' AND class in (\'boundary\',  \'place\')';
-            $sSQL .= ' AND linked_place_id is null';
-            $sSQL .= ' ORDER BY distance ASC';
-            Debug::printSQL($sSQL);
-
-            $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
-            Debug::printVar('Country place', $aPlace);
-            if ($aPlace) {
-                return new Result($aPlace['place_id']);
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Search for areas or nodes for areas or nodes between state and suburb level.
-     *
-     * @param string $sPointSQL Search point as SQL string.
-     * @param int    $iMaxRank  Maximum address rank of the feature.
-     *
-     * @return Record of the found feature or null.
-     *
-     * Searches first for polygon that contains the search point.
-     * If such a polygon is found, place nodes with a higher rank are
-     * searched inside the polygon.
-     */
-    protected function lookupPolygon($sPointSQL, $iMaxRank)
-    {
-        Debug::newFunction('lookupPolygon');
-        // polygon search begins at suburb-level
-        if ($iMaxRank > 25) {
-            $iMaxRank = 25;
-        }
-        // no polygon search over country-level
-        if ($iMaxRank < 5) {
-            $iMaxRank = 5;
-        }
-        // search for polygon
-        $sSQL = 'SELECT place_id, parent_place_id, rank_address, rank_search FROM';
-        $sSQL .= '(select place_id, parent_place_id, rank_address, rank_search, country_code, geometry';
-        $sSQL .= ' FROM placex';
-        $sSQL .= ' WHERE ST_GeometryType(geometry) in (\'ST_Polygon\', \'ST_MultiPolygon\')';
-        // Ensure that query planner doesn't use the index on rank_search.
-        $sSQL .= ' AND coalesce(rank_search, 0) between 5 and ' .$iMaxRank;
-        $sSQL .= ' AND rank_address between 4 and 25'; // needed for index selection
-        $sSQL .= ' AND geometry && '.$sPointSQL;
-        $sSQL .= ' AND type != \'postcode\' ';
-        $sSQL .= ' AND name is not null';
-        $sSQL .= ' AND indexed_status = 0 and linked_place_id is null';
-        $sSQL .= ' ORDER BY rank_search DESC LIMIT 50 ) as a';
-        $sSQL .= ' WHERE ST_Contains(geometry, '.$sPointSQL.' )';
-        $sSQL .= ' ORDER BY rank_search DESC LIMIT 1';
-        Debug::printSQL($sSQL);
-
-        $aPoly = $this->oDB->getRow($sSQL, null, 'Could not determine polygon containing the point.');
-        Debug::printVar('Polygon result', $aPoly);
-
-        if ($aPoly) {
-        // if a polygon is found, search for placenodes begins ...
-            $iRankAddress = $aPoly['rank_address'];
-            $iRankSearch = $aPoly['rank_search'];
-            $iPlaceID = $aPoly['place_id'];
-
-            if ($iRankSearch != $iMaxRank) {
-                $sSQL = 'SELECT place_id FROM ';
-                $sSQL .= '(SELECT place_id, rank_search, country_code, geometry,';
-                $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
-                $sSQL .= ' FROM placex';
-                $sSQL .= ' WHERE osm_type = \'N\'';
-                $sSQL .= ' AND rank_search > '.$iRankSearch;
-                $sSQL .= ' AND rank_search <= '.$iMaxRank;
-                $sSQL .= ' AND rank_address between 4 and 25';  // needed to select right index
-                $sSQL .= ' AND type != \'postcode\'';
-                $sSQL .= ' AND name IS NOT NULL ';
-                $sSQL .= ' AND indexed_status = 0 AND linked_place_id is null';
-                $sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
-                $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
-                $sSQL .= ' limit 100) as a';
-                $sSQL .= ' WHERE ST_Contains((SELECT geometry FROM placex WHERE place_id = '.$iPlaceID.'), geometry )';
-                $sSQL .= ' AND distance <= reverse_place_diameter(rank_search)';
-                $sSQL .= ' ORDER BY rank_search DESC, distance ASC';
-                $sSQL .= ' LIMIT 1';
-                Debug::printSQL($sSQL);
-
-                $aPlaceNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
-                Debug::printVar('Nearest place node', $aPlaceNode);
-                if ($aPlaceNode) {
-                    return $aPlaceNode;
-                }
-            }
-        }
-        return $aPoly;
-    }
-
-
-    public function lookup($fLat, $fLon, $bDoInterpolation = true)
-    {
-        return $this->lookupPoint(
-            'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)',
-            $bDoInterpolation
-        );
-    }
-
-    public function lookupPoint($sPointSQL, $bDoInterpolation = true)
-    {
-        Debug::newFunction('lookupPoint');
-        // Find the nearest point
-        $fSearchDiam = 0.006;
-        $oResult = null;
-        $aPlace = null;
-
-        // for POI or street level
-        if ($this->iMaxRank >= 26) {
-            // starts if the search is on POI or street level,
-            // searches for the nearest POI or street,
-            // if a street is found and a POI is searched for,
-            // the nearest POI which the found street is a parent of is chosen.
-            $sSQL = 'select place_id,parent_place_id,rank_address,country_code,';
-            $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
-            $sSQL .= ' FROM ';
-            $sSQL .= ' placex';
-            $sSQL .= '   WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
-            $sSQL .= '   AND';
-            $sSQL .= ' rank_address between 26 and '.$this->iMaxRank;
-            $sSQL .= ' and (name is not null or housenumber is not null';
-            $sSQL .= ' or rank_address between 26 and 27)';
-            $sSQL .= ' and (rank_address between 26 and 27';
-            $sSQL .= '      or ST_GeometryType(geometry) != \'ST_LineString\')';
-            $sSQL .= ' and class not in (\'boundary\')';
-            $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
-            $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
-            $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
-            $sSQL .= ' ORDER BY distance ASC limit 1';
-            Debug::printSQL($sSQL);
-
-            $aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
-
-            Debug::printVar('POI/street level result', $aPlace);
-            if ($aPlace) {
-                $iPlaceID = $aPlace['place_id'];
-                $oResult = new Result($iPlaceID);
-                $iRankAddress = $aPlace['rank_address'];
-            }
-
-            if ($aPlace) {
-                // if street and maxrank > streetlevel
-                if ($iRankAddress <= 27 && $this->iMaxRank > 27) {
-                    // find the closest object (up to a certain radius) of which the street is a parent of
-                    $sSQL = ' select place_id,';
-                    $sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
-                    $sSQL .= ' FROM ';
-                    $sSQL .= ' placex';
-                    // radius ?
-                    $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)';
-                    $sSQL .= ' AND parent_place_id = '.$iPlaceID;
-                    $sSQL .= ' and rank_address > 28';
-                    $sSQL .= ' and ST_GeometryType(geometry) != \'ST_LineString\'';
-                    $sSQL .= ' and (name is not null or housenumber is not null)';
-                    $sSQL .= ' and class not in (\'boundary\')';
-                    $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
-                    $sSQL .= ' ORDER BY distance ASC limit 1';
-                    Debug::printSQL($sSQL);
-
-                    $aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
-                    Debug::printVar('Closest POI result', $aStreet);
-
-                    if ($aStreet) {
-                        $aPlace = $aStreet;
-                        $oResult = new Result($aStreet['place_id']);
-                        $iRankAddress = 30;
-                    }
-                }
-
-                  // In the US we can check TIGER data for nearest housenumber
-                if (CONST_Use_US_Tiger_Data
-                    && $iRankAddress <= 27
-                    && $aPlace['country_code'] == 'us'
-                    && $this->iMaxRank >= 28
-                ) {
-                    $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search,';
-                    $sSQL .= '      (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fhnr,';
-                    $sSQL .= '      startnumber, endnumber, step,';
-                    $sSQL .= '      ST_Distance('.$sPointSQL.', linegeo) as distance';
-                    $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$oResult->iId;
-                    $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)';
-                    $sSQL .= ' ORDER BY distance ASC limit 1';
-                    Debug::printSQL($sSQL);
-
-                    $aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.');
-                    Debug::printVar('Tiger house number result', $aPlaceTiger);
-
-                    if ($aPlaceTiger) {
-                        $aPlace = $aPlaceTiger;
-                        $oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);
-                        $iRndNum = max(0, round($aPlaceTiger['fhnr'] / $aPlaceTiger['step']) * $aPlaceTiger['step']);
-                        $oResult->iHouseNumber = $aPlaceTiger['startnumber'] + $iRndNum;
-                        if ($oResult->iHouseNumber > $aPlaceTiger['endnumber']) {
-                            $oResult->iHouseNumber = $aPlaceTiger['endnumber'];
-                        }
-                        $iRankAddress = 30;
-                    }
-                }
-            }
-
-            if ($bDoInterpolation && $this->iMaxRank >= 30) {
-                $fDistance = $fSearchDiam;
-                if ($aPlace) {
-                    // We can't reliably go from the closest street to an
-                    // interpolation line because the closest interpolation
-                    // may have a different street segments as a parent.
-                    // Therefore allow an interpolation line to take precedence
-                    // even when the street is closer.
-                    $fDistance = $iRankAddress < 28 ? 0.001 : $aPlace['distance'];
-                }
-
-                $aHouse = $this->lookupInterpolation($sPointSQL, $fDistance);
-                Debug::printVar('Interpolation result', $aPlace);
-
-                if ($aHouse) {
-                    $oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
-                    $iRndNum = max(0, round($aHouse['fhnr'] / $aHouse['step']) * $aHouse['step']);
-                    $oResult->iHouseNumber = $aHouse['startnumber'] + $iRndNum;
-                    if ($oResult->iHouseNumber > $aHouse['endnumber']) {
-                        $oResult->iHouseNumber = $aHouse['endnumber'];
-                    }
-                    $aPlace = $aHouse;
-                }
-            }
-
-            if (!$aPlace) {
-                // if no POI or street is found ...
-                $oResult = $this->lookupLargeArea($sPointSQL, 25);
-            }
-        } else {
-            // lower than street level ($iMaxRank < 26 )
-            $oResult = $this->lookupLargeArea($sPointSQL, $this->iMaxRank);
-        }
-
-        Debug::printVar('Final result', $oResult);
-        return $oResult;
-    }
-}
diff --git a/lib-php/SearchContext.php b/lib-php/SearchContext.php
deleted file mode 100644 (file)
index 3223b5c..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-
-
-/**
- * Collection of search constraints that are independent of the
- * actual interpretation of the search query.
- *
- * The search context is shared between all SearchDescriptions. This
- * object mainly serves as context provider for the database queries.
- * Therefore most data is directly cached as SQL statements.
- */
-class SearchContext
-{
-    /// Search radius around a given Near reference point.
-    private $fNearRadius = false;
-    /// True if search must be restricted to viewbox only.
-    public $bViewboxBounded = false;
-
-    /// Reference point for search (as SQL).
-    public $sqlNear = '';
-    /// Viewbox selected for search (as SQL).
-    public $sqlViewboxSmall = '';
-    /// Viewbox with a larger buffer around (as SQL).
-    public $sqlViewboxLarge = '';
-    /// Reference along a route (as SQL).
-    public $sqlViewboxCentre = '';
-    /// List of countries to restrict search to (as array).
-    public $aCountryList = null;
-    /// List of countries to restrict search to (as SQL).
-    public $sqlCountryList = '';
-    /// List of place IDs to exclude (as SQL).
-    private $sqlExcludeList = '';
-    /// Subset of word ids of full words in the query.
-    private $aFullNameWords = array();
-
-    public function setFullNameWords($aWordList)
-    {
-        $this->aFullNameWords = $aWordList;
-    }
-
-    public function getFullNameTerms()
-    {
-        return $this->aFullNameWords;
-    }
-
-    /**
-     * Check if a reference point is defined.
-     *
-     * @return bool True if a reference point is defined.
-     */
-    public function hasNearPoint()
-    {
-        return $this->fNearRadius !== false;
-    }
-
-    /**
-     * Get radius around reference point.
-     *
-     * @return float Search radius around reference point.
-     */
-    public function nearRadius()
-    {
-        return $this->fNearRadius;
-    }
-
-    /**
-     * Set search reference point in WGS84.
-     *
-     * If set, then only places around this point will be taken into account.
-     *
-     * @param float $fLat    Latitude of point.
-     * @param float $fLon    Longitude of point.
-     * @param float $fRadius Search radius around point.
-     *
-     * @return void
-     */
-    public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
-    {
-        $this->fNearRadius = $fRadius;
-        $this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
-    }
-
-    /**
-     * Check if the search is geographically restricted.
-     *
-     * Searches are restricted if a reference point is given or if
-     * a bounded viewbox is set.
-     *
-     * @return bool True, if the search is geographically bounded.
-     */
-    public function isBoundedSearch()
-    {
-        return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
-    }
-
-    /**
-     * Set rectangular viewbox.
-     *
-     * The viewbox may be bounded which means that no search results
-     * must be outside the viewbox.
-     *
-     * @param float[4] $aViewBox Coordinates of the viewbox.
-     * @param bool     $bBounded True if the viewbox is bounded.
-     *
-     * @return void
-     */
-    public function setViewboxFromBox(&$aViewBox, $bBounded)
-    {
-        $this->bViewboxBounded = $bBounded;
-        $this->sqlViewboxCentre = '';
-
-        $this->sqlViewboxSmall = sprintf(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
-            $aViewBox[0],
-            $aViewBox[1],
-            $aViewBox[2],
-            $aViewBox[3]
-        );
-
-        $fHeight = abs($aViewBox[0] - $aViewBox[2]);
-        $fWidth = abs($aViewBox[1] - $aViewBox[3]);
-
-        $this->sqlViewboxLarge = sprintf(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
-            max($aViewBox[0], $aViewBox[2]) + $fHeight,
-            max($aViewBox[1], $aViewBox[3]) + $fWidth,
-            min($aViewBox[0], $aViewBox[2]) - $fHeight,
-            min($aViewBox[1], $aViewBox[3]) - $fWidth
-        );
-    }
-
-    /**
-     * Set viewbox along a route.
-     *
-     * The viewbox may be bounded which means that no search results
-     * must be outside the viewbox.
-     *
-     * @param object   $oDB          Nominatim::DB instance to use for computing the box.
-     * @param string[] $aRoutePoints List of x,y coordinates along a route.
-     * @param float    $fRouteWidth  Buffer around the route to use.
-     * @param bool     $bBounded     True if the viewbox bounded.
-     *
-     * @return void
-     */
-    public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
-    {
-        $this->bViewboxBounded = $bBounded;
-        $this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
-        $sSep = '';
-        foreach ($aRoutePoints as $aPoint) {
-            $fPoint = (float)$aPoint;
-            $this->sqlViewboxCentre .= $sSep.$fPoint;
-            $sSep = ($sSep == ' ') ? ',' : ' ';
-        }
-        $this->sqlViewboxCentre .= ")'::geometry,4326)";
-
-        $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
-        $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
-        $this->sqlViewboxSmall = "'".$sGeom."'::geometry";
-
-        $sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')';
-        $sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox');
-        $this->sqlViewboxLarge = "'".$sGeom."'::geometry";
-    }
-
-    /**
-     * Set list of excluded place IDs.
-     *
-     * @param integer[] $aExcluded List of IDs.
-     *
-     * @return void
-     */
-    public function setExcludeList($aExcluded)
-    {
-        $this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
-    }
-
-    /**
-     * Set list of countries to restrict search to.
-     *
-     * @param string[] $aCountries List of two-letter lower-case country codes.
-     *
-     * @return void
-     */
-    public function setCountryList($aCountries)
-    {
-        $this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
-        $this->aCountryList = $aCountries;
-    }
-
-    /**
-     * Extract a reference point from a query string.
-     *
-     * @param string $sQuery Query to scan.
-     *
-     * @return string The remaining query string.
-     */
-    public function setNearPointFromQuery($sQuery)
-    {
-        $aResult = parseLatLon($sQuery);
-
-        if ($aResult !== false
-            && $aResult[1] <= 90.1
-            && $aResult[1] >= -90.1
-            && $aResult[2] <= 180.1
-            && $aResult[2] >= -180.1
-        ) {
-            $this->setNearPoint($aResult[1], $aResult[2]);
-            $sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
-        }
-
-        return $sQuery;
-    }
-
-    /**
-     * Get an SQL snippet for computing the distance from the reference point.
-     *
-     * @param string $sObj SQL variable name to compute the distance from.
-     *
-     * @return string An SQL string.
-     */
-    public function distanceSQL($sObj)
-    {
-        return 'ST_Distance('.$this->sqlNear.", $sObj)";
-    }
-
-    /**
-     * Get an SQL snippet for checking if something is within range of the
-     * reference point.
-     *
-     * @param string $sObj SQL variable name to compute if it is within range.
-     *
-     * @return string An SQL string.
-     */
-    public function withinSQL($sObj)
-    {
-        return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
-    }
-
-    /**
-     * Get an SQL snippet of the importance factor of the viewbox.
-     *
-     * The importance factor is computed by checking if an object is within
-     * the viewbox and/or the extended version of the viewbox.
-     *
-     * @param string $sObj SQL variable name of object to weight the importance
-     *
-     * @return string SQL snippet of the factor with a leading multiply sign.
-     */
-    public function viewboxImportanceSQL($sObj)
-    {
-        $sSQL = '';
-
-        if ($this->sqlViewboxSmall) {
-            $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
-        }
-        if ($this->sqlViewboxLarge) {
-            $sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
-        }
-
-        return $sSQL;
-    }
-
-    /**
-     * SQL snippet checking if a place ID should be excluded.
-     *
-     * @param string $sVariable SQL variable name of place ID to check,
-     *                          potentially prefixed with more SQL.
-     *
-     * @return string SQL snippet.
-     */
-    public function excludeSQL($sVariable)
-    {
-        if ($this->sqlExcludeList) {
-            return $sVariable.$this->sqlExcludeList;
-        }
-
-        return '';
-    }
-
-    /**
-     * Check if the given country is covered by the search context.
-     *
-     * @param string $sCountryCode  Country code of the country to check.
-     *
-     * @return True, if no country code restrictions are set or the
-     *         country is included in the country list.
-     */
-    public function isCountryApplicable($sCountryCode)
-    {
-        return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList);
-    }
-
-    public function debugInfo()
-    {
-        return array(
-                'Near radius' => $this->fNearRadius,
-                'Near point (SQL)' => $this->sqlNear,
-                'Bounded viewbox' => $this->bViewboxBounded,
-                'Viewbox (SQL, small)' => $this->sqlViewboxSmall,
-                'Viewbox (SQL, large)' => $this->sqlViewboxLarge,
-                'Viewbox (SQL, centre)' => $this->sqlViewboxCentre,
-                'Countries (SQL)' => $this->sqlCountryList,
-                'Excluded IDs (SQL)' => $this->sqlExcludeList
-               );
-    }
-}
diff --git a/lib-php/SearchDescription.php b/lib-php/SearchDescription.php
deleted file mode 100644 (file)
index 5d2caf0..0000000
+++ /dev/null
@@ -1,985 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-require_once(CONST_LibDir.'/SearchContext.php');
-require_once(CONST_LibDir.'/Result.php');
-
-/**
- * Description of a single interpretation of a search query.
- */
-class SearchDescription
-{
-    /// Ranking how well the description fits the query.
-    private $iSearchRank = 0;
-    /// Country code of country the result must belong to.
-    private $sCountryCode = '';
-    /// List of word ids making up the name of the object.
-    private $aName = array();
-    /// True if the name is rare enough to force index use on name.
-    private $bRareName = false;
-    /// True if the name requires to be accompanied by address terms.
-    private $bNameNeedsAddress = false;
-    /// List of word ids making up the address of the object.
-    private $aAddress = array();
-    /// List of word ids that appear in the name but should be ignored.
-    private $aNameNonSearch = array();
-    /// List of word ids that appear in the address but should be ignored.
-    private $aAddressNonSearch = array();
-    /// Kind of search for special searches, see Nominatim::Operator.
-    private $iOperator = Operator::NONE;
-    /// Class of special feature to search for.
-    private $sClass = '';
-    /// Type of special feature to search for.
-    private $sType = '';
-    /// Housenumber of the object.
-    private $sHouseNumber = '';
-    /// Postcode for the object.
-    private $sPostcode = '';
-    /// Global search constraints.
-    private $oContext;
-
-    // Temporary values used while creating the search description.
-
-    /// Index of phrase currently processed.
-    private $iNamePhrase = -1;
-
-    /**
-     * Create an empty search description.
-     *
-     * @param object $oContext Global context to use. Will be inherited by
-     *                         all derived search objects.
-     */
-    public function __construct($oContext)
-    {
-        $this->oContext = $oContext;
-    }
-
-    /**
-     * Get current search rank.
-     *
-     * The higher the search rank the lower the likelihood that the
-     * search is a correct interpretation of the search query.
-     *
-     * @return integer Search rank.
-     */
-    public function getRank()
-    {
-        return $this->iSearchRank;
-    }
-
-    /**
-     * Extract key/value pairs from a query.
-     *
-     * Key/value pairs are recognised if they are of the form [<key>=<value>].
-     * If multiple terms of this kind are found then all terms are removed
-     * but only the first is used for search.
-     *
-     * @param string $sQuery Original query string.
-     *
-     * @return string The query string with the special search patterns removed.
-     */
-    public function extractKeyValuePairs($sQuery)
-    {
-        // Search for terms of kind [<key>=<value>].
-        preg_match_all(
-            '/\\[([\\w_]*)=([\\w_]*)\\]/',
-            $sQuery,
-            $aSpecialTermsRaw,
-            PREG_SET_ORDER
-        );
-
-        foreach ($aSpecialTermsRaw as $aTerm) {
-            $sQuery = str_replace($aTerm[0], ' ', $sQuery);
-            if (!$this->hasOperator()) {
-                $this->setPoiSearch(Operator::TYPE, $aTerm[1], $aTerm[2]);
-            }
-        }
-
-        return $sQuery;
-    }
-
-    /**
-     * Check if the combination of parameters is sensible.
-     *
-     * @return bool True, if the search looks valid.
-     */
-    public function isValidSearch()
-    {
-        if (empty($this->aName)) {
-            if ($this->sHouseNumber) {
-                return false;
-            }
-            if (!$this->sClass && !$this->sCountryCode) {
-                return false;
-            }
-        }
-        if ($this->bNameNeedsAddress && empty($this->aAddress)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /////////// Search building functions
-
-    /**
-     * Create a copy of this search description adding to search rank.
-     *
-     * @param integer $iTermCost  Cost to add to the current search rank.
-     *
-     * @return object Cloned search description.
-     */
-    public function clone($iTermCost)
-    {
-        $oSearch = clone $this;
-        $oSearch->iSearchRank += $iTermCost;
-
-        return $oSearch;
-    }
-
-    /**
-     * Check if the search currently includes a name.
-     *
-     * @param bool bIncludeNonNames  If true stop-word tokens are taken into
-     *                               account, too.
-     *
-     * @return bool True, if search has a name.
-     */
-    public function hasName($bIncludeNonNames = false)
-    {
-        return !empty($this->aName)
-               || (!empty($this->aNameNonSearch) && $bIncludeNonNames);
-    }
-
-    /**
-     * Check if the search currently includes an address term.
-     *
-     * @return bool True, if any address term is included, including stop-word
-     *              terms.
-     */
-    public function hasAddress()
-    {
-        return !empty($this->aAddress) || !empty($this->aAddressNonSearch);
-    }
-
-    /**
-     * Check if a country restriction is currently included in the search.
-     *
-     * @return bool True, if a country restriction is set.
-     */
-    public function hasCountry()
-    {
-        return $this->sCountryCode !== '';
-    }
-
-    /**
-     * Check if a postcode is currently included in the search.
-     *
-     * @return bool True, if a postcode is set.
-     */
-    public function hasPostcode()
-    {
-        return $this->sPostcode !== '';
-    }
-
-    /**
-     * Check if a house number is set for the search.
-     *
-     * @return bool True, if a house number is set.
-     */
-    public function hasHousenumber()
-    {
-        return $this->sHouseNumber !== '';
-    }
-
-    /**
-     * Check if a special type of place is requested.
-     *
-     * param integer iOperator  When set, check for the particular
-     *                          operator used for the special type.
-     *
-     * @return bool True, if speial type is requested or, if requested,
-     *              a special type with the given operator.
-     */
-    public function hasOperator($iOperator = null)
-    {
-        return $iOperator === null ? $this->iOperator != Operator::NONE : $this->iOperator == $iOperator;
-    }
-
-    /**
-     * Add the given token to the list of terms to search for in the address.
-     *
-     * @param integer iID       ID of term to add.
-     * @param bool bSearchable  Term should be used to search for result
-     *                          (i.e. term is not a stop word).
-     */
-    public function addAddressToken($iId, $bSearchable = true)
-    {
-        if ($bSearchable) {
-            $this->aAddress[$iId] = $iId;
-        } else {
-            $this->aAddressNonSearch[$iId] = $iId;
-        }
-    }
-
-    /**
-     * Add the given full-word token to the list of terms to search for in the
-     * name.
-     *
-     * @param integer iId    ID of term to add.
-     * @param bool bRareName  True if the term is infrequent enough to not
-     *                        require other constraints for efficient search.
-     */
-    public function addNameToken($iId, $bRareName)
-    {
-        $this->aName[$iId] = $iId;
-        $this->bRareName = $bRareName;
-        $this->bNameNeedsAddress = false;
-    }
-
-    /**
-     * Add the given partial token to the list of terms to search for in
-     * the name.
-     *
-     * @param integer iID            ID of term to add.
-     * @param bool bSearchable       Term should be used to search for result
-     *                               (i.e. term is not a stop word).
-     * @param bool bNeedsAddress     True if the term is too unspecific to be used
-     *                               in a stand-alone search without an address
-     *                               to narrow down the search.
-     * @param integer iPhraseNumber  Index of phrase, where the partial term
-     *                               appears.
-     */
-    public function addPartialNameToken($iId, $bSearchable, $bNeedsAddress, $iPhraseNumber)
-    {
-        if (empty($this->aName)) {
-            $this->bNameNeedsAddress = $bNeedsAddress;
-        } elseif ($bSearchable && count($this->aName) >= 2) {
-            $this->bNameNeedsAddress = false;
-        } else {
-            $this->bNameNeedsAddress &= $bNeedsAddress;
-        }
-        if ($bSearchable) {
-            $this->aName[$iId] = $iId;
-        } else {
-            $this->aNameNonSearch[$iId] = $iId;
-        }
-        $this->iNamePhrase = $iPhraseNumber;
-    }
-
-    /**
-     * Set country restriction for the search.
-     *
-     * @param string sCountryCode  Country code of country to restrict search to.
-     */
-    public function setCountry($sCountryCode)
-    {
-        $this->sCountryCode = $sCountryCode;
-        $this->iNamePhrase = -1;
-    }
-
-    /**
-     * Set postcode search constraint.
-     *
-     * @param string sPostcode  Postcode the result should have.
-     */
-    public function setPostcode($sPostcode)
-    {
-        $this->sPostcode = $sPostcode;
-        $this->iNamePhrase = -1;
-    }
-
-    /**
-     * Make this search a search for a postcode object.
-     *
-     * @param integer iId       Token Id for the postcode.
-     * @param string sPostcode  Postcode to look for.
-     */
-    public function setPostcodeAsName($iId, $sPostcode)
-    {
-        $this->iOperator = Operator::POSTCODE;
-        $this->aAddress = array_merge($this->aAddress, $this->aName);
-        $this->aName = array($iId => $sPostcode);
-        $this->bRareName = true;
-        $this->iNamePhrase = -1;
-    }
-
-    /**
-     * Set house number search cnstraint.
-     *
-     * @param string sNumber  House number the result should have.
-     */
-    public function setHousenumber($sNumber)
-    {
-        $this->sHouseNumber = $sNumber;
-        $this->iNamePhrase = -1;
-    }
-
-    /**
-     * Make this search a search for a house number.
-     *
-     * @param integer iId  Token Id for the house number.
-     */
-    public function setHousenumberAsName($iId)
-    {
-        $this->aAddress = array_merge($this->aAddress, $this->aName);
-        $this->bRareName = false;
-        $this->bNameNeedsAddress = true;
-        $this->aName = array($iId => $iId);
-        $this->iNamePhrase = -1;
-    }
-
-    /**
-     * Make this search a POI search.
-     *
-     * In a POI search, objects are not (only) searched by their name
-     * but also by the primary OSM key/value pair (class and type in Nominatim).
-     *
-     * @param integer $iOperator Type of POI search
-     * @param string  $sClass    Class (or OSM tag key) of POI.
-     * @param string  $sType     Type (or OSM tag value) of POI.
-     *
-     * @return void
-     */
-    public function setPoiSearch($iOperator, $sClass, $sType)
-    {
-        $this->iOperator = $iOperator;
-        $this->sClass = $sClass;
-        $this->sType = $sType;
-        $this->iNamePhrase = -1;
-    }
-
-    public function getNamePhrase()
-    {
-        return $this->iNamePhrase;
-    }
-
-    /**
-     * Get the global search context.
-     *
-     * @return object  Objects of global search constraints.
-     */
-    public function getContext()
-    {
-        return $this->oContext;
-    }
-
-    /////////// Query functions
-
-
-    /**
-     * Query database for places that match this search.
-     *
-     * @param object  $oDB      Nominatim::DB instance to use.
-     * @param integer $iMinRank Minimum address rank to restrict search to.
-     * @param integer $iMaxRank Maximum address rank to restrict search to.
-     * @param integer $iLimit   Maximum number of results.
-     *
-     * @return mixed[] An array with two fields: IDs contains the list of
-     *                 matching place IDs and houseNumber the houseNumber
-     *                 if applicable or -1 if not.
-     */
-    public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit)
-    {
-        $aResults = array();
-
-        if ($this->sCountryCode
-            && empty($this->aName)
-            && !$this->iOperator
-            && !$this->sClass
-            && !$this->oContext->hasNearPoint()
-        ) {
-            // Just looking for a country - look it up
-            if (4 >= $iMinRank && 4 <= $iMaxRank) {
-                $aResults = $this->queryCountry($oDB);
-            }
-        } elseif (empty($this->aName) && empty($this->aAddress)) {
-            // Neither name nor address? Then we must be
-            // looking for a POI in a geographic area.
-            if ($this->oContext->isBoundedSearch()) {
-                $aResults = $this->queryNearbyPoi($oDB, $iLimit);
-            }
-        } elseif ($this->iOperator == Operator::POSTCODE) {
-            // looking for postcode
-            $aResults = $this->queryPostcode($oDB, $iLimit);
-        } else {
-            // Ordinary search:
-            // First search for places according to name and address.
-            $aResults = $this->queryNamedPlace(
-                $oDB,
-                $iMinRank,
-                $iMaxRank,
-                $iLimit
-            );
-
-            // finally get POIs if requested
-            if ($this->sClass && !empty($aResults)) {
-                $aResults = $this->queryPoiByOperator($oDB, $aResults, $iLimit);
-            }
-        }
-
-        Debug::printDebugTable('Place IDs', $aResults);
-
-        if (!empty($aResults) && $this->sPostcode) {
-            $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
-            if ($sPlaceIds) {
-                $sSQL = 'SELECT place_id FROM placex';
-                $sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
-                $sSQL .= " AND postcode != '".$this->sPostcode."'";
-                Debug::printSQL($sSQL);
-                $aFilteredPlaceIDs = $oDB->getCol($sSQL);
-                if ($aFilteredPlaceIDs) {
-                    foreach ($aFilteredPlaceIDs as $iPlaceId) {
-                        $aResults[$iPlaceId]->iResultRank++;
-                    }
-                }
-            }
-        }
-
-        return $aResults;
-    }
-
-
-    private function queryCountry(&$oDB)
-    {
-        $sSQL = 'SELECT place_id FROM placex ';
-        $sSQL .= "WHERE country_code='".$this->sCountryCode."'";
-        $sSQL .= ' AND rank_search = 4';
-        if ($this->oContext->bViewboxBounded) {
-            $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
-        }
-        $sSQL .= ' ORDER BY st_area(geometry) DESC LIMIT 1';
-
-        Debug::printSQL($sSQL);
-
-        $iPlaceId = $oDB->getOne($sSQL);
-
-        $aResults = array();
-        if ($iPlaceId) {
-            $aResults[$iPlaceId] = new Result($iPlaceId);
-        }
-
-        return $aResults;
-    }
-
-    private function queryNearbyPoi(&$oDB, $iLimit)
-    {
-        if (!$this->sClass) {
-            return array();
-        }
-
-        $aDBResults = array();
-        $sPoiTable = $this->poiTable();
-
-        if ($oDB->tableExists($sPoiTable)) {
-            $sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct';
-            if ($this->oContext->sqlCountryList) {
-                $sSQL .= ' JOIN placex USING (place_id)';
-            }
-            if ($this->oContext->hasNearPoint()) {
-                $sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid');
-            } elseif ($this->oContext->bViewboxBounded) {
-                $sSQL .= ' WHERE ST_Contains('.$this->oContext->sqlViewboxSmall.', ct.centroid)';
-            }
-            if ($this->oContext->sqlCountryList) {
-                $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
-            }
-            $sSQL .= $this->oContext->excludeSQL(' AND place_id');
-            if ($this->oContext->sqlViewboxCentre) {
-                $sSQL .= ' ORDER BY ST_Distance(';
-                $sSQL .= $this->oContext->sqlViewboxCentre.', ct.centroid) ASC';
-            } elseif ($this->oContext->hasNearPoint()) {
-                $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC';
-            }
-            $sSQL .= " LIMIT $iLimit";
-            Debug::printSQL($sSQL);
-            $aDBResults = $oDB->getCol($sSQL);
-        }
-
-        if ($this->oContext->hasNearPoint()) {
-            $sSQL = 'SELECT place_id FROM placex WHERE ';
-            $sSQL .= 'class = :class and type = :type';
-            $sSQL .= ' AND '.$this->oContext->withinSQL('geometry');
-            $sSQL .= ' AND linked_place_id is null';
-            if ($this->oContext->sqlCountryList) {
-                $sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
-            }
-            $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC';
-            $sSQL .= " LIMIT $iLimit";
-            Debug::printSQL($sSQL);
-            $aDBResults = $oDB->getCol(
-                $sSQL,
-                array(':class' => $this->sClass, ':type' => $this->sType)
-            );
-        }
-
-        $aResults = array();
-        foreach ($aDBResults as $iPlaceId) {
-            $aResults[$iPlaceId] = new Result($iPlaceId);
-        }
-
-        return $aResults;
-    }
-
-    private function queryPostcode(&$oDB, $iLimit)
-    {
-        $sSQL = 'SELECT p.place_id FROM location_postcode p ';
-
-        if (!empty($this->aAddress)) {
-            $sSQL .= ', search_name s ';
-            $sSQL .= 'WHERE s.place_id = p.parent_place_id ';
-            $sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)';
-            $sSQL .= '      @> '.$oDB->getArraySQL($this->aAddress).' AND ';
-        } else {
-            $sSQL .= 'WHERE ';
-        }
-
-        $sSQL .= "p.postcode = '".reset($this->aName)."'";
-        $sSQL .= $this->countryCodeSQL(' AND p.country_code');
-        if ($this->oContext->bViewboxBounded) {
-            $sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
-        }
-        $sSQL .= $this->oContext->excludeSQL(' AND p.place_id');
-        $sSQL .= " LIMIT $iLimit";
-
-        Debug::printSQL($sSQL);
-
-        $aResults = array();
-        foreach ($oDB->getCol($sSQL) as $iPlaceId) {
-            $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
-        }
-
-        return $aResults;
-    }
-
-    private function queryNamedPlace(&$oDB, $iMinAddressRank, $iMaxAddressRank, $iLimit)
-    {
-        $aTerms = array();
-        $aOrder = array();
-
-        if (!empty($this->aName)) {
-            $aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName);
-        }
-        if (!empty($this->aAddress)) {
-            // For infrequent name terms disable index usage for address
-            if ($this->bRareName) {
-                $aTerms[] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> '.$oDB->getArraySQL($this->aAddress);
-            } else {
-                $aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress);
-            }
-        }
-
-        $sCountryTerm = $this->countryCodeSQL('country_code');
-        if ($sCountryTerm) {
-            $aTerms[] = $sCountryTerm;
-        }
-
-        if ($this->sHouseNumber) {
-            $aTerms[] = 'address_rank between 16 and 30';
-        } elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
-            if ($iMinAddressRank > 0) {
-                $aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
-            }
-        }
-
-        if ($this->oContext->hasNearPoint()) {
-            $aTerms[] = $this->oContext->withinSQL('centroid');
-            $aOrder[] = $this->oContext->distanceSQL('centroid');
-        } elseif ($this->sPostcode) {
-            if (empty($this->aAddress)) {
-                $aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."' AND ST_DWithin(search_name.centroid, p.geometry, 0.12))";
-            } else {
-                $aOrder[] = "(SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."')";
-            }
-        }
-
-        $sExcludeSQL = $this->oContext->excludeSQL('place_id');
-        if ($sExcludeSQL) {
-            $aTerms[] = $sExcludeSQL;
-        }
-
-        if ($this->oContext->bViewboxBounded) {
-            $aTerms[] = 'centroid && '.$this->oContext->sqlViewboxSmall;
-        }
-
-        if ($this->sHouseNumber) {
-            $sImportanceSQL = '- abs(26 - address_rank) + 3';
-        } else {
-            $sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75001-(search_rank::float/40) ELSE importance END)';
-        }
-        $sImportanceSQL .= $this->oContext->viewboxImportanceSQL('centroid');
-        $aOrder[] = "$sImportanceSQL DESC";
-
-        $aFullNameAddress = $this->oContext->getFullNameTerms();
-        if (!empty($aFullNameAddress)) {
-            $sExactMatchSQL = ' ( ';
-            $sExactMatchSQL .= ' SELECT count(*) FROM ( ';
-            $sExactMatchSQL .= '  SELECT unnest('.$oDB->getArraySQL($aFullNameAddress).')';
-            $sExactMatchSQL .= '    INTERSECT ';
-            $sExactMatchSQL .= '  SELECT unnest(nameaddress_vector)';
-            $sExactMatchSQL .= ' ) s';
-            $sExactMatchSQL .= ') as exactmatch';
-            $aOrder[] = 'exactmatch DESC';
-        } else {
-            $sExactMatchSQL = '0::int as exactmatch';
-        }
-
-        if (empty($aTerms)) {
-            return array();
-        }
-
-        if ($this->hasHousenumber()) {
-            $sHouseNumberRegex = $oDB->getDBQuoted('\\\\m'.$this->sHouseNumber.'\\\\M');
-
-            // Housenumbers on streets and places.
-            $sPlacexSql = 'SELECT array_agg(place_id) FROM placex';
-            $sPlacexSql .= ' WHERE parent_place_id = sin.place_id AND sin.address_rank < 30';
-            $sPlacexSql .= $this->oContext->excludeSQL(' AND place_id');
-            $sPlacexSql .= '       and housenumber ~* E'.$sHouseNumberRegex;
-
-            // Interpolations on streets and places.
-            $sInterpolSql = 'null';
-            $sTigerSql = 'null';
-            if (preg_match('/^[0-9]+$/', $this->sHouseNumber)) {
-                $sIpolHnr = 'WHERE parent_place_id = sin.place_id ';
-                $sIpolHnr .= '  AND startnumber is not NULL AND sin.address_rank < 30';
-                $sIpolHnr .= '  AND '.$this->sHouseNumber.' between startnumber and endnumber';
-                $sIpolHnr .= '  AND ('.$this->sHouseNumber.' - startnumber) % step = 0';
-
-                $sInterpolSql = 'SELECT array_agg(place_id) FROM location_property_osmline '.$sIpolHnr;
-                if (CONST_Use_US_Tiger_Data) {
-                    $sTigerSql = 'SELECT array_agg(place_id) FROM location_property_tiger '.$sIpolHnr;
-                    $sTigerSql .= "      and sin.country_code = 'us'";
-                }
-            }
-
-            if ($this->sClass) {
-                $iLimit = 40;
-            }
-
-            $sSelfHnr = 'SELECT * FROM placex WHERE place_id = search_name.place_id';
-            $sSelfHnr .= '    AND housenumber ~* E'.$sHouseNumberRegex;
-
-            $aTerms[] = '(address_rank < 30 or exists('.$sSelfHnr.'))';
-
-
-            $sSQL = 'SELECT sin.*, ';
-            $sSQL .=        '('.$sPlacexSql.') as placex_hnr, ';
-            $sSQL .=        '('.$sInterpolSql.') as interpol_hnr, ';
-            $sSQL .=        '('.$sTigerSql.') as tiger_hnr ';
-            $sSQL .= ' FROM (';
-            $sSQL .= '    SELECT place_id, address_rank, country_code,'.$sExactMatchSQL.',';
-            $sSQL .= '            CASE WHEN importance = 0 OR importance IS NULL';
-            $sSQL .= '               THEN 0.75001-(search_rank::float/40) ELSE importance END as importance';
-            $sSQL .= '     FROM search_name';
-            $sSQL .= '     WHERE '.join(' and ', $aTerms);
-            $sSQL .= '     ORDER BY '.join(', ', $aOrder);
-            $sSQL .= '     LIMIT 40000';
-            $sSQL .= ') as sin';
-            $sSQL .= ' ORDER BY address_rank = 30 desc, placex_hnr, interpol_hnr, tiger_hnr,';
-            $sSQL .= '          importance';
-            $sSQL .= ' LIMIT '.$iLimit;
-        } else {
-            if ($this->sClass) {
-                $iLimit = 40;
-            }
-
-            $sSQL = 'SELECT place_id, address_rank, '.$sExactMatchSQL;
-            $sSQL .= ' FROM search_name';
-            $sSQL .= ' WHERE '.join(' and ', $aTerms);
-            $sSQL .= ' ORDER BY '.join(', ', $aOrder);
-            $sSQL .= ' LIMIT '.$iLimit;
-        }
-
-        Debug::printSQL($sSQL);
-
-        $aDBResults = $oDB->getAll($sSQL, null, 'Could not get places for search terms.');
-
-        $aResults = array();
-
-        foreach ($aDBResults as $aResult) {
-            $oResult = new Result($aResult['place_id']);
-            $oResult->iExactMatches = $aResult['exactmatch'];
-            $oResult->iAddressRank = $aResult['address_rank'];
-
-            $bNeedResult = true;
-            if ($this->hasHousenumber() && $aResult['address_rank'] < 30) {
-                if ($aResult['placex_hnr']) {
-                    foreach (explode(',', substr($aResult['placex_hnr'], 1, -1)) as $sPlaceID) {
-                        $iPlaceID = intval($sPlaceID);
-                        $oHnrResult = new Result($iPlaceID);
-                        $oHnrResult->iExactMatches = $aResult['exactmatch'];
-                        $oHnrResult->iAddressRank = 30;
-                        $aResults[$iPlaceID] = $oHnrResult;
-                        $bNeedResult = false;
-                    }
-                }
-                if ($aResult['interpol_hnr']) {
-                    foreach (explode(',', substr($aResult['interpol_hnr'], 1, -1)) as $sPlaceID) {
-                        $iPlaceID = intval($sPlaceID);
-                        $oHnrResult = new Result($iPlaceID, Result::TABLE_OSMLINE);
-                        $oHnrResult->iExactMatches = $aResult['exactmatch'];
-                        $oHnrResult->iAddressRank = 30;
-                        $oHnrResult->iHouseNumber = intval($this->sHouseNumber);
-                        $aResults[$iPlaceID] = $oHnrResult;
-                        $bNeedResult = false;
-                    }
-                }
-                if ($aResult['tiger_hnr']) {
-                    foreach (explode(',', substr($aResult['tiger_hnr'], 1, -1)) as $sPlaceID) {
-                        $iPlaceID = intval($sPlaceID);
-                        $oHnrResult = new Result($iPlaceID, Result::TABLE_TIGER);
-                        $oHnrResult->iExactMatches = $aResult['exactmatch'];
-                        $oHnrResult->iAddressRank = 30;
-                        $oHnrResult->iHouseNumber = intval($this->sHouseNumber);
-                        $aResults[$iPlaceID] = $oHnrResult;
-                        $bNeedResult = false;
-                    }
-                }
-
-                if ($aResult['address_rank'] < 26) {
-                    $oResult->iResultRank += 2;
-                } else {
-                    $oResult->iResultRank++;
-                }
-            }
-
-            if ($bNeedResult) {
-                $aResults[$aResult['place_id']] = $oResult;
-            }
-        }
-
-        return $aResults;
-    }
-
-
-    private function queryPoiByOperator(&$oDB, $aParentIDs, $iLimit)
-    {
-        $aResults = array();
-        $sPlaceIDs = Result::joinIdsByTable($aParentIDs, Result::TABLE_PLACEX);
-
-        if (!$sPlaceIDs) {
-            return $aResults;
-        }
-
-        if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NAME) {
-            // If they were searching for a named class (i.e. 'Kings Head pub')
-            // then we might have an extra match
-            $sSQL = 'SELECT place_id FROM placex ';
-            $sSQL .= " WHERE place_id in ($sPlaceIDs)";
-            $sSQL .= "   AND class='".$this->sClass."' ";
-            $sSQL .= "   AND type='".$this->sType."'";
-            $sSQL .= '   AND linked_place_id is null';
-            $sSQL .= $this->oContext->excludeSQL(' AND place_id');
-            $sSQL .= ' ORDER BY rank_search ASC ';
-            $sSQL .= " LIMIT $iLimit";
-
-            Debug::printSQL($sSQL);
-
-            foreach ($oDB->getCol($sSQL) as $iPlaceId) {
-                $aResults[$iPlaceId] = new Result($iPlaceId);
-            }
-        }
-
-        // NEAR and IN are handled the same
-        if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) {
-            $sClassTable = $this->poiTable();
-            $bCacheTable = $oDB->tableExists($sClassTable);
-
-            $sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
-            Debug::printSQL($sSQL);
-            $iMaxRank = (int) $oDB->getOne($sSQL);
-
-            // For state / country level searches the normal radius search doesn't work very well
-            $sPlaceGeom = false;
-            if ($iMaxRank < 9 && $bCacheTable) {
-                // Try and get a polygon to search in instead
-                $sSQL = 'SELECT geometry FROM placex';
-                $sSQL .= " WHERE place_id in ($sPlaceIDs)";
-                $sSQL .= "   AND rank_search < $iMaxRank + 5";
-                $sSQL .= '   AND ST_Area(Box2d(geometry)) < 20';
-                $sSQL .= "   AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')";
-                $sSQL .= ' ORDER BY rank_search ASC ';
-                $sSQL .= ' LIMIT 1';
-                Debug::printSQL($sSQL);
-                $sPlaceGeom = $oDB->getOne($sSQL);
-            }
-
-            if ($sPlaceGeom) {
-                $sPlaceIDs = false;
-            } else {
-                $iMaxRank += 5;
-                $sSQL = 'SELECT place_id FROM placex';
-                $sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
-                Debug::printSQL($sSQL);
-                $aPlaceIDs = $oDB->getCol($sSQL);
-                $sPlaceIDs = join(',', $aPlaceIDs);
-            }
-
-            if ($sPlaceIDs || $sPlaceGeom) {
-                $fRange = 0.01;
-                if ($bCacheTable) {
-                    // More efficient - can make the range bigger
-                    $fRange = 0.05;
-
-                    $sOrderBySQL = '';
-                    if ($this->oContext->hasNearPoint()) {
-                        $sOrderBySQL = $this->oContext->distanceSQL('l.centroid');
-                    } elseif ($sPlaceIDs) {
-                        $sOrderBySQL = 'ST_Distance(l.centroid, f.geometry)';
-                    } elseif ($sPlaceGeom) {
-                        $sOrderBySQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
-                    }
-
-                    $sSQL = 'SELECT distinct i.place_id';
-                    if ($sOrderBySQL) {
-                        $sSQL .= ', i.order_term';
-                    }
-                    $sSQL .= ' from (SELECT l.place_id';
-                    if ($sOrderBySQL) {
-                        $sSQL .= ','.$sOrderBySQL.' as order_term';
-                    }
-                    $sSQL .= ' from '.$sClassTable.' as l';
-
-                    if ($sPlaceIDs) {
-                        $sSQL .= ',placex as f WHERE ';
-                        $sSQL .= "f.place_id in ($sPlaceIDs) ";
-                        $sSQL .= " AND ST_DWithin(l.centroid, f.centroid, $fRange)";
-                    } elseif ($sPlaceGeom) {
-                        $sSQL .= " WHERE ST_Contains('$sPlaceGeom', l.centroid)";
-                    }
-
-                    $sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
-                    $sSQL .= 'limit 300) i ';
-                    if ($sOrderBySQL) {
-                        $sSQL .= 'order by order_term asc';
-                    }
-                    $sSQL .= " limit $iLimit";
-
-                    Debug::printSQL($sSQL);
-
-                    foreach ($oDB->getCol($sSQL) as $iPlaceId) {
-                        $aResults[$iPlaceId] = new Result($iPlaceId);
-                    }
-                } else {
-                    if ($this->oContext->hasNearPoint()) {
-                        $fRange = $this->oContext->nearRadius();
-                    }
-
-                    $sOrderBySQL = '';
-                    if ($this->oContext->hasNearPoint()) {
-                        $sOrderBySQL = $this->oContext->distanceSQL('l.geometry');
-                    } else {
-                        $sOrderBySQL = 'ST_Distance(l.geometry, f.geometry)';
-                    }
-
-                    $sSQL = 'SELECT distinct l.place_id';
-                    if ($sOrderBySQL) {
-                        $sSQL .= ','.$sOrderBySQL.' as orderterm';
-                    }
-                    $sSQL .= ' FROM placex as l, placex as f';
-                    $sSQL .= " WHERE f.place_id in ($sPlaceIDs)";
-                    $sSQL .= "  AND ST_DWithin(l.geometry, f.centroid, $fRange)";
-                    $sSQL .= "  AND l.class='".$this->sClass."'";
-                    $sSQL .= "  AND l.type='".$this->sType."'";
-                    $sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
-                    if ($sOrderBySQL) {
-                        $sSQL .= 'ORDER BY orderterm ASC';
-                    }
-                    $sSQL .= " limit $iLimit";
-
-                    Debug::printSQL($sSQL);
-
-                    foreach ($oDB->getCol($sSQL) as $iPlaceId) {
-                        $aResults[$iPlaceId] = new Result($iPlaceId);
-                    }
-                }
-            }
-        }
-
-        return $aResults;
-    }
-
-    private function poiTable()
-    {
-        return 'place_classtype_'.$this->sClass.'_'.$this->sType;
-    }
-
-    private function countryCodeSQL($sVar)
-    {
-        if ($this->sCountryCode) {
-            return $sVar.' = \''.$this->sCountryCode."'";
-        }
-        if ($this->oContext->sqlCountryList) {
-            return $sVar.' in '.$this->oContext->sqlCountryList;
-        }
-
-        return '';
-    }
-
-    /////////// Sort functions
-
-
-    public static function bySearchRank($a, $b)
-    {
-        if ($a->iSearchRank == $b->iSearchRank) {
-            return $a->iOperator + strlen($a->sHouseNumber)
-                     - $b->iOperator - strlen($b->sHouseNumber);
-        }
-
-        return $a->iSearchRank < $b->iSearchRank ? -1 : 1;
-    }
-
-    //////////// Debugging functions
-
-
-    public function debugInfo()
-    {
-        return array(
-                'Search rank' => $this->iSearchRank,
-                'Country code' => $this->sCountryCode,
-                'Name terms' => $this->aName,
-                'Name terms (stop words)' => $this->aNameNonSearch,
-                'Address terms' => $this->aAddress,
-                'Address terms (stop words)' => $this->aAddressNonSearch,
-                'Address terms (full words)' => $this->aFullNameAddress ?? '',
-                'Special search' => $this->iOperator,
-                'Class' => $this->sClass,
-                'Type' => $this->sType,
-                'House number' => $this->sHouseNumber,
-                'Postcode' => $this->sPostcode
-               );
-    }
-
-    public function dumpAsHtmlTableRow(&$aWordIDs)
-    {
-        $kf = function ($k) use (&$aWordIDs) {
-            return $aWordIDs[$k] ?? '['.$k.']';
-        };
-
-        echo '<tr>';
-        echo "<td>$this->iSearchRank</td>";
-        echo '<td>'.join(', ', array_map($kf, $this->aName)).'</td>';
-        echo '<td>'.join(', ', array_map($kf, $this->aNameNonSearch)).'</td>';
-        echo '<td>'.join(', ', array_map($kf, $this->aAddress)).'</td>';
-        echo '<td>'.join(', ', array_map($kf, $this->aAddressNonSearch)).'</td>';
-        echo '<td>'.$this->sCountryCode.'</td>';
-        echo '<td>'.Operator::toString($this->iOperator).'</td>';
-        echo '<td>'.$this->sClass.'</td>';
-        echo '<td>'.$this->sType.'</td>';
-        echo '<td>'.$this->sPostcode.'</td>';
-        echo '<td>'.$this->sHouseNumber.'</td>';
-
-        echo '</tr>';
-    }
-}
diff --git a/lib-php/SearchPosition.php b/lib-php/SearchPosition.php
deleted file mode 100644 (file)
index aeeeb2c..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Description of the position of a token within a query.
- */
-class SearchPosition
-{
-    private $sPhraseType;
-
-    private $iPhrase;
-    private $iNumPhrases;
-
-    private $iToken;
-    private $iNumTokens;
-
-
-    public function __construct($sPhraseType, $iPhrase, $iNumPhrases)
-    {
-        $this->sPhraseType = $sPhraseType;
-        $this->iPhrase = $iPhrase;
-        $this->iNumPhrases = $iNumPhrases;
-    }
-
-    public function setTokenPosition($iToken, $iNumTokens)
-    {
-        $this->iToken = $iToken;
-        $this->iNumTokens = $iNumTokens;
-    }
-
-    /**
-     * Check if the phrase can be of the given type.
-     *
-     * @param string  $sType  Type of phrse requested.
-     *
-     * @return True if the phrase is untyped or of the given type.
-     */
-    public function maybePhrase($sType)
-    {
-        return $this->sPhraseType == '' || $this->sPhraseType == $sType;
-    }
-
-    /**
-     * Check if the phrase is exactly of the given type.
-     *
-     * @param string  $sType  Type of phrse requested.
-     *
-     * @return True if the phrase of the given type.
-     */
-    public function isPhrase($sType)
-    {
-        return $this->sPhraseType == $sType;
-    }
-
-    /**
-     * Return true if the token is the very first in the query.
-     */
-    public function isFirstToken()
-    {
-        return $this->iPhrase == 0 && $this->iToken == 0;
-    }
-
-    /**
-     * Check if the token is the final one in the query.
-     */
-    public function isLastToken()
-    {
-        return $this->iToken + 1 == $this->iNumTokens && $this->iPhrase + 1 == $this->iNumPhrases;
-    }
-
-    /**
-     * Check if the current token is part of the first phrase in the query.
-     */
-    public function isFirstPhrase()
-    {
-        return $this->iPhrase == 0;
-    }
-
-    /**
-     * Get the phrase position in the query.
-     */
-    public function getPhrase()
-    {
-        return $this->iPhrase;
-    }
-}
diff --git a/lib-php/Shell.php b/lib-php/Shell.php
deleted file mode 100644 (file)
index 4be1323..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Shell
-{
-    public function __construct($sBaseCmd, ...$aParams)
-    {
-        if (!$sBaseCmd) {
-            throw new \Exception('Command missing in new() call');
-        }
-        $this->baseCmd = $sBaseCmd;
-        $this->aParams = array();
-        $this->aEnv = null; // null = use the same environment as the current PHP process
-
-        $this->stdoutString = null;
-
-        foreach ($aParams as $sParam) {
-            $this->addParams($sParam);
-        }
-    }
-
-    public function addParams(...$aParams)
-    {
-        foreach ($aParams as $sParam) {
-            if (isset($sParam) && $sParam !== null && $sParam !== '') {
-                array_push($this->aParams, $sParam);
-            }
-        }
-        return $this;
-    }
-
-    public function addEnvPair($sKey, $sVal)
-    {
-        if (isset($sKey) && $sKey && isset($sVal)) {
-            if (!isset($this->aEnv)) {
-                $this->aEnv = $_ENV;
-            }
-            $this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV);
-        }
-        return $this;
-    }
-
-    public function escapedCmd()
-    {
-        $aEscaped = array_map(function ($sParam) {
-            return $this->escapeParam($sParam);
-        }, array_merge(array($this->baseCmd), $this->aParams));
-
-        return join(' ', $aEscaped);
-    }
-
-    public function run($bExitOnFail = false)
-    {
-        $sCmd = $this->escapedCmd();
-        // $aEnv does not need escaping, proc_open seems to handle it fine
-
-        $aFDs = array(
-                 0 => array('pipe', 'r'),
-                 1 => STDOUT,
-                 2 => STDERR
-                );
-        $aPipes = null;
-        $hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv);
-        if (!is_resource($hProc)) {
-            throw new \Exception('Unable to run command: ' . $sCmd);
-        }
-
-        fclose($aPipes[0]); // no stdin
-
-        $iStat = proc_close($hProc);
-
-        if ($iStat != 0 && $bExitOnFail) {
-            exit($iStat);
-        }
-
-        return $iStat;
-    }
-
-    private function escapeParam($sParam)
-    {
-        return (preg_match('/^-*\w+$/', $sParam)) ? $sParam : escapeshellarg($sParam);
-    }
-}
diff --git a/lib-php/SimpleWordList.php b/lib-php/SimpleWordList.php
deleted file mode 100644 (file)
index 7009d37..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * A word list creator based on simple splitting by space.
- *
- * Creates possible permutations of split phrases by finding all combination
- * of splitting the phrase on space boundaries.
- */
-class SimpleWordList
-{
-    const MAX_WORDSET_LEN = 20;
-    const MAX_WORDSETS = 100;
-
-    // The phrase as a list of simple terms (without spaces).
-    private $aWords;
-
-    /**
-     * Create a new word list
-     *
-     * @param string sPhrase  Phrase to create the word list from. The phrase is
-     *                        expected to be normalised, so that there are no
-     *                        subsequent spaces.
-     */
-    public function __construct($sPhrase)
-    {
-        if (strlen($sPhrase) > 0) {
-            $this->aWords = explode(' ', $sPhrase);
-        } else {
-            $this->aWords = array();
-        }
-    }
-
-    /**
-     * Get all possible tokens that are present in this word list.
-     *
-     * @return array The list of string tokens in the word list.
-     */
-    public function getTokens()
-    {
-        $aTokens = array();
-        $iNumWords = count($this->aWords);
-
-        for ($i = 0; $i < $iNumWords; $i++) {
-            $sPhrase = $this->aWords[$i];
-            $aTokens[$sPhrase] = $sPhrase;
-
-            for ($j = $i + 1; $j < $iNumWords; $j++) {
-                $sPhrase .= ' '.$this->aWords[$j];
-                $aTokens[$sPhrase] = $sPhrase;
-            }
-        }
-
-        return $aTokens;
-    }
-
-    /**
-     * Compute all possible permutations of phrase splits that result in
-     * words which are in the token list.
-     */
-    public function getWordSets($oTokens)
-    {
-        $iNumWords = count($this->aWords);
-
-        if ($iNumWords == 0) {
-            return null;
-        }
-
-        // Caches the word set for the partial phrase up to word i.
-        $aSetCache = array_fill(0, $iNumWords, array());
-
-        // Initialise first element of cache. There can only be the word.
-        if ($oTokens->containsAny($this->aWords[0])) {
-            $aSetCache[0][] = array($this->aWords[0]);
-        }
-
-        // Now do the next elements using what we already have.
-        for ($i = 1; $i < $iNumWords; $i++) {
-            for ($j = $i; $j > 0; $j--) {
-                $sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
-                if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
-                    $aPartial = array($sPartial);
-                    foreach ($aSetCache[$j - 1] as $aSet) {
-                        if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) {
-                            $aSetCache[$i][] = array_merge($aSet, $aPartial);
-                        }
-                    }
-                    if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) {
-                        usort(
-                            $aSetCache[$i],
-                            array('\Nominatim\SimpleWordList', 'cmpByArraylen')
-                        );
-                        $aSetCache[$i] = array_slice(
-                            $aSetCache[$i],
-                            0,
-                            SimpleWordList::MAX_WORDSETS
-                        );
-                    }
-                }
-            }
-
-            // finally the current full phrase
-            $sPartial = $this->aWords[0].' '.$sPartial;
-            if ($oTokens->containsAny($sPartial)) {
-                $aSetCache[$i][] = array($sPartial);
-            }
-        }
-
-        $aWordSets = $aSetCache[$iNumWords - 1];
-        usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen'));
-        return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
-    }
-
-    /**
-     * Custom search routine which takes two arrays. The array with the fewest
-     * items wins. If same number of items then the one with the longest first
-     * element wins.
-     */
-    public static function cmpByArraylen($aA, $aB)
-    {
-        $iALen = count($aA);
-        $iBLen = count($aB);
-
-        if ($iALen == $iBLen) {
-            return strlen($aB[0]) <=> strlen($aA[0]);
-        }
-
-        return ($iALen < $iBLen) ? -1 : 1;
-    }
-
-    public function debugInfo()
-    {
-        return $this->aWords;
-    }
-}
diff --git a/lib-php/SpecialSearchOperator.php b/lib-php/SpecialSearchOperator.php
deleted file mode 100644 (file)
index 94df59e..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-/**
- * Operators describing special searches.
- */
-abstract class Operator
-{
-    /// No operator selected.
-    const NONE = 0;
-    /// Search for POI of the given type.
-    const TYPE = 1;
-    /// Search for POIs near the given place.
-    const NEAR = 2;
-    /// Search for POIS in the given place.
-    const IN = 3;
-    /// Search for POIS named as given.
-    const NAME = 4;
-    /// Search for postcodes.
-    const POSTCODE = 5;
-
-    private static $aConstantNames = null;
-
-
-    public static function toString($iOperator)
-    {
-        if ($iOperator == Operator::NONE) {
-            return '';
-        }
-
-        if (Operator::$aConstantNames === null) {
-            $oReflector = new \ReflectionClass('Nominatim\Operator');
-            $aConstants = $oReflector->getConstants();
-
-            Operator::$aConstantNames = array();
-            foreach ($aConstants as $sName => $iValue) {
-                Operator::$aConstantNames[$iValue] = $sName;
-            }
-        }
-
-        return Operator::$aConstantNames[$iOperator];
-    }
-}
diff --git a/lib-php/Status.php b/lib-php/Status.php
deleted file mode 100644 (file)
index 4f1555c..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_TokenizerDir.'/tokenizer.php');
-
-use Exception;
-
-class Status
-{
-    protected $oDB;
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-    }
-
-    public function status()
-    {
-        if (!$this->oDB) {
-            throw new Exception('No database', 700);
-        }
-
-        try {
-            $this->oDB->connect();
-        } catch (\Nominatim\DatabaseError $e) {
-            throw new Exception('Database connection failed', 700);
-        }
-
-        $oTokenizer = new \Nominatim\Tokenizer($this->oDB);
-        $oTokenizer->checkStatus();
-    }
-
-    public function dataDate()
-    {
-        $sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
-        $iDataDateEpoch = $this->oDB->getOne($sSQL);
-
-        if ($iDataDateEpoch === false) {
-            throw new Exception('Import date is not available', 705);
-        }
-
-        return $iDataDateEpoch;
-    }
-
-    public function databaseVersion()
-    {
-        $sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\'';
-        return $this->oDB->getOne($sSQL);
-    }
-}
diff --git a/lib-php/TokenCountry.php b/lib-php/TokenCountry.php
deleted file mode 100644 (file)
index 3f93f45..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A country token.
- */
-class Country
-{
-    /// Database word id, if available.
-    private $iId;
-    /// Two-letter country code (lower-cased).
-    private $sCountryCode;
-
-    public function __construct($iId, $sCountryCode)
-    {
-        $this->iId = $iId;
-        $this->sCountryCode = $sCountryCode;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oSearch->hasCountry()
-               && $oPosition->maybePhrase('country')
-               && $oSearch->getContext()->isCountryApplicable($this->sCountryCode);
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        $oNewSearch = $oSearch->clone($oPosition->isLastToken() ? 1 : 6);
-        $oNewSearch->setCountry($this->sCountryCode);
-
-        return array($oNewSearch);
-    }
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'country',
-                'Info' => $this->sCountryCode
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'C';
-    }
-}
diff --git a/lib-php/TokenHousenumber.php b/lib-php/TokenHousenumber.php
deleted file mode 100644 (file)
index 62c2a62..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A house number token.
- */
-class HouseNumber
-{
-    /// Database word id, if available.
-    private $iId;
-    /// Normalized house number.
-    private $sToken;
-
-    public function __construct($iId, $sToken)
-    {
-        $this->iId = $iId;
-        $this->sToken = $sToken;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oSearch->hasHousenumber()
-               && !$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
-               && $oPosition->maybePhrase('street');
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        $aNewSearches = array();
-
-        // sanity check: if the housenumber is not mainly made
-        // up of numbers, add a penalty
-        $iSearchCost = 1;
-        if (preg_match('/\\d/', $this->sToken) === 0
-            || preg_match_all('/[^0-9 ]/', $this->sToken, $aMatches) > 3) {
-            $iSearchCost += strlen($this->sToken) - 1;
-        }
-        if (!$oSearch->hasOperator(\Nominatim\Operator::NONE)) {
-            $iSearchCost++;
-        }
-        if (empty($this->iId)) {
-            $iSearchCost++;
-        }
-        // also must not appear in the middle of the address
-        if ($oSearch->hasAddress() || $oSearch->hasPostcode()) {
-            $iSearchCost++;
-        }
-
-        $oNewSearch = $oSearch->clone($iSearchCost);
-        $oNewSearch->setHousenumber($this->sToken);
-        $aNewSearches[] = $oNewSearch;
-
-        // Housenumbers may appear in the name when the place has its own
-        // address terms.
-        if ($this->iId !== null
-            && ($oSearch->getNamePhrase() >= 0 || !$oSearch->hasName())
-            && !$oSearch->hasAddress()
-        ) {
-            $oNewSearch = $oSearch->clone($iSearchCost);
-            $oNewSearch->setHousenumberAsName($this->iId);
-
-            $aNewSearches[] = $oNewSearch;
-        }
-
-        return $aNewSearches;
-    }
-
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'house number',
-                'Info' => array('nr' => $this->sToken)
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'H';
-    }
-}
diff --git a/lib-php/TokenList.php b/lib-php/TokenList.php
deleted file mode 100644 (file)
index 9a3950a..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/TokenCountry.php');
-require_once(CONST_LibDir.'/TokenHousenumber.php');
-require_once(CONST_LibDir.'/TokenPostcode.php');
-require_once(CONST_LibDir.'/TokenSpecialTerm.php');
-require_once(CONST_LibDir.'/TokenWord.php');
-require_once(CONST_LibDir.'/TokenPartial.php');
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * Saves information about the tokens that appear in a search query.
- *
- * Tokens are sorted by their normalized form, the token word. There are different
- * kinds of tokens, represented by different Token* classes. Note that
- * tokens do not have a common base class. All tokens need to have a field
- * with the word id that points to an entry in the `word` database table
- * but otherwise the information saved about a token can be very different.
- */
-class TokenList
-{
-    // List of list of tokens indexed by their word_token.
-    private $aTokens = array();
-
-
-    /**
-     * Return total number of tokens.
-     *
-     * @return Integer
-     */
-    public function count()
-    {
-        return count($this->aTokens);
-    }
-
-    /**
-     * Check if there are tokens for the given token word.
-     *
-     * @param string $sWord Token word to look for.
-     *
-     * @return bool True if there is one or more token for the token word.
-     */
-    public function contains($sWord)
-    {
-        return isset($this->aTokens[$sWord]);
-    }
-
-    /**
-     * Check if there are partial or full tokens for the given word.
-     *
-     * @param string $sWord Token word to look for.
-     *
-     * @return bool True if there is one or more token for the token word.
-     */
-    public function containsAny($sWord)
-    {
-        return isset($this->aTokens[$sWord]);
-    }
-
-    /**
-     * Get the list of tokens for the given token word.
-     *
-     * @param string $sWord Token word to look for.
-     *
-     * @return object[] Array of tokens for the given token word or an
-     *                  empty array if no tokens could be found.
-     */
-    public function get($sWord)
-    {
-        return isset($this->aTokens[$sWord]) ? $this->aTokens[$sWord] : array();
-    }
-
-    public function getFullWordIDs()
-    {
-        $ids = array();
-
-        foreach ($this->aTokens as $aTokenList) {
-            foreach ($aTokenList as $oToken) {
-                if (is_a($oToken, '\Nominatim\Token\Word')) {
-                    $ids[$oToken->getId()] = $oToken->getId();
-                }
-            }
-        }
-
-        return $ids;
-    }
-
-    /**
-     * Add a new token for the given word.
-     *
-     * @param string $sWord  Word the token describes.
-     * @param object $oToken Token object to add.
-     *
-     * @return void
-     */
-    public function addToken($sWord, $oToken)
-    {
-        if (isset($this->aTokens[$sWord])) {
-            $this->aTokens[$sWord][] = $oToken;
-        } else {
-            $this->aTokens[$sWord] = array($oToken);
-        }
-    }
-
-    public function debugTokenByWordIdList()
-    {
-        $aWordsIDs = array();
-        foreach ($this->aTokens as $sToken => $aWords) {
-            foreach ($aWords as $aToken) {
-                $iId = $aToken->getId();
-                if ($iId !== null) {
-                    $aWordsIDs[$iId] = '#'.$sToken.'('.$aToken->debugCode().' '.$iId.')#';
-                }
-            }
-        }
-
-        return $aWordsIDs;
-    }
-
-    public function debugInfo()
-    {
-        return $this->aTokens;
-    }
-}
diff --git a/lib-php/TokenPartial.php b/lib-php/TokenPartial.php
deleted file mode 100644 (file)
index 3dc6f30..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Partial
-{
-    /// Database word id, if applicable.
-    private $iId;
-    /// Number of appearances in the database.
-    private $iSearchNameCount;
-    /// True, if the token consists exclusively of digits and spaces.
-    private $bNumberToken;
-
-    public function __construct($iId, $sToken, $iSearchNameCount)
-    {
-        $this->iId = $iId;
-        $this->bNumberToken = (bool) preg_match('#^[0-9 ]+$#', $sToken);
-        $this->iSearchNameCount = $iSearchNameCount;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oPosition->isPhrase('country');
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        $aNewSearches = array();
-
-        // Partial token in Address.
-        if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
-            && $oSearch->hasName()
-        ) {
-            $iSearchCost = $this->bNumberToken ? 2 : 1;
-            if ($this->iSearchNameCount >= CONST_Max_Word_Frequency) {
-                $iSearchCost += 1;
-            }
-
-            $oNewSearch = $oSearch->clone($iSearchCost);
-            $oNewSearch->addAddressToken(
-                $this->iId,
-                $this->iSearchNameCount < CONST_Max_Word_Frequency
-            );
-
-            $aNewSearches[] = $oNewSearch;
-        }
-
-        // Partial token in Name.
-        if ((!$oSearch->hasPostcode() && !$oSearch->hasAddress())
-            && (!$oSearch->hasName(true)
-                || $oSearch->getNamePhrase() == $oPosition->getPhrase())
-        ) {
-            $iSearchCost = 1;
-            if (!$oSearch->hasName(true)) {
-                $iSearchCost += 1;
-            }
-            if ($this->bNumberToken) {
-                $iSearchCost += 1;
-            }
-
-            $oNewSearch = $oSearch->clone($iSearchCost);
-            $oNewSearch->addPartialNameToken(
-                $this->iId,
-                $this->iSearchNameCount < CONST_Max_Word_Frequency,
-                $this->iSearchNameCount > CONST_Search_NameOnlySearchFrequencyThreshold,
-                $oPosition->getPhrase()
-            );
-
-            $aNewSearches[] = $oNewSearch;
-        }
-
-        return $aNewSearches;
-    }
-
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'partial',
-                'Info' => array(
-                           'count' => $this->iSearchNameCount
-                          )
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'w';
-    }
-}
diff --git a/lib-php/TokenPostcode.php b/lib-php/TokenPostcode.php
deleted file mode 100644 (file)
index 0ff9292..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A postcode token.
- */
-class Postcode
-{
-    /// Database word id, if available.
-    private $iId;
-    /// Full normalized postcode (upper cased).
-    private $sPostcode;
-    // Optional country code the postcode belongs to (currently unused).
-    private $sCountryCode;
-
-    public function __construct($iId, $sPostcode, $sCountryCode = '')
-    {
-        $this->iId = $iId;
-        $iSplitPos = strpos($sPostcode, '@');
-        if ($iSplitPos === false) {
-            $this->sPostcode = $sPostcode;
-        } else {
-            $this->sPostcode = substr($sPostcode, 0, $iSplitPos);
-        }
-        $this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oSearch->hasPostcode() && $oPosition->maybePhrase('postalcode');
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        $aNewSearches = array();
-
-        // If we have structured search or this is the first term,
-        // make the postcode the primary search element.
-        if ($oSearch->hasOperator(\Nominatim\Operator::NONE) && $oPosition->isFirstToken()) {
-            $oNewSearch = $oSearch->clone(1);
-            $oNewSearch->setPostcodeAsName($this->iId, $this->sPostcode);
-
-            $aNewSearches[] = $oNewSearch;
-        }
-
-        // If we have a structured search or this is not the first term,
-        // add the postcode as an addendum.
-        if (!$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
-            && ($oPosition->isPhrase('postalcode') || $oSearch->hasName())
-        ) {
-            $iPenalty = 1;
-            if (strlen($this->sPostcode) < 4) {
-                $iPenalty += 4 - strlen($this->sPostcode);
-            }
-            $oNewSearch = $oSearch->clone($iPenalty);
-            $oNewSearch->setPostcode($this->sPostcode);
-
-            $aNewSearches[] = $oNewSearch;
-        }
-
-        return $aNewSearches;
-    }
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'postcode',
-                'Info' => $this->sPostcode.'('.$this->sCountryCode.')'
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'P';
-    }
-}
diff --git a/lib-php/TokenSpecialTerm.php b/lib-php/TokenSpecialTerm.php
deleted file mode 100644 (file)
index 475ae71..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-require_once(CONST_LibDir.'/SpecialSearchOperator.php');
-
-/**
- * A word token describing a place type.
- */
-class SpecialTerm
-{
-    /// Database word id, if applicable.
-    private $iId;
-    /// Class (or OSM tag key) of the place to look for.
-    private $sClass;
-    /// Type (or OSM tag value) of the place to look for.
-    private $sType;
-    /// Relationship of the operator to the object (see Operator class).
-    private $iOperator;
-
-    public function __construct($iID, $sClass, $sType, $iOperator)
-    {
-        $this->iId = $iID;
-        $this->sClass = $sClass;
-        $this->sType = $sType;
-        $this->iOperator = $iOperator;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oSearch->hasOperator()
-               && $oPosition->isPhrase('')
-               && ($this->iOperator != \Nominatim\Operator::NONE
-                  || (!$oSearch->hasAddress() && !$oSearch->hasHousenumber() && !$oSearch->hasCountry()));
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        $iSearchCost = 0;
-
-        $iOp = $this->iOperator;
-        if ($iOp == \Nominatim\Operator::NONE) {
-            if ($oPosition->isFirstToken()
-                || $oSearch->hasName()
-                || $oSearch->getContext()->isBoundedSearch()
-            ) {
-                $iOp = \Nominatim\Operator::NAME;
-                $iSearchCost += 3;
-            } else {
-                $iOp = \Nominatim\Operator::NEAR;
-                $iSearchCost += 4;
-                if (!$oPosition->isFirstToken()) {
-                    $iSearchCost += 3;
-                }
-            }
-        } elseif ($oPosition->isFirstToken()) {
-            $iSearchCost += 2;
-        } elseif ($oPosition->isLastToken()) {
-            $iSearchCost += 4;
-        } else {
-            $iSearchCost += 6;
-        }
-
-        if ($oSearch->hasHousenumber()) {
-            $iSearchCost ++;
-        }
-
-        $oNewSearch = $oSearch->clone($iSearchCost);
-        $oNewSearch->setPoiSearch($iOp, $this->sClass, $this->sType);
-
-        return array($oNewSearch);
-    }
-
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'special term',
-                'Info' => array(
-                           'class' => $this->sClass,
-                           'type' => $this->sType,
-                           'operator' => \Nominatim\Operator::toString($this->iOperator)
-                          )
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'S';
-    }
-}
diff --git a/lib-php/TokenWord.php b/lib-php/TokenWord.php
deleted file mode 100644 (file)
index a7557d3..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim\Token;
-
-/**
- * A standard word token.
- */
-class Word
-{
-    /// Database word id, if applicable.
-    private $iId;
-    /// Number of appearances in the database.
-    private $iSearchNameCount;
-    /// Number of terms in the word.
-    private $iTermCount;
-
-    public function __construct($iId, $iSearchNameCount, $iTermCount)
-    {
-        $this->iId = $iId;
-        $this->iSearchNameCount = $iSearchNameCount;
-        $this->iTermCount = $iTermCount;
-    }
-
-    public function getId()
-    {
-        return $this->iId;
-    }
-
-    /**
-     * Check if the token can be added to the given search.
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return True if the token is compatible with the search configuration
-     *         given the position.
-     */
-    public function isExtendable($oSearch, $oPosition)
-    {
-        return !$oPosition->isPhrase('country');
-    }
-
-    /**
-     * Derive new searches by adding this token to an existing search.
-     *
-     * @param object  $oSearch      Partial search description derived so far.
-     * @param object  $oPosition    Description of the token position within
-                                    the query.
-     *
-     * @return SearchDescription[] List of derived search descriptions.
-     */
-    public function extendSearch($oSearch, $oPosition)
-    {
-        // Full words can only be a name if they appear at the beginning
-        // of the phrase. In structured search the name must forcibly in
-        // the first phrase. In unstructured search it may be in a later
-        // phrase when the first phrase is a house number.
-        if ($oSearch->hasName()
-            || !($oPosition->isFirstPhrase() || $oPosition->isPhrase(''))
-        ) {
-            if ($this->iTermCount > 1
-                && ($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
-            ) {
-                $oNewSearch = $oSearch->clone(1);
-                $oNewSearch->addAddressToken($this->iId);
-
-                return array($oNewSearch);
-            }
-        } elseif (!$oSearch->hasName(true)) {
-            $oNewSearch = $oSearch->clone(1);
-            $oNewSearch->addNameToken(
-                $this->iId,
-                CONST_Search_NameOnlySearchFrequencyThreshold
-                && $this->iSearchNameCount
-                          < CONST_Search_NameOnlySearchFrequencyThreshold
-            );
-
-            return array($oNewSearch);
-        }
-
-        return array();
-    }
-
-    public function debugInfo()
-    {
-        return array(
-                'ID' => $this->iId,
-                'Type' => 'word',
-                'Info' => array(
-                           'count' => $this->iSearchNameCount,
-                           'terms' => $this->iTermCount
-                          )
-               );
-    }
-
-    public function debugCode()
-    {
-        return 'W';
-    }
-}
diff --git a/lib-php/cmd.php b/lib-php/cmd.php
deleted file mode 100644 (file)
index 6f1299d..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/Shell.php');
-
-function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
-{
-    $aQuick = array();
-    $aCounts = array();
-
-    foreach ($aSpec as $aLine) {
-        if (is_array($aLine)) {
-            if ($aLine[0]) {
-                $aQuick['--'.$aLine[0]] = $aLine;
-            }
-            if ($aLine[1]) {
-                $aQuick['-'.$aLine[1]] = $aLine;
-            }
-            $aCounts[$aLine[0]] = 0;
-        }
-    }
-
-    $aResult = array();
-    $bUnknown = false;
-    $iSize = count($aArg);
-    for ($i = 1; $i < $iSize; $i++) {
-        if (isset($aQuick[$aArg[$i]])) {
-            $aLine = $aQuick[$aArg[$i]];
-            $aCounts[$aLine[0]]++;
-            $xVal = null;
-            if ($aLine[4] == $aLine[5]) {
-                if ($aLine[4]) {
-                    $xVal = array();
-                    for ($n = $aLine[4]; $i < $iSize && $n; $n--) {
-                        $i++;
-                        if ($i >= $iSize || $aArg[$i][0] == '-') {
-                            showUsage($aSpec, $bExitOnError, 'Parameter of  \''.$aLine[0].'\' is missing');
-                        }
-
-                        switch ($aLine[6]) {
-                            case 'realpath':
-                                $xVal[] = realpath($aArg[$i]);
-                                break;
-                            case 'realdir':
-                                $sPath = realpath(dirname($aArg[$i]));
-                                if ($sPath) {
-                                    $xVal[] = $sPath . '/' . basename($aArg[$i]);
-                                } else {
-                                    $xVal[] = $sPath;
-                                }
-                                break;
-                            case 'bool':
-                                $xVal[] = (bool)$aArg[$i];
-                                break;
-                            case 'int':
-                                $xVal[] = (int)$aArg[$i];
-                                break;
-                            case 'float':
-                                $xVal[] = (float)$aArg[$i];
-                                break;
-                            default:
-                                $xVal[] = $aArg[$i];
-                                break;
-                        }
-                    }
-                    if ($aLine[4] == 1) {
-                        $xVal = $xVal[0];
-                    }
-                } else {
-                    $xVal = true;
-                }
-            } else {
-                fail('Variable numbers of params not yet supported');
-            }
-
-            if ($aLine[3] > 1) {
-                if (!array_key_exists($aLine[0], $aResult)) {
-                    $aResult[$aLine[0]] = array();
-                }
-                $aResult[$aLine[0]][] = $xVal;
-            } else {
-                $aResult[$aLine[0]] = $xVal;
-            }
-        } else {
-            $bUnknown = $aArg[$i];
-        }
-    }
-
-    if (array_key_exists('help', $aResult)) {
-        showUsage($aSpec);
-    }
-    if ($bUnknown && $bExitOnUnknown) {
-        showUsage($aSpec, $bExitOnError, 'Unknown option \''.$bUnknown.'\'');
-    }
-
-    foreach ($aSpec as $aLine) {
-        if (is_array($aLine)) {
-            if ($aCounts[$aLine[0]] < $aLine[2]) {
-                showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is missing');
-            }
-            if ($aCounts[$aLine[0]] > $aLine[3]) {
-                showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is present too many times');
-            }
-            if ($aLine[6] == 'bool' && !array_key_exists($aLine[0], $aResult)) {
-                $aResult[$aLine[0]] = false;
-            }
-        }
-    }
-    return $bUnknown;
-}
-
-function showUsage($aSpec, $bExit = false, $sError = false)
-{
-    if ($sError) {
-        echo basename($_SERVER['argv'][0]).': '.$sError."\n";
-        echo 'Try `'.basename($_SERVER['argv'][0]).' --help` for more information.'."\n";
-        exit;
-    }
-    echo 'Usage: '.basename($_SERVER['argv'][0])."\n";
-    $bFirst = true;
-    foreach ($aSpec as $aLine) {
-        if (is_array($aLine)) {
-            if ($bFirst) {
-                $bFirst = false;
-                echo "\n";
-            }
-            $aNames = array();
-            if ($aLine[1]) {
-                $aNames[] = '-'.$aLine[1];
-            }
-            if ($aLine[0]) {
-                $aNames[] = '--'.$aLine[0];
-            }
-            $sName = join(', ', $aNames);
-            echo '  '.$sName.str_repeat(' ', 30-strlen($sName)).$aLine[7]."\n";
-        } else {
-            echo $aLine."\n";
-        }
-    }
-    echo "\n";
-    exit;
-}
-
-function info($sMsg)
-{
-    echo date('Y-m-d H:i:s == ').$sMsg."\n";
-}
-
-$aWarnings = array();
-
-
-function warn($sMsg)
-{
-    $GLOBALS['aWarnings'][] = $sMsg;
-    echo date('Y-m-d H:i:s == ').'WARNING: '.$sMsg."\n";
-}
-
-
-function repeatWarnings()
-{
-    foreach ($GLOBALS['aWarnings'] as $sMsg) {
-        echo '  * ',$sMsg."\n";
-    }
-}
-
-
-function setupHTTPProxy()
-{
-    if (!getSettingBool('HTTP_PROXY')) {
-        return;
-    }
-
-    $sProxy = 'tcp://'.getSetting('HTTP_PROXY_HOST').':'.getSetting('HTTP_PROXY_PROT');
-    $aHeaders = array();
-
-    $sLogin = getSetting('HTTP_PROXY_LOGIN');
-    $sPassword = getSetting('HTTP_PROXY_PASSWORD');
-
-    if ($sLogin && $sPassword) {
-        $sAuth = base64_encode($sLogin.':'.$sPassword);
-        $aHeaders = array('Proxy-Authorization: Basic '.$sAuth);
-    }
-
-    $aProxyHeader = array(
-                     'proxy' => $sProxy,
-                     'request_fulluri' => true,
-                     'header' => $aHeaders
-                    );
-
-    $aContext = array('http' => $aProxyHeader, 'https' => $aProxyHeader);
-    stream_context_set_default($aContext);
-}
diff --git a/lib-php/dotenv_loader.php b/lib-php/dotenv_loader.php
deleted file mode 100644 (file)
index bcddf00..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require('Symfony/Component/Dotenv/autoload.php');
-
-function loadDotEnv()
-{
-    $dotenv = new \Symfony\Component\Dotenv\Dotenv();
-    $dotenv->load(CONST_ConfigDir.'/env.defaults');
-
-    if (file_exists('.env')) {
-        $dotenv->load('.env');
-    }
-}
diff --git a/lib-php/init-cmd.php b/lib-php/init-cmd.php
deleted file mode 100644 (file)
index 44e7adb..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once('init.php');
-require_once('cmd.php');
-require_once('DebugNone.php');
diff --git a/lib-php/init-website.php b/lib-php/init-website.php
deleted file mode 100644 (file)
index 6036750..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once('init.php');
-require_once('ParameterParser.php');
-require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
-
-/***************************************************************************
- *
- * Error handling functions
- *
- */
-
-function userError($sMsg)
-{
-    throw new \Exception($sMsg, 400);
-}
-
-
-function exception_handler_json($exception)
-{
-    http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
-    header('Content-type: application/json; charset=utf-8');
-    include(CONST_LibDir.'/template/error-json.php');
-    exit();
-}
-
-function exception_handler_xml($exception)
-{
-    http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
-    header('Content-type: text/xml; charset=utf-8');
-    echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
-    include(CONST_LibDir.'/template/error-xml.php');
-    exit();
-}
-
-function shutdown_exception_handler_xml()
-{
-    $error = error_get_last();
-    if ($error !== null && $error['type'] === E_ERROR) {
-        exception_handler_xml(new \Exception($error['message'], 500));
-    }
-}
-
-function shutdown_exception_handler_json()
-{
-    $error = error_get_last();
-    if ($error !== null && $error['type'] === E_ERROR) {
-        exception_handler_json(new \Exception($error['message'], 500));
-    }
-}
-
-
-function set_exception_handler_by_format($sFormat = null)
-{
-    // Multiple calls to register_shutdown_function will cause multiple callbacks
-    // to be executed, we only want the last executed. Thus we don't want to register
-    // one by default without an explicit $sFormat set.
-
-    if (!isset($sFormat)) {
-        set_exception_handler('exception_handler_json');
-    } elseif ($sFormat == 'xml') {
-        set_exception_handler('exception_handler_xml');
-        register_shutdown_function('shutdown_exception_handler_xml');
-    } else {
-        set_exception_handler('exception_handler_json');
-        register_shutdown_function('shutdown_exception_handler_json');
-    }
-}
-// set a default
-set_exception_handler_by_format();
-
-
-/***************************************************************************
- * HTTP Reply header setup
- */
-
-if (CONST_NoAccessControl) {
-    header('Access-Control-Allow-Origin: *');
-    header('Access-Control-Allow-Methods: OPTIONS,GET');
-    if (!empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
-        header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
-    }
-}
-if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
-    exit;
-}
-
-if (CONST_Debug) {
-    header('Content-type: text/html; charset=utf-8');
-}
diff --git a/lib-php/init.php b/lib-php/init.php
deleted file mode 100644 (file)
index 9e71a76..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/DB.php');
diff --git a/lib-php/lib.php b/lib-php/lib.php
deleted file mode 100644 (file)
index f7c6e55..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-function loadSettings($sProjectDir)
-{
-    @define('CONST_InstallDir', $sProjectDir);
-    // Temporary hack to set the directory via environment instead of
-    // the installed scripts. Neither setting is part of the official
-    // set of settings.
-    defined('CONST_ConfigDir') or define('CONST_ConfigDir', $_SERVER['NOMINATIM_CONFIGDIR']);
-}
-
-function getSetting($sConfName, $sDefault = null)
-{
-    $sValue = $_SERVER['NOMINATIM_'.$sConfName];
-
-    if ($sDefault !== null && !$sValue) {
-        return $sDefault;
-    }
-
-    return $sValue;
-}
-
-function getSettingBool($sConfName)
-{
-    $sVal = strtolower(getSetting($sConfName));
-
-    return strcmp($sVal, 'yes') == 0
-           || strcmp($sVal, 'true') == 0
-           || strcmp($sVal, '1') == 0;
-}
-
-function fail($sError, $sUserError = false)
-{
-    if (!$sUserError) {
-        $sUserError = $sError;
-    }
-    error_log('ERROR: '.$sError);
-    var_dump($sUserError);
-    echo "\n";
-    exit(-1);
-}
-
-
-function getProcessorCount()
-{
-    $sCPU = file_get_contents('/proc/cpuinfo');
-    preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
-    return count($aMatches[0]);
-}
-
-
-function getTotalMemoryMB()
-{
-    $sCPU = file_get_contents('/proc/meminfo');
-    preg_match('#MemTotal: +([0-9]+) kB#', $sCPU, $aMatches);
-    return (int)($aMatches[1]/1024);
-}
-
-
-function getCacheMemoryMB()
-{
-    $sCPU = file_get_contents('/proc/meminfo');
-    preg_match('#Cached: +([0-9]+) kB#', $sCPU, $aMatches);
-    return (int)($aMatches[1]/1024);
-}
-
-function getDatabaseDate(&$oDB)
-{
-    // Find the newest node in the DB
-    $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
-    // Lookup the timestamp that node was created
-    $sLastNodeURL = 'https://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID.'/1';
-    $sLastNodeXML = file_get_contents($sLastNodeURL);
-
-    if ($sLastNodeXML === false) {
-        return false;
-    }
-
-    preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
-
-    return $aLastNodeDate[1];
-}
-
-
-function byImportance($a, $b)
-{
-    if ($a['importance'] != $b['importance']) {
-        return ($a['importance'] > $b['importance']?-1:1);
-    }
-
-    return $a['foundorder'] <=> $b['foundorder'];
-}
-
-
-function javascript_renderData($xVal, $iOptions = 0)
-{
-    $sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : '';
-    if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) {
-        // Unset, we call javascript_renderData again during exception handling
-        unset($_GET['json_callback']);
-        throw new Exception('Invalid json_callback value', 400);
-    }
-
-    $iOptions |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
-    if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) {
-        $iOptions |= JSON_PRETTY_PRINT;
-    }
-
-    $jsonout = json_encode($xVal, $iOptions);
-
-    if ($sCallback) {
-        header('Content-Type: application/javascript; charset=UTF-8');
-        echo $_GET['json_callback'].'('.$jsonout.')';
-    } else {
-        header('Content-Type: application/json; charset=UTF-8');
-        echo $jsonout;
-    }
-}
-
-function addQuotes($s)
-{
-    return "'".$s."'";
-}
-
-function parseLatLon($sQuery)
-{
-    $sFound    = null;
-    $fQueryLat = null;
-    $fQueryLon = null;
-
-    if (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9.]*)[°\s]+([0-9.]+)?[′\']*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) {
-        /*               1          2                    3                     4          5             6
-         * degrees decimal minutes
-         * N 40 26.767, W 79 58.933
-         * N 40°26.767′, W 79°58.933′
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
-        $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
-    } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\']*[\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\'\s]+([EW])\\s*/', $sQuery, $aData)) {
-        /*                     1             2                          3           4             5                       6
-         * degrees decimal minutes
-         * 40 26.767 N, 79 58.933 W
-         * 40° 26.767′ N 79° 58.933′ W
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
-        $fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
-    } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) {
-        /*                     1          2             3               4                  5          6             7               8
-         * degrees decimal seconds
-         * N 40 26 46 W 79 58 56
-         * N 40° 26′ 46″, W 79° 58′ 56″
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
-        $fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
-    } elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([EW])\\s*/', $sQuery, $aData)) {
-        /*                     1             2               3                     4           5             6               7                     8
-         * degrees decimal seconds
-         * 40 26 46 N 79 58 56 W
-         * 40° 26′ 46″ N, 79° 58′ 56″ W
-         * 40° 26′ 46.78″ N, 79° 58′ 56.89″ W
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
-        $fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
-    } elseif (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*[,\s]+([EW])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) {
-        /*                     1          2                                3          4
-         * degrees decimal
-         * N 40.446° W 79.982°
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
-        $fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
-    } elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[°\s]+([NS])[,\s]+([0-9]+[0-9]*\\.[0-9]+)[°\s]+([EW])\\s*/', $sQuery, $aData)) {
-        /*                     1                            2           3                            4
-         * degrees decimal
-         * 40.446° N 79.982° W
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
-        $fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
-    } elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[,\s]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) {
-        /*                 1                   2                              3                        4
-         * degrees decimal
-         * 12.34, 56.78
-         * 12.34 56.78
-         * [12.456,-78.90]
-         */
-        $sFound    = $aData[0];
-        $fQueryLat = $aData[2];
-        $fQueryLon = $aData[3];
-    } else {
-        return false;
-    }
-
-    return array($sFound, $fQueryLat, $fQueryLon);
-}
-
-function addressRankToGeocodeJsonType($iAddressRank)
-{
-    if ($iAddressRank >= 29 && $iAddressRank <= 30) {
-        return 'house';
-    }
-    if ($iAddressRank >= 26 && $iAddressRank < 28) {
-        return 'street';
-    }
-    if ($iAddressRank >= 22 && $iAddressRank < 26) {
-        return 'locality';
-    }
-    if ($iAddressRank >= 17 && $iAddressRank < 22) {
-        return 'district';
-    }
-    if ($iAddressRank >= 13 && $iAddressRank < 17) {
-        return 'city';
-    }
-    if ($iAddressRank >= 10 && $iAddressRank < 13) {
-        return 'county';
-    }
-    if ($iAddressRank >= 5 && $iAddressRank < 10) {
-        return 'state';
-    }
-    if ($iAddressRank >= 4 && $iAddressRank < 5) {
-        return 'country';
-    }
-
-    return 'locality';
-}
-
-if (!function_exists('array_key_last')) {
-    function array_key_last(array $array)
-    {
-        if (!empty($array)) {
-            return key(array_slice($array, -1, 1, true));
-        }
-    }
-}
diff --git a/lib-php/log.php b/lib-php/log.php
deleted file mode 100644 (file)
index 1d56773..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-
-function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
-{
-    $fStartTime = microtime(true);
-    $aStartTime = explode('.', $fStartTime);
-    if (!isset($aStartTime[1])) {
-        $aStartTime[1] = '0';
-    }
-
-    $sOutputFormat = '';
-    if (isset($_GET['format'])) {
-        $sOutputFormat = $_GET['format'];
-    }
-
-    if ($sType == 'reverse') {
-        $sOutQuery = (isset($_GET['lat'])?$_GET['lat']:'').'/';
-        if (isset($_GET['lon'])) {
-            $sOutQuery .= $_GET['lon'];
-        }
-        if (isset($_GET['zoom'])) {
-            $sOutQuery .= '/'.$_GET['zoom'];
-        }
-    } else {
-        $sOutQuery = $sQuery;
-    }
-
-    $hLog = array(
-             date('Y-m-d H:i:s', $aStartTime[0]).'.'.$aStartTime[1],
-             $_SERVER['REMOTE_ADDR'],
-             $_SERVER['QUERY_STRING'],
-             $sOutQuery,
-             $sType,
-             $fStartTime
-            );
-
-    if (CONST_Log_DB) {
-        if (isset($_GET['email'])) {
-            $sUserAgent = $_GET['email'];
-        } elseif (isset($_SERVER['HTTP_REFERER'])) {
-            $sUserAgent = $_SERVER['HTTP_REFERER'];
-        } elseif (isset($_SERVER['HTTP_USER_AGENT'])) {
-            $sUserAgent = $_SERVER['HTTP_USER_AGENT'];
-        } else {
-            $sUserAgent = '';
-        }
-        $sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
-        $sSQL .= ' values (';
-        $sSQL .= join(',', $oDB->getDBQuotedList(array(
-            $sType,
-            $hLog[0],
-            $hLog[2],
-            $hLog[1],
-            $sUserAgent,
-            join(',', $aLanguageList),
-            $sOutputFormat,
-            $hLog[3]
-        )));
-        $sSQL .= ')';
-        $oDB->exec($sSQL);
-    }
-
-    return $hLog;
-}
-
-function logEnd(&$oDB, $hLog, $iNumResults)
-{
-    $fEndTime = microtime(true);
-
-    if (CONST_Log_DB) {
-        $aEndTime = explode('.', $fEndTime);
-        if (!isset($aEndTime[1])) {
-            $aEndTime[1] = '0';
-        }
-        $sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1];
-
-        $sSQL = 'update new_query_log set endtime = '.$oDB->getDBQuoted($sEndTime).', results = '.$iNumResults;
-        $sSQL .= ' where starttime = '.$oDB->getDBQuoted($hLog[0]);
-        $sSQL .= ' and ipaddress = '.$oDB->getDBQuoted($hLog[1]);
-        $sSQL .= ' and query = '.$oDB->getDBQuoted($hLog[2]);
-        $oDB->exec($sSQL);
-    }
-
-    if (CONST_Log_File) {
-        $aOutdata = sprintf(
-            "[%s] %.4f %d %s \"%s\"\n",
-            $hLog[0],
-            $fEndTime-$hLog[5],
-            $iNumResults,
-            $hLog[4],
-            $hLog[2]
-        );
-        file_put_contents(CONST_Log_File, $aOutdata, FILE_APPEND | LOCK_EX);
-    }
-}
diff --git a/lib-php/output.php b/lib-php/output.php
deleted file mode 100644 (file)
index 44c4dde..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-
-function formatOSMType($sType, $bIncludeExternal = true)
-{
-    if ($sType == 'N') {
-        return 'node';
-    }
-    if ($sType == 'W') {
-        return 'way';
-    }
-    if ($sType == 'R') {
-        return 'relation';
-    }
-
-    if (!$bIncludeExternal) {
-        return '';
-    }
-
-    if ($sType == 'T') {
-        return 'way';
-    }
-    if ($sType == 'I') {
-        return 'way';
-    }
-
-    // not handled: P, L
-
-    return '';
-}
diff --git a/lib-php/setup_functions.php b/lib-php/setup_functions.php
deleted file mode 100755 (executable)
index 400834d..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-function getOsm2pgsqlBinary()
-{
-    $sBinary = getSetting('OSM2PGSQL_BINARY');
-
-    return $sBinary ? $sBinary : CONST_Default_Osm2pgsql;
-}
-
-function getImportStyle()
-{
-    $sStyle = getSetting('IMPORT_STYLE');
-
-    if (in_array($sStyle, array('admin', 'street', 'address', 'full', 'extratags'))) {
-        return CONST_ConfigDir.'/import-'.$sStyle.'.style';
-    }
-
-    return $sStyle;
-}
diff --git a/lib-php/template/address-geocodejson.php b/lib-php/template/address-geocodejson.php
deleted file mode 100644 (file)
index 28c605f..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-// https://github.com/geocoders/geocodejson-spec/
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
-    if (isset($sError)) {
-        $aFilteredPlaces['error'] = $sError;
-    } else {
-        $aFilteredPlaces['error'] = 'Unable to geocode';
-    }
-    javascript_renderData($aFilteredPlaces);
-} else {
-    $aFilteredPlaces = array(
-                        'type' => 'Feature',
-                        'properties' => array(
-                                         'geocoding' => array()
-                                        )
-                       );
-
-    if (isset($aPlace['place_id'])) {
-        $aFilteredPlaces['properties']['geocoding']['place_id'] = $aPlace['place_id'];
-    }
-    $sOSMType = formatOSMType($aPlace['osm_type']);
-    if ($sOSMType) {
-        $aFilteredPlaces['properties']['geocoding']['osm_type'] = $sOSMType;
-        $aFilteredPlaces['properties']['geocoding']['osm_id'] = $aPlace['osm_id'];
-    }
-
-    $aFilteredPlaces['properties']['geocoding']['osm_key'] = $aPlace['class'];
-    $aFilteredPlaces['properties']['geocoding']['osm_value'] = $aPlace['type'];
-
-    $aFilteredPlaces['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPlace['rank_address']);
-
-    $aFilteredPlaces['properties']['geocoding']['accuracy'] = (int) $fDistance;
-
-    $aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress'];
-
-    if ($aPlace['placename'] !== null) {
-        $aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
-    }
-
-    if (isset($aPlace['address'])) {
-        $aPlace['address']->addGeocodeJsonAddressParts(
-            $aFilteredPlaces['properties']['geocoding']
-        );
-
-        $aFilteredPlaces['properties']['geocoding']['admin']
-            = $aPlace['address']->getAdminLevels();
-    }
-
-    if (isset($aPlace['asgeojson'])) {
-        $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
-    } else {
-        $aFilteredPlaces['geometry'] = array(
-                                        'type' => 'Point',
-                                        'coordinates' => array(
-                                                          (float) $aPlace['lon'],
-                                                          (float) $aPlace['lat']
-                                                         )
-                                       );
-    }
-
-    javascript_renderData(array(
-                           'type' => 'FeatureCollection',
-                           'geocoding' => array(
-                                           'version' => '0.1.0',
-                                           'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
-                                           'licence' => 'ODbL',
-                                           'query' => $sQuery
-                                          ),
-                           'features' => array($aFilteredPlaces)
-                          ));
-}
diff --git a/lib-php/template/address-geojson.php b/lib-php/template/address-geojson.php
deleted file mode 100644 (file)
index dc3c383..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
-    if (isset($sError)) {
-        $aFilteredPlaces['error'] = $sError;
-    } else {
-        $aFilteredPlaces['error'] = 'Unable to geocode';
-    }
-    javascript_renderData($aFilteredPlaces);
-} else {
-    $aFilteredPlaces = array(
-                        'type' => 'Feature',
-                        'properties' => array()
-                       );
-
-    if (isset($aPlace['place_id'])) {
-        $aFilteredPlaces['properties']['place_id'] = $aPlace['place_id'];
-    }
-    $sOSMType = formatOSMType($aPlace['osm_type']);
-    if ($sOSMType) {
-        $aFilteredPlaces['properties']['osm_type'] = $sOSMType;
-        $aFilteredPlaces['properties']['osm_id'] = $aPlace['osm_id'];
-    }
-
-    $aFilteredPlaces['properties']['place_rank'] = $aPlace['rank_search'];
-
-    $aFilteredPlaces['properties']['category'] = $aPlace['class'];
-    $aFilteredPlaces['properties']['type'] = $aPlace['type'];
-
-    $aFilteredPlaces['properties']['importance'] = $aPlace['importance'];
-
-    $aFilteredPlaces['properties']['addresstype'] = strtolower($aPlace['addresstype']);
-
-    $aFilteredPlaces['properties']['name'] = $aPlace['placename'];
-
-    $aFilteredPlaces['properties']['display_name'] = $aPlace['langaddress'];
-
-    if (isset($aPlace['address'])) {
-        $aFilteredPlaces['properties']['address'] = $aPlace['address']->getAddressNames();
-    }
-    if (isset($aPlace['sExtraTags'])) {
-        $aFilteredPlaces['properties']['extratags'] = $aPlace['sExtraTags'];
-    }
-    if (isset($aPlace['sNameDetails'])) {
-        $aFilteredPlaces['properties']['namedetails'] = $aPlace['sNameDetails'];
-    }
-
-    if (isset($aPlace['aBoundingBox'])) {
-        $aFilteredPlaces['bbox'] = array(
-                                    (float) $aPlace['aBoundingBox'][2], // minlon
-                                    (float) $aPlace['aBoundingBox'][0], // minlat
-                                    (float) $aPlace['aBoundingBox'][3], // maxlon
-                                    (float) $aPlace['aBoundingBox'][1]  // maxlat
-                                   );
-    }
-
-    if (isset($aPlace['asgeojson'])) {
-        $aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
-    } else {
-        $aFilteredPlaces['geometry'] = array(
-                                        'type' => 'Point',
-                                        'coordinates' => array(
-                                                          (float) $aPlace['lon'],
-                                                          (float) $aPlace['lat']
-                                                         )
-                                       );
-    }
-
-
-    javascript_renderData(array(
-                           'type' => 'FeatureCollection',
-                           'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
-                           'features' => array($aFilteredPlaces)
-                          ));
-}
diff --git a/lib-php/template/address-json.php b/lib-php/template/address-json.php
deleted file mode 100644 (file)
index 0766eaf..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-
-if (empty($aPlace)) {
-    if (isset($sError)) {
-        $aFilteredPlaces['error'] = $sError;
-    } else {
-        $aFilteredPlaces['error'] = 'Unable to geocode';
-    }
-} else {
-    if (isset($aPlace['place_id'])) {
-        $aFilteredPlaces['place_id'] = $aPlace['place_id'];
-    }
-    $aFilteredPlaces['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
-    $sOSMType = formatOSMType($aPlace['osm_type']);
-    if ($sOSMType) {
-        $aFilteredPlaces['osm_type'] = $sOSMType;
-        $aFilteredPlaces['osm_id'] = $aPlace['osm_id'];
-    }
-    if (isset($aPlace['lat'])) {
-        $aFilteredPlaces['lat'] = $aPlace['lat'];
-    }
-    if (isset($aPlace['lon'])) {
-        $aFilteredPlaces['lon'] = $aPlace['lon'];
-    }
-
-    if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
-        $aFilteredPlaces['place_rank'] = $aPlace['rank_search'];
-
-        $aFilteredPlaces['category'] = $aPlace['class'];
-        $aFilteredPlaces['type'] = $aPlace['type'];
-
-        $aFilteredPlaces['importance'] = $aPlace['importance'];
-
-        $aFilteredPlaces['addresstype'] = strtolower($aPlace['addresstype']);
-
-        $aFilteredPlaces['name'] = $aPlace['placename'];
-    }
-
-    $aFilteredPlaces['display_name'] = $aPlace['langaddress'];
-
-    if (isset($aPlace['address'])) {
-        $aFilteredPlaces['address'] = $aPlace['address']->getAddressNames();
-    }
-    if (isset($aPlace['sExtraTags'])) {
-        $aFilteredPlaces['extratags'] = $aPlace['sExtraTags'];
-    }
-    if (isset($aPlace['sNameDetails'])) {
-        $aFilteredPlaces['namedetails'] = $aPlace['sNameDetails'];
-    }
-
-    if (isset($aPlace['aBoundingBox'])) {
-        $aFilteredPlaces['boundingbox'] = $aPlace['aBoundingBox'];
-    }
-
-    if (isset($aPlace['asgeojson'])) {
-        $aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson'], true);
-    }
-
-    if (isset($aPlace['assvg'])) {
-        $aFilteredPlaces['svg'] = $aPlace['assvg'];
-    }
-
-    if (isset($aPlace['astext'])) {
-        $aFilteredPlaces['geotext'] = $aPlace['astext'];
-    }
-
-    if (isset($aPlace['askml'])) {
-        $aFilteredPlaces['geokml'] = $aPlace['askml'];
-    }
-}
-
-javascript_renderData($aFilteredPlaces);
diff --git a/lib-php/template/address-xml.php b/lib-php/template/address-xml.php
deleted file mode 100644 (file)
index c418a4c..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\n";
-
-echo '<reversegeocode';
-echo " timestamp='".date(DATE_RFC822)."'";
-echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
-echo " querystring='".htmlspecialchars($_SERVER['QUERY_STRING'], ENT_QUOTES)."'";
-echo ">\n";
-
-if (empty($aPlace)) {
-    if (isset($sError)) {
-        echo "<error>$sError</error>";
-    } else {
-        echo '<error>Unable to geocode</error>';
-    }
-} else {
-    echo '<result';
-    if ($aPlace['place_id']) {
-        echo ' place_id="'.$aPlace['place_id'].'"';
-    }
-    $sOSMType = formatOSMType($aPlace['osm_type']);
-    if ($sOSMType) {
-        echo ' osm_type="'.$sOSMType.'"'.' osm_id="'.$aPlace['osm_id'].'"';
-    }
-    if ($aPlace['ref']) {
-        echo ' ref="'.htmlspecialchars($aPlace['ref']).'"';
-    }
-    if (isset($aPlace['lat'])) {
-        echo ' lat="'.htmlspecialchars($aPlace['lat']).'"';
-    }
-    if (isset($aPlace['lon'])) {
-        echo ' lon="'.htmlspecialchars($aPlace['lon']).'"';
-    }
-    if (isset($aPlace['aBoundingBox'])) {
-        echo ' boundingbox="';
-        echo join(',', $aPlace['aBoundingBox']);
-        echo '"';
-    }
-    echo " place_rank='".$aPlace['rank_search']."'";
-    echo " address_rank='".$aPlace['rank_address']."'";
-
-
-    if (isset($aPlace['asgeojson'])) {
-        echo ' geojson=\'';
-        echo $aPlace['asgeojson'];
-        echo '\'';
-    }
-
-    if (isset($aPlace['assvg'])) {
-        echo ' geosvg=\'';
-        echo $aPlace['assvg'];
-        echo '\'';
-    }
-
-    if (isset($aPlace['astext'])) {
-        echo ' geotext=\'';
-        echo $aPlace['astext'];
-        echo '\'';
-    }
-    echo '>'.htmlspecialchars($aPlace['langaddress']).'</result>';
-
-    if (isset($aPlace['address'])) {
-        echo '<addressparts>';
-        foreach ($aPlace['address']->getAddressNames() as $sKey => $sValue) {
-            $sKey = str_replace(' ', '_', $sKey);
-            echo "<$sKey>";
-            echo htmlspecialchars($sValue);
-            echo "</$sKey>";
-        }
-        echo '</addressparts>';
-    }
-
-    if (isset($aPlace['sExtraTags'])) {
-        echo '<extratags>';
-        foreach ($aPlace['sExtraTags'] as $sKey => $sValue) {
-            echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
-        }
-        echo '</extratags>';
-    }
-
-    if (isset($aPlace['sNameDetails'])) {
-        echo '<namedetails>';
-        foreach ($aPlace['sNameDetails'] as $sKey => $sValue) {
-            echo '<name desc="'.htmlspecialchars($sKey).'">';
-            echo htmlspecialchars($sValue);
-            echo '</name>';
-        }
-        echo '</namedetails>';
-    }
-
-    if (isset($aPlace['askml'])) {
-        echo "\n<geokml>";
-        echo $aPlace['askml'];
-        echo '</geokml>';
-    }
-}
-
-echo '</reversegeocode>';
diff --git a/lib-php/template/details-json.php b/lib-php/template/details-json.php
deleted file mode 100644 (file)
index ae80a85..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aPlaceDetails = array();
-
-$aPlaceDetails['place_id'] = (int) $aPointDetails['place_id'];
-$aPlaceDetails['parent_place_id'] = (int) $aPointDetails['parent_place_id'];
-
-$aPlaceDetails['osm_type'] = $aPointDetails['osm_type'];
-$aPlaceDetails['osm_id'] = (int) $aPointDetails['osm_id'];
-
-$aPlaceDetails['category'] = $aPointDetails['class'];
-$aPlaceDetails['type'] = $aPointDetails['type'];
-$aPlaceDetails['admin_level'] = $aPointDetails['admin_level'];
-
-$aPlaceDetails['localname'] = $aPointDetails['localname'];
-$aPlaceDetails['names'] = $aPointDetails['aNames'];
-
-$aPlaceDetails['addresstags'] = $aPointDetails['aAddressTags'];
-$aPlaceDetails['housenumber'] = $aPointDetails['housenumber'];
-$aPlaceDetails['calculated_postcode'] = $aPointDetails['postcode'];
-$aPlaceDetails['country_code'] = $aPointDetails['country_code'];
-
-$aPlaceDetails['indexed_date'] = (new DateTime('@'.$aPointDetails['indexed_epoch']))->format(DateTime::RFC3339);
-$aPlaceDetails['importance'] = (float) $aPointDetails['importance'];
-$aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_importance'];
-
-$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags'];
-$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia'];
-$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails);
-if (isset($sIcon)) {
-    $aPlaceDetails['icon'] = $sIcon;
-}
-
-$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
-$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search'];
-
-$aPlaceDetails['isarea'] = $aPointDetails['isarea'];
-$aPlaceDetails['centroid'] = array(
-                              'type' => 'Point',
-                              'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )
-                             );
-
-$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson'], true);
-
-$funcMapAddressLine = function ($aFull) {
-    return array(
-            'localname' => $aFull['localname'],
-            'place_id' => isset($aFull['place_id']) ? (int) $aFull['place_id'] : null,
-            'osm_id' => isset($aFull['osm_id']) ? (int) $aFull['osm_id'] : null,
-            'osm_type' => isset($aFull['osm_type']) ? $aFull['osm_type'] : null,
-            'place_type' => isset($aFull['place_type']) ? $aFull['place_type'] : null,
-            'class' => $aFull['class'],
-            'type' => $aFull['type'],
-            'admin_level' => isset($aFull['admin_level']) ? (int) $aFull['admin_level'] : null,
-            'rank_address' => $aFull['rank_address'] ? (int) $aFull['rank_address'] : null,
-            'distance' => (float) $aFull['distance'],
-            'isaddress' => isset($aFull['isaddress']) ? (bool) $aFull['isaddress'] : null
-           );
-};
-
-$funcMapKeyword = function ($aFull) {
-    return array(
-            'id' => (int) $aFull['word_id'],
-            'token' => $aFull['word_token']
-           );
-};
-
-if ($aAddressLines) {
-    $aPlaceDetails['address'] = array_map($funcMapAddressLine, $aAddressLines);
-}
-
-if ($aLinkedLines) {
-    $aPlaceDetails['linked_places'] = array_map($funcMapAddressLine, $aLinkedLines);
-}
-
-if ($bIncludeKeywords) {
-    $aPlaceDetails['keywords'] = array();
-
-    if ($aPlaceSearchNameKeywords) {
-        $aPlaceDetails['keywords']['name'] = array_map($funcMapKeyword, $aPlaceSearchNameKeywords);
-    } else {
-        $aPlaceDetails['keywords']['name'] = array();
-    }
-
-    if ($aPlaceSearchAddressKeywords) {
-        $aPlaceDetails['keywords']['address'] = array_map($funcMapKeyword, $aPlaceSearchAddressKeywords);
-    } else {
-        $aPlaceDetails['keywords']['address'] = array();
-    }
-}
-
-if ($bIncludeHierarchy) {
-    if ($bGroupHierarchy) {
-        $aPlaceDetails['hierarchy'] = array();
-        foreach ($aHierarchyLines as $aAddressLine) {
-            if ($aAddressLine['type'] == 'yes') {
-                $sType = $aAddressLine['class'];
-            } else {
-                $sType = $aAddressLine['type'];
-            }
-
-            if (!isset($aPlaceDetails['hierarchy'][$sType])) {
-                $aPlaceDetails['hierarchy'][$sType] = array();
-            }
-            $aPlaceDetails['hierarchy'][$sType][] = $funcMapAddressLine($aAddressLine);
-        }
-    } else {
-        $aPlaceDetails['hierarchy'] = array_map($funcMapAddressLine, $aHierarchyLines);
-    }
-}
-
-javascript_renderData($aPlaceDetails);
diff --git a/lib-php/template/error-json.php b/lib-php/template/error-json.php
deleted file mode 100644 (file)
index fea7d5c..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-    $error = array(
-              'code' => $exception->getCode(),
-              'message' => $exception->getMessage()
-             );
-
-    if (CONST_Debug) {
-        $error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
-    }
-
-    javascript_renderData(array('error' => $error));
diff --git a/lib-php/template/error-xml.php b/lib-php/template/error-xml.php
deleted file mode 100644 (file)
index a21ac19..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<error>
-    <code><?php echo $exception->getCode() ?></code>
-    <message><?php echo $exception->getMessage() ?></message>
-    <?php if (CONST_Debug) { ?>
-    <details><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></details>
-    <?php } ?>
-</error>
\ No newline at end of file
diff --git a/lib-php/template/search-batch-json.php b/lib-php/template/search-batch-json.php
deleted file mode 100644 (file)
index 430237a..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aOutput = array();
-$aOutput['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
-$aOutput['batch'] = array();
-
-foreach ($aBatchResults as $aSearchResults) {
-    if (!$aSearchResults) {
-        $aSearchResults = array();
-    }
-    $aFilteredPlaces = array();
-    foreach ($aSearchResults as $iResNum => $aPointDetails) {
-        $aPlace = array(
-                   'place_id'=>$aPointDetails['place_id'],
-                  );
-
-        $sOSMType = formatOSMType($aPointDetails['osm_type']);
-        if ($sOSMType) {
-            $aPlace['osm_type'] = $sOSMType;
-            $aPlace['osm_id'] = $aPointDetails['osm_id'];
-        }
-
-        if (isset($aPointDetails['aBoundingBox'])) {
-            $aPlace['boundingbox'] = array(
-                                      $aPointDetails['aBoundingBox'][0],
-                                      $aPointDetails['aBoundingBox'][1],
-                                      $aPointDetails['aBoundingBox'][2],
-                $aPointDetails['aBoundingBox'][3]
-                                     );
-        }
-
-        if (isset($aPointDetails['zoom'])) {
-            $aPlace['zoom'] = $aPointDetails['zoom'];
-        }
-
-        $aPlace['lat'] = $aPointDetails['lat'];
-        $aPlace['lon'] = $aPointDetails['lon'];
-        $aPlace['display_name'] = $aPointDetails['name'];
-        $aPlace['place_rank'] = $aPointDetails['rank_search'];
-
-        $aPlace['category'] = $aPointDetails['class'];
-        $aPlace['type'] = $aPointDetails['type'];
-
-        $aPlace['importance'] = $aPointDetails['importance'];
-
-        if (isset($aPointDetails['icon'])) {
-            $aPlace['icon'] = $aPointDetails['icon'];
-        }
-
-        if (isset($aPointDetails['address'])) {
-            $aPlace['address'] = $aPointDetails['address']->getAddressNames();
-        }
-
-        if (isset($aPointDetails['asgeojson'])) {
-            $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
-        }
-
-        if (isset($aPointDetails['assvg'])) {
-            $aPlace['svg'] = $aPointDetails['assvg'];
-        }
-
-        if (isset($aPointDetails['astext'])) {
-            $aPlace['geotext'] = $aPointDetails['astext'];
-        }
-
-        if (isset($aPointDetails['askml'])) {
-            $aPlace['geokml'] = $aPointDetails['askml'];
-        }
-
-        $aFilteredPlaces[] = $aPlace;
-    }
-    $aOutput['batch'][] = $aFilteredPlaces;
-}
-
-javascript_renderData($aOutput, array('geojson'));
diff --git a/lib-php/template/search-geocodejson.php b/lib-php/template/search-geocodejson.php
deleted file mode 100644 (file)
index bba41a0..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
-    $aPlace = array(
-               'type' => 'Feature',
-               'properties' => array(
-                                'geocoding' => array()
-                               )
-              );
-
-    if (isset($aPointDetails['place_id'])) {
-        $aPlace['properties']['geocoding']['place_id'] = $aPointDetails['place_id'];
-    }
-    $sOSMType = formatOSMType($aPointDetails['osm_type']);
-    if ($sOSMType) {
-        $aPlace['properties']['geocoding']['osm_type'] = $sOSMType;
-        $aPlace['properties']['geocoding']['osm_id'] = $aPointDetails['osm_id'];
-    }
-    $aPlace['properties']['geocoding']['osm_key'] = $aPointDetails['class'];
-    $aPlace['properties']['geocoding']['osm_value'] = $aPointDetails['type'];
-
-    $aPlace['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPointDetails['rank_address']);
-
-    $aPlace['properties']['geocoding']['label'] = $aPointDetails['langaddress'];
-
-    if ($aPointDetails['placename'] !== null) {
-        $aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
-    }
-
-    if (isset($aPointDetails['address'])) {
-        $aPointDetails['address']->addGeocodeJsonAddressParts(
-            $aPlace['properties']['geocoding']
-        );
-
-        $aPlace['properties']['geocoding']['admin']
-            = $aPointDetails['address']->getAdminLevels();
-    }
-
-    if (isset($aPointDetails['asgeojson'])) {
-        $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
-    } else {
-        $aPlace['geometry'] = array(
-                               'type' => 'Point',
-                               'coordinates' => array(
-                                                 (float) $aPointDetails['lon'],
-                                                 (float) $aPointDetails['lat']
-                                                )
-                              );
-    }
-    $aFilteredPlaces[] = $aPlace;
-}
-
-
-javascript_renderData(array(
-                       'type' => 'FeatureCollection',
-                       'geocoding' => array(
-                                       'version' => '0.1.0',
-                                       'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
-                                       'licence' => 'ODbL',
-                                       'query' => $sQuery
-                                      ),
-                       'features' => $aFilteredPlaces
-                      ));
diff --git a/lib-php/template/search-geojson.php b/lib-php/template/search-geojson.php
deleted file mode 100644 (file)
index 7665700..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
-    $aPlace = array(
-               'type' => 'Feature',
-               'properties' => array(
-                                'place_id'=>$aPointDetails['place_id'],
-                               )
-              );
-
-    $sOSMType = formatOSMType($aPointDetails['osm_type']);
-    if ($sOSMType) {
-        $aPlace['properties']['osm_type'] = $sOSMType;
-        $aPlace['properties']['osm_id'] = $aPointDetails['osm_id'];
-    }
-
-    if (isset($aPointDetails['aBoundingBox'])) {
-        $aPlace['bbox'] = array(
-                           (float) $aPointDetails['aBoundingBox'][2], // minlon
-                           (float) $aPointDetails['aBoundingBox'][0], // minlat
-                           (float) $aPointDetails['aBoundingBox'][3], // maxlon
-                           (float) $aPointDetails['aBoundingBox'][1]  // maxlat
-                          );
-    }
-
-    if (isset($aPointDetails['zoom'])) {
-        $aPlace['properties']['zoom'] = $aPointDetails['zoom'];
-    }
-
-    $aPlace['properties']['display_name'] = $aPointDetails['name'];
-
-    $aPlace['properties']['place_rank'] = $aPointDetails['rank_search'];
-    $aPlace['properties']['category'] = $aPointDetails['class'];
-
-    $aPlace['properties']['type'] = $aPointDetails['type'];
-
-    $aPlace['properties']['importance'] = $aPointDetails['importance'];
-
-    if (isset($aPointDetails['icon']) && $aPointDetails['icon']) {
-        $aPlace['properties']['icon'] = $aPointDetails['icon'];
-    }
-
-    if (isset($aPointDetails['address'])) {
-        $aPlace['properties']['address'] = $aPointDetails['address']->getAddressNames();
-    }
-
-    if (isset($aPointDetails['asgeojson'])) {
-        $aPlace['geometry'] = json_decode($aPointDetails['asgeojson'], true);
-    } else {
-        $aPlace['geometry'] = array(
-                               'type' => 'Point',
-                               'coordinates' => array(
-                                                 (float) $aPointDetails['lon'],
-                                                 (float) $aPointDetails['lat']
-                                                )
-                              );
-    }
-
-
-    if (isset($aPointDetails['sExtraTags'])) {
-        $aPlace['properties']['extratags'] = $aPointDetails['sExtraTags'];
-    }
-    if (isset($aPointDetails['sNameDetails'])) {
-        $aPlace['properties']['namedetails'] = $aPointDetails['sNameDetails'];
-    }
-
-    $aFilteredPlaces[] = $aPlace;
-}
-
-javascript_renderData(array(
-                       'type' => 'FeatureCollection',
-                       'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
-                       'features' => $aFilteredPlaces
-                      ));
diff --git a/lib-php/template/search-json.php b/lib-php/template/search-json.php
deleted file mode 100644 (file)
index 5fb1302..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-$aFilteredPlaces = array();
-foreach ($aSearchResults as $iResNum => $aPointDetails) {
-    $aPlace = array(
-               'place_id'=>$aPointDetails['place_id'],
-               'licence'=>'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
-              );
-
-    $sOSMType = formatOSMType($aPointDetails['osm_type']);
-    if ($sOSMType) {
-        $aPlace['osm_type'] = $sOSMType;
-        $aPlace['osm_id'] = $aPointDetails['osm_id'];
-    }
-
-    if (isset($aPointDetails['aBoundingBox'])) {
-        $aPlace['boundingbox'] = $aPointDetails['aBoundingBox'];
-    }
-
-    if (isset($aPointDetails['zoom'])) {
-        $aPlace['zoom'] = $aPointDetails['zoom'];
-    }
-
-    $aPlace['lat'] = $aPointDetails['lat'];
-    $aPlace['lon'] = $aPointDetails['lon'];
-
-    $aPlace['display_name'] = $aPointDetails['name'];
-
-    if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
-        $aPlace['place_rank'] = $aPointDetails['rank_search'];
-        $aPlace['category'] = $aPointDetails['class'];
-    } else {
-        $aPlace['class'] = $aPointDetails['class'];
-    }
-    $aPlace['type'] = $aPointDetails['type'];
-
-    $aPlace['importance'] = $aPointDetails['importance'];
-
-    if (isset($aPointDetails['icon']) && $aPointDetails['icon']) {
-        $aPlace['icon'] = $aPointDetails['icon'];
-    }
-
-    if (isset($aPointDetails['address'])) {
-        $aPlace['address'] = $aPointDetails['address']->getAddressNames();
-    }
-
-    if (isset($aPointDetails['asgeojson'])) {
-        $aPlace['geojson'] = json_decode($aPointDetails['asgeojson'], true);
-    }
-
-    if (isset($aPointDetails['assvg'])) {
-        $aPlace['svg'] = $aPointDetails['assvg'];
-    }
-
-    if (isset($aPointDetails['astext'])) {
-        $aPlace['geotext'] = $aPointDetails['astext'];
-    }
-
-    if (isset($aPointDetails['askml'])) {
-        $aPlace['geokml'] = $aPointDetails['askml'];
-    }
-
-    if (isset($aPointDetails['sExtraTags'])) {
-        $aPlace['extratags'] = $aPointDetails['sExtraTags'];
-    }
-    if (isset($aPointDetails['sNameDetails'])) {
-        $aPlace['namedetails'] = $aPointDetails['sNameDetails'];
-    }
-
-    $aFilteredPlaces[] = $aPlace;
-}
-
-javascript_renderData($aFilteredPlaces);
diff --git a/lib-php/template/search-xml.php b/lib-php/template/search-xml.php
deleted file mode 100644 (file)
index 8dda65e..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-header('content-type: text/xml; charset=UTF-8');
-
-echo '<';
-echo '?xml version="1.0" encoding="UTF-8" ?';
-echo ">\n";
-
-echo '<';
-echo (isset($sXmlRootTag)?$sXmlRootTag:'searchresults');
-echo " timestamp='".date(DATE_RFC822)."'";
-echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
-echo " querystring='".htmlspecialchars($sQuery, ENT_QUOTES)."'";
-if (isset($aMoreParams['viewbox'])) {
-    echo " viewbox='".htmlspecialchars($aMoreParams['viewbox'], ENT_QUOTES)."'";
-}
-if (isset($aMoreParams['exclude_place_ids'])) {
-    echo " exclude_place_ids='".htmlspecialchars($aMoreParams['exclude_place_ids'])."'";
-}
-echo " more_url='".htmlspecialchars($sMoreURL)."'";
-echo ">\n";
-
-foreach ($aSearchResults as $iResNum => $aResult) {
-    echo "<place place_id='".$aResult['place_id']."'";
-    $sOSMType = formatOSMType($aResult['osm_type']);
-    if ($sOSMType) {
-        echo " osm_type='$sOSMType'";
-        echo " osm_id='".$aResult['osm_id']."'";
-    }
-    echo " place_rank='".$aResult['rank_search']."'";
-    echo " address_rank='".$aResult['rank_address']."'";
-
-    if (isset($aResult['aBoundingBox'])) {
-        echo ' boundingbox="';
-        echo join(',', $aResult['aBoundingBox']);
-        echo '"';
-    }
-
-    if (isset($aResult['asgeojson'])) {
-        echo ' geojson=\'';
-        echo $aResult['asgeojson'];
-        echo '\'';
-    }
-
-    if (isset($aResult['assvg'])) {
-        echo ' geosvg=\'';
-        echo $aResult['assvg'];
-        echo '\'';
-    }
-
-    if (isset($aResult['astext'])) {
-        echo ' geotext=\'';
-        echo $aResult['astext'];
-        echo '\'';
-    }
-
-    if (isset($aResult['zoom'])) {
-        echo " zoom='".$aResult['zoom']."'";
-    }
-
-    echo " lat='".$aResult['lat']."'";
-    echo " lon='".$aResult['lon']."'";
-    echo " display_name='".htmlspecialchars($aResult['name'], ENT_QUOTES)."'";
-
-    echo " class='".htmlspecialchars($aResult['class'])."'";
-    echo " type='".htmlspecialchars($aResult['type'], ENT_QUOTES)."'";
-    echo " importance='".htmlspecialchars($aResult['importance'])."'";
-    if (isset($aResult['icon']) && $aResult['icon']) {
-        echo " icon='".htmlspecialchars($aResult['icon'], ENT_QUOTES)."'";
-    }
-
-    $bHasDelim = false;
-
-    if (isset($aResult['askml'])) {
-        if (!$bHasDelim) {
-            $bHasDelim = true;
-            echo '>';
-        }
-        echo "\n<geokml>";
-        echo $aResult['askml'];
-        echo '</geokml>';
-    }
-
-    if (isset($aResult['sExtraTags'])) {
-        if (!$bHasDelim) {
-            $bHasDelim = true;
-            echo '>';
-        }
-        echo "\n<extratags>";
-        foreach ($aResult['sExtraTags'] as $sKey => $sValue) {
-            echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
-        }
-        echo '</extratags>';
-    }
-
-    if (isset($aResult['sNameDetails'])) {
-        if (!$bHasDelim) {
-            $bHasDelim = true;
-            echo '>';
-        }
-        echo "\n<namedetails>";
-        foreach ($aResult['sNameDetails'] as $sKey => $sValue) {
-            echo '<name desc="'.htmlspecialchars($sKey).'">';
-            echo htmlspecialchars($sValue);
-            echo '</name>';
-        }
-        echo '</namedetails>';
-    }
-
-    if (isset($aResult['address'])) {
-        if (!$bHasDelim) {
-            $bHasDelim = true;
-            echo '>';
-        }
-        echo "\n";
-        foreach ($aResult['address']->getAddressNames() as $sKey => $sValue) {
-            $sKey = str_replace(' ', '_', $sKey);
-            echo "<$sKey>";
-            echo htmlspecialchars($sValue);
-            echo "</$sKey>";
-        }
-    }
-
-    if ($bHasDelim) {
-        echo '</place>';
-    } else {
-        echo '/>';
-    }
-}
-
-echo '</' . (isset($sXmlRootTag)?$sXmlRootTag:'searchresults') . '>';
diff --git a/lib-php/tokenizer/icu_tokenizer.php b/lib-php/tokenizer/icu_tokenizer.php
deleted file mode 100644 (file)
index e45d076..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
-    private $oDB;
-
-    private $oNormalizer;
-    private $oTransliterator;
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-        $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
-        $this->oTransliterator = \Transliterator::createFromRules(CONST_Transliteration);
-    }
-
-    public function checkStatus()
-    {
-        $sSQL = 'SELECT word_id FROM word WHERE word_id is not null limit 1';
-        $iWordID = $this->oDB->getOne($sSQL);
-        if ($iWordID === false) {
-            throw new \Exception('Query failed', 703);
-        }
-        if (!$iWordID) {
-            throw new \Exception('No value', 704);
-        }
-    }
-
-
-    public function normalizeString($sTerm)
-    {
-        if ($this->oNormalizer === null) {
-            return $sTerm;
-        }
-
-        return $this->oNormalizer->transliterate($sTerm);
-    }
-
-
-    public function mostFrequentWords($iNum)
-    {
-        $sSQL = "SELECT word FROM word WHERE type = 'W'";
-        $sSQL .= "ORDER BY info->'count' DESC LIMIT ".$iNum;
-        return $this->oDB->getCol($sSQL);
-    }
-
-
-    private function makeStandardWord($sTerm)
-    {
-        return trim($this->oTransliterator->transliterate(' '.$sTerm.' '));
-    }
-
-
-    public function tokensForSpecialTerm($sTerm)
-    {
-        $aResults = array();
-
-        $sSQL = "SELECT word_id, info->>'class' as class, info->>'type' as type ";
-        $sSQL .= '   FROM word WHERE word_token = :term and type = \'S\'';
-
-        Debug::printVar('Term', $sTerm);
-        Debug::printSQL($sSQL);
-        $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $this->makeStandardWord($sTerm)));
-
-        Debug::printVar('Results', $aSearchWords);
-
-        foreach ($aSearchWords as $aSearchTerm) {
-            $aResults[] = new \Nominatim\Token\SpecialTerm(
-                $aSearchTerm['word_id'],
-                $aSearchTerm['class'],
-                $aSearchTerm['type'],
-                \Nominatim\Operator::TYPE
-            );
-        }
-
-        Debug::printVar('Special term tokens', $aResults);
-
-        return $aResults;
-    }
-
-
-    public function extractTokensFromPhrases(&$aPhrases)
-    {
-        $sNormQuery = '';
-        $aWordLists = array();
-        $aTokens = array();
-        foreach ($aPhrases as $iPhrase => $oPhrase) {
-            $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
-            $sPhrase = $this->makeStandardWord($oPhrase->getPhrase());
-            Debug::printVar('Phrase', $sPhrase);
-
-            $oWordList = new SimpleWordList($sPhrase);
-            $aTokens = array_merge($aTokens, $oWordList->getTokens());
-            $aWordLists[] = $oWordList;
-        }
-
-        Debug::printVar('Tokens', $aTokens);
-        Debug::printVar('WordLists', $aWordLists);
-
-        $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
-
-        foreach ($aPhrases as $iPhrase => $oPhrase) {
-            $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
-        }
-
-        return $oValidTokens;
-    }
-
-
-    private function computeValidTokens($aTokens, $sNormQuery)
-    {
-        $oValidTokens = new TokenList();
-
-        if (!empty($aTokens)) {
-            $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
-
-            // Try more interpretations for Tokens that could not be matched.
-            foreach ($aTokens as $sToken) {
-                if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
-                    if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
-                        // US ZIP+4 codes - merge in the 5-digit ZIP code
-                        $oValidTokens->addToken(
-                            $sToken,
-                            new Token\Postcode(null, $aData[1], 'us')
-                        );
-                    } elseif (preg_match('/^[0-9]+$/', $sToken)) {
-                        // Unknown single word token with a number.
-                        // Assume it is a house number.
-                        $oValidTokens->addToken(
-                            $sToken,
-                            new Token\HouseNumber(null, trim($sToken))
-                        );
-                    }
-                }
-            }
-        }
-
-        return $oValidTokens;
-    }
-
-
-    private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
-    {
-        // Check which tokens we have, get the ID numbers
-        $sSQL = 'SELECT word_id, word_token, type, word,';
-        $sSQL .= "      info->>'op' as operator,";
-        $sSQL .= "      info->>'class' as class, info->>'type' as ctype,";
-        $sSQL .= "      info->>'count' as count,";
-        $sSQL .= "      info->>'lookup' as lookup";
-        $sSQL .= ' FROM word WHERE word_token in (';
-        $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
-
-        Debug::printSQL($sSQL);
-
-        $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
-
-        foreach ($aDBWords as $aWord) {
-            $iId = (int) $aWord['word_id'];
-            $sTok = $aWord['word_token'];
-
-            switch ($aWord['type']) {
-                case 'C':  // country name tokens
-                    if ($aWord['word'] !== null) {
-                        $oValidTokens->addToken(
-                            $sTok,
-                            new Token\Country($iId, $aWord['word'])
-                        );
-                    }
-                    break;
-                case 'H':  // house number tokens
-                    $sLookup = $aWord['lookup'] ?? $aWord['word_token'];
-                    $oValidTokens->addToken($sTok, new Token\HouseNumber($iId, $sLookup));
-                    break;
-                case 'P':  // postcode tokens
-                    // Postcodes are not normalized, so they may have content
-                    // that makes SQL injection possible. Reject postcodes
-                    // that would need special escaping.
-                    if ($aWord['word'] !== null
-                        && pg_escape_string($aWord['word']) == $aWord['word']
-                    ) {
-                        $iSplitPos = strpos($aWord['word'], '@');
-                        if ($iSplitPos === false) {
-                            $sPostcode = $aWord['word'];
-                        } else {
-                            $sPostcode = substr($aWord['word'], 0, $iSplitPos);
-                        }
-
-                        $oValidTokens->addToken(
-                            $sTok,
-                            new Token\Postcode($iId, $sPostcode, null)
-                        );
-                    }
-                    break;
-                case 'S':  // tokens for classification terms (special phrases)
-                    if ($aWord['class'] !== null && $aWord['ctype'] !== null) {
-                        $oValidTokens->addToken($sTok, new Token\SpecialTerm(
-                            $iId,
-                            $aWord['class'],
-                            $aWord['ctype'],
-                            (isset($aWord['operator'])) ? Operator::NEAR : Operator::NONE
-                        ));
-                    }
-                    break;
-                case 'W': // full-word tokens
-                    $oValidTokens->addToken($sTok, new Token\Word(
-                        $iId,
-                        (int) $aWord['count'],
-                        substr_count($aWord['word_token'], ' ')
-                    ));
-                    break;
-                case 'w':  // partial word terms
-                    $oValidTokens->addToken($sTok, new Token\Partial(
-                        $iId,
-                        $aWord['word_token'],
-                        (int) $aWord['count']
-                    ));
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-}
diff --git a/lib-php/tokenizer/legacy_tokenizer.php b/lib-php/tokenizer/legacy_tokenizer.php
deleted file mode 100644 (file)
index 6f3d230..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class Tokenizer
-{
-    private $oDB;
-
-    private $oNormalizer = null;
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-        $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
-    }
-
-    public function checkStatus()
-    {
-        $sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
-        if ($sStandardWord === false) {
-            throw new \Exception('Module failed', 701);
-        }
-
-        if ($sStandardWord != 'a') {
-            throw new \Exception('Module call failed', 702);
-        }
-
-        $sSQL = "SELECT word_id FROM word WHERE word_token IN (' a')";
-        $iWordID = $this->oDB->getOne($sSQL);
-        if ($iWordID === false) {
-            throw new \Exception('Query failed', 703);
-        }
-        if (!$iWordID) {
-            throw new \Exception('No value', 704);
-        }
-    }
-
-
-    public function normalizeString($sTerm)
-    {
-        if ($this->oNormalizer === null) {
-            return $sTerm;
-        }
-
-        return $this->oNormalizer->transliterate($sTerm);
-    }
-
-
-    public function mostFrequentWords($iNum)
-    {
-        $sSQL = 'SELECT word FROM word WHERE word is not null ';
-        $sSQL .= 'ORDER BY search_name_count DESC LIMIT '.$iNum;
-        return $this->oDB->getCol($sSQL);
-    }
-
-
-    public function tokensForSpecialTerm($sTerm)
-    {
-        $aResults = array();
-
-        $sSQL = 'SELECT word_id, class, type FROM word ';
-        $sSQL .= '   WHERE word_token = \' \' || make_standard_name(:term)';
-        $sSQL .= '   AND class is not null AND class not in (\'place\')';
-
-        Debug::printVar('Term', $sTerm);
-        Debug::printSQL($sSQL);
-        $aSearchWords = $this->oDB->getAll($sSQL, array(':term' => $sTerm));
-
-        Debug::printVar('Results', $aSearchWords);
-
-        foreach ($aSearchWords as $aSearchTerm) {
-            $aResults[] = new \Nominatim\Token\SpecialTerm(
-                $aSearchTerm['word_id'],
-                $aSearchTerm['class'],
-                $aSearchTerm['type'],
-                \Nominatim\Operator::TYPE
-            );
-        }
-
-        Debug::printVar('Special term tokens', $aResults);
-
-        return $aResults;
-    }
-
-
-    public function extractTokensFromPhrases(&$aPhrases)
-    {
-        // First get the normalized version of all phrases
-        $sNormQuery = '';
-        $sSQL = 'SELECT ';
-        $aParams = array();
-        foreach ($aPhrases as $iPhrase => $oPhrase) {
-            $sNormQuery .= ','.$this->normalizeString($oPhrase->getPhrase());
-            $sSQL .= 'make_standard_name(:' .$iPhrase.') as p'.$iPhrase.',';
-            $aParams[':'.$iPhrase] = $oPhrase->getPhrase();
-
-            // Conflicts between US state abbreviations and various words
-            // for 'the' in different languages
-            switch (strtolower($oPhrase->getPhrase())) {
-                case 'il':
-                    $aParams[':'.$iPhrase] = 'illinois';
-                    break;
-                case 'al':
-                    $aParams[':'.$iPhrase] = 'alabama';
-                    break;
-                case 'la':
-                    $aParams[':'.$iPhrase] = 'louisiana';
-                    break;
-                default:
-                    $aParams[':'.$iPhrase] = $oPhrase->getPhrase();
-                    break;
-            }
-        }
-        $sSQL = substr($sSQL, 0, -1);
-
-        Debug::printSQL($sSQL);
-        Debug::printVar('SQL parameters', $aParams);
-
-        $aNormPhrases = $this->oDB->getRow($sSQL, $aParams);
-
-        Debug::printVar('SQL result', $aNormPhrases);
-
-        // now compute all possible tokens
-        $aWordLists = array();
-        $aTokens = array();
-        foreach ($aNormPhrases as $sPhrase) {
-            $oWordList = new SimpleWordList($sPhrase);
-
-            foreach ($oWordList->getTokens() as $sToken) {
-                $aTokens[' '.$sToken] = ' '.$sToken;
-                $aTokens[$sToken] = $sToken;
-            }
-
-            $aWordLists[] = $oWordList;
-        }
-
-        Debug::printVar('Tokens', $aTokens);
-        Debug::printVar('WordLists', $aWordLists);
-
-        $oValidTokens = $this->computeValidTokens($aTokens, $sNormQuery);
-
-        foreach ($aPhrases as $iPhrase => $oPhrase) {
-            $oPhrase->setWordSets($aWordLists[$iPhrase]->getWordSets($oValidTokens));
-        }
-
-        return $oValidTokens;
-    }
-
-
-    private function computeValidTokens($aTokens, $sNormQuery)
-    {
-        $oValidTokens = new TokenList();
-
-        if (!empty($aTokens)) {
-            $this->addTokensFromDB($oValidTokens, $aTokens, $sNormQuery);
-
-            // Try more interpretations for Tokens that could not be matched.
-            foreach ($aTokens as $sToken) {
-                if ($sToken[0] != ' ' && !$oValidTokens->contains($sToken)) {
-                    if (preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) {
-                        // US ZIP+4 codes - merge in the 5-digit ZIP code
-                        $oValidTokens->addToken(
-                            $sToken,
-                            new Token\Postcode(null, $aData[1], 'us')
-                        );
-                    } elseif (preg_match('/^[0-9]+$/', $sToken)) {
-                        // Unknown single word token with a number.
-                        // Assume it is a house number.
-                        $oValidTokens->addToken(
-                            $sToken,
-                            new Token\HouseNumber(null, trim($sToken))
-                        );
-                    }
-                }
-            }
-        }
-
-        return $oValidTokens;
-    }
-
-
-    private function addTokensFromDB(&$oValidTokens, $aTokens, $sNormQuery)
-    {
-        // Check which tokens we have, get the ID numbers
-        $sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
-        $sSQL .= ' operator, coalesce(search_name_count, 0) as count';
-        $sSQL .= ' FROM word WHERE word_token in (';
-        $sSQL .= join(',', $this->oDB->getDBQuotedList($aTokens)).')';
-
-        Debug::printSQL($sSQL);
-
-        $aDBWords = $this->oDB->getAll($sSQL, null, 'Could not get word tokens.');
-
-        foreach ($aDBWords as $aWord) {
-            $oToken = null;
-            $iId = (int) $aWord['word_id'];
-
-            if ($aWord['class']) {
-                // Special terms need to appear in their normalized form.
-                // (postcodes are not normalized in the word table)
-                $sNormWord = $this->normalizeString($aWord['word']);
-                if ($aWord['word'] && strpos($sNormQuery, $sNormWord) === false) {
-                    continue;
-                }
-
-                if ($aWord['class'] == 'place' && $aWord['type'] == 'house') {
-                    $oToken = new Token\HouseNumber($iId, trim($aWord['word_token']));
-                } elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') {
-                    if ($aWord['word']
-                        && pg_escape_string($aWord['word']) == $aWord['word']
-                    ) {
-                        $oToken = new Token\Postcode(
-                            $iId,
-                            $aWord['word'],
-                            $aWord['country_code']
-                        );
-                    }
-                } else {
-                    // near and in operator the same at the moment
-                    $oToken = new Token\SpecialTerm(
-                        $iId,
-                        $aWord['class'],
-                        $aWord['type'],
-                        $aWord['operator'] ? Operator::NEAR : Operator::NONE
-                    );
-                }
-            } elseif ($aWord['country_code']) {
-                $oToken = new Token\Country($iId, $aWord['country_code']);
-            } elseif ($aWord['word_token'][0] == ' ') {
-                $oToken = new Token\Word(
-                    $iId,
-                    (int) $aWord['count'],
-                    substr_count($aWord['word_token'], ' ')
-                );
-            // For backward compatibility: ignore all partial tokens with more
-            // than one word.
-            } elseif (strpos($aWord['word_token'], ' ') === false) {
-                $oToken = new Token\Partial(
-                    $iId,
-                    $aWord['word_token'],
-                    (int) $aWord['count']
-                );
-            }
-
-            if ($oToken) {
-                // remove any leading spaces
-                if ($aWord['word_token'][0] == ' ') {
-                    $oValidTokens->addToken(substr($aWord['word_token'], 1), $oToken);
-                } else {
-                    $oValidTokens->addToken($aWord['word_token'], $oToken);
-                }
-            }
-        }
-    }
-}
diff --git a/lib-php/website/deletable.php b/lib-php/website/deletable.php
deleted file mode 100644 (file)
index ffb202f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$sSQL = 'select placex.place_id, country_code,';
-$sSQL .= " name->'name' as name, i.* from placex, import_polygon_delete i";
-$sSQL .= ' where placex.osm_id = i.osm_id and placex.osm_type = i.osm_type';
-$sSQL .= ' and placex.class = i.class and placex.type = i.type';
-$aPolygons = $oDB->getAll($sSQL, null, 'Could not get list of deleted OSM elements.');
-
-if (CONST_Debug) {
-    var_dump($aPolygons);
-    exit;
-}
-
-if ($sOutputFormat == 'json') {
-    javascript_renderData($aPolygons);
-}
diff --git a/lib-php/website/details.php b/lib-php/website/details.php
deleted file mode 100644 (file)
index 98fb6ef..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-require_once(CONST_LibDir.'/AddressDetails.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$sPlaceId = $oParams->getString('place_id');
-$sOsmType = $oParams->getSet('osmtype', array('N', 'W', 'R'));
-$iOsmId = $oParams->getInt('osmid', 0);
-$sClass = $oParams->getString('class');
-
-$bIncludeKeywords = $oParams->getBool('keywords', false);
-$bIncludeAddressDetails = $oParams->getBool('addressdetails', false);
-$bIncludeLinkedPlaces = $oParams->getBool('linkedplaces', true);
-$bIncludeHierarchy = $oParams->getBool('hierarchy', false);
-$bGroupHierarchy = $oParams->getBool('group_hierarchy', false);
-$bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson', false);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$sLanguagePrefArraySQL = $oDB->getArraySQL($oDB->getDBQuotedList($aLangPrefOrder));
-
-if ($sOsmType && $iOsmId !== 0) {
-    $sSQL = 'SELECT place_id FROM placex WHERE osm_type = :type AND osm_id = :id';
-    $aSQLParams = array(':type' => $sOsmType, ':id' => $iOsmId);
-    // osm_type and osm_id are not unique enough
-    if ($sClass) {
-        $sSQL .= ' AND class= :class';
-        $aSQLParams[':class'] = $sClass;
-    }
-    $sSQL .= ' ORDER BY class ASC';
-    $sPlaceId = $oDB->getOne($sSQL, $aSQLParams);
-
-
-    // Nothing? Maybe it's an interpolation.
-    // XXX Simply returns the first parent street it finds. It should
-    //     get a house number and get the right interpolation.
-    if (!$sPlaceId && $sOsmType == 'W' && (!$sClass || $sClass == 'place')) {
-        $sSQL = 'SELECT place_id FROM location_property_osmline'
-                .' WHERE osm_id = :id LIMIT 1';
-        $sPlaceId = $oDB->getOne($sSQL, array(':id' => $iOsmId));
-    }
-
-    // Be nice about our error messages for broken geometry
-
-    if (!$sPlaceId && $oDB->tableExists('import_polygon_error')) {
-        $sSQL = 'SELECT ';
-        $sSQL .= '    osm_type, ';
-        $sSQL .= '    osm_id, ';
-        $sSQL .= '    errormessage, ';
-        $sSQL .= '    class, ';
-        $sSQL .= '    type, ';
-        $sSQL .= "    get_name_by_language(name,$sLanguagePrefArraySQL) AS localname,";
-        $sSQL .= '    ST_AsText(prevgeometry) AS prevgeom, ';
-        $sSQL .= '    ST_AsText(newgeometry) AS newgeom';
-        $sSQL .= ' FROM import_polygon_error ';
-        $sSQL .= ' WHERE osm_type = :type';
-        $sSQL .= '   AND osm_id = :id';
-        $sSQL .= ' ORDER BY updated DESC';
-        $sSQL .= ' LIMIT 1';
-        $aPointDetails = $oDB->getRow($sSQL, array(':type' => $sOsmType, ':id' => $iOsmId));
-        if ($aPointDetails) {
-            if (preg_match('/\[(-?\d+\.\d+) (-?\d+\.\d+)\]/', $aPointDetails['errormessage'], $aMatches)) {
-                $aPointDetails['error_x'] = $aMatches[1];
-                $aPointDetails['error_y'] = $aMatches[2];
-            } else {
-                $aPointDetails['error_x'] = 0;
-                $aPointDetails['error_y'] = 0;
-            }
-            include(CONST_LibDir.'/template/details-error-'.$sOutputFormat.'.php');
-            exit;
-        }
-    }
-
-    if ($sPlaceId === false) {
-        throw new \Exception('No place with that OSM ID found.', 404);
-    }
-} else {
-    if ($sPlaceId === false) {
-        userError('Required parameters missing. Need either osmtype/osmid or place_id.');
-    }
-}
-
-$iPlaceID = (int)$sPlaceId;
-
-if (CONST_Use_US_Tiger_Data) {
-    $iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_tiger WHERE place_id = '.$iPlaceID);
-    if ($iParentPlaceID) {
-        $iPlaceID = $iParentPlaceID;
-    }
-}
-
-// interpolated house numbers
-$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_property_osmline WHERE place_id = '.$iPlaceID);
-if ($iParentPlaceID) {
-    $iPlaceID = $iParentPlaceID;
-}
-
-// artificial postcodes
-$iParentPlaceID = $oDB->getOne('SELECT parent_place_id FROM location_postcode WHERE place_id = '.$iPlaceID);
-if ($iParentPlaceID) {
-    $iPlaceID = $iParentPlaceID;
-}
-
-$hLog = logStart($oDB, 'details', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-// Get the details for this point
-$sSQL = 'SELECT place_id, osm_type, osm_id, class, type, name, admin_level,';
-$sSQL .= '    housenumber, postcode, country_code,';
-$sSQL .= '    importance, wikipedia,';
-$sSQL .= '    ROUND(EXTRACT(epoch FROM indexed_date)) AS indexed_epoch,';
-$sSQL .= '    parent_place_id, ';
-$sSQL .= '    rank_address, ';
-$sSQL .= '    rank_search, ';
-$sSQL .= "    get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
-$sSQL .= "    ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea, ";
-$sSQL .= '    ST_y(centroid) AS lat, ';
-$sSQL .= '    ST_x(centroid) AS lon, ';
-$sSQL .= '    CASE ';
-$sSQL .= '       WHEN importance = 0 OR importance IS NULL ';
-$sSQL .= '       THEN 0.75-(rank_search::float/40) ';
-$sSQL .= '       ELSE importance ';
-$sSQL .= '       END as calculated_importance, ';
-if ($bIncludePolygonAsGeoJSON) {
-    $sSQL .= '    ST_AsGeoJSON(CASE ';
-    $sSQL .= '                WHEN ST_NPoints(geometry) > 5000 ';
-    $sSQL .= '                THEN ST_SimplifyPreserveTopology(geometry, 0.0001) ';
-    $sSQL .= '                ELSE geometry ';
-    $sSQL .= '                END) as asgeojson';
-} else {
-    $sSQL .= '    ST_AsGeoJSON(centroid) as asgeojson';
-}
-$sSQL .= ' FROM placex ';
-$sSQL .= " WHERE place_id = $iPlaceID";
-
-$aPointDetails = $oDB->getRow($sSQL, null, 'Could not get details of place object.');
-
-if (!$aPointDetails) {
-    throw new \Exception('No place with that place ID found.', 404);
-}
-
-$aPointDetails['localname'] = $aPointDetails['localname']?$aPointDetails['localname']:$aPointDetails['housenumber'];
-
-// Get all alternative names (languages, etc)
-$sSQL = 'SELECT (each(name)).key,(each(name)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(name)).key";
-$aPointDetails['aNames'] = $oDB->getAssoc($sSQL);
-
-// Address tags
-$sSQL = 'SELECT (each(address)).key as key,(each(address)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY key";
-$aPointDetails['aAddressTags'] = $oDB->getAssoc($sSQL);
-
-// Extra tags
-$sSQL = 'SELECT (each(extratags)).key,(each(extratags)).value FROM placex ';
-$sSQL .= "WHERE place_id = $iPlaceID ORDER BY (each(extratags)).key";
-$aPointDetails['aExtraTags'] = $oDB->getAssoc($sSQL);
-
-// Address
-$aAddressLines = false;
-if ($bIncludeAddressDetails) {
-    $oDetails = new Nominatim\AddressDetails($oDB, $iPlaceID, -1, $sLanguagePrefArraySQL);
-    $aAddressLines = $oDetails->getAddressDetails(true);
-}
-
-// Linked places
-$aLinkedLines = false;
-if ($bIncludeLinkedPlaces) {
-    $sSQL = 'SELECT placex.place_id, osm_type, osm_id, class, type, housenumber,';
-    $sSQL .= ' admin_level, rank_address, ';
-    $sSQL .= " ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,";
-    $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, ";
-    $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
-    $sSQL .= ' length(name::text) AS namelength ';
-    $sSQL .= ' FROM ';
-    $sSQL .= '    placex, ';
-    $sSQL .= '    ( ';
-    $sSQL .= '      SELECT centroid AS placegeometry ';
-    $sSQL .= '      FROM placex ';
-    $sSQL .= "      WHERE place_id = $iPlaceID ";
-    $sSQL .= '    ) AS x';
-    $sSQL .= " WHERE linked_place_id = $iPlaceID";
-    $sSQL .= ' ORDER BY ';
-    $sSQL .= '   rank_address ASC, ';
-    $sSQL .= '   rank_search ASC, ';
-    $sSQL .= "   get_name_by_language(name, $sLanguagePrefArraySQL), ";
-    $sSQL .= '   housenumber';
-    $aLinkedLines = $oDB->getAll($sSQL);
-}
-
-// All places this is an immediate parent of
-$aHierarchyLines = false;
-if ($bIncludeHierarchy) {
-    $sSQL = 'SELECT obj.place_id, osm_type, osm_id, class, type, housenumber,';
-    $sSQL .= " admin_level, rank_address, ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') AS isarea,";
-    $sSQL .= " ST_DistanceSpheroid(geometry, placegeometry, 'SPHEROID[\"WGS 84\",6378137,298.257223563, AUTHORITY[\"EPSG\",\"7030\"]]') AS distance, ";
-    $sSQL .= " get_name_by_language(name,$sLanguagePrefArraySQL) AS localname, ";
-    $sSQL .= ' length(name::text) AS namelength ';
-    $sSQL .= ' FROM ';
-    $sSQL .= '    ( ';
-    $sSQL .= '      SELECT placex.place_id, osm_type, osm_id, class, type, housenumber, admin_level, rank_address, rank_search, geometry, name ';
-    $sSQL .= '      FROM placex ';
-    $sSQL .= "      WHERE parent_place_id = $iPlaceID ";
-    $sSQL .= '      ORDER BY ';
-    $sSQL .= '         rank_address ASC, ';
-    $sSQL .= '         rank_search ASC ';
-    $sSQL .= '      LIMIT 500 ';
-    $sSQL .= '    ) AS obj,';
-    $sSQL .= '    ( ';
-    $sSQL .= '      SELECT centroid AS placegeometry ';
-    $sSQL .= '      FROM placex ';
-    $sSQL .= "      WHERE place_id = $iPlaceID ";
-    $sSQL .= '    ) AS x';
-    $sSQL .= ' ORDER BY ';
-    $sSQL .= '    rank_address ASC, ';
-    $sSQL .= '    rank_search ASC, ';
-    $sSQL .= '    localname, ';
-    $sSQL .= '    housenumber';
-    $aHierarchyLines = $oDB->getAll($sSQL);
-}
-
-$aPlaceSearchNameKeywords = false;
-$aPlaceSearchAddressKeywords = false;
-if ($bIncludeKeywords) {
-    $sSQL = "SELECT * FROM search_name WHERE place_id = $iPlaceID";
-    $aPlaceSearchName = $oDB->getRow($sSQL);
-
-    if (!empty($aPlaceSearchName)) {
-        $sWordIds = substr($aPlaceSearchName['name_vector'], 1, -1);
-        if (!empty($sWordIds)) {
-            $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')';
-            $aPlaceSearchNameKeywords = $oDB->getAll($sSQL);
-        }
-
-        $sWordIds = substr($aPlaceSearchName['nameaddress_vector'], 1, -1);
-        if (!empty($sWordIds)) {
-            $sSQL = 'SELECT * FROM word WHERE word_id in ('.$sWordIds.')';
-            $aPlaceSearchAddressKeywords = $oDB->getAll($sSQL);
-        }
-    }
-}
-
-logEnd($oDB, $hLog, 1);
-
-include(CONST_LibDir.'/template/details-'.$sOutputFormat.'.php');
diff --git a/lib-php/website/lookup.php b/lib-php/website/lookup.php
deleted file mode 100644 (file)
index 3a7ddb8..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml');
-set_exception_handler_by_format($sOutputFormat);
-
-// Preferred language
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$hLog = logStart($oDB, 'place', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-$aSearchResults = array();
-$aCleanedQueryParts = array();
-
-$oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->loadParamArray($oParams);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-
-$aOsmIds = explode(',', $oParams->getString('osm_ids', ''));
-
-if (count($aOsmIds) > CONST_Places_Max_ID_count) {
-    userError('Bulk User: Only ' . CONST_Places_Max_ID_count . ' ids are allowed in one request.');
-}
-
-foreach ($aOsmIds as $sItem) {
-    // Skip empty sItem
-    if (empty($sItem)) {
-        continue;
-    }
-
-    $sType = $sItem[0];
-    $iId = (int) substr($sItem, 1);
-    if ($iId > 0 && ($sType == 'N' || $sType == 'W' || $sType == 'R')) {
-        $aCleanedQueryParts[] = $sType . $iId;
-        $oPlace = $oPlaceLookup->lookupOSMID($sType, $iId);
-        if ($oPlace) {
-            // we want to use the search-* output templates, so we need to fill
-            // $aSearchResults and slightly change the (reverse search) oPlace
-            // key names
-            $oResult = $oPlace;
-            unset($oResult['aAddress']);
-            if (isset($oPlace['aAddress'])) {
-                $oResult['address'] = $oPlace['aAddress'];
-            }
-            if ($sOutputFormat != 'geocodejson') {
-                unset($oResult['langaddress']);
-                $oResult['name'] = $oPlace['langaddress'];
-            }
-
-            $aOutlineResult = $oPlaceLookup->getOutlines(
-                $oPlace['place_id'],
-                $oPlace['lon'],
-                $oPlace['lat'],
-                Nominatim\ClassTypes\getDefRadius($oPlace)
-            );
-
-            if ($aOutlineResult) {
-                $oResult = array_merge($oResult, $aOutlineResult);
-            }
-
-            $aSearchResults[] = $oResult;
-        }
-    }
-}
-
-
-if (CONST_Debug) {
-    exit;
-}
-
-$sXmlRootTag = 'lookupresults';
-$sQuery = join(',', $aCleanedQueryParts);
-// we initialize these to avoid warnings in our logfile
-$sViewBox = '';
-$bShowPolygons = '';
-$aExcludePlaceIDs = array();
-$sMoreURL = '';
-
-logEnd($oDB, $hLog, 1);
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php');
diff --git a/lib-php/website/polygons.php b/lib-php/website/polygons.php
deleted file mode 100644 (file)
index 5a90abe..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('json'), 'json');
-set_exception_handler_by_format($sOutputFormat);
-
-$iDays = $oParams->getInt('days', false);
-$bReduced = $oParams->getBool('reduced', false);
-$sClass = $oParams->getString('class', false);
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$iTotalBroken = (int) $oDB->getOne('SELECT count(*) FROM import_polygon_error');
-
-$aPolygons = array();
-while ($iTotalBroken && empty($aPolygons)) {
-    $sSQL = 'SELECT osm_type, osm_id, class, type, name->\'name\' as "name",';
-    $sSQL .= 'country_code, errormessage, updated';
-    $sSQL .= ' FROM import_polygon_error';
-
-    $aWhere = array();
-    if ($iDays) {
-        $aWhere[] = "updated > 'now'::timestamp - '".$iDays." day'::interval";
-        $iDays++;
-    }
-
-    if ($bReduced) {
-        $aWhere[] = "errormessage like 'Area reduced%'";
-    }
-    if ($sClass) {
-        $sWhere[] = "class = '".pg_escape_string($sClass)."'";
-    }
-
-    if (!empty($aWhere)) {
-        $sSQL .= ' WHERE '.join(' and ', $aWhere);
-    }
-
-    $sSQL .= ' ORDER BY updated desc LIMIT 1000';
-    $aPolygons = $oDB->getAll($sSQL);
-}
-
-if (CONST_Debug) {
-    var_dump($aPolygons);
-    exit;
-}
-
-if ($sOutputFormat == 'json') {
-    javascript_renderData($aPolygons);
-}
diff --git a/lib-php/website/reverse-only-search.php b/lib-php/website/reverse-only-search.php
deleted file mode 100644 (file)
index 43cbd26..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2');
-set_exception_handler_by_format($sOutputFormat);
-
-throw new Exception('Reverse-only import does not support forward searching.', 404);
diff --git a/lib-php/website/reverse.php b/lib-php/website/reverse.php
deleted file mode 100644 (file)
index f24c655..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/PlaceLookup.php');
-require_once(CONST_LibDir.'/ReverseGeocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oParams = new Nominatim\ParameterParser();
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml');
-set_exception_handler_by_format($sOutputFormat);
-
-// Preferred language
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-
-$hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
-
-$oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->loadParamArray($oParams);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-
-$sOsmType = $oParams->getSet('osm_type', array('N', 'W', 'R'));
-$iOsmId = $oParams->getInt('osm_id', -1);
-$fLat = $oParams->getFloat('lat');
-$fLon = $oParams->getFloat('lon');
-$iZoom = $oParams->getInt('zoom', 18);
-
-if ($sOsmType && $iOsmId > 0) {
-    $aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId);
-} elseif ($fLat !== false && $fLon !== false) {
-    $oReverseGeocode = new Nominatim\ReverseGeocode($oDB);
-    $oReverseGeocode->setZoom($iZoom);
-
-    $oLookup = $oReverseGeocode->lookup($fLat, $fLon);
-
-    if ($oLookup) {
-        $aPlaces = $oPlaceLookup->lookup(array($oLookup->iId => $oLookup));
-        if (!empty($aPlaces)) {
-            $aPlace = reset($aPlaces);
-        }
-    }
-} else {
-    userError('Need coordinates or OSM object to lookup.');
-}
-
-if (isset($aPlace)) {
-    $aOutlineResult = $oPlaceLookup->getOutlines(
-        $aPlace['place_id'],
-        $aPlace['lon'],
-        $aPlace['lat'],
-        Nominatim\ClassTypes\getDefRadius($aPlace),
-        $fLat,
-        $fLon
-    );
-
-    if ($aOutlineResult) {
-        $aPlace = array_merge($aPlace, $aOutlineResult);
-    }
-} else {
-    $aPlace = array();
-}
-
-logEnd($oDB, $hLog, count($aPlace) ? 1 : 0);
-
-if (CONST_Debug) {
-    var_dump($aPlace);
-    exit;
-}
-
-if ($sOutputFormat == 'geocodejson') {
-    $sQuery = $fLat.','.$fLon;
-    if (isset($aPlace['place_id'])) {
-        $fDistance = $oDB->getOne(
-            'SELECT ST_Distance(ST_SetSRID(ST_Point(:lon,:lat),4326), centroid) FROM placex where place_id = :placeid',
-            array(':lon' => $fLon, ':lat' => $fLat, ':placeid' => $aPlace['place_id'])
-        );
-    }
-}
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/address-'.$sOutputTemplate.'.php');
diff --git a/lib-php/website/search.php b/lib-php/website/search.php
deleted file mode 100644 (file)
index e1aab79..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/log.php');
-require_once(CONST_LibDir.'/Geocode.php');
-require_once(CONST_LibDir.'/output.php');
-ini_set('memory_limit', '200M');
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-$oDB->connect();
-$oParams = new Nominatim\ParameterParser();
-
-$oGeocode = new Nominatim\Geocode($oDB);
-
-$aLangPrefOrder = $oParams->getPreferredLanguages();
-$oGeocode->setLanguagePreference($aLangPrefOrder);
-
-// Format for output
-$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'jsonv2');
-set_exception_handler_by_format($sOutputFormat);
-
-$oGeocode->loadParamArray($oParams, null);
-
-if (CONST_Search_BatchMode && isset($_GET['batch'])) {
-    $aBatch = json_decode($_GET['batch'], true);
-    $aBatchResults = array();
-    foreach ($aBatch as $aBatchParams) {
-        $oBatchGeocode = clone $oGeocode;
-        $oBatchParams = new Nominatim\ParameterParser($aBatchParams);
-        $oBatchGeocode->loadParamArray($oBatchParams);
-        $oBatchGeocode->setQueryFromParams($oBatchParams);
-        $aSearchResults = $oBatchGeocode->lookup();
-        $aBatchResults[] = $aSearchResults;
-    }
-    include(CONST_LibDir.'/template/search-batch-json.php');
-    exit;
-}
-
-$oGeocode->setQueryFromParams($oParams);
-
-if (!$oGeocode->getQueryString()
-    && isset($_SERVER['PATH_INFO'])
-    && strlen($_SERVER['PATH_INFO']) > 0
-    && $_SERVER['PATH_INFO'][0] == '/'
-) {
-    $sQuery = substr(rawurldecode($_SERVER['PATH_INFO']), 1);
-
-    // reverse order of '/' separated string
-    $aPhrases = explode('/', $sQuery);
-    $aPhrases = array_reverse($aPhrases);
-    $sQuery = join(', ', $aPhrases);
-    $oGeocode->setQuery($sQuery);
-}
-
-$hLog = logStart($oDB, 'search', $oGeocode->getQueryString(), $aLangPrefOrder);
-
-// Ignore requests from a faulty app.
-if ($oGeocode->getQueryString() === '-1,-1') {
-  $aSearchResults = array();
-} else {
-  $aSearchResults = $oGeocode->lookup();
-}
-
-logEnd($oDB, $hLog, count($aSearchResults));
-
-$sQuery = $oGeocode->getQueryString();
-
-$aMoreParams = $oGeocode->getMoreUrlParams();
-$aMoreParams['format'] = $sOutputFormat;
-if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-    $aMoreParams['accept-language'] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
-}
-
-if (isset($_SERVER['REQUEST_SCHEME'])
-    && isset($_SERVER['HTTP_HOST'])
-    && isset($_SERVER['DOCUMENT_URI'])
-) {
-    $sMoreURL = $_SERVER['REQUEST_SCHEME'].'://'
-                .$_SERVER['HTTP_HOST'].$_SERVER['DOCUMENT_URI'].'/?'
-                .http_build_query($aMoreParams);
-} else {
-    $sMoreURL = '/search.php?'.http_build_query($aMoreParams);
-}
-
-if (CONST_Debug) {
-    exit;
-}
-
-$sOutputTemplate = ($sOutputFormat == 'jsonv2') ? 'json' : $sOutputFormat;
-include(CONST_LibDir.'/template/search-'.$sOutputTemplate.'.php');
diff --git a/lib-php/website/status.php b/lib-php/website/status.php
deleted file mode 100644 (file)
index 8ab11cc..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/ParameterParser.php');
-require_once(CONST_LibDir.'/Status.php');
-
-$oParams = new Nominatim\ParameterParser();
-$sOutputFormat = $oParams->getSet('format', array('text', 'json'), 'text');
-
-$oDB = new Nominatim\DB(CONST_Database_DSN);
-
-if ($sOutputFormat == 'json') {
-    header('content-type: application/json; charset=UTF-8');
-}
-
-
-try {
-    $oStatus = new Nominatim\Status($oDB);
-    $oStatus->status();
-
-    if ($sOutputFormat == 'json') {
-        $epoch = $oStatus->dataDate();
-        $aResponse = array(
-                      'status' => 0,
-                      'message' => 'OK',
-                      'server' => gethostname(),
-                      '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(),
-                      'server' => gethostname()
-                     );
-        javascript_renderData($aResponse);
-    } else {
-        header('HTTP/1.0 500 Internal Server Error');
-        echo 'ERROR: '.$oErr->getMessage();
-    }
-}
diff --git a/lib-sql/tokenizer/legacy_tokenizer.sql b/lib-sql/tokenizer/legacy_tokenizer.sql
deleted file mode 100644 (file)
index c21d051..0000000
+++ /dev/null
@@ -1,426 +0,0 @@
--- SPDX-License-Identifier: GPL-2.0-only
---
--- This file is part of Nominatim. (https://nominatim.org)
---
--- Copyright (C) 2022 by the Nominatim developer community.
--- For a full list of authors see the git log.
-
--- 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_is_street_address(info JSONB)
-  RETURNS BOOLEAN
-AS $$
-  SELECT info->>'street' is not null or info->>'place_search' is null;
-$$ LANGUAGE SQL IMMUTABLE;
-
-
-CREATE OR REPLACE FUNCTION token_has_addr_street(info JSONB)
-  RETURNS BOOLEAN
-AS $$
-  SELECT info->>'street' is not null and info->>'street' != '{}';
-$$ 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_get_postcode(info JSONB)
-  RETURNS TEXT
-AS $$
-  SELECT info->>'postcode';
-$$ 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 bracket a full word as well
-    words := regexp_split_to_array(value, E'[(]');
-    IF array_upper(words, 1) > 1 THEN
-      s := make_standard_name(words[1]);
-      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;
diff --git a/lib-sql/tokenizer/legacy_tokenizer_indices.sql b/lib-sql/tokenizer/legacy_tokenizer_indices.sql
deleted file mode 100644 (file)
index 016c518..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
--- SPDX-License-Identifier: GPL-2.0-only
---
--- This file is part of Nominatim. (https://nominatim.org)
---
--- Copyright (C) 2022 by the Nominatim developer community.
--- For a full list of authors see the git log.
-
--- Required for details lookup.
-CREATE INDEX IF NOT EXISTS idx_word_word_id
-  ON word USING BTREE (word_id) {{db.tablespace.search_index}};
diff --git a/lib-sql/tokenizer/legacy_tokenizer_tables.sql b/lib-sql/tokenizer/legacy_tokenizer_tables.sql
deleted file mode 100644 (file)
index 7969f1c..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
--- SPDX-License-Identifier: GPL-2.0-only
---
--- This file is part of Nominatim. (https://nominatim.org)
---
--- Copyright (C) 2022 by the Nominatim developer community.
--- For a full list of authors see the git log.
-
-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}}";
index 3c1ff80bb692d16c50ece187f151f8a239a5141c..6a24e81680ee1243e69c119a45ed981cc0ab549e 100644 (file)
@@ -1,8 +1,12 @@
 site_name: Nominatim Manual
 theme:
+  font: false
   name: material
   features:
     - navigation.tabs
+    - toc.integrate
+  plugins:
+    - privacy
 copyright: Copyright &copy; Nominatim developer community
 docs_dir: docs
 site_url: https://nominatim.org
@@ -22,8 +26,7 @@ nav:
         - 'Basic Installation': 'admin/Installation.md'
         - 'Import' : 'admin/Import.md'
         - 'Update' : 'admin/Update.md'
-        - 'Deploy (Python frontend)' : 'admin/Deployment-Python.md'
-        - 'Deploy (PHP frontend)' : 'admin/Deployment-PHP.md'
+        - 'Deploy' : 'admin/Deployment-Python.md'
         - 'Nominatim UI'  : 'admin/Setup-Nominatim-UI.md'
         - 'Advanced Installations' : 'admin/Advanced-Installations.md'
         - 'Maintenance' : 'admin/Maintenance.md'
@@ -68,7 +71,8 @@ markdown_extensions:
         alternate_style: true
     - def_list
     - toc:
-        permalink: 
+        toc_depth: 4
+        permalink: 🔗
 extra_css: [extra.css, styles.css]
 exclude_docs: |
     mk_install_instructions.py
diff --git a/module/CMakeLists.txt b/module/CMakeLists.txt
deleted file mode 100644 (file)
index 01831f9..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-# just use the pgxs makefile
-
-foreach(suffix ${PostgreSQL_ADDITIONAL_VERSIONS} "16" "15" "14" "13" "12" "11" "10" "9.6")
-    list(APPEND PG_CONFIG_HINTS
-         "/usr/pgsql-${suffix}/bin")
-endforeach()
-
-find_program(PG_CONFIG pg_config HINTS ${PG_CONFIG_HINTS})
-
-
-
-execute_process(COMMAND ${PG_CONFIG} --pgxs
-            OUTPUT_VARIABLE PGXS
-            OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-if (NOT EXISTS "${PGXS}")
-message(FATAL_ERROR "Postgresql server package not found.")
-endif()
-
-ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/dummy
-        COMMAND PGXS=${PGXS} PG_CONFIG=${PG_CONFIG} MODSRCDIR=${CMAKE_CURRENT_SOURCE_DIR} $(MAKE) -f ${CMAKE_CURRENT_SOURCE_DIR}/Makefile
-        COMMENT "Running external makefile ${PGXS}"
-        )
-
-ADD_CUSTOM_TARGET( nominatim_lib ALL
-        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dummy
-        )
-
diff --git a/module/Makefile b/module/Makefile
deleted file mode 100644 (file)
index dd037a0..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-MODULES = nominatim
-PG_CPPFLAGS = -I$(MODSRCDIR)
-include $(PGXS)
-
-VPATH = $(MODSRCDIR)
-
-all:
-       chmod 755 nominatim.so
-
-install:
-       @echo Library does not need to be installed.
diff --git a/module/nominatim.c b/module/nominatim.c
deleted file mode 100644 (file)
index 54632f7..0000000
+++ /dev/null
@@ -1,301 +0,0 @@
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-#include "postgres.h"
-#include "fmgr.h"
-#include "mb/pg_wchar.h"
-#include <utfasciitable.h>
-
-#if PG_MAJORVERSION_NUM > 15
-#include "varatt.h"
-#endif
-
-PG_MODULE_MAGIC;
-
-Datum transliteration( PG_FUNCTION_ARGS );
-Datum gettokenstring( PG_FUNCTION_ARGS );
-void str_replace(char* buffer, int* len, int* changes, char* from, int fromlen, char* to, int tolen, int);
-void str_dupspaces(char* buffer);
-
-PG_FUNCTION_INFO_V1( transliteration );
-Datum
-transliteration( PG_FUNCTION_ARGS )
-{
-       static char * ascii = UTFASCII;
-       static uint16 asciilookup[65536] = UTFASCIILOOKUP;
-       char * asciipos;
-
-       text *source;
-       unsigned char *sourcedata;
-       int sourcedatalength;
-
-        unsigned int c1,c2,c3,c4;
-       unsigned int * wchardata;
-       unsigned int * wchardatastart;
-
-       text *result;
-       unsigned char *resultdata;
-       int resultdatalength;
-       int iLen;
-
-       if (GetDatabaseEncoding() != PG_UTF8) 
-       {
-               ereport(ERROR,
-                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                         errmsg("requires UTF8 database encoding")));
-       }
-
-       if (PG_ARGISNULL(0))
-       {
-               PG_RETURN_NULL();
-       }
-
-       // The original string
-       source = PG_GETARG_TEXT_P(0);
-       sourcedata = (unsigned char *)VARDATA(source);
-       sourcedatalength = VARSIZE(source) - VARHDRSZ;
-
-       // Intermediate wchar version of string
-       wchardatastart = wchardata = (unsigned int *)palloc((sourcedatalength+1)*sizeof(int));
-
-       // Based on pg_utf2wchar_with_len from wchar.c
-       // Postgresql strings are not zero terminalted
-        while (sourcedatalength > 0)
-        {
-                if ((*sourcedata & 0x80) == 0)
-                {
-                        *wchardata = *sourcedata++;
-                       wchardata++;
-                        sourcedatalength--;
-                }
-                else if ((*sourcedata & 0xe0) == 0xc0)
-                {
-                        if (sourcedatalength < 2) break;
-                        c1 = *sourcedata++ & 0x1f;
-                        c2 = *sourcedata++ & 0x3f;
-                        *wchardata = (c1 << 6) | c2;
-                       if (*wchardata < 65536) wchardata++;
-                        sourcedatalength -= 2;
-                }
-                else if ((*sourcedata & 0xf0) == 0xe0)
-                {
-                        if (sourcedatalength < 3) break;
-                        c1 = *sourcedata++ & 0x0f;
-                        c2 = *sourcedata++ & 0x3f;
-                        c3 = *sourcedata++ & 0x3f;
-                        *wchardata = (c1 << 12) | (c2 << 6) | c3;
-                       if (*wchardata < 65536) wchardata++;
-                        sourcedatalength -= 3;
-                }
-                else if ((*sourcedata & 0xf8) == 0xf0)
-                {
-                        if (sourcedatalength < 4) break;
-                        c1 = *sourcedata++ & 0x07;
-                        c2 = *sourcedata++ & 0x3f;
-                        c3 = *sourcedata++ & 0x3f;
-                        c4 = *sourcedata++ & 0x3f;
-                        *wchardata = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
-                       if (*wchardata < 65536) wchardata++;
-                        sourcedatalength -= 4;
-                }
-                else if ((*sourcedata & 0xfc) == 0xf8)
-                {
-                       // table does not extend beyond 4 char long, just skip
-                       if (sourcedatalength < 5) break;
-                       sourcedatalength -= 5;
-                       sourcedata += 5;
-               }
-                else if ((*sourcedata & 0xfe) == 0xfc)
-                {
-                       // table does not extend beyond 4 char long, just skip
-                       if (sourcedatalength < 6) break;
-                       sourcedatalength -= 6;
-                       sourcedata += 6;
-               }
-                else
-                {
-                       // assume lenngth 1, silently drop bogus characters
-                        sourcedatalength--;
-                       sourcedata += 1;
-                }
-        }
-        *wchardata = 0;
-
-       // calc the length of transliteration string
-       resultdatalength = 0;
-       wchardata = wchardatastart;
-       while(*wchardata)
-       {
-               if (*(asciilookup + *wchardata) > 0) resultdatalength += *(ascii + *(asciilookup + *wchardata));
-               wchardata++;
-       }
-
-       // allocate & create the result
-       result = (text *)palloc(resultdatalength + VARHDRSZ);
-       SET_VARSIZE(result, resultdatalength + VARHDRSZ);
-       resultdata = (unsigned char *)VARDATA(result);
-
-       wchardata = wchardatastart;
-       while(*wchardata)
-       {
-               if (*(asciilookup + *wchardata) > 0)
-               {
-                       asciipos = ascii + *(asciilookup + *wchardata);
-                       for(iLen = *asciipos; iLen > 0; iLen--)
-                       {
-                               asciipos++;
-                               *resultdata = *asciipos;
-                               resultdata++;
-                       }
-               }
-               /*else
-               {
-                       ereport( WARNING, ( errcode( ERRCODE_SUCCESSFUL_COMPLETION ),
-                             errmsg( "missing char: %i\n", *wchardata )));
-                       
-               }*/
-               wchardata++;
-       }
-
-       pfree(wchardatastart);
-
-       PG_RETURN_TEXT_P(result);
-}
-
-// Set isspace=1 if the replacement _only_ adds a space before the search string.  I.e. to == " " + from
-void str_replace(char* buffer, int* len, int* changes, char* from, int fromlen, char* to, int tolen, int isspace)
-{
-        char *p;
-
-        // Search string is too long to be present
-        if (fromlen > *len) return;
-
-        p = strstr(buffer, from);
-        while(p)
-        {
-                if (!isspace || (p > buffer && *(p-1) != ' '))
-                {
-                        (*changes)++;
-                        if (tolen != fromlen) memmove(p+tolen, p+fromlen, *len-(p-buffer)+1);
-                        memcpy(p, to, tolen);
-                        *len += tolen - fromlen;
-                }
-                p = strstr(p+1, from);
-        }
-}
-
-void str_dupspaces(char* buffer)
-{
-        char *out;
-        int wasspace;
-
-        out = buffer;
-        wasspace = 0;
-        while(*buffer)
-        {
-                if (wasspace && *buffer != ' ') wasspace = 0;
-                if (!wasspace)
-                {
-                        *out = *buffer;
-                        out++;
-                        wasspace = (*buffer == ' ');
-                }
-                buffer++;
-        }
-        *out = 0;
-}
-
-PG_FUNCTION_INFO_V1( gettokenstring );
-Datum
-gettokenstring( PG_FUNCTION_ARGS )
-{
-       text *source;
-       unsigned char *sourcedata;
-       int sourcedatalength;
-
-       char * buffer;
-       int len;
-       int changes;
-
-       text *result;
-
-       if (GetDatabaseEncoding() != PG_UTF8) 
-       {
-               ereport(ERROR,
-                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                         errmsg("requires UTF8 database encoding")));
-       }
-
-       if (PG_ARGISNULL(0))
-       {
-               PG_RETURN_NULL();
-       }
-
-       // The original string
-       source = PG_GETARG_TEXT_P(0);
-       sourcedata = (unsigned char *)VARDATA(source);
-       sourcedatalength = VARSIZE(source) - VARHDRSZ;
-
-       // Buffer for doing the replace in - string could get slightly longer (double is massive overkill)
-       buffer = (char *)palloc((sourcedatalength*2)*sizeof(char));
-       memcpy(buffer+1, sourcedata, sourcedatalength);
-       buffer[0] = 32;
-       buffer[sourcedatalength+1] = 32;
-       buffer[sourcedatalength+2] = 0;
-       len = sourcedatalength+3;
-
-       changes = 1;
-       str_dupspaces(buffer);
-       while(changes)
-       {
-               changes = 0;
-               #include <tokenstringreplacements.inc>
-               str_dupspaces(buffer);
-       }
-
-       // 'and' in various languages
-       str_replace(buffer, &len, &changes, " and ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " und ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " en ", 4, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " et ", 4, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " y ", 3, " ", 1, 0);
-
-       // 'the' (and similar)
-       str_replace(buffer, &len, &changes, " the ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " der ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " den ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " die ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " das ", 5, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " la ", 4, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " le ", 4, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " el ", 4, " ", 1, 0);
-       str_replace(buffer, &len, &changes, " il ", 4, " ", 1, 0);
-
-       // german
-       str_replace(buffer, &len, &changes, "ae", 2, "a", 1, 0);
-       str_replace(buffer, &len, &changes, "oe", 2, "o", 1, 0);
-       str_replace(buffer, &len, &changes, "ue", 2, "u", 1, 0);
-       str_replace(buffer, &len, &changes, "sss", 3, "ss", 2, 0);
-       str_replace(buffer, &len, &changes, "ih", 2, "i", 1, 0);
-       str_replace(buffer, &len, &changes, "eh", 2, "e", 1, 0);
-
-       // russian
-       str_replace(buffer, &len, &changes, "ie", 2, "i", 1, 0);
-       str_replace(buffer, &len, &changes, "yi", 2, "i", 1, 0);
-
-       // allocate & create the result
-       len--;// Drop the terminating zero
-       result = (text *)palloc(len + VARHDRSZ);
-       SET_VARSIZE(result, len + VARHDRSZ);
-       memcpy(VARDATA(result), buffer, len);
-
-       pfree(buffer);
-
-       PG_RETURN_TEXT_P(result);
-}
-
diff --git a/module/tokenstringreplacements.inc b/module/tokenstringreplacements.inc
deleted file mode 100644 (file)
index 9f4975a..0000000
+++ /dev/null
@@ -1,884 +0,0 @@
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-       str_replace(buffer, &len, &changes, " national wildlife refuge area ", 31, " nwra ", 6, 0);
-       str_replace(buffer, &len, &changes, " national recreation area ", 26, " nra ", 5, 0);
-       str_replace(buffer, &len, &changes, " air national guard base ", 25, " angb ", 6, 0);
-       str_replace(buffer, &len, &changes, " zhilishchien komplieks ", 24, " zh k ", 6, 0);
-       str_replace(buffer, &len, &changes, " trung tam thuong mdhi ", 23, " tttm ", 6, 0);
-       str_replace(buffer, &len, &changes, " poligono industrial ", 21, " pgind ", 7, 0);
-       str_replace(buffer, &len, &changes, " trung hoc pho thong ", 21, " thpt ", 6, 0);
-       str_replace(buffer, &len, &changes, " onze lieve vrouw e ", 20, " olv ", 5, 0);
-       str_replace(buffer, &len, &changes, " strada provinciale ", 20, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, "onze lieve vrouw e ", 19, " olv ", 5, 0);
-       str_replace(buffer, &len, &changes, " punto kilometrico ", 19, " pk ", 4, 0);
-       str_replace(buffer, &len, &changes, " cong vien van hoa ", 19, " cvvh ", 6, 0);
-       str_replace(buffer, &len, &changes, " can cu khong quan ", 19, " cckq ", 6, 0);
-       str_replace(buffer, &len, &changes, "strada provinciale ", 19, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " strada regionale ", 18, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " strada comunale ", 17, " sc ", 4, 0);
-       str_replace(buffer, &len, &changes, "strada regionale ", 17, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " trung hoc co so ", 17, " thcs ", 6, 0);
-       str_replace(buffer, &len, &changes, " san bay quoc te ", 17, " sbqt ", 6, 0);
-       str_replace(buffer, &len, &changes, " cong ty co phyn ", 17, " ctcp ", 6, 0);
-       str_replace(buffer, &len, &changes, " khu cong nghiep ", 17, " kcn ", 5, 0);
-       str_replace(buffer, &len, &changes, " air force base ", 16, " afb ", 5, 0);
-       str_replace(buffer, &len, &changes, " strada statale ", 16, " ss ", 4, 0);
-       str_replace(buffer, &len, &changes, " vien bcyo tang ", 16, " vbt ", 5, 0);
-       str_replace(buffer, &len, &changes, "strada comunale ", 16, " sc ", 4, 0);
-       str_replace(buffer, &len, &changes, " circunvalacion ", 16, " ccvcn ", 7, 0);
-       str_replace(buffer, &len, &changes, " paseo maritimo ", 16, " psmar ", 7, 0);
-       str_replace(buffer, &len, &changes, " wielkopolskie ", 15, " wlkp ", 6, 0);
-       str_replace(buffer, &len, &changes, " national park ", 15, " np ", 4, 0);
-       str_replace(buffer, &len, &changes, " middle school ", 15, " ms ", 4, 0);
-       str_replace(buffer, &len, &changes, " international ", 15, " intl ", 6, 0);
-       str_replace(buffer, &len, &changes, " burgermeister ", 15, " bgm ", 5, 0);
-       str_replace(buffer, &len, &changes, " vuon quoc gia ", 15, " vqg ", 5, 0);
-       str_replace(buffer, &len, &changes, " qucyng truong ", 15, " qt ", 4, 0);
-       str_replace(buffer, &len, &changes, "strada statale ", 15, " ss ", 4, 0);
-       str_replace(buffer, &len, &changes, " state highway ", 15, " sh ", 4, 0);
-       str_replace(buffer, &len, &changes, "burgermeister ", 14, " bgm ", 5, 0);
-       str_replace(buffer, &len, &changes, " right of way ", 14, " rowy ", 6, 0);
-       str_replace(buffer, &len, &changes, " hauptbahnhof ", 14, " hbf ", 5, 0);
-       str_replace(buffer, &len, &changes, " apartamentos ", 14, " aptos ", 7, 0);
-       str_replace(buffer, &len, &changes, " wielkopolski ", 14, " wlkp ", 6, 0);
-       str_replace(buffer, &len, &changes, " burgemeester ", 14, " bg ", 4, 0);
-       str_replace(buffer, &len, &changes, " camino nuevo ", 14, " c n ", 5, 0);
-       str_replace(buffer, &len, &changes, " camino hondo ", 14, " c h ", 5, 0);
-       str_replace(buffer, &len, &changes, " urbanizacion ", 14, " urb ", 5, 0);
-       str_replace(buffer, &len, &changes, " camino viejo ", 14, " c v ", 5, 0);
-       str_replace(buffer, &len, &changes, " wielkopolska ", 14, " wlkp ", 6, 0);
-       str_replace(buffer, &len, &changes, " wojewodztwie ", 14, " woj ", 5, 0);
-       str_replace(buffer, &len, &changes, " county route ", 14, " cr ", 4, 0);
-       str_replace(buffer, &len, &changes, " prolongacion ", 14, " prol ", 6, 0);
-       str_replace(buffer, &len, &changes, " thoroughfare ", 14, " thor ", 6, 0);
-       str_replace(buffer, &len, &changes, " san van dong ", 14, " svd ", 5, 0);
-       str_replace(buffer, &len, &changes, " tong cong ty ", 14, " tct ", 5, 0);
-       str_replace(buffer, &len, &changes, " khu nghi mat ", 14, " knm ", 5, 0);
-       str_replace(buffer, &len, &changes, " nha thi dzu ", 13, " ntd ", 5, 0);
-       str_replace(buffer, &len, &changes, " khu du lich ", 13, " kdl ", 5, 0);
-       str_replace(buffer, &len, &changes, " demarcacion ", 13, " demar ", 7, 0);
-       str_replace(buffer, &len, &changes, " cau ldhc bo ", 13, " clb ", 5, 0);
-       str_replace(buffer, &len, &changes, " interchange ", 13, " intg ", 6, 0);
-       str_replace(buffer, &len, &changes, " distributor ", 13, " dstr ", 6, 0);
-       str_replace(buffer, &len, &changes, " state route ", 13, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " wojewodztwo ", 13, " woj ", 5, 0);
-       str_replace(buffer, &len, &changes, " reservation ", 13, " res ", 5, 0);
-       str_replace(buffer, &len, &changes, " monseigneur ", 13, " mgr ", 5, 0);
-       str_replace(buffer, &len, &changes, " transversal ", 13, " trval ", 7, 0);
-       str_replace(buffer, &len, &changes, " extrarradio ", 13, " extrr ", 7, 0);
-       str_replace(buffer, &len, &changes, " high school ", 13, " hs ", 4, 0);
-       str_replace(buffer, &len, &changes, " mazowieckie ", 13, " maz ", 5, 0);
-       str_replace(buffer, &len, &changes, " residencial ", 13, " resid ", 7, 0);
-       str_replace(buffer, &len, &changes, " cong truong ", 13, " ct ", 4, 0);
-       str_replace(buffer, &len, &changes, " cooperativa ", 13, " coop ", 6, 0);
-       str_replace(buffer, &len, &changes, " diseminado ", 12, " disem ", 7, 0);
-       str_replace(buffer, &len, &changes, " barranquil ", 12, " bqllo ", 7, 0);
-       str_replace(buffer, &len, &changes, " fire track ", 12, " ftrk ", 6, 0);
-       str_replace(buffer, &len, &changes, " south east ", 12, " se ", 4, 0);
-       str_replace(buffer, &len, &changes, " north east ", 12, " ne ", 4, 0);
-       str_replace(buffer, &len, &changes, " university ", 12, " univ ", 6, 0);
-       str_replace(buffer, &len, &changes, " south west ", 12, " sw ", 4, 0);
-       str_replace(buffer, &len, &changes, " monasterio ", 12, " mtrio ", 7, 0);
-       str_replace(buffer, &len, &changes, " vecindario ", 12, " vecin ", 7, 0);
-       str_replace(buffer, &len, &changes, " carreterin ", 12, " ctrin ", 7, 0);
-       str_replace(buffer, &len, &changes, " callejuela ", 12, " cjla ", 6, 0);
-       str_replace(buffer, &len, &changes, " north-east ", 12, " ne ", 4, 0);
-       str_replace(buffer, &len, &changes, " south-west ", 12, " sw ", 4, 0);
-       str_replace(buffer, &len, &changes, " gebroeders ", 12, " gebr ", 6, 0);
-       str_replace(buffer, &len, &changes, " serviceway ", 12, " swy ", 5, 0);
-       str_replace(buffer, &len, &changes, " quadrangle ", 12, " qdgl ", 6, 0);
-       str_replace(buffer, &len, &changes, " commandant ", 12, " cmdt ", 6, 0);
-       str_replace(buffer, &len, &changes, " extramuros ", 12, " extrm ", 7, 0);
-       str_replace(buffer, &len, &changes, " escalinata ", 12, " escal ", 7, 0);
-       str_replace(buffer, &len, &changes, " north-west ", 12, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " bulevardul ", 12, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " particular ", 12, " parti ", 7, 0);
-       str_replace(buffer, &len, &changes, " mazowiecka ", 12, " maz ", 5, 0);
-       str_replace(buffer, &len, &changes, " mazowiecki ", 12, " maz ", 5, 0);
-       str_replace(buffer, &len, &changes, " north west ", 12, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " industrial ", 12, " ind ", 5, 0);
-       str_replace(buffer, &len, &changes, " costanilla ", 12, " cstan ", 7, 0);
-       str_replace(buffer, &len, &changes, " khach sdhn ", 12, " ks ", 4, 0);
-       str_replace(buffer, &len, &changes, " south-east ", 12, " se ", 4, 0);
-       str_replace(buffer, &len, &changes, " phi truong ", 12, " pt ", 4, 0);
-       str_replace(buffer, &len, &changes, " expressway ", 12, " exp ", 5, 0);
-       str_replace(buffer, &len, &changes, " fondamenta ", 12, " f ta ", 6, 0);
-       str_replace(buffer, &len, &changes, " apartments ", 12, " apts ", 6, 0);
-       str_replace(buffer, &len, &changes, " cul de sac ", 12, " cds ", 5, 0);
-       str_replace(buffer, &len, &changes, " corralillo ", 12, " crrlo ", 7, 0);
-       str_replace(buffer, &len, &changes, " mitropolit ", 12, " mit ", 5, 0);
-       str_replace(buffer, &len, &changes, " etorbidea ", 11, " etorb ", 7, 0);
-       str_replace(buffer, &len, &changes, " ploshchad ", 11, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " cobertizo ", 11, " cbtiz ", 7, 0);
-       str_replace(buffer, &len, &changes, " underpass ", 11, " upas ", 6, 0);
-       str_replace(buffer, &len, &changes, " crossroad ", 11, " crd ", 5, 0);
-       str_replace(buffer, &len, &changes, " fundatura ", 11, " fnd ", 5, 0);
-       str_replace(buffer, &len, &changes, " foreshore ", 11, " fshr ", 6, 0);
-       str_replace(buffer, &len, &changes, " parklands ", 11, " pkld ", 6, 0);
-       str_replace(buffer, &len, &changes, " esplanade ", 11, " esp ", 5, 0);
-       str_replace(buffer, &len, &changes, " centreway ", 11, " cnwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " formation ", 11, " form ", 6, 0);
-       str_replace(buffer, &len, &changes, " explanada ", 11, " expla ", 7, 0);
-       str_replace(buffer, &len, &changes, " viviendas ", 11, " vvdas ", 7, 0);
-       str_replace(buffer, &len, &changes, " northeast ", 11, " ne ", 4, 0);
-       str_replace(buffer, &len, &changes, " cong vien ", 11, " cv ", 4, 0);
-       str_replace(buffer, &len, &changes, " northwest ", 11, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " buildings ", 11, " bldgs ", 7, 0);
-       str_replace(buffer, &len, &changes, " errepidea ", 11, " err ", 5, 0);
-       str_replace(buffer, &len, &changes, " extension ", 11, " ex ", 4, 0);
-       str_replace(buffer, &len, &changes, " municipal ", 11, " mun ", 5, 0);
-       str_replace(buffer, &len, &changes, " southeast ", 11, " se ", 4, 0);
-       str_replace(buffer, &len, &changes, " sanatorio ", 11, " sanat ", 7, 0);
-       str_replace(buffer, &len, &changes, " thanh pho ", 11, " tp ", 4, 0);
-       str_replace(buffer, &len, &changes, " firetrail ", 11, " fit ", 5, 0);
-       str_replace(buffer, &len, &changes, " santuario ", 11, " santu ", 7, 0);
-       str_replace(buffer, &len, &changes, " southwest ", 11, " sw ", 4, 0);
-       str_replace(buffer, &len, &changes, " autopista ", 11, " auto ", 6, 0);
-       str_replace(buffer, &len, &changes, " president ", 11, " pres ", 6, 0);
-       str_replace(buffer, &len, &changes, " rinconada ", 11, " rcda ", 6, 0);
-       str_replace(buffer, &len, &changes, " kardinaal ", 11, " kard ", 6, 0);
-       str_replace(buffer, &len, &changes, " plazoleta ", 11, " pzta ", 6, 0);
-       str_replace(buffer, &len, &changes, " duong sat ", 11, " ds ", 4, 0);
-       str_replace(buffer, &len, &changes, " trung tam ", 11, " tt ", 4, 0);
-       str_replace(buffer, &len, &changes, " piazzetta ", 11, " pta ", 5, 0);
-       str_replace(buffer, &len, &changes, " boardwalk ", 11, " bwlk ", 6, 0);
-       str_replace(buffer, &len, &changes, " bulievard ", 11, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " luitenant ", 11, " luit ", 6, 0);
-       str_replace(buffer, &len, &changes, " courtyard ", 11, " ctyd ", 6, 0);
-       str_replace(buffer, &len, &changes, " reservoir ", 11, " res ", 5, 0);
-       str_replace(buffer, &len, &changes, " bulevardu ", 11, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " community ", 11, " comm ", 6, 0);
-       str_replace(buffer, &len, &changes, " concourse ", 11, " con ", 5, 0);
-       str_replace(buffer, &len, &changes, " profiesor ", 11, " prof ", 6, 0);
-       str_replace(buffer, &len, &changes, " promenade ", 11, " prom ", 6, 0);
-       str_replace(buffer, &len, &changes, " gienieral ", 11, " ghien ", 7, 0);
-       str_replace(buffer, &len, &changes, " puistikko ", 11, " pko ", 5, 0);
-       str_replace(buffer, &len, &changes, " balneario ", 11, " balnr ", 7, 0);
-       str_replace(buffer, &len, &changes, " carretera ", 11, " ctra ", 6, 0);
-       str_replace(buffer, &len, &changes, " ingenieur ", 11, " ir ", 4, 0);
-       str_replace(buffer, &len, &changes, " boulevard ", 11, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " deviation ", 11, " devn ", 6, 0);
-       str_replace(buffer, &len, &changes, " hipodromo ", 11, " hipod ", 7, 0);
-       str_replace(buffer, &len, &changes, " professor ", 11, " prof ", 6, 0);
-       str_replace(buffer, &len, &changes, " triangle ", 10, " tri ", 5, 0);
-       str_replace(buffer, &len, &changes, " dotsient ", 10, " dots ", 6, 0);
-       str_replace(buffer, &len, &changes, " boundary ", 10, " bdy ", 5, 0);
-       str_replace(buffer, &len, &changes, " salizada ", 10, " s da ", 6, 0);
-       str_replace(buffer, &len, &changes, " trunkway ", 10, " tkwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " cinturon ", 10, " cint ", 6, 0);
-       str_replace(buffer, &len, &changes, "president ", 10, " pres ", 6, 0);
-       str_replace(buffer, &len, &changes, " military ", 10, " mil ", 5, 0);
-       str_replace(buffer, &len, &changes, " jonkheer ", 10, " jhr ", 5, 0);
-       str_replace(buffer, &len, &changes, " motorway ", 10, " mwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " steenweg ", 10, " stwg ", 6, 0);
-       str_replace(buffer, &len, &changes, " crescent ", 10, " cr ", 4, 0);
-       str_replace(buffer, &len, &changes, " kanunnik ", 10, " kan ", 5, 0);
-       str_replace(buffer, &len, &changes, " koningin ", 10, " kon ", 5, 0);
-       str_replace(buffer, &len, &changes, " crossing ", 10, " xing ", 6, 0);
-       str_replace(buffer, &len, &changes, " callejon ", 10, " cjon ", 6, 0);
-       str_replace(buffer, &len, &changes, " pasadizo ", 10, " pzo ", 5, 0);
-       str_replace(buffer, &len, &changes, " crossway ", 10, " cowy ", 6, 0);
-       str_replace(buffer, &len, &changes, " cottages ", 10, " cotts ", 7, 0);
-       str_replace(buffer, &len, &changes, " mountain ", 10, " mtn ", 5, 0);
-       str_replace(buffer, &len, &changes, " business ", 10, " bus ", 5, 0);
-       str_replace(buffer, &len, &changes, " pierwszy ", 10, " 1 ", 3, 0);
-       str_replace(buffer, &len, &changes, " pierwsza ", 10, " 1 ", 3, 0);
-       str_replace(buffer, &len, &changes, " pierwsze ", 10, " 1 ", 3, 0);
-       str_replace(buffer, &len, &changes, " barriada ", 10, " barda ", 7, 0);
-       str_replace(buffer, &len, &changes, " entrance ", 10, " ent ", 5, 0);
-       str_replace(buffer, &len, &changes, " causeway ", 10, " cway ", 6, 0);
-       str_replace(buffer, &len, &changes, " generaal ", 10, " gen ", 5, 0);
-       str_replace(buffer, &len, &changes, " driveway ", 10, " dvwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " township ", 10, " twp ", 5, 0);
-       str_replace(buffer, &len, &changes, " stazione ", 10, " staz ", 6, 0);
-       str_replace(buffer, &len, &changes, " broadway ", 10, " bway ", 6, 0);
-       str_replace(buffer, &len, &changes, " alleyway ", 10, " alwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " quadrant ", 10, " qdrt ", 6, 0);
-       str_replace(buffer, &len, &changes, " apeadero ", 10, " apdro ", 7, 0);
-       str_replace(buffer, &len, &changes, " arboleda ", 10, " arb ", 5, 0);
-       str_replace(buffer, &len, &changes, " escalera ", 10, " esca ", 6, 0);
-       str_replace(buffer, &len, &changes, " rdhp hat ", 10, " rh ", 4, 0);
-       str_replace(buffer, &len, &changes, " transito ", 10, " trans ", 7, 0);
-       str_replace(buffer, &len, &changes, " ddhi hoc ", 10, " dh ", 4, 0);
-       str_replace(buffer, &len, &changes, " travesia ", 10, " trva ", 6, 0);
-       str_replace(buffer, &len, &changes, " barranco ", 10, " branc ", 7, 0);
-       str_replace(buffer, &len, &changes, " namestie ", 10, " nam ", 5, 0);
-       str_replace(buffer, &len, &changes, " viaducto ", 10, " vcto ", 6, 0);
-       str_replace(buffer, &len, &changes, " convento ", 10, " cnvto ", 7, 0);
-       str_replace(buffer, &len, &changes, " estacion ", 10, " estcn ", 7, 0);
-       str_replace(buffer, &len, &changes, "puistikko ", 10, " pko ", 5, 0);
-       str_replace(buffer, &len, &changes, " precinct ", 10, " pct ", 5, 0);
-       str_replace(buffer, &len, &changes, " heiligen ", 10, " hl ", 4, 0);
-       str_replace(buffer, &len, &changes, " edificio ", 10, " edifc ", 7, 0);
-       str_replace(buffer, &len, &changes, " prazuela ", 10, " przla ", 7, 0);
-       str_replace(buffer, &len, &changes, " thi trzn ", 10, " tt ", 4, 0);
-       str_replace(buffer, &len, &changes, " ridgeway ", 10, " rgwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " riverway ", 10, " rvwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " corredor ", 10, " crrdo ", 7, 0);
-       str_replace(buffer, &len, &changes, " passatge ", 10, " ptge ", 6, 0);
-       str_replace(buffer, &len, &changes, " junction ", 10, " jnc ", 5, 0);
-       str_replace(buffer, &len, &changes, " hospital ", 10, " hosp ", 6, 0);
-       str_replace(buffer, &len, &changes, " highroad ", 10, " hrd ", 5, 0);
-       str_replace(buffer, &len, &changes, " torrente ", 10, " trrnt ", 7, 0);
-       str_replace(buffer, &len, &changes, " avinguda ", 10, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, " portillo ", 10, " ptilo ", 7, 0);
-       str_replace(buffer, &len, &changes, " diagonal ", 10, " diag ", 6, 0);
-       str_replace(buffer, &len, &changes, " buu dien ", 10, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " alqueria ", 10, " alque ", 7, 0);
-       str_replace(buffer, &len, &changes, " poligono ", 10, " polig ", 7, 0);
-       str_replace(buffer, &len, &changes, " roadside ", 10, " rdsd ", 6, 0);
-       str_replace(buffer, &len, &changes, " glorieta ", 10, " gta ", 5, 0);
-       str_replace(buffer, &len, &changes, " fundacul ", 10, " fdc ", 5, 0);
-       str_replace(buffer, &len, &changes, " cao dang ", 10, " cd ", 4, 0);
-       str_replace(buffer, &len, &changes, " rosebowl ", 10, " rsbl ", 6, 0);
-       str_replace(buffer, &len, &changes, " complejo ", 10, " compj ", 7, 0);
-       str_replace(buffer, &len, &changes, " carretil ", 10, " crtil ", 7, 0);
-       str_replace(buffer, &len, &changes, " intrarea ", 10, " int ", 5, 0);
-       str_replace(buffer, &len, &changes, " gran via ", 10, " g v ", 5, 0);
-       str_replace(buffer, &len, &changes, " approach ", 10, " app ", 5, 0);
-       str_replace(buffer, &len, &changes, " stradela ", 10, " sdla ", 6, 0);
-       str_replace(buffer, &len, &changes, " conjunto ", 10, " cjto ", 6, 0);
-       str_replace(buffer, &len, &changes, " arterial ", 10, " artl ", 6, 0);
-       str_replace(buffer, &len, &changes, " plazuela ", 10, " plzla ", 7, 0);
-       str_replace(buffer, &len, &changes, " frontage ", 10, " frtg ", 6, 0);
-       str_replace(buffer, &len, &changes, " faubourg ", 10, " fg ", 4, 0);
-       str_replace(buffer, &len, &changes, " mansions ", 10, " mans ", 6, 0);
-       str_replace(buffer, &len, &changes, " turnpike ", 10, " tpk ", 5, 0);
-       str_replace(buffer, &len, &changes, " piazzale ", 10, " p le ", 6, 0);
-       str_replace(buffer, &len, &changes, " tieu hoc ", 10, " th ", 4, 0);
-       str_replace(buffer, &len, &changes, " bulevard ", 10, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " sendera ", 9, " sedra ", 7, 0);
-       str_replace(buffer, &len, &changes, " cutting ", 9, " cutt ", 6, 0);
-       str_replace(buffer, &len, &changes, " cantina ", 9, " canti ", 7, 0);
-       str_replace(buffer, &len, &changes, " cantera ", 9, " cantr ", 7, 0);
-       str_replace(buffer, &len, &changes, " rotonda ", 9, " rtda ", 6, 0);
-       str_replace(buffer, &len, &changes, " pasillo ", 9, " psllo ", 7, 0);
-       str_replace(buffer, &len, &changes, " landing ", 9, " ldg ", 5, 0);
-       str_replace(buffer, &len, &changes, " kolonel ", 9, " kol ", 5, 0);
-       str_replace(buffer, &len, &changes, " cong ty ", 9, " cty ", 5, 0);
-       str_replace(buffer, &len, &changes, " fairway ", 9, " fawy ", 6, 0);
-       str_replace(buffer, &len, &changes, " highway ", 9, " hwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " lookout ", 9, " lkt ", 5, 0);
-       str_replace(buffer, &len, &changes, " meander ", 9, " mr ", 4, 0);
-       str_replace(buffer, &len, &changes, " carrera ", 9, " cra ", 5, 0);
-       str_replace(buffer, &len, &changes, " station ", 9, " stn ", 5, 0);
-       str_replace(buffer, &len, &changes, " kapitan ", 9, " kap ", 5, 0);
-       str_replace(buffer, &len, &changes, " medical ", 9, " med ", 5, 0);
-       str_replace(buffer, &len, &changes, " broeder ", 9, " br ", 4, 0);
-       str_replace(buffer, &len, &changes, " poblado ", 9, " pbdo ", 6, 0);
-       str_replace(buffer, &len, &changes, " impasse ", 9, " imp ", 5, 0);
-       str_replace(buffer, &len, &changes, " gardens ", 9, " gdn ", 5, 0);
-       str_replace(buffer, &len, &changes, " nha tho ", 9, " nt ", 4, 0);
-       str_replace(buffer, &len, &changes, " nha hat ", 9, " nh ", 4, 0);
-       str_replace(buffer, &len, &changes, " freeway ", 9, " fwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " trasera ", 9, " tras ", 6, 0);
-       str_replace(buffer, &len, &changes, " portico ", 9, " prtco ", 7, 0);
-       str_replace(buffer, &len, &changes, " terrace ", 9, " ter ", 5, 0);
-       str_replace(buffer, &len, &changes, " heights ", 9, " hts ", 5, 0);
-       str_replace(buffer, &len, &changes, " camping ", 9, " campg ", 7, 0);
-       str_replace(buffer, &len, &changes, " callizo ", 9, " cllzo ", 7, 0);
-       str_replace(buffer, &len, &changes, " footway ", 9, " ftwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " calzada ", 9, " czada ", 7, 0);
-       str_replace(buffer, &len, &changes, " dominee ", 9, " ds ", 4, 0);
-       str_replace(buffer, &len, &changes, " meadows ", 9, " mdws ", 6, 0);
-       str_replace(buffer, &len, &changes, " sendero ", 9, " send ", 6, 0);
-       str_replace(buffer, &len, &changes, " osiedle ", 9, " os ", 4, 0);
-       str_replace(buffer, &len, &changes, " estrada ", 9, " estda ", 7, 0);
-       str_replace(buffer, &len, &changes, " avenida ", 9, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, " zgornji ", 9, " zg ", 4, 0);
-       str_replace(buffer, &len, &changes, " zgornje ", 9, " zg ", 4, 0);
-       str_replace(buffer, &len, &changes, " zgornja ", 9, " zg ", 4, 0);
-       str_replace(buffer, &len, &changes, " arrabal ", 9, " arral ", 7, 0);
-       str_replace(buffer, &len, &changes, " espalda ", 9, " eslda ", 7, 0);
-       str_replace(buffer, &len, &changes, " entrada ", 9, " entd ", 6, 0);
-       str_replace(buffer, &len, &changes, " kleiner ", 9, " kl ", 4, 0);
-       str_replace(buffer, &len, &changes, " kleines ", 9, " kl ", 4, 0);
-       str_replace(buffer, &len, &changes, " viaduct ", 9, " via ", 5, 0);
-       str_replace(buffer, &len, &changes, " roadway ", 9, " rdwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " strasse ", 9, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " spodnje ", 9, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " spodnji ", 9, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " spodnja ", 9, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " fabrica ", 9, " fca ", 5, 0);
-       str_replace(buffer, &len, &changes, " muntele ", 9, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " maantee ", 9, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " srednje ", 9, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " unterer ", 9, " u ", 3, 0);
-       str_replace(buffer, &len, &changes, " unteres ", 9, " u ", 3, 0);
-       str_replace(buffer, &len, &changes, " plateau ", 9, " plat ", 6, 0);
-       str_replace(buffer, &len, &changes, " srednji ", 9, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " empresa ", 9, " empr ", 6, 0);
-       str_replace(buffer, &len, &changes, " angosta ", 9, " angta ", 7, 0);
-       str_replace(buffer, &len, &changes, " costera ", 9, " coste ", 7, 0);
-       str_replace(buffer, &len, &changes, " tinh lo ", 9, " tl ", 4, 0);
-       str_replace(buffer, &len, &changes, " quoc lo ", 9, " ql ", 4, 0);
-       str_replace(buffer, &len, &changes, " auf der ", 9, " a d ", 5, 0);
-       str_replace(buffer, &len, &changes, " bulvari ", 9, " bl ", 4, 0);
-       str_replace(buffer, &len, &changes, " ddhi lo ", 9, " dl ", 4, 0);
-       str_replace(buffer, &len, &changes, " namesti ", 9, " nam ", 5, 0);
-       str_replace(buffer, &len, &changes, " passeig ", 9, " pg ", 4, 0);
-       str_replace(buffer, &len, &changes, " carrero ", 9, " cro ", 5, 0);
-       str_replace(buffer, &len, &changes, " cortijo ", 9, " crtjo ", 7, 0);
-       str_replace(buffer, &len, &changes, " san bay ", 9, " sb ", 4, 0);
-       str_replace(buffer, &len, &changes, " riviera ", 9, " rvra ", 6, 0);
-       str_replace(buffer, &len, &changes, " caddesi ", 9, " cd ", 4, 0);
-       str_replace(buffer, &len, &changes, " andador ", 9, " andad ", 7, 0);
-       str_replace(buffer, &len, &changes, " walkway ", 9, " wkwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " granden ", 9, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " grosser ", 9, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " grosses ", 9, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " reserve ", 9, " res ", 5, 0);
-       str_replace(buffer, &len, &changes, " alameda ", 9, " alam ", 6, 0);
-       str_replace(buffer, &len, &changes, " retreat ", 9, " rtt ", 5, 0);
-       str_replace(buffer, &len, &changes, " acequia ", 9, " aceq ", 6, 0);
-       str_replace(buffer, &len, &changes, " platsen ", 9, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " bahnhof ", 9, " bf ", 4, 0);
-       str_replace(buffer, &len, &changes, " autovia ", 9, " autov ", 7, 0);
-       str_replace(buffer, &len, &changes, " srednja ", 9, " sr ", 4, 0);
-       str_replace(buffer, &len, &changes, " galeria ", 9, " gale ", 6, 0);
-       str_replace(buffer, &len, &changes, " circuit ", 9, " cct ", 5, 0);
-       str_replace(buffer, &len, &changes, " svingen ", 9, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " plassen ", 9, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " mirador ", 9, " mrdor ", 7, 0);
-       str_replace(buffer, &len, &changes, " laneway ", 9, " lnwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " kolonia ", 9, " kol ", 5, 0);
-       str_replace(buffer, &len, &changes, " outlook ", 9, " otlk ", 6, 0);
-       str_replace(buffer, &len, &changes, " caravan ", 9, " cvn ", 5, 0);
-       str_replace(buffer, &len, &changes, " osiedlu ", 9, " os ", 4, 0);
-       str_replace(buffer, &len, &changes, " palacio ", 9, " palac ", 7, 0);
-       str_replace(buffer, &len, &changes, " pantano ", 9, " pant ", 6, 0);
-       str_replace(buffer, &len, &changes, " partida ", 9, " ptda ", 6, 0);
-       str_replace(buffer, &len, &changes, " calleja ", 9, " cllja ", 7, 0);
-       str_replace(buffer, &len, &changes, " mevrouw ", 9, " mevr ", 6, 0);
-       str_replace(buffer, &len, &changes, " meester ", 9, " mr ", 4, 0);
-       str_replace(buffer, &len, &changes, " pastoor ", 9, " past ", 6, 0);
-       str_replace(buffer, &len, &changes, " prinses ", 9, " pr ", 4, 0);
-       str_replace(buffer, &len, &changes, " bulevar ", 9, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " tollway ", 9, " tlwy ", 6, 0);
-       str_replace(buffer, &len, &changes, "steenweg ", 9, " stwg ", 6, 0);
-       str_replace(buffer, &len, &changes, " caserio ", 9, " csrio ", 7, 0);
-       str_replace(buffer, &len, &changes, " mercado ", 9, " merc ", 6, 0);
-       str_replace(buffer, &len, &changes, " alejach ", 9, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " kvartal ", 9, " kv ", 4, 0);
-       str_replace(buffer, &len, &changes, " parkway ", 9, " pwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " passage ", 9, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " pathway ", 9, " pway ", 6, 0);
-       str_replace(buffer, &len, &changes, " splaiul ", 9, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " soseaua ", 9, " sos ", 5, 0);
-       str_replace(buffer, &len, &changes, " colonia ", 9, " col ", 5, 0);
-       str_replace(buffer, &len, &changes, " wielkie ", 9, " wlk ", 5, 0);
-       str_replace(buffer, &len, &changes, " trzecie ", 9, " 3 ", 3, 0);
-       str_replace(buffer, &len, &changes, " llanura ", 9, " llnra ", 7, 0);
-       str_replace(buffer, &len, &changes, " malecon ", 9, " malec ", 7, 0);
-       str_replace(buffer, &len, &changes, " trzecia ", 9, " 3 ", 3, 0);
-       str_replace(buffer, &len, &changes, " trailer ", 9, " trlr ", 6, 0);
-       str_replace(buffer, &len, &changes, " cuadra ", 8, " cuadr ", 7, 0);
-       str_replace(buffer, &len, &changes, " cty cp ", 8, " ctcp ", 6, 0);
-       str_replace(buffer, &len, &changes, " paraje ", 8, " praje ", 7, 0);
-       str_replace(buffer, &len, &changes, " parque ", 8, " pque ", 6, 0);
-       str_replace(buffer, &len, &changes, " piazza ", 8, " p za ", 6, 0);
-       str_replace(buffer, &len, &changes, " puerta ", 8, " pta ", 5, 0);
-       str_replace(buffer, &len, &changes, " little ", 8, " lt ", 4, 0);
-       str_replace(buffer, &len, &changes, " pueblo ", 8, " pblo ", 6, 0);
-       str_replace(buffer, &len, &changes, " puente ", 8, " pnte ", 6, 0);
-       str_replace(buffer, &len, &changes, " jardin ", 8, " jdin ", 6, 0);
-       str_replace(buffer, &len, &changes, " granja ", 8, " granj ", 7, 0);
-       str_replace(buffer, &len, &changes, " market ", 8, " mkt ", 5, 0);
-       str_replace(buffer, &len, &changes, " pasaje ", 8, " psaje ", 7, 0);
-       str_replace(buffer, &len, &changes, " rotary ", 8, " rty ", 5, 0);
-       str_replace(buffer, &len, &changes, " corral ", 8, " crral ", 7, 0);
-       str_replace(buffer, &len, &changes, " siding ", 8, " sdng ", 6, 0);
-       str_replace(buffer, &len, &changes, " nucleo ", 8, " ncleo ", 7, 0);
-       str_replace(buffer, &len, &changes, " muelle ", 8, " muell ", 7, 0);
-       str_replace(buffer, &len, &changes, " carril ", 8, " crril ", 7, 0);
-       str_replace(buffer, &len, &changes, " portal ", 8, " prtal ", 7, 0);
-       str_replace(buffer, &len, &changes, " ramble ", 8, " rmbl ", 6, 0);
-       str_replace(buffer, &len, &changes, " pocket ", 8, " pkt ", 5, 0);
-       str_replace(buffer, &len, &changes, " chalet ", 8, " chlet ", 7, 0);
-       str_replace(buffer, &len, &changes, " canton ", 8, " cant ", 6, 0);
-       str_replace(buffer, &len, &changes, " ladera ", 8, " ldera ", 7, 0);
-       str_replace(buffer, &len, &changes, " parade ", 8, " pde ", 5, 0);
-       str_replace(buffer, &len, &changes, " dehesa ", 8, " dhsa ", 6, 0);
-       str_replace(buffer, &len, &changes, " museum ", 8, " mus ", 5, 0);
-       str_replace(buffer, &len, &changes, " middle ", 8, " mid ", 5, 0);
-       str_replace(buffer, &len, &changes, " cuesta ", 8, " custa ", 7, 0);
-       str_replace(buffer, &len, &changes, " gracht ", 8, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " virful ", 8, " vf ", 4, 0);
-       str_replace(buffer, &len, &changes, " m tele ", 8, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " varful ", 8, " vf ", 4, 0);
-       str_replace(buffer, &len, &changes, " str la ", 8, " sdla ", 6, 0);
-       str_replace(buffer, &len, &changes, " arcade ", 8, " arc ", 5, 0);
-       str_replace(buffer, &len, &changes, " strada ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " access ", 8, " accs ", 6, 0);
-       str_replace(buffer, &len, &changes, " bajada ", 8, " bjada ", 7, 0);
-       str_replace(buffer, &len, &changes, " veliki ", 8, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, "strasse ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " velike ", 8, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " untere ", 8, " u ", 3, 0);
-       str_replace(buffer, &len, &changes, " velika ", 8, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " artery ", 8, " arty ", 6, 0);
-       str_replace(buffer, &len, &changes, " avenue ", 8, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, " miasto ", 8, " m ", 3, 0);
-       str_replace(buffer, &len, &changes, " bypass ", 8, " byp ", 5, 0);
-       str_replace(buffer, &len, &changes, " placem ", 8, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " barrio ", 8, " bo ", 4, 0);
-       str_replace(buffer, &len, &changes, " center ", 8, " ctr ", 5, 0);
-       str_replace(buffer, &len, &changes, " bldngs ", 8, " bldgs ", 7, 0);
-       str_replace(buffer, &len, &changes, " puerto ", 8, " pto ", 5, 0);
-       str_replace(buffer, &len, &changes, " wielka ", 8, " wlk ", 5, 0);
-       str_replace(buffer, &len, &changes, " tunnel ", 8, " tun ", 5, 0);
-       str_replace(buffer, &len, &changes, " wielki ", 8, " wlk ", 5, 0);
-       str_replace(buffer, &len, &changes, " bridge ", 8, " bri ", 5, 0);
-       str_replace(buffer, &len, &changes, " trzeci ", 8, " 3 ", 3, 0);
-       str_replace(buffer, &len, &changes, " veliko ", 8, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " quelle ", 8, " qu ", 4, 0);
-       str_replace(buffer, &len, &changes, " acceso ", 8, " acces ", 7, 0);
-       str_replace(buffer, &len, &changes, " bulvar ", 8, " bl ", 4, 0);
-       str_replace(buffer, &len, &changes, " sokagi ", 8, " sk ", 4, 0);
-       str_replace(buffer, &len, &changes, "platsen ", 8, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " stigen ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " brucke ", 8, " br ", 4, 0);
-       str_replace(buffer, &len, &changes, " an der ", 8, " a d ", 5, 0);
-       str_replace(buffer, &len, &changes, " thi xa ", 8, " tx ", 4, 0);
-       str_replace(buffer, &len, &changes, " nordre ", 8, " ndr ", 5, 0);
-       str_replace(buffer, &len, &changes, " rambla ", 8, " rbla ", 6, 0);
-       str_replace(buffer, &len, &changes, " sondre ", 8, " sdr ", 5, 0);
-       str_replace(buffer, &len, &changes, "quoc lo ", 8, " ql ", 4, 0);
-       str_replace(buffer, &len, &changes, " phuong ", 8, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, " vastra ", 8, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " carrer ", 8, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, " oberes ", 8, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, " raitti ", 8, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " puisto ", 8, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " arroyo ", 8, " arry ", 6, 0);
-       str_replace(buffer, &len, &changes, " penger ", 8, " pgr ", 5, 0);
-       str_replace(buffer, &len, &changes, " oberer ", 8, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, " kleine ", 8, " kl ", 4, 0);
-       str_replace(buffer, &len, &changes, " grosse ", 8, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, "granden ", 8, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " villas ", 8, " vlls ", 6, 0);
-       str_replace(buffer, &len, &changes, " taival ", 8, " tvl ", 5, 0);
-       str_replace(buffer, &len, &changes, " in der ", 8, " i d ", 5, 0);
-       str_replace(buffer, &len, &changes, " centre ", 8, " ctr ", 5, 0);
-       str_replace(buffer, &len, &changes, " drugie ", 8, " 2 ", 3, 0);
-       str_replace(buffer, &len, &changes, " dokter ", 8, " dr ", 4, 0);
-       str_replace(buffer, &len, &changes, " grange ", 8, " gra ", 5, 0);
-       str_replace(buffer, &len, &changes, " doctor ", 8, " dr ", 4, 0);
-       str_replace(buffer, &len, &changes, " vicolo ", 8, " v lo ", 6, 0);
-       str_replace(buffer, &len, &changes, " kort e ", 8, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, " koning ", 8, " kon ", 5, 0);
-       str_replace(buffer, &len, &changes, " straat ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " svieti ", 8, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " callej ", 8, " cjon ", 6, 0);
-       str_replace(buffer, &len, &changes, " ground ", 8, " grnd ", 6, 0);
-       str_replace(buffer, &len, &changes, " vereda ", 8, " vreda ", 7, 0);
-       str_replace(buffer, &len, &changes, " chemin ", 8, " ch ", 4, 0);
-       str_replace(buffer, &len, &changes, " street ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " strand ", 8, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " sainte ", 8, " ste ", 5, 0);
-       str_replace(buffer, &len, &changes, " camino ", 8, " cno ", 5, 0);
-       str_replace(buffer, &len, &changes, " garden ", 8, " gdn ", 5, 0);
-       str_replace(buffer, &len, &changes, " follow ", 8, " folw ", 6, 0);
-       str_replace(buffer, &len, &changes, " estate ", 8, " est ", 5, 0);
-       str_replace(buffer, &len, &changes, " doktor ", 8, " d r ", 5, 0);
-       str_replace(buffer, &len, &changes, " subway ", 8, " sbwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " ulitsa ", 8, " ul ", 4, 0);
-       str_replace(buffer, &len, &changes, " square ", 8, " sq ", 4, 0);
-       str_replace(buffer, &len, &changes, " towers ", 8, " twrs ", 6, 0);
-       str_replace(buffer, &len, &changes, "plassen ", 8, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " county ", 8, " co ", 4, 0);
-       str_replace(buffer, &len, &changes, " brazal ", 8, " brzal ", 7, 0);
-       str_replace(buffer, &len, &changes, " circus ", 8, " crcs ", 6, 0);
-       str_replace(buffer, &len, &changes, "svingen ", 8, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " rampla ", 8, " rampa ", 7, 0);
-       str_replace(buffer, &len, &changes, " bloque ", 8, " blque ", 7, 0);
-       str_replace(buffer, &len, &changes, " circle ", 8, " cir ", 5, 0);
-       str_replace(buffer, &len, &changes, " island ", 8, " is ", 4, 0);
-       str_replace(buffer, &len, &changes, " common ", 8, " comm ", 6, 0);
-       str_replace(buffer, &len, &changes, " ribera ", 8, " rbra ", 6, 0);
-       str_replace(buffer, &len, &changes, " sector ", 8, " sect ", 6, 0);
-       str_replace(buffer, &len, &changes, " rincon ", 8, " rcon ", 6, 0);
-       str_replace(buffer, &len, &changes, " van de ", 8, " vd ", 4, 0);
-       str_replace(buffer, &len, &changes, " corner ", 8, " cnr ", 5, 0);
-       str_replace(buffer, &len, &changes, " subida ", 8, " sbida ", 7, 0);
-       str_replace(buffer, &len, &changes, " banda ", 7, " b ", 3, 0);
-       str_replace(buffer, &len, &changes, " bulev ", 7, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " barro ", 7, " bo ", 4, 0);
-       str_replace(buffer, &len, &changes, " cllon ", 7, " cjon ", 6, 0);
-       str_replace(buffer, &len, &changes, " p zza ", 7, " p za ", 6, 0);
-       str_replace(buffer, &len, &changes, " drugi ", 7, " 2 ", 3, 0);
-       str_replace(buffer, &len, &changes, " druga ", 7, " 2 ", 3, 0);
-       str_replace(buffer, &len, &changes, " placu ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " aleji ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " aleja ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " aleje ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " stary ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " stara ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " dolny ", 7, " dln ", 5, 0);
-       str_replace(buffer, &len, &changes, " dolna ", 7, " dln ", 5, 0);
-       str_replace(buffer, &len, &changes, " gorne ", 7, " gn ", 4, 0);
-       str_replace(buffer, &len, &changes, " gorna ", 7, " gn ", 4, 0);
-       str_replace(buffer, &len, &changes, " stare ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " gorny ", 7, " gn ", 4, 0);
-       str_replace(buffer, &len, &changes, " ulicy ", 7, " ul ", 4, 0);
-       str_replace(buffer, &len, &changes, " ulica ", 7, " ul ", 4, 0);
-       str_replace(buffer, &len, &changes, " o l v ", 7, " olv ", 5, 0);
-       str_replace(buffer, &len, &changes, " plein ", 7, " pln ", 5, 0);
-       str_replace(buffer, &len, &changes, " markt ", 7, " mkt ", 5, 0);
-       str_replace(buffer, &len, &changes, " lange ", 7, " l ", 3, 0);
-       str_replace(buffer, &len, &changes, " viale ", 7, " v le ", 6, 0);
-       str_replace(buffer, &len, &changes, "gracht ", 7, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " prins ", 7, " pr ", 4, 0);
-       str_replace(buffer, &len, &changes, "straat ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " plass ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " sving ", 7, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " gaten ", 7, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " veien ", 7, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " vliet ", 7, " vlt ", 5, 0);
-       str_replace(buffer, &len, &changes, " dolne ", 7, " dln ", 5, 0);
-       str_replace(buffer, &len, &changes, " b dul ", 7, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " sodra ", 7, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " norra ", 7, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " gamla ", 7, " gla ", 5, 0);
-       str_replace(buffer, &len, &changes, " grand ", 7, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " vagen ", 7, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " gatan ", 7, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " ostra ", 7, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, "vastra ", 7, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " cadde ", 7, " cd ", 4, 0);
-       str_replace(buffer, &len, &changes, " duong ", 7, " d ", 3, 0);
-       str_replace(buffer, &len, &changes, " sokak ", 7, " sk ", 4, 0);
-       str_replace(buffer, &len, &changes, " plats ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, "stigen ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " vayla ", 7, " vla ", 5, 0);
-       str_replace(buffer, &len, &changes, "taival ", 7, " tvl ", 5, 0);
-       str_replace(buffer, &len, &changes, " sveti ", 7, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " aukio ", 7, " auk ", 5, 0);
-       str_replace(buffer, &len, &changes, " sveta ", 7, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, " cesta ", 7, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, " piata ", 7, " pta ", 5, 0);
-       str_replace(buffer, &len, &changes, " aleea ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " kaari ", 7, " kri ", 5, 0);
-       str_replace(buffer, &len, &changes, "penger ", 7, " pgr ", 5, 0);
-       str_replace(buffer, &len, &changes, " ranta ", 7, " rt ", 4, 0);
-       str_replace(buffer, &len, &changes, " rinne ", 7, " rn ", 4, 0);
-       str_replace(buffer, &len, &changes, "raitti ", 7, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, "puisto ", 7, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " polku ", 7, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, " porta ", 7, " pta ", 5, 0);
-       str_replace(buffer, &len, &changes, " ponte ", 7, " p te ", 6, 0);
-       str_replace(buffer, &len, &changes, " paseo ", 7, " po ", 4, 0);
-       str_replace(buffer, &len, &changes, " fbrca ", 7, " fca ", 5, 0);
-       str_replace(buffer, &len, &changes, " allee ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " cours ", 7, " crs ", 5, 0);
-       str_replace(buffer, &len, &changes, "sainte ", 7, " ste ", 5, 0);
-       str_replace(buffer, &len, &changes, "square ", 7, " sq ", 4, 0);
-       str_replace(buffer, &len, &changes, " largo ", 7, " l go ", 6, 0);
-       str_replace(buffer, &len, &changes, " wharf ", 7, " whrf ", 6, 0);
-       str_replace(buffer, &len, &changes, " corte ", 7, " c te ", 6, 0);
-       str_replace(buffer, &len, &changes, " corso ", 7, " c so ", 6, 0);
-       str_replace(buffer, &len, &changes, " campo ", 7, " c po ", 6, 0);
-       str_replace(buffer, &len, &changes, " santa ", 7, " sta ", 5, 0);
-       str_replace(buffer, &len, &changes, " calle ", 7, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, " strip ", 7, " strp ", 6, 0);
-       str_replace(buffer, &len, &changes, " alley ", 7, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " north ", 7, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " block ", 7, " blk ", 5, 0);
-       str_replace(buffer, &len, &changes, " gully ", 7, " gly ", 5, 0);
-       str_replace(buffer, &len, &changes, " sielo ", 7, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " brace ", 7, " br ", 4, 0);
-       str_replace(buffer, &len, &changes, " ronde ", 7, " rnde ", 6, 0);
-       str_replace(buffer, &len, &changes, " grove ", 7, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " break ", 7, " brk ", 5, 0);
-       str_replace(buffer, &len, &changes, " roads ", 7, " rds ", 5, 0);
-       str_replace(buffer, &len, &changes, " track ", 7, " trk ", 5, 0);
-       str_replace(buffer, &len, &changes, " house ", 7, " ho ", 4, 0);
-       str_replace(buffer, &len, &changes, " trail ", 7, " trl ", 5, 0);
-       str_replace(buffer, &len, &changes, " mount ", 7, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " cross ", 7, " crss ", 6, 0);
-       str_replace(buffer, &len, &changes, " beach ", 7, " bch ", 5, 0);
-       str_replace(buffer, &len, &changes, " point ", 7, " pt ", 4, 0);
-       str_replace(buffer, &len, &changes, " basin ", 7, " basn ", 6, 0);
-       str_replace(buffer, &len, &changes, " green ", 7, " gn ", 4, 0);
-       str_replace(buffer, &len, &changes, " plaza ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " lille ", 7, " ll ", 4, 0);
-       str_replace(buffer, &len, &changes, " slope ", 7, " slpe ", 6, 0);
-       str_replace(buffer, &len, &changes, " placa ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " place ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " shunt ", 7, " shun ", 6, 0);
-       str_replace(buffer, &len, &changes, " saint ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " ulice ", 7, " ul ", 4, 0);
-       str_replace(buffer, &len, &changes, " amble ", 7, " ambl ", 6, 0);
-       str_replace(buffer, &len, &changes, " route ", 7, " rt ", 4, 0);
-       str_replace(buffer, &len, &changes, " sound ", 7, " snd ", 5, 0);
-       str_replace(buffer, &len, &changes, " store ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " front ", 7, " frnt ", 6, 0);
-       str_replace(buffer, &len, &changes, " elbow ", 7, " elb ", 5, 0);
-       str_replace(buffer, &len, &changes, " glade ", 7, " gl ", 4, 0);
-       str_replace(buffer, &len, &changes, " south ", 7, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " round ", 7, " rnd ", 5, 0);
-       str_replace(buffer, &len, &changes, " drive ", 7, " dr ", 4, 0);
-       str_replace(buffer, &len, &changes, " croft ", 7, " cft ", 5, 0);
-       str_replace(buffer, &len, &changes, " platz ", 7, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " ferry ", 7, " fy ", 4, 0);
-       str_replace(buffer, &len, &changes, " ridge ", 7, " rdge ", 6, 0);
-       str_replace(buffer, &len, &changes, " tanav ", 7, " tn ", 4, 0);
-       str_replace(buffer, &len, &changes, " banan ", 7, " ba ", 4, 0);
-       str_replace(buffer, &len, &changes, " quays ", 7, " qys ", 5, 0);
-       str_replace(buffer, &len, &changes, " sankt ", 7, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " vkhod ", 7, " vkh ", 5, 0);
-       str_replace(buffer, &len, &changes, " chase ", 7, " ch ", 4, 0);
-       str_replace(buffer, &len, &changes, " vista ", 7, " vsta ", 6, 0);
-       str_replace(buffer, &len, &changes, " rhein ", 7, " rh ", 4, 0);
-       str_replace(buffer, &len, &changes, " court ", 7, " ct ", 4, 0);
-       str_replace(buffer, &len, &changes, "brucke ", 7, " br ", 4, 0);
-       str_replace(buffer, &len, &changes, " upper ", 7, " up ", 4, 0);
-       str_replace(buffer, &len, &changes, " river ", 7, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " range ", 7, " rnge ", 6, 0);
-       str_replace(buffer, &len, &changes, " lower ", 7, " lr ", 4, 0);
-       str_replace(buffer, &len, &changes, " kalea ", 7, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, " crest ", 7, " crst ", 6, 0);
-       str_replace(buffer, &len, &changes, " obere ", 7, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, " manor ", 7, " mnr ", 5, 0);
-       str_replace(buffer, &len, &changes, " byway ", 7, " bywy ", 6, 0);
-       str_replace(buffer, &len, &changes, " reach ", 7, " rch ", 5, 0);
-       str_replace(buffer, &len, &changes, " copse ", 7, " cps ", 5, 0);
-       str_replace(buffer, &len, &changes, "quelle ", 7, " qu ", 4, 0);
-       str_replace(buffer, &len, &changes, " creek ", 7, " cr ", 4, 0);
-       str_replace(buffer, &len, &changes, " close ", 7, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, " fort ", 6, " ft ", 4, 0);
-       str_replace(buffer, &len, &changes, " apch ", 6, " app ", 5, 0);
-       str_replace(buffer, &len, &changes, " mont ", 6, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " bdul ", 6, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, "saint ", 6, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " back ", 6, " bk ", 4, 0);
-       str_replace(buffer, &len, &changes, " c le ", 6, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, "place ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " frwy ", 6, " fwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " quai ", 6, " qu ", 4, 0);
-       str_replace(buffer, &len, &changes, " ally ", 6, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " m te ", 6, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, " lane ", 6, " ln ", 4, 0);
-       str_replace(buffer, &len, &changes, "aukio ", 6, " auk ", 5, 0);
-       str_replace(buffer, &len, &changes, " loop ", 6, " lp ", 4, 0);
-       str_replace(buffer, &len, &changes, " line ", 6, " ln ", 4, 0);
-       str_replace(buffer, &len, &changes, " alue ", 6, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " link ", 6, " lk ", 4, 0);
-       str_replace(buffer, &len, &changes, " glde ", 6, " gl ", 4, 0);
-       str_replace(buffer, &len, &changes, " alea ", 6, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " gate ", 6, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " intr ", 6, " int ", 5, 0);
-       str_replace(buffer, &len, &changes, " gdns ", 6, " gdn ", 5, 0);
-       str_replace(buffer, &len, &changes, " hird ", 6, " hrd ", 5, 0);
-       str_replace(buffer, &len, &changes, " varf ", 6, " vf ", 4, 0);
-       str_replace(buffer, &len, &changes, " virf ", 6, " vf ", 4, 0);
-       str_replace(buffer, &len, &changes, " hgts ", 6, " hts ", 5, 0);
-       str_replace(buffer, &len, &changes, " expy ", 6, " exp ", 5, 0);
-       str_replace(buffer, &len, &changes, "markt ", 6, " mkt ", 5, 0);
-       str_replace(buffer, &len, &changes, " bypa ", 6, " byp ", 5, 0);
-       str_replace(buffer, &len, &changes, "o l v ", 6, " olv ", 5, 0);
-       str_replace(buffer, &len, &changes, " cres ", 6, " cr ", 4, 0);
-       str_replace(buffer, &len, &changes, " bdwy ", 6, " bway ", 6, 0);
-       str_replace(buffer, &len, &changes, " csac ", 6, " cds ", 5, 0);
-       str_replace(buffer, &len, &changes, " nowy ", 6, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " laan ", 6, " ln ", 4, 0);
-       str_replace(buffer, &len, &changes, " crsg ", 6, " xing ", 6, 0);
-       str_replace(buffer, &len, &changes, "vliet ", 6, " vlt ", 5, 0);
-       str_replace(buffer, &len, &changes, " city ", 6, " cty ", 5, 0);
-       str_replace(buffer, &len, &changes, "sving ", 6, " sv ", 4, 0);
-       str_replace(buffer, &len, &changes, "plass ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, "gaten ", 6, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, "veien ", 6, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " gata ", 6, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " sint ", 6, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " caus ", 6, " cway ", 6, 0);
-       str_replace(buffer, &len, &changes, " cove ", 6, " cv ", 4, 0);
-       str_replace(buffer, &len, &changes, "plein ", 6, " pln ", 5, 0);
-       str_replace(buffer, &len, &changes, " cswy ", 6, " cway ", 6, 0);
-       str_replace(buffer, &len, &changes, " plac ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " nowa ", 6, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " kolo ", 6, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, " katu ", 6, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, " duze ", 6, " dz ", 4, 0);
-       str_replace(buffer, &len, &changes, " blvd ", 6, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " p ta ", 6, " pta ", 5, 0);
-       str_replace(buffer, &len, &changes, " maly ", 6, " ml ", 4, 0);
-       str_replace(buffer, &len, &changes, " mala ", 6, " ml ", 4, 0);
-       str_replace(buffer, &len, &changes, " bdge ", 6, " bri ", 5, 0);
-       str_replace(buffer, &len, &changes, " nowe ", 6, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, " brdg ", 6, " bri ", 5, 0);
-       str_replace(buffer, &len, &changes, " male ", 6, " ml ", 4, 0);
-       str_replace(buffer, &len, &changes, " drwy ", 6, " dvwy ", 6, 0);
-       str_replace(buffer, &len, &changes, " duza ", 6, " dz ", 4, 0);
-       str_replace(buffer, &len, &changes, " utca ", 6, " u ", 3, 0);
-       str_replace(buffer, &len, &changes, " east ", 6, " e ", 3, 0);
-       str_replace(buffer, &len, &changes, " duzy ", 6, " dz ", 4, 0);
-       str_replace(buffer, &len, &changes, "kaari ", 6, " kri ", 5, 0);
-       str_replace(buffer, &len, &changes, " quan ", 6, " q ", 3, 0);
-       str_replace(buffer, &len, &changes, " svwy ", 6, " swy ", 5, 0);
-       str_replace(buffer, &len, &changes, " shwy ", 6, " sh ", 4, 0);
-       str_replace(buffer, &len, &changes, " road ", 6, " rd ", 4, 0);
-       str_replace(buffer, &len, &changes, "sankt ", 6, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " quay ", 6, " qy ", 4, 0);
-       str_replace(buffer, &len, &changes, "plats ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " rise ", 6, " ri ", 4, 0);
-       str_replace(buffer, &len, &changes, " berg ", 6, " bg ", 4, 0);
-       str_replace(buffer, &len, &changes, " tcty ", 6, " tct ", 5, 0);
-       str_replace(buffer, &len, &changes, " viad ", 6, " via ", 5, 0);
-       str_replace(buffer, &len, &changes, " view ", 6, " vw ", 4, 0);
-       str_replace(buffer, &len, &changes, " vdct ", 6, " via ", 5, 0);
-       str_replace(buffer, &len, &changes, " vale ", 6, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " avda ", 6, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, " grad ", 6, " ghr ", 5, 0);
-       str_replace(buffer, &len, &changes, " walk ", 6, " wlk ", 5, 0);
-       str_replace(buffer, &len, &changes, " west ", 6, " w ", 3, 0);
-       str_replace(buffer, &len, &changes, " yard ", 6, " yd ", 4, 0);
-       str_replace(buffer, &len, &changes, " blok ", 6, " bl ", 4, 0);
-       str_replace(buffer, &len, &changes, " terr ", 6, " ter ", 5, 0);
-       str_replace(buffer, &len, &changes, " cmno ", 6, " cno ", 5, 0);
-       str_replace(buffer, &len, &changes, " stra ", 6, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " thfr ", 6, " thor ", 6, 0);
-       str_replace(buffer, &len, &changes, " turn ", 6, " tn ", 4, 0);
-       str_replace(buffer, &len, &changes, " tpke ", 6, " tpk ", 5, 0);
-       str_replace(buffer, &len, &changes, " burg ", 6, " bg ", 4, 0);
-       str_replace(buffer, &len, &changes, "vayla ", 6, " vla ", 5, 0);
-       str_replace(buffer, &len, &changes, "vagen ", 6, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " tori ", 6, " tr ", 4, 0);
-       str_replace(buffer, &len, &changes, "gatan ", 6, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, "grand ", 6, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " pass ", 6, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " pkwy ", 6, " pwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " park ", 6, " pk ", 4, 0);
-       str_replace(buffer, &len, &changes, "rinne ", 6, " rn ", 4, 0);
-       str_replace(buffer, &len, &changes, " mtwy ", 6, " mwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " mndr ", 6, " mr ", 4, 0);
-       str_replace(buffer, &len, &changes, " kyla ", 6, " kl ", 4, 0);
-       str_replace(buffer, &len, &changes, " kuja ", 6, " kj ", 4, 0);
-       str_replace(buffer, &len, &changes, "platz ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, "ranta ", 6, " rt ", 4, 0);
-       str_replace(buffer, &len, &changes, " mile ", 6, " mi ", 4, 0);
-       str_replace(buffer, &len, &changes, " pfad ", 6, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, " mews ", 6, " m ", 3, 0);
-       str_replace(buffer, &len, &changes, "polku ", 6, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, " psge ", 6, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " plza ", 6, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, "ostra ", 6, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, "gamla ", 6, " gla ", 5, 0);
-       str_replace(buffer, &len, &changes, " stig ", 6, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, "norra ", 6, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, "sodra ", 6, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " pike ", 6, " pk ", 4, 0);
-       str_replace(buffer, &len, &changes, " dorf ", 6, " df ", 4, 0);
-       str_replace(buffer, &len, &changes, " piaz ", 6, " p za ", 6, 0);
-       str_replace(buffer, &len, &changes, " phwy ", 6, " pway ", 6, 0);
-       str_replace(buffer, &len, &changes, "pfad ", 5, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, " mnt ", 5, " mt ", 4, 0);
-       str_replace(buffer, &len, &changes, "gata ", 5, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " bhf ", 5, " bf ", 4, 0);
-       str_replace(buffer, &len, &changes, " bad ", 5, " b ", 3, 0);
-       str_replace(buffer, &len, &changes, "gate ", 5, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " zum ", 5, " z ", 3, 0);
-       str_replace(buffer, &len, &changes, "stig ", 5, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " blv ", 5, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, "kuja ", 5, " kj ", 4, 0);
-       str_replace(buffer, &len, &changes, " bul ", 5, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " str ", 5, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, "alue ", 5, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " cen ", 5, " ctr ", 5, 0);
-       str_replace(buffer, &len, &changes, " ave ", 5, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, "kyla ", 5, " kl ", 4, 0);
-       str_replace(buffer, &len, &changes, " ale ", 5, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " spl ", 5, " sp ", 4, 0);
-       str_replace(buffer, &len, &changes, " all ", 5, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, " k s ", 5, " ks ", 4, 0);
-       str_replace(buffer, &len, &changes, " aly ", 5, " al ", 4, 0);
-       str_replace(buffer, &len, &changes, "dorf ", 5, " df ", 4, 0);
-       str_replace(buffer, &len, &changes, " bvd ", 5, " bd ", 4, 0);
-       str_replace(buffer, &len, &changes, " vag ", 5, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " iii ", 5, " 3 ", 3, 0);
-       str_replace(buffer, &len, &changes, " tie ", 5, " t ", 3, 0);
-       str_replace(buffer, &len, &changes, " sok ", 5, " sk ", 4, 0);
-       str_replace(buffer, &len, &changes, "burg ", 5, " bg ", 4, 0);
-       str_replace(buffer, &len, &changes, "katu ", 5, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, "berg ", 5, " bg ", 4, 0);
-       str_replace(buffer, &len, &changes, "tori ", 5, " tr ", 4, 0);
-       str_replace(buffer, &len, &changes, " kte ", 5, " k ", 3, 0);
-       str_replace(buffer, &len, &changes, " gro ", 5, " gr ", 4, 0);
-       str_replace(buffer, &len, &changes, " grn ", 5, " gn ", 4, 0);
-       str_replace(buffer, &len, &changes, " gld ", 5, " gl ", 4, 0);
-       str_replace(buffer, &len, &changes, " san ", 5, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " hse ", 5, " ho ", 4, 0);
-       str_replace(buffer, &len, &changes, " gte ", 5, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, " rte ", 5, " rt ", 4, 0);
-       str_replace(buffer, &len, &changes, " rue ", 5, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " che ", 5, " ch ", 4, 0);
-       str_replace(buffer, &len, &changes, " pas ", 5, " ps ", 4, 0);
-       str_replace(buffer, &len, &changes, " plz ", 5, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " pnt ", 5, " pt ", 4, 0);
-       str_replace(buffer, &len, &changes, " pky ", 5, " pwy ", 5, 0);
-       str_replace(buffer, &len, &changes, " pza ", 5, " pl ", 4, 0);
-       str_replace(buffer, &len, &changes, " rvr ", 5, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " riv ", 5, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " lit ", 5, " lt ", 4, 0);
-       str_replace(buffer, &len, &changes, " p k ", 5, " pk ", 4, 0);
-       str_replace(buffer, &len, &changes, " lwr ", 5, " lr ", 4, 0);
-       str_replace(buffer, &len, &changes, " low ", 5, " lr ", 4, 0);
-       str_replace(buffer, &len, &changes, " sth ", 5, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " crk ", 5, " cr ", 4, 0);
-       str_replace(buffer, &len, &changes, "pres ", 5, " pres ", 6, 1);
-       str_replace(buffer, &len, &changes, "laan ", 5, " ln ", 4, 0);
-       str_replace(buffer, &len, &changes, " bda ", 5, " b ", 3, 0);
-       str_replace(buffer, &len, &changes, " vei ", 5, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " via ", 5, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " way ", 5, " wy ", 4, 0);
-       str_replace(buffer, &len, &changes, " upr ", 5, " up ", 4, 0);
-       str_replace(buffer, &len, &changes, " avd ", 5, " av ", 4, 0);
-       str_replace(buffer, &len, &changes, " crt ", 5, " ct ", 4, 0);
-       str_replace(buffer, &len, &changes, "stwg ", 5, " stwg ", 6, 1);
-       str_replace(buffer, &len, &changes, "sint ", 5, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " v d ", 5, " vd ", 4, 0);
-       str_replace(buffer, &len, &changes, " van ", 5, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " drv ", 5, " dr ", 4, 0);
-       str_replace(buffer, &len, &changes, " tce ", 5, " ter ", 5, 0);
-       str_replace(buffer, &len, &changes, " va ", 4, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " oa ", 4, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, " sa ", 4, " s ", 3, 0);
-       str_replace(buffer, &len, &changes, " na ", 4, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, "bgm ", 4, " bgm ", 5, 1);
-       str_replace(buffer, &len, &changes, " nw ", 4, " n ", 3, 0);
-       str_replace(buffer, &len, &changes, "vag ", 4, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " im ", 4, " 1 ", 3, 0);
-       str_replace(buffer, &len, &changes, "vla ", 4, " vla ", 5, 1);
-       str_replace(buffer, &len, &changes, "gla ", 4, " gla ", 5, 1);
-       str_replace(buffer, &len, &changes, " am ", 4, " a ", 3, 0);
-       str_replace(buffer, &len, &changes, " ph ", 4, " p ", 3, 0);
-       str_replace(buffer, &len, &changes, "rue ", 4, " r ", 3, 0);
-       str_replace(buffer, &len, &changes, " ga ", 4, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, "ste ", 4, " ste ", 5, 1);
-       str_replace(buffer, &len, &changes, "str ", 4, " st ", 4, 0);
-       str_replace(buffer, &len, &changes, " cl ", 4, " c ", 3, 0);
-       str_replace(buffer, &len, &changes, " vn ", 4, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, " gt ", 4, " g ", 3, 0);
-       str_replace(buffer, &len, &changes, "vei ", 4, " v ", 3, 0);
-       str_replace(buffer, &len, &changes, "vlt ", 4, " vlt ", 5, 1);
-       str_replace(buffer, &len, &changes, " ce ", 4, " cv ", 4, 0);
-       str_replace(buffer, &len, &changes, " ii ", 4, " 2 ", 3, 0);
-       str_replace(buffer, &len, &changes, "pln ", 4, " pln ", 5, 1);
-       str_replace(buffer, &len, &changes, "olv ", 4, " olv ", 5, 1);
-       str_replace(buffer, &len, &changes, "mkt ", 4, " mkt ", 5, 1);
-       str_replace(buffer, &len, &changes, "tvl ", 4, " tvl ", 5, 1);
-       str_replace(buffer, &len, &changes, " ob ", 4, " o ", 3, 0);
-       str_replace(buffer, &len, &changes, "pgr ", 4, " pgr ", 5, 1);
-       str_replace(buffer, &len, &changes, " in ", 4, " 1 ", 3, 0);
-       str_replace(buffer, &len, &changes, " mw ", 4, " m ", 3, 0);
-       str_replace(buffer, &len, &changes, "kri ", 4, " kri ", 5, 1);
-       str_replace(buffer, &len, &changes, "pko ", 4, " pko ", 5, 1);
-       str_replace(buffer, &len, &changes, "auk ", 4, " auk ", 5, 1);
-       str_replace(buffer, &len, &changes, "tie ", 4, " t ", 3, 0);
-       str_replace(buffer, &len, &changes, " i ", 3, " 1 ", 3, 0);
diff --git a/module/utfasciitable.h b/module/utfasciitable.h
deleted file mode 100644 (file)
index a099ed6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-#define UTFASCII " \x00""\x01"" \x01""0\x01""1\x01""2\x01""3\x01""4\x01""5\x01""6\x01""7\x01""8\x01""9\x01""a\x01""b\x01""c\x01""d\x01""e\x01""f\x01""g\x01""h\x01""i\x01""j\x01""k\x01""l\x01""m\x01""n\x01""o\x01""p\x01""q\x01""r\x01""s\x01""t\x01""u\x01""v\x01""w\x01""x\x01""y\x01""z\x02""ps\x02""ss\x03""deg\x01""-\x02""14\x02""12\x02""34\x02""ae\x02""th\x02""ij\x02""ng\x02""oe\x02""hv\x02""oi\x02""yr\x02""sh\x02""zh\x02""ts\x02""dz\x02""lj\x02""nj\x02""ou\x02""db\x02""qp\x04""stop\x02""lz\x02""tc\x02""fn\x02""ls\x02""ww\x0a""extra-high\x04""high\x03""mid\x04""tone\x09""extra-low\x03""yin\x04""yang\x04""down\x02""up\x04""left\x05""right\x04""ring\x06""middle\x05""tilde\x06""raised\x05""begin\x03""end\x05""shelf\x05""below\x04""heta\x05""sampi\x0a""pamphylian\x02""ks\x02""ph\x02""kh\x05""koppa\x02""st\x02""sp\x02""ch\x02""ti\x03""sho\x03""san\x02""ie\x02""io\x02""dj\x02""gj\x02""yi\x03""tsh\x02""kj\x03""dzh\x04""shch\x04""hard\x02""iu\x02""ia\x02""gh\x02""ot\x04""1000\x06""100000\x07""1000000\x03""tts\x02""el\x02""en\x02""em\x08""palochka\x03""ghe\x02""ha\x02""de\x03""dje\x03""zje\x04""dzje\x03""lje\x03""nje\x03""sje\x03""tje\x02""ze\x03""lha\x03""rha\x03""yae\x02""qa\x02""we\x05""aleut\x02""rh\x02""ew\x04""alef\x04""ayin\x02""oy\x05""sanah\x05""safha\x05""misra\x0b""sallallahou\x06""alayhe\x0b""rahmatullah\x04""radi\x09""takhallus\x05""hamza\x03""teh\x02""dh\x03""ain\x05""keheh\x05""farsi\x02""an\x02""un\x02""in\x05""small\x03""dot\x03""beh\x03""qaf\x02""tt\x03""tth\x02""bh\x03""hah\x02""ny\x02""dy\x03""cch\x02""dd\x02""dt\x03""ddh\x02""rr\x02""hy\x02""yu\x03""yeh\x03""sad\x03""dal\x03""reh\x06""sindhi\x03""heh\x05""alaph\x02""yh\x07""persian\x07""sogdian\x04""seen\x03""feh\x04""meem\x04""noon\x03""lam\x03""waw\x03""kaf\x05""alifu\x02""hh\x04""ainu\x02""aa\x02""ee\x02""oo\x02""ey\x02""oa\x03""naa\x0a""dagbasinna\x02""ba\x02""pa\x02""ta\x02""ja\x03""cha\x02""da\x02""ra\x03""rra\x02""sa\x03""gba\x02""fa\x02""ka\x02""la\x02""na\x02""ma\x03""nya\x02""wa\x02""ya\x04""jona\x0b""candrabindu\x08""anusvara\x07""visarga\x02""ii\x02""uu\x06""candra\x02""ai\x02""au\x03""kha\x02""ga\x03""gha\x03""nga\x02""ca\x03""jha\x03""tta\x04""ttha\x03""dda\x04""ddha\x03""nna\x03""tha\x03""dha\x04""nnna\x03""pha\x03""bha\x03""lla\x04""llla\x02""va\x03""sha\x03""ssa\x05""nukta\x08""avagraha\x06""virama\x06""udatta\x08""anudatta\x04""khha\x04""ghha\x02""za\x05""dddha\x03""yya\x02""ll\x03""gga\x03""jja\x04""ddda\x03""bba\x06""khanda\x02""on\x02""jh\x02""nn\x03""nnn\x03""lll\x03""aum\x05""udaat\x03""khh\x03""ghh\x04""dddh\x02""yy\x06""yakash\x02""rs\x02""bb\x03""geo\x03""tsa\x03""dza\x05""tuumu\x0b""jihvamuliya\x0b""upadhmaniya\x06""chillu\x08""iruyanna\x06""eyanna\x0a""alpapraana\x07""yayanna\x07""rayanna\x07""dantaja\x09""muurdhaja\x0a""aela-pilla\x05""ketti\x04""diga\x0c""gaetta-pilla\x07""kombuva\x05""kombu\x0b""gayanukitta\x02""ko\x03""kho\x02""yo\x03""tho\x02""so\x07""phinthu\x0b""lakkhangyao\x03""mai\x08""nikhahit\x08""yamakkan\x07""fongman\x0a""angkhankhu\x06""khomut\x03""aae\x02""no\x03""nng\x03""jny\x03""nyj\x04""nndd\x02""nd\x02""mb\x02""lo\x02""om\x04""half\x02""am\x02""ue\x03""uue\x03""yar\x02""ao\x04""tsha\x04""dzha\x03""zha\x02""-a\x04""kssa\x0a""fixed-form\x03""kka\x04""rjes\x04""rnam\x03""sna\x03""lci\x04""mchu\x03""gru\x02""ei\x02""ay\x04""rdel\x02""hn\x02""hm\x04""nnya\x04""shan\x03""mon\x04""tall\x04""asat\x06""medial\x05""great\x03""ssh\x03""kss\x07""western\x07""eastern\x05""rumai\x03""rae\x04""char\x04""jhan\x03""hae\x02""he\x03""hie\x03""har\x03""hoe\x03""ban\x03""gan\x03""don\x03""vin\x03""zen\x03""tan\x03""kan\x03""las\x03""man\x03""nar\x03""par\x04""zhar\x03""tar\x04""phar\x04""khar\x04""ghan\x03""qar\x04""shin\x04""chin\x03""can\x03""jil\x03""cil\x03""xan\x02""fi\x02""yn\x05""elifi\x08""georgian\x03""nny\x06""filler\x02""eo\x03""yeo\x02""ye\x03""wae\x03""weo\x02""wi\x02""eu\x03""a-o\x03""a-u\x04""ya-o\x05""ya-yo\x04""eo-o\x04""eo-u\x05""eo-eu\x05""yeo-o\x05""yeo-u\x04""o-eo\x03""o-e\x04""o-ye\x03""o-o\x03""o-u\x05""yo-ya\x06""yo-yae\x06""yo-yeo\x04""yo-o\x04""yo-i\x03""u-a\x04""u-ae\x07""u-eo-eu\x04""u-ye\x03""u-u\x04""yu-a\x05""yu-eo\x04""yu-e\x06""yu-yeo\x05""yu-ye\x04""yu-u\x04""yu-i\x04""eu-u\x05""eu-eu\x04""yi-u\x03""i-a\x04""i-ya\x03""i-o\x03""i-u\x04""i-eu\x07""i-araea\x05""araea\x08""araea-eo\x02""xh\x0c""nieun-tikeut\x0a""nieun-sios\x0d""nieun-pansios\x0d""nieun-thieuth\x0d""tikeut-kiyeok\x0c""tikeut-rieul\x11""rieul-kiyeok-sios\x0b""rieul-nieun\x0c""rieul-tikeut\x12""rieul-tikeut-hieuh\x0b""hieuh-mieum\x0b""hieuh-pieup\x0b""yeorinhieuh\x02""gg\x03""laa\x02""jj\x02""nb\x02""dg\x02""rn\x02""mn\x02""bg\x02""bn\x03""sza\x02""bs\x03""bsg\x03""bst\x03""bsb\x03""bss\x03""bsj\x02""bj\x02""bc\x02""bt\x02""bp\x03""bbn\x02""sg\x02""sn\x02""sd\x02""sr\x02""sm\x02""sb\x03""sbg\x03""sss\x02""sj\x02""sc\x02""sk\x04""shee\x03""she\x04""shwa\x03""qoa\x03""qha\x03""qhu\x02""ck\x04""qhee\x03""qhe\x02""pb\x02""pn\x04""qhwi\x05""qhwaa\x05""qhwee\x04""qhwe\x04""u-eo\x03""u-i\x02""gs\x02""nh\x02""lg\x02""lm\x02""lb\x02""lt\x02""lp\x02""lh\x02""gl\x03""gsg\x02""ns\x02""nz\x02""nt\x02""tl\x03""lgs\x02""ln\x02""ld\x03""lth\x03""lmg\x03""lms\x03""lbs\x03""lbh\x03""rnp\x03""lss\x02""lk\x02""lq\x02""mg\x02""ml\x02""ms\x03""mss\x02""mz\x02""mc\x02""mh\x02""bl\x02""sl\x02""hl\x02""hb\x03""ddi\x04""ddaa\x04""ddee\x03""dde\x03""ddo\x04""ddwa\x02""hu\x02""hi\x03""haa\x03""hee\x02""ho\x03""jwa\x02""lu\x02""li\x03""lee\x02""le\x03""lwa\x03""hha\x03""hhu\x03""hhi\x04""hhaa\x04""hhee\x03""hhe\x03""hho\x04""hhwa\x02""mu\x02""mi\x03""maa\x03""mee\x02""me\x02""mo\x03""mwa\x03""szu\x03""szi\x04""szaa\x04""szee\x03""sze\x03""szo\x04""szwa\x02""ru\x02""ri\x03""raa\x03""ree\x02""re\x02""ro\x03""rwa\x02""su\x02""si\x03""saa\x03""see\x02""se\x03""swa\x03""shu\x03""shi\x04""shaa\x02""qu\x02""qi\x03""qaa\x03""qee\x02""qe\x02""qo\x04""tzoa\x03""qwa\x02""fu\x03""qwi\x04""qwaa\x04""qwee\x03""qwe\x02""fo\x03""fwa\x03""qhi\x04""qhaa\x03""qho\x03""pwa\x04""qhwa\x03""mya\x02""bu\x02""bi\x03""baa\x03""bee\x02""be\x02""bo\x03""bwa\x02""vu\x02""vi\x03""vaa\x03""vee\x02""ve\x02""vo\x03""vwa\x02""tu\x03""taa\x03""tee\x02""te\x02""to\x03""twa\x02""cu\x02""ci\x03""caa\x03""cee\x02""ce\x02""co\x03""cwa\x02""xa\x02""xu\x02""xi\x03""xaa\x03""xee\x02""xe\x02""xo\x03""bwe\x03""xwa\x03""fwi\x03""xwi\x04""xwaa\x04""xwee\x03""xwe\x04""pwee\x03""pwe\x02""nu\x02""ni\x03""nee\x02""ne\x03""nwa\x03""nyu\x03""nyi\x04""nyaa\x04""nyee\x03""nye\x03""nyo\x04""nywa\x02""ku\x02""ki\x03""kaa\x03""kee\x02""ke\x03""kwa\x03""kwi\x04""kwaa\x04""kwee\x03""kwe\x03""kxa\x03""kxu\x03""kxi\x04""kxaa\x04""kxee\x03""kxe\x03""kxo\x03""hna\x04""kxwa\x04""kxwi\x05""kxwaa\x05""kxwee\x04""kxwe\x03""qua\x03""que\x02""wu\x03""waa\x03""wee\x02""wo\x02""sv\x02""di\x02""zu\x02""zi\x03""zaa\x03""zee\x02""zo\x03""zwa\x03""zhu\x03""zhi\x04""zhaa\x04""zhee\x03""zhe\x03""zho\x04""zhwa\x03""yaa\x03""yee\x02""du\x03""daa\x03""dee\x02""do\x03""dwa\x03""ddu\x02""ju\x02""ji\x03""jaa\x03""jee\x02""je\x02""jo\x02""gu\x02""gi\x03""gaa\x03""gee\x02""ge\x02""go\x03""gwa\x03""gwi\x04""gwaa\x04""gwee\x03""gwe\x03""ggu\x03""ggi\x04""ggaa\x04""ggee\x03""gge\x03""ggo\x03""thu\x03""thi\x04""thaa\x04""thee\x03""the\x04""thwa\x03""chu\x03""chi\x04""chaa\x04""chee\x03""che\x03""cho\x04""chwa\x03""phu\x03""phi\x04""phaa\x04""phee\x03""phe\x03""pho\x04""phwa\x03""tsu\x03""tsi\x04""tsaa\x04""tsee\x03""tse\x03""tso\x04""tswa\x03""tza\x03""tzu\x03""tzi\x04""tzaa\x04""tzee\x03""tze\x03""tzo\x03""faa\x03""fee\x02""fe\x02""pu\x02""pi\x03""paa\x03""pee\x02""pe\x02""po\x03""rya\x03""fya\x02""gv\x02""lv\x03""nah\x02""nv\x03""qui\x03""quo\x03""quu\x03""quv\x02""dv\x03""dla\x03""tla\x03""tle\x03""tli\x03""tlo\x03""tlu\x03""tlv\x03""tsv\x02""wv\x02""yv\x03""aai\x03""wii\x03""woo\x04""paai\x03""pii\x03""poo\x03""pwi\x04""pwii\x03""pwo\x04""pwoo\x04""pwaa\x04""taai\x03""tii\x03""too\x03""twe\x03""twi\x04""twii\x03""two\x04""twoo\x04""twaa\x03""tte\x03""tti\x03""tto\x04""kaai\x03""kii\x03""koo\x04""kwii\x03""kwo\x04""kwoo\x02""kw\x03""keh\x03""kih\x03""koh\x03""kah\x04""caai\x03""cii\x03""coo\x03""cwe\x03""cwi\x04""cwii\x03""cwo\x04""cwoo\x04""cwaa\x04""maai\x03""mii\x03""moo\x03""mwe\x03""mwi\x04""mwii\x03""mwo\x04""mwoo\x04""mwaa\x04""naai\x03""nii\x03""noo\x03""nwe\x04""nwaa\x04""laai\x03""lii\x03""loo\x03""lwe\x03""lwi\x04""lwii\x03""lwo\x04""lwoo\x04""lwaa\x04""saai\x03""sii\x03""soo\x03""swe\x03""swi\x04""swii\x03""swo\x04""swoo\x04""swaa\x02""sw\x03""skw\x04""spwa\x04""stwa\x04""skwa\x04""scwa\x04""shii\x04""shoo\x04""shwe\x04""shwi\x05""shwii\x04""shwo\x05""shwoo\x05""shwaa\x04""yaai\x03""yii\x03""yoo\x03""ywe\x03""ywi\x04""ywii\x03""ywo\x04""ywoo\x03""ywa\x04""ywaa\x04""raai\x03""rii\x03""roo\x04""rwaa\x04""faai\x03""fii\x03""foo\x04""fwaa\x04""thii\x04""thoo\x05""thwaa\x04""tthe\x04""tthi\x04""ttho\x03""tye\x03""tyi\x03""tyo\x03""tya\x03""hii\x03""hoo\x02""hk\x04""qaai\x03""qii\x03""qoo\x04""tlhe\x04""tlhi\x04""tlho\x04""tlha\x05""ngaai\x03""ngi\x04""ngii\x03""ngo\x04""ngoo\x04""ngaa\x03""lhi\x04""lhii\x03""lho\x04""lhoo\x04""lhaa\x03""ghu\x03""gho\x04""ghee\x03""ghi\x03""hwu\x03""hwo\x03""hwe\x04""hwee\x03""hwi\x03""hwa\x03""ttu\x04""ttee\x03""khu\x03""khe\x04""khee\x03""khi\x03""kku\x03""kko\x03""kke\x04""kkee\x03""kki\x02""kk\x03""jju\x03""jjo\x03""jje\x04""jjee\x03""jji\x03""dlu\x03""dlo\x03""dle\x04""dlee\x03""dli\x03""lhu\x03""lhe\x04""lhee\x04""tlhu\x05""tlhee\x04""tlee\x03""dzu\x03""dzo\x03""dze\x04""dzee\x03""dzi\x04""ttsu\x04""ttso\x04""ttse\x05""ttsee\x04""ttsi\x04""ttsa\x03""qai\x04""ngai\x04""nngi\x05""nngii\x04""nngo\x05""nngoo\x04""nnga\x05""nngaa\x03""sso\x02""ac\x03""ear\x03""ior\x08""boundary\x03""ang\x03""zra\x04""todo\x04""sibe\x06""manchu\x02""uk\x03""uuv\x02""ry\x03""ryy\x02""ly\x03""lyy\x02""ua\x02""kr\x03""yan\x09""mukphreng\x09""kemphreng\x04""sa-i\x02""eh\x03""aue\x05""tone-\x03""kva\x03""xva\x05""vowel\x03""aay\x02""uy\x03""oay\x03""uey\x02""iy\x05""final\x03""lae\x04""laev\x04""ngka\x03""mpa\x03""nra\x04""nyca\x03""ulu\x05""cecek\x06""surang\x05""bisah\x05""akara\x05""ikara\x05""ukara\x05""ekara\x06""aikara\x05""okara\x07""rerekan\x06""tedung\x04""suku\x06""taling\x05""pepet\x04""khot\x04""tzir\x02""ef\x03""zal\x06""asyura\x08""panyecek\x09""panglayar\x09""pangwisad\x09""pamingkal\x08""panyakra\x07""panyiku\x08""panghulu\x07""panyuku\x0a""panaelaeng\x08""panolong\x07""pamepet\x0a""paneuleung\x07""pamaaeh\x03""sya\x03""kla\x03""gla\x03""pla\x03""fla\x03""bla\x03""mla\x03""hla\x07""nyin-do\x04""kang\x03""ran\x02""at\x02""ag\x02""al\x03""aak\x03""aaj\x03""aam\x03""aaw\x02""is\x02""ih\x03""iny\x02""ir\x02""uc\x02""ud\x03""unn\x02""ep\x03""edd\x03""err\x03""ott\x02""ob\x02""ov\x02""oh\x07""capital\x04""open\x08""sideways\x03""top\x06""bottom\x06""voiced\x06""turned\x05""alpha\x05""schwa\x03""eng\x04""beta\x05""greek\x05""delta\x05""gamma\x03""rho\x08""cyrillic\x07""insular\x04""iota\x07""upsilon\x03""esh\x03""ezh\x03""eth\x08""reversed\x07""dotless\x06""script\x06""barred\x05""theta\x09""flattened\x02""av\x02""zr\x02""jy\x02""cy\x0c""middle-welsh\x07""epsilon\x03""eta\x07""omicron\x05""omega\x03"" ha\x03"" ga\x03"" zi\x04"" pai\x05"" yong\x05"" bing\x03""tie\x02""et\x03"" xi\x06"" zheng\x06"" chong\x05"" ping\x05"" shan\x06""shapes\x05"" xian\x04"" qia\x05"" jiao\x04"" jue\x04"" hui\x03"" li\x03"" mo\x04"" jin\x05"" zhuo\x04"" shu\x03"" ji\x03"" lu\x03"" le\x04"" you\x04"" sui\x04"" lan\x05"" peng\x03"" bi\x04"" nen\x04"" xia\x04"" zao\x03"" ti\x04"" jie\x04"" nao\x04"" shi\x04"" hua\x05"" lian\x05"" jian\x05"" beng\x06"" jiang\x05"" xing\x04"" bie\x04"" zai\x05"" chou\x04"" sou\x05"" niao\x04"" die\x06"" huang\x04"" dun\x03"" yi\x04"" tuo\x05"" jing\x04"" dai\x04"" cha\x04"" fen\x02""pp\x04"" wan\x04"" sao\x04"" xiu\x04"" gao\x04"" xue\x05"" weng\x03""ecu\x02""cl\x02""cr\x02""ff\x03""mil\x03""pts\x02""dr\x03"" fu\x04"" kou\x04"" chu\x04"" zhe\x03""iii\x02""iv\x03""vii\x04""viii\x02""ix\x03""xii\x05"" tian\x04"" suo\x04"" she\x06"" zhuan\x05"" tang\x06"" zhuai\x04"" yao\x03"" tu\x03"" mi\x05"" zhen\x04"" xie\x04"" lei\x04"" gai\x05"" juan\x05""above\x04"" qiu\x05"" ding\x04"" que\x03""and\x03"" ao\x04"" mei\x03"" ge\x04""with\x03"" qu\x04"" hou\x03""azu\x04""buky\x04""vede\x07""glagoli\x05""dobro\x05""yestu\x07""zhivete\x05""dzelo\x06""zemlja\x04""izhe\x07""initial\x06""djervi\x04""kako\x07""ljudije\x07""myslite\x05""nashi\x03""onu\x06""pokoji\x05""ritsi\x05""slovo\x06""tvrido\x03""uku\x05""fritu\x04""heru\x03""otu\x04""shta\x06""chrivi\x04""yeru\x04""yeri\x04""yati\x07""spidery\x03""yus\x07""iotated\x03""big\x04""fita\x07""izhitsa\x07""shtapic\x0a""trokutasti\x08""latinate\x08""tailless\x04""alfa\x04""vida\x05""dalda\x03""eie\x03""sou\x04""zata\x04""hate\x06""thethe\x05""iauda\x04""kapa\x05""laula\x03""ksi\x04""sima\x03""tau\x03""psi\x03""oou\x09""dialect-p\x03""old\x0d""cryptogrammic\x07""crossed\x08""akhmimic\x08""l-shaped\x03""yab\x04""yabh\x03""yag\x05""yaghh\x06""berber\x03""yaj\x03""yad\x04""yadh\x04""yadd\x05""yaddh\x03""yey\x03""yaf\x03""yak\x06""tuareg\x05""yakhh\x03""yah\x04""yahh\x04""yakh\x03""yaq\x04""yazh\x07""ahaggar\x03""yal\x03""yam\x03""yap\x04""yarr\x04""yagh\x04""ayer\x03""yas\x04""yass\x04""yash\x03""yat\x04""yath\x04""yach\x04""yatt\x03""yav\x03""yaw\x03""yay\x03""yaz\x0a""tawellemet\x04""yazz\x0d""labialization\x03""loa\x03""moa\x03""roa\x03""soa\x04""shoa\x03""boa\x03""toa\x03""coa\x03""noa\x04""nyoa\x03""zoa\x03""doa\x04""ddoa\x03""joa\x04""thoa\x04""choa\x04""phoa\x03""poa\x04""ggwa\x04""ggwi\x05""ggwee\x04""ggwe\x03""ssu\x03""ssi\x04""ssaa\x04""ssee\x03""sse\x03""cca\x03""ccu\x03""cci\x04""ccaa\x04""ccee\x03""cce\x03""cco\x03""zza\x03""zzu\x03""zzi\x04""zzaa\x04""zzee\x03""zze\x03""zzo\x04""ccha\x04""cchu\x04""cchi\x05""cchaa\x05""cchee\x04""cche\x04""ccho\x03""qya\x03""qyu\x03""qyi\x04""qyaa\x04""qyee\x03""qye\x03""qyo\x03""kya\x03""kyu\x03""kyi\x04""kyaa\x04""kyee\x03""kye\x03""kyo\x03""xya\x03""xyu\x03""xyi\x04""xyaa\x04""xyee\x03""xye\x03""xyo\x03""gya\x03""gyu\x03""gyi\x04""gyaa\x04""gyee\x03""gye\x03""gyo\x02""er\x02""es\x05""shcha\x05""es-te\x05""djerv\x09""monograph\x08""iotified\x06""little\x04""full\x08""surround\x08""overlaid\x02""gn\x06""kiyeok\x0b""ssangkiyeok\x0b""kiyeok-sios\x05""nieun\x0b""nieun-cieuc\x0b""nieun-hieuh\x06""tikeut\x0b""ssangtikeut\x05""rieul\x0c""rieul-kiyeok\x0b""rieul-mieum\x0b""rieul-pieup\x0a""rieul-sios\x0d""rieul-thieuth\x0d""rieul-phieuph\x0b""rieul-hieuh\x05""mieum\x05""pieup\x0a""ssangpieup\x0a""pieup-sios\x04""sios\x09""ssangsios\x05""ieung\x05""cieuc\x0a""ssangcieuc\x07""chieuch\x07""khieukh\x07""thieuth\x07""phieuph\x05""hieuh\x0a""ssangnieun\x10""rieul-pieup-sios\x0d""rieul-pansios\x11""rieul-yeorinhieuh\x0b""mieum-pieup\x0a""mieum-sios\x0d""mieum-pansios\x0d""kapyeounmieum\x0c""pieup-kiyeok\x0c""pieup-tikeut\x11""pieup-sios-kiyeok\x11""pieup-sios-tikeut\x0b""pieup-cieuc\x0d""pieup-thieuth\x0d""kapyeounpieup\x12""kapyeounssangpieup\x0b""sios-kiyeok\x0a""sios-nieun\x0b""sios-tikeut\x0a""sios-pieup\x0a""sios-cieuc\x07""pansios\x0a""ssangieung\x08""yesieung\x0d""yesieung-sios\x10""yesieung-pansios\x0f""kapyeounphieuph\x0a""ssanghieuh\x06""araeae\x03""enn\x03""onn\x03""ann\x03""inn\x02""im\x03""ngg\x04""ainn\x04""aunn\x03""ong\x04""innn\x05""ojeon\x06""chamko\x05""jueui\x04"" kua\x03"" wu\x04"" yin\x03"" si\x03"" ye\x04"" nuo\x03"" xu\x06"" xiong\x04"" liu\x04"" lin\x06"" xiang\x04"" xin\x04"" pan\x03"" ma\x05"" qian\x06"" zhong\x02"" n\x06"" cheng\x05"" fang\x04"" zuo\x05"" zhou\x05"" dong\x03"" su\x06"" jiong\x05"" wang\x04"" zhu\x05"" long\x05"" ying\x05"" miao\x03"" yu\x04"" luo\x05"" chai\x04"" hun\x04"" rao\x04"" han\x04"" tai\x03"" ai\x04"" jun\x02"" l\x05"" xiao\x05"" tiao\x04"" zha\x03"" ku\x03"" er\x05"" nang\x03"" qi\x04"" chi\x03"" mu\x03"" se\x06"" qiong\x03"" sa\x03"" pu\x03"" ta\x03"" ou\x05"" mian\x04"" wen\x05"" diao\x04"" mie\x05"" quan\x04"" cai\x06"" liang\x03"" gu\x04"" mao\x04"" gua\x04"" man\x05"" chui\x05"" huan\x05"" gong\x04"" nan\x05"" dian\x04"" yan\x03"" ci\x05"" lang\x03"" he\x04"" tou\x05"" pian\x02"" e\x04"" qie\x04"" rui\x05"" chan\x04"" dan\x04"" duo\x04"" fei\x05"" bang\x03"" ba\x05"" kuai\x05"" shen\x03"" pi\x05"" yang\x04"" bei\x04"" che\x05"" suan\x05"" heng\x04"" gui\x04"" lou\x04"" sun\x04"" zou\x04"" zhi\x04"" jia\x03"" hu\x03"" la\x03"" ke\x04"" wei\x05"" zhao\x04"" kui\x04"" fan\x06"" zhang\x05"" song\x04"" nei\x05"" chen\x04"" guo\x03"" ng\x03"" fa\x04"" hao\x04"" pou\x05"" hong\x04"" tun\x03"" bo\x04"" nie\x04"" wai\x05"" shou\x05"" ling\x04"" lun\x05"" chun\x04"" rou\x03"" ze\x06"" sheng\x04"" bai\x04"" gou\x03"" na\x03"" cu\x04"" kuo\x04"" lao\x04"" huo\x04"" sai\x05"" rong\x03"" ju\x04"" pao\x04"" can\x05"" nian\x05"" xuan\x04"" qin\x03"" bu\x05"" zang\x05"" mang\x04"" dui\x04"" bao\x06"" chang\x04"" gun\x05"" liao\x03"" da\x05"" meng\x05"" qiao\x05"" rang\x04"" yun\x04"" tao\x04"" lai\x04"" ban\x05"" chuo\x03"" nu\x04"" ran\x04"" sha\x04"" dou\x03"" po\x05"" tong\x06"" qiang\x04"" xun\x05"" pang\x04"" cao\x03"" an\x04"" mai\x04"" yue\x05"" huai\x04"" zan\x04"" hai\x05"" luan\x05"" ning\x03"" ya\x05"" ming\x04"" zui\x04"" cui\x03"" de\x05"" bian\x04"" nou\x04"" tui\x05"" zhan\x04"" cen\x04"" min\x03"" zu\x03"" ni\x04"" cuo\x04"" pei\x05"" gang\x05"" yuan\x05"" biao\x04"" dao\x04"" jiu\x04"" run\x03"" wo\x05"" cuan\x04"" ren\x04"" kai\x04"" men\x07"" chuang\x05"" feng\x05"" zhai\x03"" di\x04"" ben\x05"" zong\x05"" ceng\x05"" hang\x04"" nin\x05"" kong\x04"" lie\x06"" kuang\x04"" san\x03"" te\x05"" shun\x03"" ce\x04"" ang\x03"" ru\x07"" shuang\x05"" guai\x03"" wa\x05"" shai\x05"" tuan\x05"" piao\x04"" kun\x04"" qun\x06"" chuai\x05"" shao\x05"" duan\x04"" gen\x06"" guang\x04"" cou\x05"" nuan\x05"" reng\x04"" mou\x04"" nai\x05"" guan\x04"" hen\x06"" chuan\x05"" kuan\x05"" qing\x04"" pin\x05"" kang\x03"" du\x05"" neng\x04"" tan\x05"" cang\x05"" chao\x05"" nong\x04"" kan\x04"" ken\x05"" ting\x04"" gan\x04"" niu\x05"" ruan\x05"" cong\x05"" zeng\x05"" shui\x05"" geng\x05"" shuo\x05"" zuan\x05"" zhui\x03"" en\x05"" leng\x04"" cun\x03"" ne\x04"" bin\x04"" ruo\x04"" kao\x05"" dang\x05"" teng\x03"" ri\x05"" deng\x03"" za\x06"" niang\x03"" ca\x05"" sang\x05"" keng\x06"" shuai\x04"" pie\x04"" tie\x06"" shuan\x05"" chua\x04"" zen\x06"" shang\x03"" pa\x04"" fou\x04"" diu\x03"" fo\x03"" ka\x04"" lia\x04"" zun\x05"" seng\x05"" zhun\x06"" zhuen\x05"" shua\x02"" a\x04"" pen\x02"" m\x04"" gem\x03"" yo\x03"" re\x04"" dia\x04""inch\x06""gallon\x04""giga\x06""guinea\x08""kilogram\x08""kilowatt\x07""gramton\x06""koruna\x08""shilling\x05""dozen\x04""desi\x06""dollar\x07""percent\x08""building\x05""farad\x05""franc\x07""hectare\x04""peso\x07""pfennig\x05""point\x03""hon\x05""micro\x04""mile\x04""mark\x06""micron\x05""rupee\x05""ruble\x03""rem\x08""roentgen\x05""meiji\x02""gb\x03""cal\x02""pf\x09""microgram\x02""hz\x03""khz\x02""mm\x03""ms2\x03""kpa\x03""gpa\x05""rads2\x02""mv\x02""nw\x02""cc\x02""cd\x02""gy\x03""mol\x07"" zhuang\x04"" zei\x02"" t\x05"" zhua\x04"" sen\x04"" hei\x04"" hal\x06"" ppwun\x04"" nay\x04"" yai\x06"" sasou\x04"" kes\x05"" saai\x05"" haai\x03"" so\x07"" akutsu\x05"" gake\x05"" gomi\x04"" ama\x04"" sho\x04"" ten\x04"" gei\x03"" ki\x04"" lue\x04"" miu\x05"" moku\x06"" tochi\x06"" kasei\x07"" kunugi\x06"" hazou\x08"" katsura\x05"" tamo\x0a"" shitamizu\x07"" shibui\x05"" tani\x05"" suei\x05"" diou\x08"" oozutsu\x0d"" tsumekanmuri\x04"" swu\x0c"" deshiguramu\x0b"" miriguramu\x0b"" hekutogura\x07"" tatamu\x04"" nue\x07"" utsubo\x02"" o\x04"" sik\x07"" sasara\x05"" yana\x03""bup\x05"" hata\x03""pap\x04""purx\x05"" kuji\x08"" shinshi\x04""nbap\x05"" kume\x04""nbyx\x09"" nukamiso\x03"" ro\x04""hmyx\x05""hmyrx\x07"" sukumo\x06"" kouji\x05"" kinu\x05"" wata\x04"" sok\x05"" kase\x06"" yingl\x07"" kasuri\x05"" nawa\x07"" odoshi\x05"" horo\x04"" sem\x05"" jung\x03"" un\x04""zzyr\x08"" kaakeru\x04""ssyt\x04""zhux\x09"" yashinau\x03""jyt\x03""qie\x04""njup\x04""nyuo\x08"" shikato\x03""xie\x0a"" tsuraneru\x03""een\x04""ween\x04""bhee\x04""mbee\x04""kpee\x05""mgbee\x04""gbee\x04""dhee\x05""dhhee\x04""ndee\x04""njee\x05""nggee\x03""hin\x03""win\x03""bhi\x03""mbi\x03""kpi\x04""mgbi\x03""gbi\x03""dhi\x04""dhhi\x03""ndi\x03""nji\x04""nggi\x04""ngan\x03""han\x03""wan\x03""mba\x04""kpan\x04""mgba\x04""dhha\x03""nda\x03""nja\x04""ngga\x03""oon\x04""woon\x04""bhoo\x03""boo\x04""mboo\x04""kpoo\x05""mgboo\x04""gboo\x03""voo\x04""dhoo\x05""dhhoo\x03""doo\x04""ndoo\x03""zoo\x04""zhoo\x03""joo\x04""njoo\x05""nggoo\x03""goo\x04""nyoo\x03""hun\x03""wun\x03""bhu\x03""mbu\x03""kpu\x04""mgbu\x03""gbu\x03""dhu\x04""dhhu\x03""ndu\x03""nju\x04""nggu\x04""ngon\x03""won\x03""bho\x03""mbo\x03""kpo\x04""mgbo\x03""gbo\x04""gbon\x03""dho\x04""dhho\x03""ndo\x03""njo\x04""nggo\x04""ngen\x03""hen\x03""wen\x03""bhe\x03""mbe\x03""kpe\x04""kpen\x04""mgbe\x03""gbe\x04""gben\x03""dhe\x04""dhhe\x03""nde\x04""ngge\x05""nggen\x03""gen\x0a""lengthener\x05""ndole\x06""zemlya\x05""broad\x07""neutral\x06""closed\x07""blended\x04""soft\x09""monocular\x09""binocular\x06""double\x0b""multiocular\x03""dwe\x04""dzwe\x04""zhwe\x04""dzze\x04""tswe\x04""tsse\x04""tche\x07""chinese\x06""dotted\x09""left-stem\x05""lower\x08""inverted\x06""stress\x0d""egyptological\x04""heng\x02""tz\x08""tresillo\x09""cuatrillo\x06""broken\x03""rum\x02""vy\x0a""visigothic\x05""thorn\x04""vend\x03""con\x02""us\x03""dum\x03""lum\x03""mum\x03""num\x03""tum\x02""um\x0a""circumflex\x05""colon\x06""equals\x08""saltillo\x08""dvisvara\x07""hasanta\x03""jho\x04""ddho\x03""rro\x09""alternate\x09""voiceless\x09""aspirated\x05""haaru\x03""hta\x04""shya\x04""nyja\x02""ea\x04""ngue\x04""chha\x04""nhue\x03""nha\x04""nhja\x03""nue\x03""ppa\x03""mue\x0b"" obiyaakasu\x04"" noy\x05"" tara\x07"" yadoru\x07"" hesaki\x04""gyon\x05"" sori\x07"" yofune\x05"" susa\x06"" usagi\x04"" nuc\x0b"" kutabireru\x05"" yaji\x07"" sonoko\x04"" hie\x04""nyan\x05"" hagi\x04"" ebi\x09"" kamakiri\x03""dab\x0a"" kamishimo\x05"" yuki\x04"" ena\x06"" hitoe\x08"" chihaya\x07"" tasuki\x08"" yasashi\x03""ren\x03""roe\x07"" segare\x06"" nerau\x07"" utsuke\x03""rim\x09"" shitsuke\x07"" yagate\x07"" suberu\x04"" sip\x03"" ip\x07"" totemo\x04"" kep\x05"" sako\x07"" appare\x06"" otoko\x0b"" sakenomoto\x09"" ishiyumi\x07"" habaki\x06"" irori\x06"" ngaak\x08"" kasugai\x06"" pyeng\x04""byun\x07"" kazari\x05"" yari\x05"" yuru\x07"" phwung\x04""song\x05"" tomo\x07"" kohaze\x03"" on\x07"" oroshi\x05"" shuu\x04"" eri\x07"" namazu\x05"" todo\x07"" kajika\x03""yon\x05"" bora\x05"" mate\x05"" gori\x05"" ugui\x06"" asari\x0a"" subashiri\x09"" kazunoko\x07"" shachi\x06"" dojou\x08"" sukesou\x08"" muroaji\x07"" haraka\x02"" z\x09"" hatahata\x04"" eso\x05"" kyou\x07"" shiira\x06"" mutsu\x04"" nio\x05"" yiao\x06"" shigi\x08"" chidori\x05"" toki\x08"" ikaruga\x07"" kakesu\x06"" isuka\x0c"" kikuitadaki\x08"" tsugumi\x04""jjog\x04""jjon\x04""jjol\x04""jjom\x04""jjob\x04""jjos\x05""jjong\x04""jjoc\x04""jjwa\x05""jjwag\x05""jjwal\x06""jjwass\x05""jjwae\x02""it\x02""ip\x03""iet\x03""iex\x03""iep\x02""ax\x02""ap\x03""uox\x02""uo\x03""uop\x02""ox\x02""op\x02""ex\x03""bit\x03""bix\x03""bip\x04""biet\x04""biex\x03""bie\x04""biep\x03""bat\x03""bax\x03""bap\x04""buox\x03""buo\x04""buop\x03""bot\x03""box\x03""bop\x03""bex\x03""bep\x03""but\x03""bux\x04""burx\x03""bur\x03""byt\x03""byx\x02""by\x03""byp\x04""byrx\x03""byr\x03""pit\x03""pix\x03""pip\x04""piex\x03""pie\x04""piep\x03""pat\x03""pax\x04""puox\x03""puo\x04""puop\x03""pot\x03""pox\x03""pop\x03""put\x03""pux\x03""pup\x03""pur\x03""pyt\x03""pyx\x02""py\x03""pyp\x04""pyrx\x03""pyr\x04""bbit\x04""bbix\x03""bbi\x04""bbip\x05""bbiet\x05""bbiex\x04""bbie\x05""bbiep\x04""bbat\x04""bbax\x04""bbap\x05""bbuox\x04""bbuo\x05""bbuop\x04""bbot\x04""bbox\x03""bbo\x04""bbop\x04""bbex\x03""bbe\x04""bbep\x04""bbut\x04""bbux\x03""bbu\x04""bbup\x05""bburx\x04""bbur\x04""bbyt\x04""bbyx\x03""bby\x04""bbyp\x04""nbit\x04""nbix\x03""nbi\x04""nbip\x05""nbiex\x04""nbie\x05""nbiep\x04""nbat\x04""nbax\x03""nba\x04""nbot\x04""nbox\x03""nbo\x04""nbop\x04""nbut\x04""nbux\x03""nbu\x04""nbup\x05""nburx\x04""nbur\x04""nbyt\x03""nby\x04""nbyp\x05""nbyrx\x04""nbyr\x04""hmit\x04""hmix\x03""hmi\x04""hmip\x05""hmiex\x04""hmie\x05""hmiep\x04""hmat\x04""hmax\x03""hma\x04""hmap\x05""hmuox\x04""hmuo\x05""hmuop\x04""hmot\x04""hmox\x03""hmo\x04""hmop\x04""hmut\x04""hmux\x03""hmu\x04""hmup\x05""hmurx\x04""hmur\x03""hmy\x04""hmyp\x04""hmyr\x03""mit\x03""mix\x03""mip\x04""miex\x03""mie\x04""miep\x03""mat\x03""max\x03""map\x04""muot\x04""muox\x03""muo\x04""muop\x03""mot\x03""mox\x03""mop\x03""mex\x03""mut\x03""mux\x03""mup\x04""murx\x03""mur\x03""myt\x03""myx\x02""my\x03""myp\x03""fit\x03""fix\x03""fip\x03""fat\x03""fax\x03""fap\x03""fox\x03""fop\x03""fut\x03""fux\x03""fup\x04""furx\x03""fur\x03""fyt\x03""fyx\x02""fy\x03""fyp\x03""vit\x03""vix\x03""vip\x04""viet\x04""viex\x03""vie\x04""viep\x03""vat\x03""vax\x03""vap\x03""vot\x03""vox\x03""vop\x03""vex\x03""vep\x03""vut\x03""vux\x03""vup\x04""vurx\x03""vur\x03""vyt\x03""vyx\x03""vyp\x04""vyrx\x03""vyr\x03""dit\x03""dix\x03""dip\x04""diex\x03""die\x04""diep\x03""dat\x03""dax\x03""dap\x04""duox\x03""duo\x03""dox\x03""dop\x03""dex\x03""dep\x03""dut\x03""dux\x03""dup\x04""durx\x03""dur\x03""tit\x03""tix\x03""tip\x04""tiex\x04""tiep\x03""tat\x03""tax\x03""tap\x04""tuot\x04""tuox\x03""tuo\x04""tuop\x03""tot\x03""tox\x03""tex\x03""tep\x03""tut\x03""tux\x03""tup\x04""turx\x03""tur\x04""ddit\x04""ddix\x04""ddip\x05""ddiex\x04""ddie\x05""ddiep\x04""ddat\x04""ddax\x04""ddap\x05""dduox\x04""dduo\x05""dduop\x04""ddot\x04""ddox\x04""ddop\x04""ddex\x04""ddep\x04""ddut\x04""ddux\x04""ddup\x05""ddurx\x04""ddur\x04""ndit\x04""ndix\x04""ndip\x05""ndiex\x04""ndie\x04""ndat\x04""ndax\x04""ndap\x04""ndot\x04""ndox\x04""ndop\x04""ndex\x04""ndep\x04""ndut\x04""ndux\x04""ndup\x05""ndurx\x04""ndur\x04""hnit\x04""hnix\x03""hni\x04""hnip\x05""hniet\x05""hniex\x04""hnie\x05""hniep\x04""hnat\x04""hnax\x04""hnap\x05""hnuox\x04""hnuo\x04""hnot\x04""hnox\x04""hnop\x04""hnex\x03""hne\x04""hnep\x04""hnut\x03""nit\x03""nix\x03""nip\x04""niex\x03""nie\x04""niep\x03""nax\x03""nap\x04""nuox\x03""nuo\x04""nuop\x03""not\x03""nox\x03""nop\x03""nex\x03""nep\x03""nut\x03""nux\x03""nup\x04""nurx\x03""nur\x04""hlit\x04""hlix\x03""hli\x04""hlip\x05""hliex\x04""hlie\x05""hliep\x04""hlat\x04""hlax\x04""hlap\x05""hluox\x04""hluo\x05""hluop\x04""hlox\x03""hlo\x04""hlop\x04""hlex\x03""hle\x04""hlep\x04""hlut\x04""hlux\x03""hlu\x04""hlup\x05""hlurx\x04""hlur\x04""hlyt\x04""hlyx\x03""hly\x04""hlyp\x05""hlyrx\x04""hlyr\x03""lit\x03""lix\x03""lip\x04""liet\x04""liex\x03""lie\x04""liep\x03""lat\x03""lax\x03""lap\x04""luot\x04""luox\x03""luo\x04""luop\x03""lot\x03""lox\x03""lop\x03""lex\x03""lep\x03""lut\x03""lux\x03""lup\x04""lurx\x03""lur\x03""lyt\x03""lyx\x03""lyp\x04""lyrx\x03""lyr\x03""git\x03""gix\x03""gip\x04""giet\x04""giex\x03""gie\x04""giep\x03""gat\x03""gax\x03""gap\x04""guot\x04""guox\x03""guo\x04""guop\x03""got\x03""gox\x03""gop\x03""get\x03""gex\x03""gep\x03""gut\x03""gux\x03""gup\x04""gurx\x03""gur\x03""kit\x03""kix\x03""kip\x04""kiex\x03""kie\x04""kiep\x03""kat\x03""kax\x03""kap\x04""kuox\x03""kuo\x04""kuop\x03""kot\x03""kox\x03""kop\x03""ket\x03""kex\x03""kep\x03""kut\x03""kux\x03""kup\x04""kurx\x03""kur\x04""ggit\x04""ggix\x05""ggiex\x04""ggie\x05""ggiep\x04""ggat\x04""ggax\x04""ggap\x05""gguot\x05""gguox\x04""gguo\x05""gguop\x04""ggot\x04""ggox\x04""ggop\x04""gget\x04""ggex\x04""ggep\x04""ggut\x04""ggux\x04""ggup\x05""ggurx\x04""ggur\x05""mgiex\x04""mgie\x04""mgat\x04""mgax\x03""mga\x04""mgap\x05""mguox\x04""mguo\x05""mguop\x04""mgot\x04""mgox\x03""mgo\x04""mgop\x04""mgex\x03""mge\x04""mgep\x04""mgut\x04""mgux\x03""mgu\x04""mgup\x05""mgurx\x04""mgur\x04""hxit\x04""hxix\x03""hxi\x04""hxip\x05""hxiet\x05""hxiex\x04""hxie\x05""hxiep\x04""hxat\x04""hxax\x03""hxa\x04""hxap\x05""hxuot\x05""hxuox\x04""hxuo\x05""hxuop\x04""hxot\x04""hxox\x03""hxo\x04""hxop\x04""hxex\x03""hxe\x04""hxep\x05""ngiex\x04""ngie\x05""ngiep\x04""ngat\x04""ngax\x04""ngap\x05""nguot\x05""nguox\x04""nguo\x04""ngot\x04""ngox\x04""ngop\x04""ngex\x03""nge\x04""ngep\x03""hit\x04""hiex\x03""hat\x03""hax\x03""hap\x04""huot\x04""huox\x03""huo\x04""huop\x03""hot\x03""hox\x03""hop\x03""hex\x03""hep\x03""wat\x03""wax\x03""wap\x04""wuox\x03""wuo\x04""wuop\x03""wox\x03""wop\x03""wex\x03""wep\x03""zit\x03""zix\x03""zip\x04""ziex\x03""zie\x04""ziep\x03""zat\x03""zax\x03""zap\x04""zuox\x03""zuo\x04""zuop\x03""zot\x03""zox\x03""zop\x03""zex\x03""zep\x03""zut\x03""zux\x03""zup\x04""zurx\x03""zur\x03""zyt\x03""zyx\x02""zy\x03""zyp\x04""zyrx\x03""zyr\x03""cit\x03""cix\x03""cip\x04""ciet\x04""ciex\x03""cie\x04""ciep\x03""cat\x03""cax\x03""cap\x04""cuox\x03""cuo\x04""cuop\x03""cot\x03""cox\x03""cop\x03""cex\x03""cep\x03""cut\x03""cux\x03""cup\x04""curx\x03""cur\x03""cyt\x03""cyx\x03""cyp\x04""cyrx\x03""cyr\x04""zzit\x04""zzix\x04""zzip\x05""zziet\x05""zziex\x04""zzie\x05""zziep\x04""zzat\x04""zzax\x04""zzap\x04""zzox\x04""zzop\x04""zzex\x04""zzep\x04""zzux\x04""zzup\x05""zzurx\x04""zzur\x04""zzyt\x04""zzyx\x03""zzy\x04""zzyp\x05""zzyrx\x04""nzit\x04""nzix\x03""nzi\x04""nzip\x05""nziex\x04""nzie\x05""nziep\x04""nzat\x04""nzax\x03""nza\x04""nzap\x05""nzuox\x04""nzuo\x04""nzox\x04""nzop\x04""nzex\x03""nze\x04""nzux\x03""nzu\x04""nzup\x05""nzurx\x04""nzur\x04""nzyt\x04""nzyx\x03""nzy\x04""nzyp\x05""nzyrx\x04""nzyr\x03""sit\x03""six\x03""sip\x04""siex\x03""sie\x04""siep\x03""sat\x03""sax\x03""sap\x04""suox\x03""suo\x04""suop\x03""sot\x03""sox\x03""sop\x03""sex\x03""sep\x03""sut\x03""sux\x03""sup\x04""surx\x03""sur\x03""syt\x03""syx\x02""sy\x03""syp\x04""syrx\x03""syr\x04""ssit\x04""ssix\x04""ssip\x05""ssiex\x04""ssie\x05""ssiep\x04""ssat\x04""ssax\x04""ssap\x04""ssot\x04""ssox\x04""ssop\x04""ssex\x04""ssep\x04""ssut\x04""ssux\x04""ssup\x04""ssyx\x03""ssy\x04""ssyp\x05""ssyrx\x04""ssyr\x04""zhat\x04""zhax\x04""zhap\x05""zhuox\x04""zhuo\x05""zhuop\x04""zhot\x04""zhox\x04""zhop\x04""zhet\x04""zhex\x04""zhep\x04""zhut\x04""zhup\x05""zhurx\x04""zhur\x04""zhyt\x04""zhyx\x03""zhy\x04""zhyp\x05""zhyrx\x04""zhyr\x04""chat\x04""chax\x04""chap\x05""chuot\x05""chuox\x04""chuo\x05""chuop\x04""chot\x04""chox\x04""chop\x04""chet\x04""chex\x04""chep\x04""chux\x04""chup\x05""churx\x04""chur\x04""chyt\x04""chyx\x03""chy\x04""chyp\x05""chyrx\x04""chyr\x04""rrax\x05""rruox\x04""rruo\x04""rrot\x04""rrox\x04""rrop\x04""rret\x04""rrex\x03""rre\x04""rrep\x04""rrut\x04""rrux\x03""rru\x04""rrup\x05""rrurx\x04""rrur\x04""rryt\x04""rryx\x03""rry\x04""rryp\x05""rryrx\x04""rryr\x04""nrat\x04""nrax\x04""nrap\x04""nrox\x03""nro\x04""nrop\x04""nret\x04""nrex\x03""nre\x04""nrep\x04""nrut\x04""nrux\x03""nru\x04""nrup\x05""nrurx\x04""nrur\x04""nryt\x04""nryx\x03""nry\x04""nryp\x05""nryrx\x04""nryr\x04""shat\x04""shax\x04""shap\x05""shuox\x04""shuo\x05""shuop\x04""shot\x04""shox\x04""shop\x04""shet\x04""shex\x04""shep\x04""shut\x04""shux\x04""shup\x05""shurx\x04""shur\x04""shyt\x04""shyx\x03""shy\x04""shyp\x05""shyrx\x04""shyr\x03""rat\x03""rax\x03""rap\x04""ruox\x03""ruo\x04""ruop\x03""rot\x03""rox\x03""rop\x03""rex\x03""rep\x03""rut\x03""rux\x03""rup\x04""rurx\x03""rur\x03""ryt\x03""ryx\x03""ryp\x04""ryrx\x03""ryr\x03""jit\x03""jix\x03""jip\x04""jiet\x04""jiex\x03""jie\x04""jiep\x04""juot\x04""juox\x03""juo\x04""juop\x03""jot\x03""jox\x03""jop\x03""jut\x03""jux\x03""jup\x04""jurx\x03""jur\x03""jyx\x03""jyp\x04""jyrx\x03""jyr\x03""qit\x03""qix\x03""qip\x04""qiet\x04""qiex\x04""qiep\x04""quot\x04""quox\x04""quop\x03""qot\x03""qox\x03""qop\x03""qut\x03""qux\x03""qup\x04""qurx\x03""qur\x03""qyt\x03""qyx\x02""qy\x03""qyp\x04""qyrx\x03""qyr\x04""jjit\x04""jjix\x04""jjip\x05""jjiet\x05""jjiex\x04""jjie\x05""jjiep\x05""jjuox\x04""jjuo\x05""jjuop\x04""jjot\x04""jjox\x04""jjop\x04""jjut\x04""jjux\x04""jjup\x05""jjurx\x04""jjur\x04""jjyt\x04""jjyx\x03""jjy\x04""jjyp\x04""njit\x04""njix\x04""njip\x05""njiet\x05""njiex\x04""njie\x05""njiep\x05""njuox\x04""njuo\x04""njot\x04""njox\x04""njop\x04""njux\x05""njurx\x04""njur\x04""njyt\x04""njyx\x03""njy\x04""njyp\x05""njyrx\x04""njyr\x04""nyit\x04""nyix\x04""nyip\x05""nyiet\x05""nyiex\x04""nyie\x05""nyiep\x05""nyuox\x05""nyuop\x04""nyot\x04""nyox\x04""nyop\x04""nyut\x04""nyux\x04""nyup\x03""xit\x03""xix\x03""xip\x04""xiet\x04""xiex\x04""xiep\x04""xuox\x03""xuo\x03""xot\x03""xox\x03""xop\x03""xyt\x03""xyx\x02""xy\x03""xyp\x04""xyrx\x03""xyr\x03""yit\x03""yix\x03""yip\x04""yiet\x04""yiex\x03""yie\x04""yiep\x04""yuot\x04""yuox\x03""yuo\x04""yuop\x03""yot\x03""yox\x03""yop\x03""yut\x03""yux\x03""yup\x04""yurx\x03""yur\x03""yyt\x03""yyx\x03""yyp\x04""yyrx\x03""yyr\x03""kug\x03""kun\x03""kul\x03""kum\x03""kub\x03""kus\x04""kung\x04""kweo\x05""kweon\x05""kweol\x06""kweong\x05""kweng\x04""kwig\x04""kwin\x04""kwil\x04""kwim\x04""kwib\x04""kwis\x05""kwing\x04""kyun\x04""kyul\x04""kyum\x03""keu\x04""keug\x04""keun\x04""keul\x04""keum\x04""keub\x05""keung\x03""kig\x03""kin\x03""kil\x03""kim\x03""kib\x03""kis\x04""king\x03""tag\x03""tal\x04""talg\x03""tam\x03""tab\x03""tas\x04""tass\x04""tang\x03""tae\x04""taeg\x04""taen\x04""tael\x04""taem\x04""taeb\x04""taes\x05""taess\x05""taeng\x05""tyang\x03""teo\x04""teog\x04""teon\x04""teol\x05""teolm\x04""teom\x04""teob\x04""teos\x05""teoss\x05""teong\x03""teg\x03""ten\x03""tel\x03""tem\x03""teb\x03""tes\x04""teng\x04""tyeo\x05""tyeon\x06""tyeoss\x04""tyen\x03""tog\x03""ton\x03""tol\x03""tom\x03""tob\x03""tos\x04""tong\x04""twan\x04""twae\x03""toe\x04""toen\x04""toes\x05""toeng\x03""tug\x03""tun\x03""tul\x03""tub\x03""tus\x04""tung\x04""tweo\x06""tweoss\x04""twig\x04""twin\x04""twil\x04""twim\x04""twib\x05""twing\x03""tyu\x04""tyun\x04""tyul\x04""tyum\x05""tyung\x03""teu\x04""teug\x04""teun\x04""teud\x04""teul\x05""teulm\x04""teum\x04""teub\x04""teus\x04""tyin\x04""tyil\x04""tyim\x04""tyib\x03""tig\x03""tin\x03""til\x03""tim\x03""tib\x03""tis\x04""ting\x03""pag\x04""pagg\x03""pan\x03""pal\x04""palm\x03""pam\x03""pab\x03""pas\x04""pass\x04""pang\x03""pae\x04""paeg\x04""paen\x04""pael\x04""paem\x04""paeb\x04""paes\x05""paess\x05""paeng\x03""pya\x04""pyag\x03""peo\x04""peog\x04""peon\x04""peol\x04""peom\x04""peob\x04""peos\x05""peoss\x05""peong\x03""peg\x03""pen\x03""pel\x03""pem\x03""peb\x03""pes\x04""peng\x04""pyeo\x05""pyeon\x05""pyeol\x05""pyeom\x05""pyeob\x06""pyeoss\x06""pyeong\x03""pye\x04""pyel\x04""pyeb\x04""pyes\x03""pog\x03""pon\x03""pol\x03""pom\x03""pob\x03""pos\x04""pong\x05""pwang\x03""poe\x04""poen\x03""pyo\x04""pyon\x04""pyol\x04""pyob\x04""pyos\x03""pug\x03""pun\x03""pud\x03""pul\x04""pulm\x03""pum\x03""pub\x03""pus\x04""pung\x04""pweo\x06""pweong\x04""pwin\x04""pwil\x04""pwim\x04""pwis\x03""pyu\x04""pyun\x04""pyul\x04""pyum\x04""pyus\x05""pyung\x03""peu\x04""peun\x04""peul\x04""peum\x04""peub\x04""peus\x03""pig\x03""pin\x03""pil\x03""pim\x03""pib\x03""pis\x04""ping\x03""hag\x03""hal\x04""halt\x03""ham\x03""hab\x03""has\x04""hang\x04""haeg\x04""haen\x04""hael\x04""haem\x04""haeb\x04""haes\x05""haess\x05""haeng\x03""hya\x05""hyang\x03""heo\x04""heog\x04""heon\x04""heol\x05""heolm\x04""heom\x04""heob\x04""heos\x05""heong\x03""heg\x03""hel\x03""hem\x03""heb\x03""hes\x04""hyeo\x05""hyeog\x05""hyeon\x05""hyeol\x05""hyeom\x05""hyeob\x05""hyeos\x06""hyeoss\x06""hyeong\x03""hye\x04""hyen\x04""hyel\x04""hyeb\x03""hog\x03""hol\x04""holt\x03""hom\x03""hob\x03""hos\x04""hong\x04""hwag\x04""hwan\x04""hwal\x04""hwas\x05""hwang\x04""hwae\x05""hwaeg\x05""hwaen\x05""hwaes\x06""hwaeng\x04""hoeg\x04""hoen\x04""hoel\x04""hoeb\x04""hoes\x05""hoeng\x03""hyo\x04""hyon\x04""hyol\x04""hyob\x04""hyos\x03""hug\x03""hul\x04""hult\x03""hum\x03""hus\x04""hung\x04""hweo\x05""hweon\x05""hweol\x05""hweom\x06""hweong\x04""hweg\x03""gag\x04""gagg\x04""gags\x04""ganj\x04""ganh\x03""gad\x03""gal\x04""galg\x04""galm\x04""galb\x04""gals\x04""galt\x04""galp\x04""galh\x03""gam\x03""gab\x04""gabs\x03""gas\x04""gass\x04""gang\x03""gaj\x03""gac\x03""gak\x03""gah\x03""gae\x04""gaeg\x05""gaegg\x05""gaegs\x04""gaen\x05""gaenj\x05""gaenh\x04""gaed\x04""gael\x05""gaelg\x05""gaelm\x05""gaelb\x05""gaels\x05""gaelt\x05""gaelp\x05""gaelh\x04""gaem\x04""gaeb\x05""gaebs\x04""gaes\x05""gaess\x05""gaeng\x04""gaej\x04""gaec\x04""gaek\x04""gaet\x04""gaep\x04""gaeh\x04""gyag\x05""gyagg\x05""gyags\x04""gyan\x05""gyanj\x05""gyanh\x04""gyad\x04""gyal\x05""gyalg\x05""gyalm\x05""gyalb\x05""gyals\x05""gyalt\x05""gyalp\x05""gyalh\x04""gyam\x04""gyab\x05""gyabs\x04""gyas\x05""gyass\x05""gyang\x04""gyaj\x04""gyac\x04""gyak\x04""gyat\x04""gyap\x04""gyah\x04""gyae\x05""gyaeg\x06""gyaegg\x06""gyaegs\x05""gyaen\x06""gyaenj\x06""gyaenh\x05""gyaed\x05""gyael\x06""gyaelg\x06""gyaelm\x06""gyaelb\x06""gyaels\x06""gyaelt\x06""gyaelp\x06""gyaelh\x05""gyaem\x05""gyaeb\x06""gyaebs\x05""gyaes\x06""gyaess\x06""gyaeng\x05""gyaej\x05""gyaec\x05""gyaek\x05""gyaet\x05""gyaep\x05""gyaeh\x04""geog\x05""geogg\x05""geogs\x04""geon\x05""geonj\x05""geonh\x04""geod\x04""geol\x05""geolg\x05""geolm\x05""geolb\x05""geols\x05""geolt\x05""geolp\x05""geolh\x04""geom\x04""geob\x05""geobs\x04""geos\x05""geoss\x05""geong\x04""geoj\x04""geoc\x04""geok\x04""geot\x04""geop\x04""geoh\x03""geg\x04""gegg\x04""gegs\x04""genj\x04""genh\x03""ged\x03""gel\x04""gelg\x04""gelm\x04""gelb\x04""gels\x04""gelt\x04""gelp\x04""gelh\x03""gem\x03""geb\x04""gebs\x03""ges\x04""gess\x04""geng\x03""gej\x03""gec\x03""gek\x03""geh\x04""gyeo\x05""gyeog\x06""gyeogg\x06""gyeogs\x05""gyeon\x06""gyeonj\x06""gyeonh\x05""gyeod\x05""gyeol\x06""gyeolg\x06""gyeolm\x06""gyeolb\x06""gyeols\x06""gyeolt\x06""gyeolp\x06""gyeolh\x05""gyeom\x05""gyeob\x06""gyeobs\x05""gyeos\x06""gyeoss\x06""gyeong\x05""gyeoj\x05""gyeoc\x05""gyeok\x05""gyeot\x05""gyeop\x05""gyeoh\x04""gyeg\x05""gyegg\x05""gyegs\x04""gyen\x05""gyenj\x05""gyenh\x04""gyed\x04""gyel\x05""gyelg\x05""gyelm\x05""gyelb\x05""gyels\x05""gyelt\x05""gyelp\x05""gyelh\x04""gyem\x04""gyeb\x05""gyebs\x04""gyes\x05""gyess\x05""gyeng\x04""gyej\x04""gyec\x04""gyek\x04""gyet\x04""gyep\x04""gyeh\x03""gog\x04""gogg\x04""gogs\x03""gon\x04""gonj\x04""gonh\x03""god\x03""gol\x04""golg\x04""golm\x04""golb\x04""gols\x04""golt\x04""golp\x04""golh\x03""gom\x03""gob\x04""gobs\x03""gos\x04""goss\x04""gong\x03""goj\x03""goc\x03""gok\x03""goh\x04""gwag\x05""gwagg\x05""gwags\x04""gwan\x05""gwanj\x05""gwanh\x04""gwad\x04""gwal\x05""gwalg\x05""gwalm\x05""gwalb\x05""gwals\x05""gwalt\x05""gwalp\x05""gwalh\x04""gwam\x04""gwab\x05""gwabs\x04""gwas\x05""gwass\x05""gwang\x04""gwaj\x04""gwac\x04""gwak\x04""gwat\x04""gwap\x04""gwah\x04""gwae\x05""gwaeg\x06""gwaegg\x06""gwaegs\x05""gwaen\x06""gwaenj\x06""gwaenh\x05""gwaed\x05""gwael\x06""gwaelg\x06""gwaelm\x06""gwaelb\x06""gwaels\x06""gwaelt\x06""gwaelp\x06""gwaelh\x05""gwaem\x05""gwaeb\x06""gwaebs\x05""gwaes\x06""gwaess\x06""gwaeng\x05""gwaej\x05""gwaec\x05""gwaek\x05""gwaet\x05""gwaep\x05""gwaeh\x03""goe\x04""goeg\x05""goegg\x05""goegs\x04""goen\x05""goenj\x05""goenh\x04""goed\x04""goel\x05""goelg\x05""goelm\x05""goelb\x05""goels\x05""goelt\x05""goelp\x05""goelh\x04""goem\x04""goeb\x05""goebs\x04""goes\x05""goess\x05""goeng\x04""goej\x04""goec\x04""goek\x04""goet\x04""goep\x04""goeh\x04""gyog\x05""gyogg\x05""gyogs\x05""gyonj\x05""gyonh\x04""gyod\x04""gyol\x05""gyolg\x05""gyolm\x05""gyolb\x05""gyols\x05""gyolt\x05""gyolp\x05""gyolh\x04""gyom\x04""gyob\x05""gyobs\x04""gyos\x05""gyoss\x05""gyong\x04""gyoj\x04""gyoc\x04""gyok\x04""gyot\x04""gyop\x04""gyoh\x03""gug\x04""gugg\x04""gugs\x03""gun\x04""gunj\x04""gunh\x03""gud\x03""gul\x04""gulg\x04""gulm\x04""gulb\x04""guls\x04""gult\x04""gulp\x04""gulh\x03""gum\x03""gub\x04""gubs\x03""gus\x04""guss\x04""gung\x03""guj\x03""guc\x03""guk\x03""guh\x04""gweo\x05""gweog\x06""gweogg\x06""gweogs\x05""gweon\x06""gweonj\x06""gweonh\x05""gweod\x05""gweol\x06""gweolg\x06""gweolm\x06""gweolb\x06""gweols\x06""gweolt\x06""gweolp\x06""gweolh\x05""gweom\x05""gweob\x06""gweobs\x05""gweos\x06""gweoss\x06""gweong\x05""gweoj\x05""gweoc\x05""gweok\x05""gweot\x05""gweop\x05""gweoh\x04""gweg\x05""gwegg\x05""gwegs\x04""gwen\x05""gwenj\x05""gwenh\x04""gwed\x04""gwel\x05""gwelg\x05""gwelm\x05""gwelb\x05""gwels\x05""gwelt\x05""gwelp\x05""gwelh\x04""gwem\x04""gweb\x05""gwebs\x04""gwes\x05""gwess\x05""gweng\x04""gwej\x04""gwec\x04""gwek\x04""gwet\x04""gwep\x04""gweh\x04""gwig\x05""gwigg\x05""gwigs\x04""gwin\x05""gwinj\x05""gwinh\x04""gwid\x04""gwil\x05""gwilg\x05""gwilm\x05""gwilb\x05""gwils\x05""gwilt\x05""gwilp\x05""gwilh\x04""gwim\x04""gwib\x05""gwibs\x04""gwis\x05""gwiss\x05""gwing\x04""gwij\x04""gwic\x04""gwik\x04""gwit\x04""gwip\x04""gwih\x04""gyug\x05""gyugg\x05""gyugs\x04""gyun\x05""gyunj\x05""gyunh\x04""gyud\x04""gyul\x05""gyulg\x05""gyulm\x05""gyulb\x05""gyuls\x05""gyult\x05""gyulp\x05""gyulh\x04""gyum\x04""gyub\x05""gyubs\x04""gyus\x05""gyuss\x05""gyung\x04""gyuj\x04""gyuc\x04""gyuk\x04""gyut\x04""gyup\x04""gyuh\x03""geu\x04""geug\x05""geugg\x05""geugs\x04""geun\x05""geunj\x05""geunh\x04""geud\x04""geul\x05""geulg\x05""geulm\x05""geulb\x05""geuls\x05""geult\x05""geulp\x05""geulh\x04""geum\x04""geub\x05""geubs\x04""geus\x04""geuj\x04""geuc\x04""geuk\x04""geut\x04""geup\x04""geuh\x04""gyig\x05""gyigg\x05""gyigs\x04""gyin\x05""gyinj\x05""gyinh\x04""gyid\x04""gyil\x05""gyilg\x05""gyilm\x05""gyilb\x05""gyils\x05""gyilt\x05""gyilp\x05""gyilh\x04""gyim\x04""gyib\x05""gyibs\x04""gyis\x05""gyiss\x05""gying\x04""gyij\x04""gyic\x04""gyik\x04""gyit\x04""gyip\x04""gyih\x03""gig\x04""gigg\x04""gigs\x03""gin\x04""ginj\x04""ginh\x03""gid\x03""gil\x04""gilg\x04""gilm\x04""gilb\x04""gils\x04""gilt\x04""gilp\x04""gilh\x03""gim\x03""gib\x04""gibs\x03""gis\x04""giss\x04""ging\x03""gij\x03""gic\x03""gik\x03""gih\x04""ggag\x05""ggagg\x05""ggags\x04""ggan\x05""gganj\x05""gganh\x04""ggad\x04""ggal\x05""ggalg\x05""ggalm\x05""ggalb\x05""ggals\x05""ggalt\x05""ggalp\x05""ggalh\x04""ggam\x04""ggab\x05""ggabs\x04""ggas\x05""ggass\x05""ggang\x04""ggaj\x04""ggac\x04""ggak\x04""ggah\x04""ggae\x05""ggaeg\x06""ggaegg\x06""ggaegs\x05""ggaen\x06""ggaenj\x06""ggaenh\x05""ggaed\x05""ggael\x06""ggaelg\x06""ggaelm\x06""ggaelb\x06""ggaels\x06""ggaelt\x06""ggaelp\x06""ggaelh\x05""ggaem\x05""ggaeb\x06""ggaebs\x05""ggaes\x06""ggaess\x06""ggaeng\x05""ggaej\x05""ggaec\x05""ggaek\x05""ggaet\x05""ggaep\x05""ggaeh\x04""ggya\x05""ggyag\x06""ggyagg\x06""ggyags\x05""ggyan\x06""ggyanj\x06""ggyanh\x05""ggyad\x05""ggyal\x06""ggyalg\x06""ggyalm\x06""ggyalb\x06""ggyals\x06""ggyalt\x06""ggyalp\x06""ggyalh\x05""ggyam\x05""ggyab\x06""ggyabs\x05""ggyas\x06""ggyass\x06""ggyang\x05""ggyaj\x05""ggyac\x05""ggyak\x05""ggyat\x05""ggyap\x05""ggyah\x05""ggyae\x06""ggyaeg\x07""ggyaegg\x07""ggyaegs\x06""ggyaen\x07""ggyaenj\x07""ggyaenh\x06""ggyaed\x06""ggyael\x07""ggyaelg\x07""ggyaelm\x07""ggyaelb\x07""ggyaels\x07""ggyaelt\x07""ggyaelp\x07""ggyaelh\x06""ggyaem\x06""ggyaeb\x07""ggyaebs\x06""ggyaes\x07""ggyaess\x07""ggyaeng\x06""ggyaej\x06""ggyaec\x06""ggyaek\x06""ggyaet\x06""ggyaep\x06""ggyaeh\x04""ggeo\x05""ggeog\x06""ggeogg\x06""ggeogs\x05""ggeon\x06""ggeonj\x06""ggeonh\x05""ggeod\x05""ggeol\x06""ggeolg\x06""ggeolm\x06""ggeolb\x06""ggeols\x06""ggeolt\x06""ggeolp\x06""ggeolh\x05""ggeom\x05""ggeob\x06""ggeobs\x05""ggeos\x06""ggeoss\x06""ggeong\x05""ggeoj\x05""ggeoc\x05""ggeok\x05""ggeot\x05""ggeop\x05""ggeoh\x04""ggeg\x05""ggegg\x05""ggegs\x04""ggen\x05""ggenj\x05""ggenh\x04""gged\x04""ggel\x05""ggelg\x05""ggelm\x05""ggelb\x05""ggels\x05""ggelt\x05""ggelp\x05""ggelh\x04""ggem\x04""ggeb\x05""ggebs\x04""gges\x05""ggess\x05""ggeng\x04""ggej\x04""ggec\x04""ggek\x04""ggeh\x05""ggyeo\x06""ggyeog\x07""ggyeogg\x07""ggyeogs\x06""ggyeon\x07""ggyeonj\x07""ggyeonh\x06""ggyeod\x06""ggyeol\x07""ggyeolg\x07""ggyeolm\x07""ggyeolb\x07""ggyeols\x07""ggyeolt\x07""ggyeolp\x07""ggyeolh\x06""ggyeom\x06""ggyeob\x07""ggyeobs\x06""ggyeos\x07""ggyeoss\x07""ggyeong\x06""ggyeoj\x06""ggyeoc\x06""ggyeok\x06""ggyeot\x06""ggyeop\x06""ggyeoh\x04""ggye\x05""ggyeg\x06""ggyegg\x06""ggyegs\x05""ggyen\x06""ggyenj\x06""ggyenh\x05""ggyed\x05""ggyel\x06""ggyelg\x06""ggyelm\x06""ggyelb\x06""ggyels\x06""ggyelt\x06""ggyelp\x06""ggyelh\x05""ggyem\x05""ggyeb\x06""ggyebs\x05""ggyes\x06""ggyess\x06""ggyeng\x05""ggyej\x05""ggyec\x05""ggyek\x05""ggyet\x05""ggyep\x05""ggyeh\x04""ggog\x05""ggogg\x05""ggogs\x04""ggon\x05""ggonj\x05""ggonh\x04""ggod\x04""ggol\x05""ggolg\x05""ggolm\x05""ggolb\x05""ggols\x05""ggolt\x05""ggolp\x05""ggolh\x04""ggom\x04""ggob\x05""ggobs\x04""ggos\x05""ggoss\x05""ggong\x04""ggoj\x04""ggoc\x04""ggok\x04""ggoh\x05""ggwag\x06""ggwagg\x06""ggwags\x05""ggwan\x06""ggwanj\x06""ggwanh\x05""ggwad\x05""ggwal\x06""ggwalg\x06""ggwalm\x06""ggwalb\x06""ggwals\x06""ggwalt\x06""ggwalp\x06""ggwalh\x05""ggwam\x05""ggwab\x06""ggwabs\x05""ggwas\x06""ggwass\x06""ggwang\x05""ggwaj\x05""ggwac\x05""ggwak\x05""ggwat\x05""ggwap\x05""ggwah\x05""ggwae\x06""ggwaeg\x07""ggwaegg\x07""ggwaegs\x06""ggwaen\x07""ggwaenj\x07""ggwaenh\x06""ggwaed\x06""ggwael\x07""ggwaelg\x07""ggwaelm\x07""ggwaelb\x07""ggwaels\x07""ggwaelt\x07""ggwaelp\x07""ggwaelh\x06""ggwaem\x06""ggwaeb\x07""ggwaebs\x06""ggwaes\x07""ggwaess\x07""ggwaeng\x06""ggwaej\x06""ggwaec\x06""ggwaek\x06""ggwaet\x06""ggwaep\x06""ggwaeh\x04""ggoe\x05""ggoeg\x06""ggoegg\x06""ggoegs\x05""ggoen\x06""ggoenj\x06""ggoenh\x05""ggoed\x05""ggoel\x06""ggoelg\x06""ggoelm\x06""ggoelb\x06""ggoels\x06""ggoelt\x06""ggoelp\x06""ggoelh\x05""ggoem\x05""ggoeb\x06""ggoebs\x05""ggoes\x06""ggoess\x06""ggoeng\x05""ggoej\x05""ggoec\x05""ggoek\x05""ggoet\x05""ggoep\x05""ggoeh\x04""ggyo\x05""ggyog\x06""ggyogg\x06""ggyogs\x05""ggyon\x06""ggyonj\x06""ggyonh\x05""ggyod\x05""ggyol\x06""ggyolg\x06""ggyolm\x06""ggyolb\x06""ggyols\x06""ggyolt\x06""ggyolp\x06""ggyolh\x05""ggyom\x05""ggyob\x06""ggyobs\x05""ggyos\x06""ggyoss\x06""ggyong\x05""ggyoj\x05""ggyoc\x05""ggyok\x05""ggyot\x05""ggyop\x05""ggyoh\x04""ggug\x05""ggugg\x05""ggugs\x04""ggun\x05""ggunj\x05""ggunh\x04""ggud\x04""ggul\x05""ggulg\x05""ggulm\x05""ggulb\x05""gguls\x05""ggult\x05""ggulp\x05""ggulh\x04""ggum\x04""ggub\x05""ggubs\x04""ggus\x05""gguss\x05""ggung\x04""gguj\x04""gguc\x04""gguk\x04""gguh\x05""ggweo\x06""ggweog\x07""ggweogg\x07""ggweogs\x06""ggweon\x07""ggweonj\x07""ggweonh\x06""ggweod\x06""ggweol\x07""ggweolg\x07""ggweolm\x07""ggweolb\x07""ggweols\x07""ggweolt\x07""ggweolp\x07""ggweolh\x06""ggweom\x06""ggweob\x07""ggweobs\x06""ggweos\x07""ggweoss\x07""ggweong\x06""ggweoj\x06""ggweoc\x06""ggweok\x06""ggweot\x06""ggweop\x06""ggweoh\x05""ggweg\x06""ggwegg\x06""ggwegs\x05""ggwen\x06""ggwenj\x06""ggwenh\x05""ggwed\x05""ggwel\x06""ggwelg\x06""ggwelm\x06""ggwelb\x06""ggwels\x06""ggwelt\x06""ggwelp\x06""ggwelh\x05""ggwem\x05""ggweb\x06""ggwebs\x05""ggwes\x06""ggwess\x06""ggweng\x05""ggwej\x05""ggwec\x05""ggwek\x05""ggwet\x05""ggwep\x05""ggweh\x05""ggwig\x06""ggwigg\x06""ggwigs\x05""ggwin\x06""ggwinj\x06""ggwinh\x05""ggwid\x05""ggwil\x06""ggwilg\x06""ggwilm\x06""ggwilb\x06""ggwils\x06""ggwilt\x06""ggwilp\x06""ggwilh\x05""ggwim\x05""ggwib\x06""ggwibs\x05""ggwis\x06""ggwiss\x06""ggwing\x05""ggwij\x05""ggwic\x05""ggwik\x05""ggwit\x05""ggwip\x05""ggwih\x04""ggyu\x05""ggyug\x06""ggyugg\x06""ggyugs\x05""ggyun\x06""ggyunj\x06""ggyunh\x05""ggyud\x05""ggyul\x06""ggyulg\x06""ggyulm\x06""ggyulb\x06""ggyuls\x06""ggyult\x06""ggyulp\x06""ggyulh\x05""ggyum\x05""ggyub\x06""ggyubs\x05""ggyus\x06""ggyuss\x06""ggyung\x05""ggyuj\x05""ggyuc\x05""ggyuk\x05""ggyut\x05""ggyup\x05""ggyuh\x04""ggeu\x05""ggeug\x06""ggeugg\x06""ggeugs\x05""ggeun\x06""ggeunj\x06""ggeunh\x05""ggeud\x05""ggeul\x06""ggeulg\x06""ggeulm\x06""ggeulb\x06""ggeuls\x06""ggeult\x06""ggeulp\x06""ggeulh\x05""ggeum\x05""ggeub\x06""ggeubs\x05""ggeus\x06""ggeuss\x06""ggeung\x05""ggeuj\x05""ggeuc\x05""ggeuk\x05""ggeut\x05""ggeup\x05""ggeuh\x04""ggyi\x05""ggyig\x06""ggyigg\x06""ggyigs\x05""ggyin\x06""ggyinj\x06""ggyinh\x05""ggyid\x05""ggyil\x06""ggyilg\x06""ggyilm\x06""ggyilb\x06""ggyils\x06""ggyilt\x06""ggyilp\x06""ggyilh\x05""ggyim\x05""ggyib\x06""ggyibs\x05""ggyis\x06""ggyiss\x06""ggying\x05""ggyij\x05""ggyic\x05""ggyik\x05""ggyit\x05""ggyip\x05""ggyih\x04""ggig\x05""ggigg\x05""ggigs\x04""ggin\x05""gginj\x05""gginh\x04""ggid\x04""ggil\x05""ggilg\x05""ggilm\x05""ggilb\x05""ggils\x05""ggilt\x05""ggilp\x05""ggilh\x04""ggim\x04""ggib\x05""ggibs\x04""ggis\x05""ggiss\x05""gging\x04""ggij\x04""ggic\x04""ggik\x04""ggip\x04""ggih\x03""nag\x04""nagg\x04""nags\x03""nan\x04""nanj\x04""nanh\x03""nad\x03""nal\x04""nalg\x04""nalm\x04""nalb\x04""nals\x04""nalt\x04""nalp\x04""nalh\x03""nam\x03""nab\x04""nabs\x03""nas\x04""nass\x04""nang\x03""naj\x03""nac\x03""nak\x03""nat\x03""nae\x04""naeg\x05""naegg\x05""naegs\x04""naen\x05""naenj\x05""naenh\x04""naed\x04""nael\x05""naelg\x05""naelm\x05""naelb\x05""naels\x05""naelt\x05""naelp\x05""naelh\x04""naem\x04""naeb\x05""naebs\x04""naes\x05""naess\x05""naeng\x04""naej\x04""naec\x04""naek\x04""naet\x04""naep\x04""naeh\x04""nyag\x05""nyagg\x05""nyags\x05""nyanj\x05""nyanh\x04""nyad\x04""nyal\x05""nyalg\x05""nyalm\x05""nyalb\x05""nyals\x05""nyalt\x05""nyalp\x05""nyalh\x04""nyam\x04""nyab\x05""nyabs\x04""nyas\x05""nyass\x05""nyang\x04""nyaj\x04""nyac\x04""nyak\x04""nyat\x04""nyap\x04""nyah\x04""nyae\x05""nyaeg\x06""nyaegg\x06""nyaegs\x05""nyaen\x06""nyaenj\x06""nyaenh\x05""nyaed\x05""nyael\x06""nyaelg\x06""nyaelm\x06""nyaelb\x06""nyaels\x06""nyaelt\x06""nyaelp\x06""nyaelh\x05""nyaem\x05""nyaeb\x06""nyaebs\x05""nyaes\x06""nyaess\x06""nyaeng\x05""nyaej\x05""nyaec\x05""nyaek\x05""nyaet\x05""nyaep\x05""nyaeh\x03""neo\x04""neog\x05""neogg\x05""neogs\x04""neon\x05""neonj\x05""neonh\x04""neod\x04""neol\x05""neolg\x05""neolm\x05""neolb\x05""neols\x05""neolt\x05""neolp\x05""neolh\x04""neom\x04""neob\x05""neobs\x04""neos\x05""neoss\x05""neong\x04""neoj\x04""neoc\x04""neok\x04""neot\x04""neop\x04""neoh\x03""neg\x04""negg\x04""negs\x03""nen\x04""nenj\x04""nenh\x03""ned\x03""nel\x04""nelg\x04""nelm\x04""nelb\x04""nels\x04""nelt\x04""nelp\x04""nelh\x03""nem\x03""neb\x04""nebs\x03""nes\x04""ness\x04""neng\x03""nej\x03""nec\x03""nek\x03""net\x03""neh\x04""nyeo\x05""nyeog\x06""nyeogg\x06""nyeogs\x05""nyeon\x06""nyeonj\x06""nyeonh\x05""nyeod\x05""nyeol\x06""nyeolg\x06""nyeolm\x06""nyeolb\x06""nyeols\x06""nyeolt\x06""nyeolp\x06""nyeolh\x05""nyeom\x05""nyeob\x06""nyeobs\x05""nyeos\x06""nyeoss\x06""nyeong\x05""nyeoj\x05""nyeoc\x05""nyeok\x05""nyeot\x05""nyeop\x05""nyeoh\x04""nyeg\x05""nyegg\x05""nyegs\x04""nyen\x05""nyenj\x05""nyenh\x04""nyed\x04""nyel\x05""nyelg\x05""nyelm\x05""nyelb\x05""nyels\x05""nyelt\x05""nyelp\x05""nyelh\x04""nyem\x04""nyeb\x05""nyebs\x04""nyes\x05""nyess\x05""nyeng\x04""nyej\x04""nyec\x04""nyek\x04""nyet\x04""nyep\x04""nyeh\x03""nog\x04""nogg\x04""nogs\x03""non\x04""nonj\x04""nonh\x03""nod\x03""nol\x04""nolg\x04""nolm\x04""nolb\x04""nols\x04""nolt\x04""nolp\x04""nolh\x03""nom\x03""nob\x04""nobs\x03""nos\x04""noss\x04""nong\x03""noj\x03""noc\x03""nok\x03""noh\x04""nwag\x05""nwagg\x05""nwags\x04""nwan\x05""nwanj\x05""nwanh\x04""nwad\x04""nwal\x05""nwalg\x05""nwalm\x05""nwalb\x05""nwals\x05""nwalt\x05""nwalp\x05""nwalh\x04""nwam\x04""nwab\x05""nwabs\x04""nwas\x05""nwass\x05""nwang\x04""nwaj\x04""nwac\x04""nwak\x04""nwat\x04""nwap\x04""nwah\x04""nwae\x05""nwaeg\x06""nwaegg\x06""nwaegs\x05""nwaen\x06""nwaenj\x06""nwaenh\x05""nwaed\x05""nwael\x06""nwaelg\x06""nwaelm\x06""nwaelb\x06""nwaels\x06""nwaelt\x06""nwaelp\x06""nwaelh\x05""nwaem\x05""nwaeb\x06""nwaebs\x05""nwaes\x06""nwaess\x06""nwaeng\x05""nwaej\x05""nwaec\x05""nwaek\x05""nwaet\x05""nwaep\x05""nwaeh\x03""noe\x04""noeg\x05""noegg\x05""noegs\x04""noen\x05""noenj\x05""noenh\x04""noed\x04""noel\x05""noelg\x05""noelm\x05""noelb\x05""noels\x05""noelt\x05""noelp\x05""noelh\x04""noem\x04""noeb\x05""noebs\x04""noes\x05""noess\x05""noeng\x04""noej\x04""noec\x04""noek\x04""noet\x04""noep\x04""noeh\x04""nyog\x05""nyogg\x05""nyogs\x04""nyon\x05""nyonj\x05""nyonh\x04""nyod\x04""nyol\x05""nyolg\x05""nyolm\x05""nyolb\x05""nyols\x05""nyolt\x05""nyolp\x05""nyolh\x04""nyom\x04""nyob\x05""nyobs\x04""nyos\x05""nyoss\x05""nyong\x04""nyoj\x04""nyoc\x04""nyok\x04""nyoh\x03""nug\x04""nugg\x04""nugs\x03""nun\x04""nunj\x04""nunh\x03""nud\x03""nul\x04""nulg\x04""nulm\x04""nulb\x04""nuls\x04""nult\x04""nulp\x04""nulh\x03""nub\x04""nubs\x03""nus\x04""nuss\x04""nung\x03""nuj\x03""nuc\x03""nuk\x03""nuh\x04""nweo\x05""nweog\x06""nweogg\x06""nweogs\x05""nweon\x06""nweonj\x06""nweonh\x05""nweod\x05""nweol\x06""nweolg\x06""nweolm\x06""nweolb\x06""nweols\x06""nweolt\x06""nweolp\x06""nweolh\x05""nweom\x05""nweob\x06""nweobs\x05""nweos\x06""nweoss\x06""nweong\x05""nweoj\x05""nweoc\x05""nweok\x05""nweot\x05""nweop\x05""nweoh\x04""nweg\x05""nwegg\x05""nwegs\x04""nwen\x05""nwenj\x05""nwenh\x04""nwed\x04""nwel\x05""nwelg\x05""nwelm\x05""nwelb\x05""nwels\x05""nwelt\x05""nwelp\x05""nwelh\x04""nwem\x04""nweb\x05""nwebs\x04""nwes\x05""nwess\x05""nweng\x04""nwej\x04""nwec\x04""nwek\x04""nwet\x04""nwep\x04""nweh\x03""nwi\x04""nwig\x05""nwigg\x05""nwigs\x04""nwin\x05""nwinj\x05""nwinh\x04""nwid\x04""nwil\x05""nwilg\x05""nwilm\x05""nwilb\x05""nwils\x05""nwilt\x05""nwilp\x05""nwilh\x04""nwim\x04""nwib\x05""nwibs\x04""nwis\x05""nwiss\x05""nwing\x04""nwij\x04""nwic\x04""nwik\x04""nwit\x04""nwip\x04""nwih\x04""nyug\x05""nyugg\x05""nyugs\x04""nyun\x05""nyunj\x05""nyunh\x04""nyud\x04""nyul\x05""nyulg\x05""nyulm\x05""nyulb\x05""nyuls\x05""nyult\x05""nyulp\x05""nyulh\x04""nyum\x04""nyub\x05""nyubs\x04""nyus\x05""nyuss\x05""nyung\x04""nyuj\x04""nyuc\x04""nyuk\x04""nyuh\x03""neu\x04""neug\x05""neugg\x05""neugs\x04""neun\x05""neunj\x05""neunh\x04""neud\x04""neul\x05""neulg\x05""neulm\x05""neulb\x05""neuls\x05""neult\x05""neulp\x05""neulh\x04""neum\x04""neub\x05""neubs\x04""neus\x05""neuss\x05""neung\x04""neuj\x04""neuc\x04""neuk\x04""neut\x04""neup\x04""neuh\x04""nyig\x05""nyigg\x05""nyigs\x04""nyin\x05""nyinj\x05""nyinh\x04""nyid\x04""nyil\x05""nyilg\x05""nyilm\x05""nyilb\x05""nyils\x05""nyilt\x05""nyilp\x05""nyilh\x04""nyim\x04""nyib\x05""nyibs\x04""nyis\x05""nyiss\x05""nying\x04""nyij\x04""nyic\x04""nyik\x04""nyih\x03""nig\x04""nigg\x04""nigs\x03""nin\x04""ninj\x04""ninh\x03""nid\x03""nil\x04""nilg\x04""nilm\x04""nilb\x04""nils\x04""nilt\x04""nilp\x04""nilh\x03""nim\x03""nib\x04""nibs\x03""nis\x04""niss\x04""ning\x03""nij\x03""nic\x03""nik\x03""nih\x03""dag\x04""dagg\x04""dags\x03""dan\x04""danj\x04""danh\x03""dad\x04""dalg\x04""dalm\x04""dalb\x04""dals\x04""dalt\x04""dalp\x04""dalh\x03""dam\x04""dabs\x03""das\x04""dass\x04""dang\x03""daj\x03""dac\x03""dak\x03""dah\x03""dae\x04""daeg\x05""daegg\x05""daegs\x04""daen\x05""daenj\x05""daenh\x04""daed\x04""dael\x05""daelg\x05""daelm\x05""daelb\x05""daels\x05""daelt\x05""daelp\x05""daelh\x04""daem\x04""daeb\x05""daebs\x04""daes\x05""daess\x05""daeng\x04""daej\x04""daec\x04""daek\x04""daet\x04""daep\x04""daeh\x03""dya\x04""dyag\x05""dyagg\x05""dyags\x04""dyan\x05""dyanj\x05""dyanh\x04""dyad\x04""dyal\x05""dyalg\x05""dyalm\x05""dyalb\x05""dyals\x05""dyalt\x05""dyalp\x05""dyalh\x04""dyam\x04""dyab\x05""dyabs\x04""dyas\x05""dyass\x05""dyang\x04""dyaj\x04""dyac\x04""dyak\x04""dyat\x04""dyap\x04""dyah\x04""dyae\x05""dyaeg\x06""dyaegg\x06""dyaegs\x05""dyaen\x06""dyaenj\x06""dyaenh\x05""dyaed\x05""dyael\x06""dyaelg\x06""dyaelm\x06""dyaelb\x06""dyaels\x06""dyaelt\x06""dyaelp\x06""dyaelh\x05""dyaem\x05""dyaeb\x06""dyaebs\x05""dyaes\x06""dyaess\x06""dyaeng\x05""dyaej\x05""dyaec\x05""dyaek\x05""dyaet\x05""dyaep\x05""dyaeh\x03""deo\x04""deog\x05""deogg\x05""deogs\x04""deon\x05""deonj\x05""deonh\x04""deod\x04""deol\x05""deolg\x05""deolm\x05""deolb\x05""deols\x05""deolt\x05""deolp\x05""deolh\x04""deom\x04""deob\x05""deobs\x04""deos\x05""deoss\x05""deong\x04""deoj\x04""deoc\x04""deok\x04""deot\x04""deop\x04""deoh\x04""degg\x04""degs\x03""den\x04""denj\x04""denh\x03""ded\x03""del\x04""delg\x04""delm\x04""delb\x04""dels\x04""delt\x04""delp\x04""delh\x03""dem\x03""deb\x04""debs\x03""des\x04""dess\x04""deng\x03""dej\x03""dec\x03""dek\x03""det\x03""deh\x04""dyeo\x05""dyeog\x06""dyeogg\x06""dyeogs\x05""dyeon\x06""dyeonj\x06""dyeonh\x05""dyeod\x05""dyeol\x06""dyeolg\x06""dyeolm\x06""dyeolb\x06""dyeols\x06""dyeolt\x06""dyeolp\x06""dyeolh\x05""dyeom\x05""dyeob\x06""dyeobs\x05""dyeos\x06""dyeoss\x06""dyeong\x05""dyeoj\x05""dyeoc\x05""dyeok\x05""dyeot\x05""dyeop\x05""dyeoh\x03""dye\x04""dyeg\x05""dyegg\x05""dyegs\x04""dyen\x05""dyenj\x05""dyenh\x04""dyed\x04""dyel\x05""dyelg\x05""dyelm\x05""dyelb\x05""dyels\x05""dyelt\x05""dyelp\x05""dyelh\x04""dyem\x04""dyeb\x05""dyebs\x04""dyes\x05""dyess\x05""dyeng\x04""dyej\x04""dyec\x04""dyek\x04""dyet\x04""dyep\x04""dyeh\x03""dog\x04""dogg\x04""dogs\x04""donj\x04""donh\x03""dod\x03""dol\x04""dolg\x04""dolm\x04""dolb\x04""dols\x04""dolt\x04""dolp\x04""dolh\x03""dom\x03""dob\x04""dobs\x03""dos\x04""doss\x04""dong\x03""doj\x03""doc\x03""dok\x03""doh\x04""dwag\x05""dwagg\x05""dwags\x04""dwan\x05""dwanj\x05""dwanh\x04""dwad\x04""dwal\x05""dwalg\x05""dwalm\x05""dwalb\x05""dwals\x05""dwalt\x05""dwalp\x05""dwalh\x04""dwam\x04""dwab\x05""dwabs\x04""dwas\x05""dwass\x05""dwang\x04""dwaj\x04""dwac\x04""dwak\x04""dwat\x04""dwap\x04""dwah\x04""dwae\x05""dwaeg\x06""dwaegg\x06""dwaegs"
-#define UTFASCIILOOKUP {1,1,1,1,1,1,1,1,1,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,2,2,1,1,2,2,2,2,4,6,8,10,12,14,16,18,20,22,2,2,1,1,1,1,1,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,1,2,1,1,1,1,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,28,76,1,72,1,79,1,28,24,1,1,2,58,2,82,86,8,10,1,64,54,1,2,6,52,1,88,91,94,1,24,24,24,24,24,24,97,28,32,32,32,32,40,40,40,40,30,50,52,52,52,52,52,70,52,64,64,64,64,64,100,79,24,24,24,24,24,24,97,28,32,32,32,32,40,40,40,40,30,50,52,52,52,52,52,2,52,64,64,64,64,72,100,72,24,24,24,24,24,24,28,28,28,28,28,28,28,28,30,30,30,30,32,32,32,32,32,32,32,32,32,32,36,36,36,36,36,36,36,36,38,38,38,38,40,40,40,40,40,40,40,40,40,40,103,103,42,42,44,44,44,46,46,46,46,46,46,46,46,46,46,50,50,50,50,50,50,50,106,106,52,52,52,52,52,52,109,109,58,58,58,58,58,58,60,60,60,60,60,60,60,60,62,62,62,62,62,62,64,64,64,64,64,64,64,64,64,64,64,64,68,68,72,72,72,74,74,74,74,74,74,60,26,26,26,26,16,16,52,28,28,30,30,30,30,30,10,1,32,34,34,36,36,112,40,40,44,44,46,46,68,50,50,52,52,52,115,115,54,54,118,8,8,121,121,62,62,62,62,64,64,72,66,72,72,74,74,124,124,124,124,8,14,14,127,68,1,1,1,1,130,130,130,133,133,133,136,136,136,24,24,40,40,52,52,64,64,64,64,64,64,64,64,64,64,1,24,24,24,24,97,97,36,36,36,36,44,44,52,52,52,52,124,124,42,130,30,130,36,36,112,68,50,50,24,24,97,97,52,52,24,24,24,24,32,32,32,32,40,40,40,40,52,52,52,52,58,58,58,58,64,64,64,64,60,60,62,62,72,72,38,38,50,30,139,139,74,74,24,24,32,32,52,52,52,52,52,52,52,52,72,72,46,50,62,42,142,145,24,28,28,46,62,60,74,148,148,26,64,66,32,32,42,42,56,56,58,58,72,72,24,24,24,26,52,28,30,30,32,1,1,32,32,32,32,42,36,36,36,36,64,72,38,38,40,40,40,46,46,46,153,68,68,48,50,50,50,52,109,52,34,58,58,58,58,58,58,58,58,58,60,60,42,60,60,62,62,64,64,66,1,68,72,72,74,74,74,74,1,1,1,28,1,26,32,36,38,42,44,46,56,1,1,130,130,130,127,127,156,159,162,153,165,1,38,38,44,38,42,58,58,58,58,68,72,1,1,1,1,1,1,1,1,1,1,1,1,66,1,66,1,2,2,2,2,1,2,2,2,2,1,1,1,66,1,2,66,2,1,2,1,1,58,70,36,46,60,70,1,168,179,184,188,193,203,207,66,1,1,212,217,220,225,231,236,236,236,243,249,256,262,256,262,266,266,220,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,272,272,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,272,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,24,32,40,52,64,28,30,38,48,58,62,66,70,278,278,283,283,1,2,289,289,0,0,1,0,0,0,1,0,0,0,0,0,1,1,24,2,32,32,40,0,52,0,64,52,40,24,26,36,30,32,74,32,100,40,44,46,48,50,300,52,54,58,0,60,62,64,303,306,76,52,40,64,24,32,32,40,64,24,26,36,30,32,74,32,100,40,44,46,48,50,70,52,54,58,60,60,62,64,303,306,76,52,40,64,52,64,52,0,26,100,64,64,64,303,54,2,309,309,315,315,68,68,56,56,318,318,121,121,34,34,306,306,38,38,36,36,321,321,324,324,44,58,28,42,0,0,0,327,327,0,331,331,0,0,0,0,335,338,341,344,335,130,40,347,42,133,136,350,354,40,64,357,24,26,66,36,30,335,124,74,40,40,44,46,48,50,52,54,58,60,62,64,34,306,127,321,121,361,366,72,1,32,371,374,24,26,66,36,30,335,124,74,40,40,44,46,48,50,52,54,58,60,62,64,34,306,127,321,121,361,366,72,1,32,371,374,335,338,341,344,335,130,40,347,42,133,136,350,354,40,64,357,52,52,32,32,335,335,32,32,335,335,52,52,338,338,300,300,76,76,34,34,72,72,72,72,64,64,52,52,52,52,380,380,56,56,383,1,1,1,1,0,388,395,40,40,1,1,58,58,36,36,36,36,36,36,124,124,74,74,44,44,44,44,44,44,44,44,50,50,106,106,54,54,306,306,60,60,62,62,64,64,64,64,306,306,403,403,321,321,321,321,38,38,321,321,321,321,1,124,124,44,44,407,407,50,50,410,410,321,321,413,413,416,24,24,24,24,97,97,335,335,1,1,1,1,124,124,74,74,130,130,40,40,40,40,52,52,52,52,52,52,32,32,64,64,64,64,64,64,321,321,425,425,72,72,425,425,429,429,429,429,432,432,435,435,439,439,443,443,448,448,452,452,456,456,460,460,464,464,407,407,467,467,471,471,475,475,479,479,482,482,485,485,407,407,410,410,0,0,0,0,0,0,0,0,0,0,0,0,0,24,26,36,30,32,74,32,32,62,124,40,46,306,127,44,38,130,377,321,48,72,50,121,52,321,54,42,491,60,66,62,58,127,68,54,44,52,34,0,0,220,0,0,0,0,0,0,0,24,26,36,30,32,74,32,32,62,124,40,46,306,127,44,38,130,377,321,48,72,50,121,52,321,54,42,491,60,66,62,58,127,68,54,44,52,34,494,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,24,52,40,32,32,24,24,52,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,497,26,36,30,38,66,74,306,62,72,44,44,46,48,48,50,50,60,502,54,54,127,127,56,58,121,62,0,0,0,0,0,66,507,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,510,0,516,0,0,0,0,0,0,0,0,0,0,0,522,528,540,547,559,564,0,497,0,0,0,0,0,0,0,0,0,0,574,24,497,68,497,72,497,26,580,62,100,42,38,306,30,584,58,74,60,121,60,30,62,74,587,36,591,591,597,597,597,0,34,56,44,46,48,50,38,68,497,72,603,606,609,24,64,40,68,0,0,0,0,0,0,0,0,66,612,618,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,622,626,497,497,497,497,179,179,179,64,179,630,633,26,62,62,54,100,637,640,38,644,647,38,321,650,654,30,30,657,584,660,30,30,30,664,58,58,58,58,58,58,42,58,60,60,60,60,60,62,377,34,34,34,66,34,303,56,56,306,44,44,44,106,44,36,36,50,36,36,36,46,46,46,46,50,50,50,50,50,38,321,667,38,38,580,68,109,109,64,670,670,68,66,72,72,72,68,32,673,72,72,0,97,677,626,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,681,685,0,6,8,10,12,14,16,18,20,22,121,30,377,689,689,696,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,700,700,26,36,36,30,30,38,68,74,38,62,62,72,706,44,46,48,50,60,60,32,54,54,60,56,58,121,62,709,709,709,24,24,24,24,24,24,32,32,32,32,40,40,64,64,64,52,0,0,0,0,0,70,56,0,0,0,0,0,0,717,717,717,622,622,622,622,622,622,622,640,640,681,681,685,725,587,587,587,730,730,591,591,591,734,734,739,739,739,744,685,685,725,640,640,725,685,640,497,497,597,597,597,748,748,673,673,640,725,725,752,38,121,50,58,26,46,44,756,66,48,34,584,100,46,36,644,60,30,74,62,72,54,42,321,630,762,306,100,74,121,60,30,62,74,765,377,56,68,24,770,40,773,64,776,32,779,52,782,0,785,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,24,773,40,32,64,776,52,789,50,800,803,806,809,812,816,819,822,826,829,833,836,839,842,845,848,842,429,852,855,848,858,858,858,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,863,875,884,24,24,770,40,892,64,895,58,46,898,32,32,905,898,52,52,908,836,911,915,918,922,926,812,809,929,848,933,937,942,946,951,806,955,816,959,842,963,803,968,800,972,845,855,819,822,839,976,980,985,988,992,826,429,0,0,996,1002,770,40,892,64,895,58,664,898,32,32,905,898,52,52,908,1011,0,0,0,1018,1025,0,0,0,0,0,479,1034,1039,1044,1047,471,833,1053,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,0,179,898,0,0,0,0,0,0,0,0,1060,1064,148,1068,1073,0,863,875,884,0,24,770,40,892,64,895,58,46,0,0,32,905,0,0,52,908,836,911,915,918,922,926,812,809,929,848,933,937,942,946,951,806,955,816,959,842,0,803,968,800,972,845,855,819,0,839,0,0,0,988,992,826,429,0,0,996,1002,770,40,892,64,895,58,664,0,0,32,905,0,0,52,908,1011,1077,0,0,0,0,0,0,0,0,0,0,0,0,0,822,471,0,1053,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,819,819,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,38,0,24,770,40,892,64,895,58,46,410,32,32,905,1084,52,52,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,1093,54,303,26,637,48,72,58,664,46,46,1097,66,121,79,60,38,0,0,996,0,770,40,892,64,895,58,664,410,32,32,905,1084,52,52,908,1011,0,0,1101,1105,0,0,0,0,0,0,56,1111,1115,74,1119,491,34,1124,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,1127,0,0,0,0,0,0,0,0,0,0,0,50,50,38,0,24,770,40,892,64,895,58,664,0,0,32,905,0,0,52,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,0,54,303,26,637,48,72,58,0,46,976,0,985,121,79,60,38,0,0,996,1002,770,40,892,64,895,58,664,898,0,32,905,898,0,52,908,1011,0,0,0,0,0,0,0,0,0,0,0,0,0,0,664,491,0,1124,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,58,58,1134,1134,0,0,0,0,0,0,0,0,0,0,0,0,0,863,50,884,0,24,770,40,892,64,895,58,46,0,0,773,905,0,0,776,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,0,54,303,26,1137,48,72,58,0,46,1057,0,66,121,992,60,38,0,0,996,1002,770,40,892,64,895,58,664,0,0,773,905,0,0,776,908,1011,0,0,0,0,0,0,0,0,0,0,0,1111,1115,74,664,471,34,1053,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,50,38,0,0,1140,0,0,0,0,0,0,0,0,0,0,0,0,50,50,38,0,24,770,40,892,64,895,58,0,410,32,32,905,1084,52,52,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,963,54,303,26,637,48,855,58,822,46,1057,980,66,121,79,60,38,0,0,0,0,770,40,892,64,895,58,664,410,32,32,905,1084,52,52,908,1011,0,0,1101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,664,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,38,0,24,770,40,892,64,895,58,46,0,32,32,905,0,52,52,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,0,54,303,26,637,48,72,58,822,46,1057,0,985,121,79,60,38,0,0,0,1002,770,40,892,64,895,58,664,0,32,32,905,0,52,52,908,1011,0,0,0,0,0,0,0,0,0,0,1144,1148,0,0,664,491,0,1124,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,6,8,10,6,8,10,1152,0,0,50,38,0,24,770,40,892,64,895,58,46,0,32,773,905,0,52,776,908,44,911,915,918,106,28,812,42,929,644,630,937,942,946,1090,62,955,816,959,50,1093,54,968,800,972,48,72,58,664,46,1057,1097,66,988,79,60,38,0,0,996,1002,770,40,892,64,895,58,664,0,32,773,905,0,52,776,908,1011,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,833,0,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,0,1158,1170,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,38,0,24,770,40,892,64,895,58,46,0,32,773,905,0,52,776,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,0,54,303,26,637,48,72,58,664,46,1057,980,66,121,79,60,38,0,0,0,1002,770,40,892,64,895,58,664,0,32,773,905,0,52,776,908,1011,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,664,1057,46,1057,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,1182,1182,1182,1182,1182,1182,0,0,50,38,0,24,770,40,892,64,895,58,46,1189,32,773,905,1198,52,776,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,1205,54,303,26,637,48,72,58,664,46,1057,1205,66,121,79,60,38,1216,1224,0,1232,770,40,892,64,895,58,664,1240,32,773,905,0,52,776,908,0,0,1250,1261,1267,1261,1267,1261,0,1267,0,1272,1285,1267,1293,1285,1285,1097,1299,664,1057,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1267,1267,0,0,0,0,0,0,0,0,0,0,0,0,0,1311,50,38,1314,24,770,40,892,64,895,58,46,1318,32,773,905,1321,52,776,908,44,306,36,377,106,28,321,42,1087,644,630,633,654,660,1090,62,100,30,584,50,1325,54,303,26,637,48,72,58,664,46,1057,1097,66,121,79,60,38,1328,0,0,0,770,40,892,64,895,58,905,1336,32,773,905,1348,52,776,908,1352,1361,1370,0,6,8,10,12,14,16,18,20,22,1378,1389,0,0,0,0,664,1057,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1311,50,38,1314,24,770,97,1396,40,892,64,895,58,664,46,1057,32,773,905,52,776,908,1321,0,1400,44,306,36,377,106,1403,28,321,42,1087,644,1407,1411,630,633,654,660,1090,1415,62,100,30,584,50,770,1420,54,303,26,637,48,1423,72,58,1426,46,0,0,66,121,79,60,38,1057,34,0,0,0,0,0,0,0,0,770,97,1396,40,892,64,14,895,18,58,32,773,905,52,776,908,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,664,1057,0,0,0,0,0,0,0,0,0,0,0,0,1429,44,306,306,306,306,306,106,650,321,321,321,321,72,30,62,100,100,100,50,30,62,100,100,100,50,26,54,303,34,303,34,303,48,72,58,58,46,46,68,60,60,60,38,46,1432,38,1432,24,24,770,1437,40,892,1440,1443,64,895,0,0,0,0,1447,637,32,97,52,905,905,1451,812,809,0,848,933,937,942,48,951,806,955,816,959,842,803,968,800,972,845,1144,1454,1148,1459,852,1464,1044,1468,855,819,839,988,992,826,429,24,1471,1476,1487,822,0,0,0,0,770,40,892,64,895,58,664,46,1057,32,773,52,776,1491,1496,40,44,306,1501,306,0,1505,106,321,1509,60,1514,0,644,0,0,836,911,915,918,30,38,100,100,0,50,26,54,303,34,303,34,955,48,72,58,803,46,800,68,845,1144,60,38,1459,852,1464,1044,24,855,770,1437,40,892,72,1124,64,895,1476,52,46,644,0,0,32,1518,52,1521,905,0,0,0,0,0,0,0,0,48,1524,1524,0,0,0,0,0,0,0,0,0,0,0,0,1529,1532,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1101,911,915,918,922,926,812,809,929,848,1535,933,937,942,946,951,806,955,816,959,842,803,968,800,972,845,855,819,839,852,826,429,976,24,1540,40,892,64,895,32,1545,52,908,1549,770,40,892,64,895,32,905,1545,1545,32,875,618,884,1011,1554,1559,1559,1559,1559,1566,44,306,36,377,106,28,321,42,20,644,630,633,654,660,1090,62,100,30,584,50,54,303,26,637,48,127,350,130,357,68,124,74,1545,72,58,46,121,1572,60,38,24,1576,58,1580,1580,1580,1588,1588,1588,770,40,892,64,895,58,664,46,1057,32,773,52,776,48,38,40,892,1540,1540,1540,1540,1540,1540,1540,1540,1540,1540,1540,1540,1596,1596,44,306,36,377,106,28,321,42,20,644,630,633,654,660,1090,62,100,30,584,50,54,303,26,637,48,127,350,130,357,68,124,74,1602,72,58,46,121,79,60,38,24,1576,68,72,58,1606,70,1611,1616,1620,1623,482,1627,1631,0,0,0,0,0,0,0,0,0,0,603,1635,1639,1643,410,1647,1651,1655,609,1659,1663,1667,1671,1084,1675,1679,1602,331,1684,606,1688,1693,1698,1703,1707,1712,1717,1721,1725,1606,1729,1611,1616,1620,1623,482,1627,1631,1733,1736,1739,1639,587,0,1745,0,0,0,44,306,36,377,106,28,321,42,1087,644,1754,630,633,654,660,1090,630,100,30,584,50,54,303,26,637,48,72,58,46,68,60,38,1057,24,0,40,892,64,895,32,0,52,908,0,770,40,892,64,895,32,905,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,58,46,32,121,79,58,664,46,1057,58,664,46,1057,0,0,0,0,0,0,1758,24,97,855,475,1765,32,1768,1772,52,852,1775,109,1318,64,1779,482,1783,670,1786,347,40,1789,1793,1797,1802,1808,1813,1818,1824,1830,1836,1841,1845,1850,1854,1858,1864,1871,1878,1883,1888,1892,1897,1905,1910,1914,1919,1925,1930,1937,1943,1948,1953,1958,1964,1969,1973,1978,1982,1986,1991,1999,2005,24,26,36,30,32,66,74,62,40,44,46,48,50,52,54,124,58,60,62,64,54,44,36,56,121,321,28,74,28,321,70,42,38,32,72,68,2014,109,2017,2030,2041,2055,2069,2083,2096,2114,2126,2139,24,26,36,30,32,66,74,62,40,44,46,48,50,52,54,124,58,60,62,64,54,44,36,56,121,321,28,74,28,321,70,42,38,32,72,68,2014,109,34,2158,2170,2182,0,0,0,0,0,0,36,2194,50,30,654,58,48,26,1137,60,79,2197,42,2201,28,44,62,54,38,106,1090,1420,2204,2207,2210,664,491,2210,1423,2213,2216,2219,2222,2226,2229,2233,2237,2241,2245,2249,2252,2255,2258,2219,2261,2265,2268,2271,2274,2277,2280,2283,2287,60,2291,2294,2297,315,318,121,2300,2305,327,2309,74,36,30,48,26,60,74,2314,42,28,62,54,50,42,0,0,2318,2322,2326,321,2329,2334,2338,2341,762,56,2344,2349,2355,2361,0,0,800,24,97,855,475,1765,32,1768,1772,52,852,1775,109,1318,64,1779,482,1783,670,1786,347,40,1789,1793,1797,1802,1808,1813,1818,1824,1830,1836,1841,1845,1850,1854,1858,1864,1871,1878,1883,1888,1892,1897,1905,1910,1914,1919,1925,1930,1937,1943,1948,1953,1958,1964,1969,1973,1978,1982,1986,1982,64,2366,1910,2371,895,770,773,32,52,852,36,2194,2375,50,136,2378,30,46,2381,2384,2387,162,2390,2393,2396,48,26,2226,60,79,106,42,28,44,62,54,38,2399,2402,106,1420,2406,2409,2412,2207,2415,2418,2422,2425,2428,1057,2432,2436,2440,2444,2448,2452,153,2456,2459,2462,2465,1423,2468,2471,2475,2478,2481,2213,2484,2258,303,2341,2265,2271,2487,2280,74,36,79,1318,306,50,2406,2409,2338,2341,1529,2490,1532,2493,56,2496,2500,2505,2510,2514,2518,429,2523,2526,2529,2533,1620,2537,2540,839,2544,2547,2197,2550,2554,1426,2557,2561,2565,2569,2573,2578,2583,2587,2591,845,2596,2599,2602,2606,2610,2613,2616,2222,2620,2624,2628,2633,2638,2642,2646,819,2651,2654,2657,2661,2665,2668,2671,826,2675,2678,2681,2685,2689,1325,2692,988,2696,2700,2704,2300,2305,327,2309,479,2709,2712,2715,2719,2723,2726,2729,2734,2738,2741,2745,2750,2755,2759,2762,2318,2322,2766,2770,2329,2334,2775,2779,2783,2788,2344,2349,2355,2361,0,0,800,2792,2795,2798,2802,2806,2809,2812,985,2816,2819,2822,2826,2830,2833,2836,806,2840,324,2843,2847,2851,2854,2857,926,2861,2864,2867,2871,2875,2878,2881,2885,2888,2891,2894,2898,2902,2905,2908,2912,2916,2920,2924,2929,2934,2938,2943,842,2947,2950,785,2953,2957,1400,2960,848,2964,2968,2972,2977,2982,2986,2990,24,32,40,52,64,66,915,836,836,2995,2998,3001,3005,3009,1311,2526,3012,2523,3016,3020,3025,3030,1426,2544,3034,3038,3042,3046,3051,3056,3060,3064,3068,2957,3073,3078,3084,3090,3095,3099,852,3103,1783,3106,3110,482,3114,2678,1325,2675,3117,816,806,432,2851,3120,1044,3123,3126,3129,3133,464,3137,3140,1464,3144,3148,3152,3157,3162,3166,3170,855,670,347,3175,3179,1772,1318,855,816,3183,3120,3186,3190,432,3194,3197,942,3201,2496,2500,2505,2510,2514,2518,809,3205,3208,3211,3215,3219,3222,2540,915,3225,3228,3231,3235,3239,3242,0,3245,0,3249,3253,3258,3263,0,0,1060,3267,3271,3275,3280,3285,3289,0,955,3293,3297,3301,3306,3311,1321,3315,812,3320,3324,3328,3333,3338,3342,3346,968,3351,3355,3359,3364,3369,3373,3377,1144,3382,3386,3390,3395,3400,3404,3408,3413,3417,3421,3425,3430,3435,3439,0,833,2738,1733,3443,3447,3451,2759,2762,803,3454,3457,3460,3464,3468,3471,2779,3474,2788,3478,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,32,40,52,64,66,915,836,3239,3228,3242,3225,3482,429,1620,2526,2537,2523,112,839,2554,2547,1426,2544,3485,845,2610,2599,2613,2596,842,3064,3488,2957,2950,1400,2947,3492,3095,3099,3495,3499,3503,3507,826,60,2689,2678,1325,2675,3117,816,806,432,2851,3120,324,3194,3183,3511,3514,3518,3522,3526,3530,3534,3538,1144,3400,3386,3404,3382,3542,852,482,1783,3114,3103,3546,855,1772,347,1318,670,3549,0,0,0,0,0,0,0,0,0,0,0,0,32,3552,40,892,52,776,776,773,40,24,770,482,482,1783,1783,3556,3556,3114,3114,3560,3560,3560,852,852,3106,3106,3106,905,68,0,62,44,121,60,50,68,50,0,68,28,0,46,410,609,1084,603,3468,3564,3457,3569,3471,3573,3573,2533,2526,803,3460,2943,2943,3577,3577,3581,3581,3586,3586,3590,3590,2779,2779,3595,3595,3595,54,54,38,2851,3600,324,3605,2854,3609,3609,3190,3120,806,2843,3613,3613,3617,3617,3621,3621,3626,3626,3630,3630,2857,2857,3635,3635,3635,62,3640,3644,3648,933,3009,3652,2998,3657,1311,3661,3661,836,3001,3030,3030,3016,3016,3665,3665,3670,3670,3674,3674,3012,3012,3020,3020,3020,44,3679,3682,3686,3690,3694,2875,3698,2864,3703,2878,3707,3707,926,2867,3711,3711,3715,3715,3719,3719,3724,3724,3728,3728,2881,2881,3733,3733,3733,28,100,2610,3738,2599,3743,2613,3747,3747,845,2602,3751,3751,3755,3755,3759,3759,3764,3764,3768,3768,2616,2616,3773,3773,3773,48,48,2481,48,48,2957,3778,2950,3783,1400,3787,3787,842,785,3791,3791,2960,2960,3795,3795,3795,50,106,2378,2554,3800,2547,3805,1426,3809,3809,839,2197,3813,3813,3817,3817,3821,3821,3826,3826,3830,3830,2557,2557,3835,3835,46,46,46,2689,3840,2678,3845,1325,3849,3849,826,2681,3853,3853,3857,3857,3861,3861,3866,3866,3870,3870,2692,2692,3875,3875,3875,60,60,3880,60,2297,3883,3880,3887,3892,3897,3902,2305,2700,3907,327,3912,988,2704,3917,3917,3922,3922,3927,3927,3933,3933,3938,3938,2309,2309,3944,3944,121,1772,3950,347,3955,1318,3959,3959,855,3175,3963,3963,3967,3967,3971,3971,3976,3976,3980,3980,3985,3985,3989,3989,3989,72,72,72,347,2665,2665,2554,3994,2654,3999,2668,4003,1426,819,2657,839,4007,4007,58,58,58,3451,4012,1733,4017,2759,4021,833,3443,4025,4025,34,3311,3311,3297,3297,4030,4030,1321,4035,955,3301,4040,4040,100,4046,4051,4056,937,633,4061,4065,4069,4073,1620,2526,4077,2537,4081,429,2529,38,38,4085,4088,2712,4093,2726,4097,479,2715,56,4101,4106,4111,4116,2665,2654,2668,819,4121,4127,4131,4136,4140,922,4145,106,1403,2305,2700,327,988,3311,3297,1321,955,100,4150,4154,4159,4163,467,4168,2396,3311,3297,4030,1321,4035,955,3301,100,26,32,40,52,24,482,1783,3114,852,2957,2950,1400,842,3009,2998,1311,836,1620,2526,2537,429,4173,4177,425,4181,4186,918,2651,2668,2665,2661,2654,819,3103,3114,482,3110,1783,852,4190,4194,4198,4202,4207,4211,3293,1321,3311,3306,3297,955,4215,3648,3640,4219,3644,933,3454,3471,3468,3464,3457,803,54,3225,3242,3239,3235,3228,915,4224,1314,4228,4232,4237,911,4241,4245,4249,4253,4258,1487,4262,2947,1400,2957,2953,2950,842,2596,2613,2610,2606,2599,845,670,1318,1772,3179,347,855,3205,3205,3222,3219,3215,3208,3208,809,4265,4269,4273,4277,4282,1064,2544,1426,2554,2550,2547,839,4286,4290,4294,4298,4303,3514,4307,4159,4311,4315,4150,467,4320,4111,4101,4325,4106,4116,3534,3530,3522,4331,3526,3518,3123,3137,464,3133,3126,1044,74,74,4336,4340,4344,4348,4353,1148,2675,1325,2689,2685,2678,826,2696,327,2305,2300,2700,988,121,3382,3404,3400,3395,3386,1144,3320,3342,3338,3333,3324,812,4357,4362,4367,4372,4378,4383,70,839,4388,4392,4397,4402,4408,4413,4419,4424,0,0,0,0,0,0,0,0,0,836,26,46,34,60,50,38,30,62,28,56,48,36,106,74,58,24,52,64,32,40,321,100,303,54,70,54,1426,2833,988,4430,826,34,66,64,118,72,68,100,100,24,52,4434,97,52,52,52,109,1084,58,44,28,44,36,106,36,36,68,38,38,38,38,50,50,50,40,32,42,36,97,24,1765,54,74,60,60,60,28,74,62,62,30,26,26,54,54,32,48,48,48,46,46,106,106,30,52,4437,4441,2709,2709,2709,60,118,118,118,56,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4445,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,24,32,40,52,64,109,1440,773,842,4454,800,803,479,915,845,839,826,988,806,816,812,809,855,819,852,833,836,911,1144,1044,2529,4458,467,3148,3324,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4462,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4467,4472,4472,4472,4472,4472,0,0,0,0,0,0,0,0,44,306,36,377,106,28,321,42,1087,644,62,633,30,660,1090,62,100,30,584,50,54,303,26,637,48,72,58,46,66,121,79,60,38,46,56,24,770,40,892,64,4479,895,4482,4486,4489,4493,4496,32,905,776,776,908,24,770,770,40,892,72,1124,64,895,4500,109,855,335,32,97,905,776,908,48,38,24,0,0,0,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4503,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,836,911,915,918,922,926,812,809,929,4506,806,955,816,959,842,803,968,800,972,845,855,819,839,852,988,992,826,429,0,0,0,24,40,64,773,905,776,908,32,52,855,819,852,0,0,0,0,836,922,875,806,842,803,845,819,839,4510,4520,4530,0,0,0,0,3809,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,836,2885,922,1144,826,855,806,955,839,803,968,845,833,985,429,479,911,1454,842,24,40,773,4535,64,776,52,1440,32,4538,905,0,0,4542,4542,4542,4542,4542,0,0,0,0,0,0,0,0,0,0,0,179,479,179,179,179,836,2885,922,179,179,179,1144,826,855,179,179,179,806,955,842,179,179,179,803,968,845,179,179,179,833,985,839,179,179,179,429,816,800,179,179,4548,4552,0,0,0,0,0,0,4556,770,892,64,895,32,97,52,782,1440,1521,4562,4566,507,4569,4573,4577,4580,4580,4580,4580,4580,4580,4580,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,4586,4590,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,836,915,922,4595,803,800,845,4600,806,816,842,4604,926,809,848,4608,855,819,839,985,826,24,429,40,64,32,52,97,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4613,4613,4617,4623,4630,4636,4636,4642,4642,4648,4648,819,819,839,839,4654,4660,4667,4667,836,836,915,915,922,926,926,809,809,848,806,806,816,816,842,806,806,816,816,842,803,803,800,800,845,855,819,839,852,826,826,826,429,4673,4681,4613,4613,4688,4688,819,819,839,839,4693,4693,4693,4693,4700,4700,0,752,4706,4711,4716,2830,4719,4723,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4730,4739,4749,24,40,64,97,52,32,1786,836,479,915,922,926,809,1044,848,806,816,842,803,833,985,800,845,855,819,839,852,826,2885,429,4759,4769,4778,4786,4795,4803,4814,4823,4831,4842,0,0,0,911,4850,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,836,4854,911,915,4858,922,926,812,809,848,806,955,816,842,803,4862,968,833,4866,800,4870,845,4874,1144,1454,1148,855,819,839,429,4878,985,826,988,852,24,855,819,770,40,52,776,64,895,32,44,48,46,50,54,58,62,4882,4890,4895,996,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,933,937,942,0,6,8,10,12,14,16,18,20,22,839,4899,4902,4454,4905,2197,4908,4912,4916,4920,2547,4924,4927,4930,4934,2544,4937,4940,4943,4566,2554,4947,4950,410,4954,1426,4958,4962,4965,4968,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4971,4971,97,4971,4971,4971,4971,4971,4979,40,4971,4971,4971,4971,4971,4971,4971,4984,4984,4984,109,4971,4993,4997,4971,4971,4971,4971,4971,4984,4984,4984,4971,4971,4971,4971,5004,587,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,4971,24,5011,5018,5011,26,30,32,5024,4979,5011,36,5011,44,48,5030,52,4979,4993,4997,54,62,64,4984,5011,66,587,5034,5039,5045,5039,3324,40,58,64,66,5034,5051,5057,3355,3324,1440,26,30,34,48,50,54,58,58,60,62,74,36,5061,5070,100,40,5078,54,64,5083,26,30,34,36,44,46,48,50,54,58,60,5091,66,70,74,24,5018,30,32,32,4979,5024,40,52,5091,64,5095,5011,28,28,5099,5103,34,5112,5120,5011,40,5078,4971,4971,42,46,46,4971,48,5011,50,50,4971,5127,3355,60,5091,62,64,5083,4971,66,5011,74,74,74,5095,5134,0,0,0,0,0,0,0,0,0,0,58,0,0,0,0,0,0,0,0,5140,97,1451,5150,28,5070,5099,36,4971,44,46,4971,4971,50,4971,4971,58,60,60,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,24,26,26,26,26,26,26,28,28,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,34,34,24,32,40,52,64,52,64,773,50,106,26,54,56,36,48,46,60,121,62,30,321,42,72,58,68,34,44,911,127,74,38,5153,2396,124,321,48,32,40,52,64,52,64,106,26,54,56,36,48,62,30,321,42,127,72,68,44,36,38,5156,644,130,32,40,4577,64,64,106,44,36,38,54,121,62,30,42,34,36,38,127,74,58,321,124,40,44,58,34,124,64,64,64,64,66,66,66,66,68,38,70,68,48,68,68,24,40,44,106,28,630,633,654,1090,62,30,54,303,79,124,74,24,62,124,377,106,28,1087,933,660,62,584,79,5159,124,74,64,72,637,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,40,40,40,40,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,64,64,64,64,64,64,64,64,64,64,64,64,64,64,72,72,72,72,72,72,72,72,5162,5162,5162,5162,72,72,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5175,5175,5175,5175,5175,5175,0,0,5175,5175,5175,5175,5175,5175,0,0,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5078,5187,5187,5187,5187,5187,5187,0,0,5187,5187,5187,5187,5187,5187,0,0,5083,5083,5083,5083,5083,5083,5083,5083,0,5083,0,5083,0,5083,0,5083,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5018,5018,5175,5175,5183,5183,5078,5078,5187,5187,5083,5083,5195,5195,0,0,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5018,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5183,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5195,5018,5018,5018,5018,5018,0,5018,5018,5018,5018,5018,5018,5018,0,0,0,0,0,5183,5183,5183,0,5183,5183,5175,5175,5183,5183,5183,0,0,0,5078,5078,5078,5078,0,0,5078,5078,5078,5078,5078,5078,0,0,0,0,5083,5083,5083,5083,5057,5057,5083,5083,5083,5083,5083,5083,5057,0,0,0,0,0,5195,5195,5195,0,5195,5195,5187,5187,5195,5195,5195,0,0,0,5201,0,2,2,2,2,5205,2,2,5209,5213,2,2,2,2,2,2,2,2,2,2,0,0,0,5218,1,1,1,1,1,1,1,1,1,1,1,0,0,0,2,0,0,5224,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,5230,0,0,2,0,0,0,0,0,0,5234,0,1,1,0,0,5237,0,0,5241,0,0,0,0,0,0,5248,0,0,0,5255,2,2,0,0,0,0,0,0,0,0,0,0,5261,0,0,5267,5267,5274,40,0,5280,0,0,0,0,0,0,0,5285,0,0,0,50,0,0,0,0,0,0,0,0,0,0,5291,0,0,0,0,0,24,32,52,70,5024,0,0,0,0,0,0,0,0,0,5296,0,0,0,0,0,0,0,0,5301,0,0,0,0,0,0,0,0,0,5305,0,5309,0,0,0,5314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5325,0,0,0,0,0,0,0,0,0,0,0,0,0,5329,0,0,0,0,0,0,0,0,0,0,4971,5333,0,0,0,4971,0,0,0,0,2,0,0,0,0,0,4971,0,0,0,0,5337,2,0,0,0,0,0,4971,5078,0,0,0,4971,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5342,0,0,5347,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5352,0,0,0,0,0,0,0,0,0,0,5358,0,0,0,0,0,0,0,0,0,0,5325,0,0,0,0,0,0,0,5362,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5367,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5372,0,0,0,0,0,0,0,0,0,5320,0,0,0,0,5377,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5381,0,0,0,0,0,0,0,5386,0,0,0,0,0,0,0,0,0,5391,0,0,0,0,0,0,0,0,0,5396,5401,5401,0,0,0,5407,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5413,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5419,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5426,5386,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5432,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5248,0,0,5437,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5442,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5448,0,0,0,0,0,0,0,0,0,0,0,0,0,5453,5459,0,0,0,0,0,0,0,5453,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5464,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5471,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5476,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5325,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,24,26,26,26,26,26,26,28,28,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,34,34,36,36,38,38,38,38,38,38,38,38,38,38,40,40,40,40,44,44,44,44,44,44,46,46,46,46,46,46,46,46,48,48,48,48,48,48,50,50,50,50,50,50,50,50,52,52,52,52,52,52,52,52,54,54,54,54,58,58,58,58,58,58,58,58,60,60,60,60,60,60,60,60,60,60,62,62,62,62,62,62,62,62,64,64,64,64,64,64,64,64,64,64,66,66,66,66,68,68,68,68,68,68,68,68,68,68,70,70,70,70,72,72,74,74,74,74,74,74,38,62,68,72,24,60,24,26,28,30,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,40,40,40,40,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,64,64,64,64,64,64,64,64,64,64,64,64,64,64,72,72,72,72,72,72,72,72,16,18,20,22,0,0,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,32,32,32,32,32,32,0,0,32,32,32,32,32,32,0,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,52,52,52,52,52,52,5386,0,52,52,52,52,52,52,2,5480,64,64,64,64,64,64,64,64,0,64,0,64,0,64,0,64,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,24,24,32,32,32,32,40,40,52,52,64,64,52,52,0,0,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,24,24,24,24,24,0,24,24,24,24,24,24,24,0,40,0,0,0,32,32,32,0,32,32,32,32,32,32,32,0,0,0,40,40,40,40,0,0,40,40,40,40,40,40,0,0,0,0,64,64,64,64,58,58,64,64,64,64,64,64,58,0,0,0,0,0,52,52,52,0,52,52,52,52,52,52,52,0,0,0,5485,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5491,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,5496,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5501,0,0,0,0,0,0,0,0,0,0,5506,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5509,0,5255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5514,0,0,0,5519,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5524,0,0,0,0,0,5529,0,0,0,0,0,5534,0,0,0,5540,5544,5547,5550,46,5553,50,5557,1134,68,2406,30,1786,44,62,5561,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5564,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5568,0,5573,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5578,0,0,0,0,5564,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,40,892,5583,5587,66,2819,5590,5594,5599,70,2891,5602,46,28,30,48,40,892,5583,5587,66,2819,5590,5594,5599,70,2891,5602,46,28,30,48,6,30,10,12,14,16,18,20,22,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,0,66,0,0,0,0,0,5606,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5612,0,0,0,0,5617,0,0,0,0,0,0,0,0,0,5622,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5629,0,0,0,0,5291,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5635,0,0,0,5629,0,0,0,0,5642,0,0,0,0,0,0,0,0,0,0,0,0,0,5647,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5651,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5325,5655,0,0,0,0,0,0,0,0,5285,0,0,0,5367,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5661,5661,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5666,0,0,0,0,5671,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,5676,5682,0,0,0,0,5688,0,0,0,0,0,0,0,0,0,0,5693,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5699,0,0,0,0,0,0,5291,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5476,5704,5704,0,0,0,0,0,0,0,0,0,0,0,5708,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5712,0,0,5717,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5721,5721,5721,5721,5721,5721,5721,5721,5721,5721,5721,609,609,0,5721,5721,0,0,609,609,5721,5726,0,609,609,609,0,5464,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5693,0,0,0,0,0,0,0,0,5730,0,0,2,2,0,0,0,0,0,0,0,0,5721,0,0,0,0,0,0,0,0,0,0,5682,5682,5682,0,0,0,5721,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5721,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,272,272,272,272,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,5682,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5735,5739,5744,5749,5757,5763,5769,5777,5783,5790,5795,40,5803,5810,5815,5823,5831,5837,5841,5848,5854,5860,5867,5871,5877,5882,3468,5886,3386,5891,988,5898,5903,5908,5913,670,5921,5921,1318,5925,5933,5925,5937,5942,5950,5958,5969,0,5735,5739,5744,5749,5757,5763,5769,5777,5783,5790,5795,40,5803,5810,5815,5823,5831,5837,5841,5848,5854,5860,5867,5871,5877,5882,3468,5886,3386,5891,988,5898,5903,5908,5913,670,5921,5921,1318,5925,5933,5925,5937,5942,5950,5958,5969,0,46,46,46,54,58,24,62,38,38,44,44,74,74,5018,48,24,0,66,68,68,66,1432,1432,5978,32,58,52,4971,42,4971,0,0,5987,5987,5992,5992,5051,5051,5997,5997,6003,6003,6007,6007,6011,6011,6016,6016,6021,6021,6028,6028,6034,6034,6039,6039,2599,2599,2950,2950,6045,6045,52,52,3457,3457,2668,2668,6049,6049,6054,6054,4500,4500,1733,1733,4237,4237,6058,6058,6062,6062,6066,6066,6076,6076,6080,6080,6066,6066,6066,6066,6080,6080,6076,6076,283,283,6094,6094,6076,6076,6076,6076,6102,6102,6066,6066,6076,6076,6076,6076,6111,6111,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,6076,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,603,1635,1639,1643,410,1647,1651,1655,609,1659,1663,1667,1671,1084,1675,1679,1602,331,1684,606,1688,1693,1698,1703,1707,1712,1717,1721,1725,1606,1729,1611,1616,1620,1623,482,1627,1631,0,0,0,0,0,0,0,0,0,0,855,6120,6124,6129,6133,6139,6146,6150,6154,6159,6164,6170,6174,6178,6182,6189,6195,6139,6182,6199,3175,6204,6182,6209,6182,347,6213,6218,6182,6226,6230,4506,6182,6182,6234,670,1447,6238,6243,6182,6248,6253,6257,6262,6267,6271,6276,6281,6286,6290,6294,6298,6302,6313,0,0,0,0,0,0,0,0,0,6318,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6332,6336,6340,6344,6348,6353,6357,6361,6365,6369,782,6374,6378,6382,6387,6391,6396,6401,6406,6410,6415,6420,6426,0,0,0,0,0,0,0,0,0,992,6431,6435,6439,6444,6449,4430,0,6453,6457,6461,6465,6470,6475,6479,0,6483,6487,6491,6495,6500,6505,6509,0,6513,6518,6523,6528,6534,6540,6545,0,6550,6554,6558,6562,6567,6572,6576,0,6580,6584,6588,6592,6597,6602,6606,0,6610,6614,6618,6622,6627,6632,6636,0,6640,6644,6648,6652,6657,6662,6666,0,2806,2830,425,432,3162,464,836,407,413,410,52,3468,6670,6673,2851,429,3400,3338,988,6676,5937,6682,24,335,6688,6694,6267,670,6704,6713,5933,6704,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220,5682,220,5682,6720,6725,6725,6725,6725,6725,6725,6734,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,24,24,40,40,64,64,32,32,52,52,836,915,2998,3228,2995,3225,3009,3239,1311,3242,826,1044,2678,3126,2675,3123,2689,464,1325,3137,806,816,324,3120,2840,2840,3183,2851,432,2854,3194,842,2950,2947,2957,1400,429,800,803,2526,70,3457,2523,2792,3454,1620,2806,3468,2537,2809,3471,845,2599,2596,2610,2613,855,855,670,670,1318,1318,819,2654,2651,2665,2668,852,852,1783,482,3114,50,2816,836,3009,0,0,0,0,0,0,0,0,0,2,24,24,40,40,64,64,32,32,52,52,836,915,2998,3228,2995,3225,3009,3239,1311,3242,826,1044,2678,3126,2675,3123,2689,66,66,66,66,816,324,3120,2840,2840,3183,2851,432,2854,3194,842,2950,2947,2957,1400,429,800,803,2526,2795,3457,2523,2792,3454,1620,2806,3468,2537,2809,3471,845,2599,2596,2610,2613,855,855,670,670,1318,1318,819,2654,2651,2665,2668,852,52,1783,482,3114,50,2816,836,3009,985,2819,2830,2833,0,0,0,0,0,0,0,0,0,0,26,54,48,34,30,62,50,46,36,44,38,42,56,70,124,321,121,58,74,28,60,24,52,32,4535,905,1518,908,139,603,410,4454,5030,6670,40,64,371,66,106,6743,4927,0,0,0,6746,6753,6765,6777,6783,6795,6807,6814,6826,6832,6845,6857,6869,6880,6894,6908,6920,6926,6932,6943,6954,6959,6969,6975,6981,6992,7000,7008,7016,7024,24,97,855,475,1765,32,1768,1772,52,852,1775,109,1318,64,1779,482,1783,670,1786,347,40,0,7030,2017,2030,2041,2096,2126,7041,7058,7072,7090,7102,7113,7127,7141,7154,7167,7185,7203,7215,7229,7243,7262,7274,7285,7297,7308,7319,7327,7338,7347,7361,7378,7394,2182,1858,1864,1883,1930,1937,1948,1999,7405,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2792,3126,3208,3225,773,7412,776,7416,4934,7420,7424,4943,7428,7431,7435,7440,1437,1429,7445,7449,54,62,44,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2995,2678,2675,2854,2947,429,2526,2523,1620,2537,2596,819,2654,2651,2665,2668,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7454,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7460,7467,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,0,26,0,44,0,46,0,28,40,34,0,48,60,54,0,32,0,38,0,52,0,58,0,30,42,36,0,50,62,56,0,0,0,0,0,64,0,66,0,0,0,0,0,70,0,0,0,0,0,0,0,74,0,0,0,0,68,0,0,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5688,5606,0,0,7473,7478,7482,0,0,0,0,0,7487,0,0,0,0,0,0,0,0,0,7491,0,0,0,0,0,5442,0,0,0,0,7495,0,0,5688,0,0,0,7500,5426,0,7504,7511,7516,7521,5218,7528,5655,5491,7478,7533,0,0,7538,7542,5476,7548,7555,7558,0,0,0,0,5314,7565,5708,7478,7571,0,7576,7582,7588,5476,7592,7599,5666,5386,7605,0,0,0,0,7500,0,0,0,5381,5459,7495,7588,5476,7610,7616,5413,0,0,0,5347,7622,5476,5301,5325,7628,7632,7637,0,0,0,7643,7500,5296,7648,0,7576,0,7653,5237,7658,7663,5296,7667,7538,7672,5629,7675,7681,7687,7628,7692,7696,7700,7706,7710,7715,7653,5629,7719,0,7723,5666,7730,0,0,5296,7734,7738,5320,0,7742,7658,0,7746,7752,7757,7628,7763,7667,5453,5661,5337,0,0,5617,0,5666,5301,0,7632,0,5325,0,0,0,0,7768,0,7774,7779,7786,7790,0,7795,5342,0,0,7790,7800,0,5391,5301,0,7599,5568,7805,5655,0,0,0,5224,7811,7582,7817,0,0,5401,7592,5329,5426,0,7823,5661,0,5358,5381,7588,0,0,0,5337,5426,7706,0,7828,5564,7632,5280,5381,0,0,7834,7839,0,7843,0,0,7849,0,5301,5396,7853,7858,0,7667,7864,7867,5476,5291,7872,5407,0,7710,5248,7710,0,7672,0,7516,5291,7588,7675,7877,0,0,7605,7883,5407,7576,7888,5661,5301,0,7710,5237,5407,0,5325,0,7893,5573,7898,5568,0,7904,7779,7908,0,7849,0,5291,5666,7914,7920,7924,7672,7930,7864,5329,0,0,7935,7495,7940,7946,7628,0,7952,5476,5274,7817,7957,0,5333,5391,0,7962,5642,5381,7967,0,5699,7482,0,7972,7977,7982,7986,5730,7990,0,5485,7663,0,7864,5573,5661,5573,7994,0,0,7811,7588,5337,0,7667,7999,7500,5391,0,0,8005,0,7849,5671,7834,5688,5476,5396,0,8010,8015,7883,7565,8022,5708,5564,8028,7849,5337,5396,0,8033,8039,8044,5396,5301,8048,8052,8057,0,7487,0,0,5333,7516,5476,5730,0,7500,5726,7696,0,0,0,0,0,0,0,8028,7994,5661,5377,8062,8068,8073,8077,7482,0,0,0,0,0,0,8082,8087,7904,7491,5325,7853,7653,7592,7582,7752,5329,5448,8039,8093,0,5606,8099,0,0,0,0,0,0,0,7491,5391,5529,5501,8104,8110,7888,8115,7864,5661,0,7864,8119,7752,7800,7982,5717,5367,7800,5358,5325,5730,7972,0,0,0,0,8126,7663,0,0,8131,7883,8126,8073,8136,5301,7675,5519,0,0,0,0,0,7582,5377,8140,8144,8149,7972,7663,5237,0,7867,0,0,0,0,5573,5325,8154,7738,7834,7500,0,8159,0,0,0,0,7491,7521,0,5367,7571,5476,7839,0,0,5274,7658,8164,5476,7972,5476,5274,8170,5325,7653,0,8174,5301,0,5347,8179,7653,7834,0,0,7834,7653,0,7710,8184,8154,0,5358,5367,5534,8190,0,5337,8196,7500,8028,5358,8052,5485,5708,5708,0,0,0,0,8170,0,7571,8201,5381,7663,8205,7839,8048,0,0,0,0,8077,7511,8211,8217,0,5358,8222,0,5573,7653,5606,8227,0,0,0,0,5564,7888,7628,7491,8005,7653,7908,0,7908,0,7610,0,8201,7710,5661,8077,7843,5476,0,7800,8015,5367,8234,0,0,5325,8239,7491,5325,7482,0,8245,5476,5661,8052,5218,7653,7877,7658,5629,7972,8222,8249,7952,7877,5666,0,5237,0,0,8255,8261,8267,0,7610,5564,0,0,7786,0,0,5396,8039,0,5524,8272,0,5261,8277,8077,5564,5524,7867,8282,0,0,5237,7500,8005,8249,8287,0,5325,8293,7675,5476,7628,5476,7834,0,8297,8052,8302,0,5337,0,7528,5358,0,7828,0,8201,0,7487,7696,0,7790,8267,0,0,8255,0,8174,0,0,7495,5381,0,7696,7888,0,0,0,7888,0,0,7867,0,7742,5448,8179,8307,0,5352,5476,0,7571,8312,7867,8316,7528,5337,7930,7610,0,0,0,0,0,0,7738,5347,7800,8322,7576,7834,0,5329,0,5514,7746,0,7872,8048,5496,5386,0,5442,0,5320,7858,0,8005,8302,0,5274,7972,0,0,0,5401,8329,7500,5651,5296,7715,0,8334,5476,8131,5629,7706,8267,5320,5564,5476,8245,0,5401,8340,8179,8170,5329,7588,5362,5708,8345,7542,0,0,0,0,8297,7914,8349,7653,8354,7696,5708,5274,7538,0,0,5347,0,8354,7582,5534,8359,8249,5453,5509,5651,8077,5726,8365,5401,7972,5209,8370,7500,8052,8329,7972,8010,8104,8131,0,8104,8375,7605,8087,8239,5381,5661,5693,5381,8164,8211,0,5717,5642,8381,5476,7843,5218,7482,0,7588,0,7516,8387,7790,8391,8397,7628,7491,8131,5651,7667,7752,0,0,7828,7610,0,5426,8402,8255,7746,8249,8196,0,5509,8407,7663,0,8411,8417,5401,5309,0,7805,7571,8073,0,5642,8422,5325,0,8039,5325,7994,0,0,7500,8184,8267,0,7904,5578,8170,7994,5237,7706,5476,5661,7839,5688,8068,5453,7706,5325,0,0,0,7828,8149,8427,0,0,7482,8433,5325,5296,5437,5347,5386,8170,8196,5491,0,5381,7500,0,5218,8307,7710,0,8438,5464,5342,7990,8443,8052,7558,5529,8447,7710,5401,8345,7710,0,7521,7924,5396,8451,5688,8149,5564,8217,8211,7843,5480,7653,8211,8073,0,7706,7653,0,7610,0,7681,8149,7706,8365,5651,8456,8427,7521,8461,0,7706,0,5329,0,8267,7864,7768,8438,7994,7768,5320,8438,0,0,8391,5642,5291,5301,7908,8461,8467,8245,0,8149,7957,7542,5708,8473,0,8211,8479,0,5708,0,5237,5564,0,8484,8489,8316,5726,7864,0,5325,5325,5396,5285,8397,8473,8249,8126,7994,5325,5708,7628,8052,8217,8494,8447,8498,0,5301,5329,5453,5396,8277,0,7672,0,5651,7628,0,8170,0,0,8427,0,5476,0,5325,5358,0,8504,0,8010,5717,7692,5381,7622,0,0,8316,0,7839,5358,8509,5301,0,7962,7495,0,5325,8514,5274,5280,7864,7790,0,0,7853,0,8255,0,0,7478,0,8519,5377,5401,5358,0,8211,5529,8527,5666,0,5241,5573,7800,7610,0,7482,0,5241,7542,8375,8077,5476,0,5325,5325,8533,7628,8484,7811,8539,0,8093,5325,8543,7687,7839,7883,8239,5476,7999,5274,7710,7839,7710,7834,7843,8307,7610,7877,0,8422,5496,7663,7710,0,7616,5496,7853,0,8422,5496,5642,8548,0,0,8255,5401,8196,5329,7834,0,0,5476,7877,7592,5419,0,5485,0,7582,0,5676,7653,8539,0,0,8062,0,7710,8438,5358,0,8329,5329,0,5617,5358,0,5358,0,5274,7994,5432,7696,5676,0,5655,7930,5476,7628,5726,8365,5651,8447,7487,0,0,0,5261,7658,7715,5485,8411,8164,8554,8179,0,0,0,0,0,8539,8316,7738,5426,0,7888,5237,8316,0,5377,5261,5407,7972,0,7482,0,0,7811,7548,7706,0,0,5661,5661,8115,7994,0,0,7738,8427,8381,0,0,0,5476,8504,5320,5496,5314,0,7746,5325,7565,8456,7663,8010,5708,8196,5280,7675,0,0,8255,0,8316,0,5337,0,8543,5564,5573,7605,0,5573,0,8560,8566,5291,0,5496,8571,8577,5301,7500,0,7628,8370,5301,5730,7817,7990,8467,8407,5296,0,8582,7592,8365,5564,7867,7930,5237,7839,8334,0,5237,5688,5464,0,0,5442,8589,0,8407,8407,8594,8514,8093,8087,7828,8179,5459,7935,5352,0,8170,5325,8277,5606,8467,0,7774,7706,7628,5401,0,0,0,0,7628,5325,7994,5651,8402,5661,7500,5237,5688,5296,0,7628,7867,8598,7805,7888,7957,0,8334,7658,7576,7482,0,7893,7914,8467,5476,7643,7719,7491,8438,5501,7849,0,7482,8604,8447,5708,8527,5401,8227,7877,7538,8539,0,5329,0,5476,5396,0,8422,7864,5396,7962,8447,5401,5301,5274,7834,7610,8514,5407,0,0,8411,7628,8154,7622,5442,8370,0,5333,5381,7994,5476,7811,7849,8179,5347,7482,5661,0,7632,8093,7542,8154,0,8494,0,0,5717,0,5459,5218,5325,8608,8613,5237,8617,7500,5476,7982,5325,5726,5606,0,7542,7715,0,7790,7482,5671,7904,5274,7790,7565,8387,0,8022,7994,5529,0,8625,8484,7864,5209,8402,5358,8631,0,8577,0,0,7908,0,8370,0,7605,5248,5274,8190,0,5688,8456,7952,7696,7817,7723,0,8149,5301,8033,8589,8073,8494,8057,0,7888,0,8594,7738,7972,8473,7786,0,0,5224,7972,7582,7558,7999,8028,7516,8312,5325,8438,7994,7935,8131,0,8613,0,8201,0,8005,8149,7653,7616,7972,5381,5426,5661,8329,5261,7542,5661,7588,8370,5651,7643,0,0,5296,8136,8022,8543,7511,5381,5464,5347,0,7982,8307,8154,5717,5642,8604,7952,5407,5407,5442,5309,7538,5296,8514,8179,7672,7920,7924,8170,8170,5699,0,0,8635,0,8484,5396,5274,5661,0,7588,7893,8604,7491,0,0,0,8196,5296,8068,0,8322,5237,5476,0,8249,8641,5347,8052,7839,8533,8647,7632,5651,0,0,0,5661,8073,5296,7706,5661,0,0,8073,7542,8282,5285,5291,8653,8022,8170,7864,8077,0,5459,5459,0,7952,0,7706,7805,0,7628,8196,0,7990,5564,0,8539,5274,7952,7849,8658,7653,8316,8073,5261,5358,5329,7491,8447,8663,8589,7757,5329,7853,5401,7990,8589,5655,8663,5401,7790,0,7542,7990,8670,8255,5358,0,7482,0,5261,7588,7730,7872,5314,5329,8093,5496,0,7811,0,0,7977,8282,7982,8307,0,7957,0,5676,7990,5612,5717,5578,5693,8676,7605,7834,8334,5496,0,0,0,0,5476,0,0,5337,8234,5642,5642,5391,7817,7706,8682,0,0,5730,5651,5564,7982,8687,7883,0,0,7834,0,0,5726,0,8227,8391,0,8222,0,0,0,5274,0,0,0,7790,7843,7823,8456,8033,0,0,8694,0,7867,5491,0,8653,5459,5329,0,0,0,0,7628,7658,7877,7800,7746,7811,0,8699,7811,5730,5485,8073,5274,5301,5309,0,8211,8647,8052,7924,0,5274,7588,7994,7935,0,5309,8554,7849,0,8635,8093,0,8217,0,7734,8354,8073,0,5296,5459,7834,8170,5285,7908,8577,7628,5377,0,7478,8062,7675,8052,0,0,0,0,5464,5564,0,0,5471,0,8705,5285,0,7528,0,0,8467,5291,5396,0,7898,8711,0,0,7994,0,5712,7487,8411,5329,0,0,0,7849,5617,7672,5213,8164,5688,8577,7817,5274,5237,0,0,5453,0,0,0,5661,5666,0,8498,5314,7893,7571,5459,5325,7849,5325,0,0,0,0,0,5647,5274,7834,5629,7738,8539,5291,8608,7653,5642,8170,7872,7898,0,8077,5606,8716,0,0,5337,7746,0,0,8716,5426,7706,0,8682,8316,7696,7977,8196,7790,7864,5301,7710,0,7849,5381,5325,0,8721,5730,5671,0,5501,7719,0,5325,0,7723,7849,0,5274,5381,5396,5358,0,0,5655,0,0,5391,0,8022,7972,8543,0,0,0,7843,5358,5274,7898,5491,0,0,7920,7877,5358,7588,8154,8727,7616,8732,5419,5362,7786,7565,0,0,7738,8402,0,8407,8297,8739,7935,8245,7982,8402,5329,5676,5329,7542,8174,5655,0,5301,8340,7706,0,0,5377,8093,5726,5401,5329,5320,7817,5578,8473,5309,8745,0,0,8548,7734,5309,8473,5407,8234,0,0,0,8577,5301,7632,7914,7746,5407,8539,7930,0,5401,0,8329,8751,5699,7610,8397,0,5291,0,5617,0,5661,0,5347,8140,5476,7495,5301,8354,0,5476,0,5325,8756,5661,0,5209,7990,5296,5726,0,0,0,8631,0,8329,0,7914,5568,7867,8302,7500,8387,8312,8443,5337,5209,5401,5309,5367,5476,7867,5651,5285,0,7710,5391,0,7482,5305,5476,0,7719,5309,7491,0,5699,7935,8375,0,5241,0,0,0,0,0,0,8402,0,8345,5519,8179,8732,7687,0,5325,8073,0,0,7843,8422,0,8093,7864,8494,5401,8762,8514,5347,7994,8676,7908,7663,5437,5296,5476,5305,5209,8543,5413,0,5358,5301,5329,7632,0,7883,0,5699,8033,0,7558,8484,5568,5325,8093,0,8670,8509,7872,8287,8766,0,7957,8222,0,0,8222,8164,0,5666,0,0,5726,0,0,7972,8772,8164,8443,7616,7790,8716,8411,0,0,5629,7653,5372,8164,0,0,7734,0,8772,0,8297,8381,8577,5459,5459,7548,0,7672,7883,0,7952,5325,8447,5476,8184,7628,7599,8039,8115,7834,8402,5274,5285,5320,5564,8456,0,0,0,0,8201,8411,7710,7730,5476,8411,0,8217,5347,0,7637,0,8190,7628,7628,0,0,0,0,7738,0,0,0,0,8170,5661,5237,5407,0,7533,7738,8190,5274,5453,0,0,0,0,0,5651,5325,8131,7752,0,7599,5337,8115,5358,5651,0,5661,8010,5476,0,5666,7616,0,5309,5617,7482,5325,0,7588,0,0,0,7599,7746,7588,5476,5437,7719,5325,7632,0,7790,7687,5342,7972,8411,5301,0,0,0,0,0,0,0,8255,8721,0,5655,0,8077,7667,5661,5642,5661,0,8766,0,0,7610,8033,5651,5699,0,8136,0,0,0,7588,5661,8073,5693,8498,0,8519,7935,7653,7883,8052,0,0,0,7914,5651,7877,8514,7653,8402,5291,7849,7893,5391,7935,7914,7555,5564,7800,0,0,0,0,5476,5442,0,0,8222,5666,7990,7828,5358,5342,5717,5358,5476,5274,8447,7616,7605,8104,8527,7500,8647,7478,8239,8777,7967,0,8411,5642,7811,5213,5448,0,8217,5485,5237,0,8039,0,0,7834,5529,5573,7946,7616,0,0,0,5401,5274,7811,0,0,5401,5261,8777,7930,5407,5320,8010,7828,0,7904,7628,0,0,7700,5666,5476,5491,0,7877,8783,0,5309,5362,0,0,0,8239,5712,8484,0,7511,7653,0,5218,5309,7710,8504,8789,0,0,8062,5606,0,0,0,0,8073,7723,0,5320,8402,5296,8783,8307,8625,7864,7994,5501,8772,0,8099,7849,5218,5296,0,7628,8548,7834,5688,7999,7592,7658,0,0,0,0,0,0,8422,7516,7592,7687,0,7849,0,7500,0,0,0,8402,8745,5305,0,0,5413,5301,0,0,7834,5717,5305,7930,5676,5459,8670,0,7478,7834,0,5291,0,7658,7653,0,7828,5325,5381,0,0,0,5661,7986,8010,8154,5237,8077,5651,8297,8498,7482,5651,0,5291,0,8316,5509,0,5301,8670,8571,8795,8282,0,7681,0,7930,7491,7858,7877,7982,8800,0,8345,8104,7542,7930,0,5501,0,5480,5480,7571,8093,0,7952,0,5391,5730,8577,0,7487,0,7930,8504,8762,8073,7779,7839,5358,5325,8548,0,7849,5301,8467,8354,0,7877,8539,5666,5309,5248,7487,7734,5476,0,0,7811,8272,8613,7616,7616,7648,7482,5391,7482,5291,8068,8190,0,0,7867,7605,0,0,5337,0,0,5237,5391,5476,5305,0,0,7982,7675,7478,0,5485,8805,5391,8447,0,7738,0,5573,7877,8647,7757,5386,5386,8811,8131,7628,5730,0,0,0,7982,7924,0,5274,0,8164,7957,7999,8179,8239,8647,8370,8010,7653,7883,8427,0,7738,7605,8282,5407,7628,5314,5337,5301,0,0,0,7877,5401,0,0,8484,7734,5688,7817,5209,7628,0,0,8705,8816,5712,0,8484,0,7500,5255,8411,7790,0,0,0,0,5476,5337,0,5255,0,8222,5296,0,0,0,8201,8211,7986,5647,7478,5301,8093,0,5325,7667,0,7888,5291,5491,7930,0,0,0,0,0,7986,8411,5342,5647,5459,0,0,0,0,0,7888,0,0,5342,5358,5647,7719,8179,5647,7746,0,7672,0,0,8427,5358,5325,8433,0,5301,0,0,5342,0,5320,0,0,7864,0,0,0,0,7723,7632,7482,8068,7786,7628,5666,7930,8028,7858,5401,5688,5401,0,0,5301,5693,8631,7576,0,5426,8608,8010,5352,8126,5480,0,7864,8126,7706,5573,7817,8316,7653,7558,7977,7811,5426,7828,8349,7582,7864,8821,8577,8119,7742,8539,7628,8732,8164,0,5629,8827,8647,8617,5329,8316,5241,5301,7730,0,0,0,0,8625,5476,7653,5661,7632,7511,0,7883,0,0,8772,0,0,0,5337,7823,0,8461,7667,7710,5568,5509,5301,7511,8577,5367,0,8345,7628,8170,8110,8329,0,8451,8179,8833,5218,5564,8821,0,5237,5320,5285,5285,7653,8015,0,0,8839,8033,8010,5325,0,0,7786,7478,0,7867,5320,0,5480,8762,7487,8297,7715,5564,8093,5325,5519,8190,8716,0,5381,5301,8245,5325,0,7672,7914,5301,7843,8845,7482,0,8196,7867,7935,5337,8201,5464,5699,8277,0,0,7500,7898,7990,7706,0,8119,0,0,7576,5464,8422,7982,7930,0,0,0,5325,7786,0,5524,7637,7538,7605,8422,8422,5401,7843,0,0,0,5491,7663,5274,0,5237,0,8422,8179,5514,0,5381,5501,8658,0,5642,8479,7977,5666,7834,5329,8422,7616,7920,7632,5301,5432,0,7790,8126,0,0,5642,7849,8104,7982,8381,5442,5301,5629,7811,5358,0,7935,7924,8245,5708,5529,0,0,0,8297,0,5372,5509,7738,8222,0,7834,0,7605,8387,8010,5337,0,8422,8249,5617,5309,7786,7706,8255,5285,7834,0,8795,7746,5274,8589,8136,0,7811,8816,7558,0,5291,5237,7706,8608,5712,7786,0,0,8010,5726,7877,8598,5358,7790,8851,7786,8062,7811,7632,8560,7977,7768,0,8211,8201,7786,0,7715,7663,7616,8598,7843,5381,8539,7977,0,8751,8504,7834,8762,8539,0,7843,5274,0,5426,7930,8345,5651,7706,7706,8494,5617,7628,7977,7558,5642,7616,7924,5325,5381,7653,8438,7957,8509,5642,7834,7962,7952,5464,7616,8119,5496,5401,0,8190,8732,7935,8447,5726,7622,8154,7628,7823,7982,8554,0,7542,5617,5419,5708,8349,8211,8427,8411,5285,5291,8789,5358,5391,5301,5305,8577,7763,5305,5237,7877,5726,5285,8154,0,7500,7700,8316,5730,7628,0,0,8073,8857,0,8287,0,5381,0,5426,5296,5391,0,0,0,5642,7628,7898,5381,5578,0,5617,8539,7582,7839,5564,8438,5655,5655,0,7834,7757,8062,7817,0,7672,8625,7986,8402,8048,8451,7834,0,5381,0,8039,5612,5509,5241,8077,7757,8277,7738,8402,0,8234,0,0,0,0,7746,0,8438,8170,7628,0,7999,8115,0,0,7533,7849,8131,8062,8149,7478,8287,0,5329,8140,5401,0,8255,5320,0,0,8433,0,5296,7588,8519,0,7610,0,5386,8772,7883,7994,8811,8245,5301,0,5274,7533,7986,0,5453,8359,7616,5274,5347,5305,7904,0,5564,5358,0,8154,5476,7511,0,0,5676,8154,7558,8307,7864,0,7834,8863,8762,7706,7628,7768,8154,8077,7946,8170,5617,0,0,5352,8391,8340,7957,5301,8104,0,8402,5261,0,7706,0,8277,8093,8239,8705,7628,5386,8287,7706,5476,8184,0,5407,8387,0,7805,0,0,0,5358,7883,8312,8184,7972,8783,5606,5606,8110,5476,8577,8345,7849,7723,5301,0,5209,7588,8467,8387,8762,5509,0,7582,5337,5296,5407,7872,8211,8170,0,0,8345,5342,8277,7643,8322,0,7888,0,8136,8179,5377,7500,8484,5464,7706,5381,7790,7834,0,7972,8422,0,7663,8334,8777,5629,8869,7643,7706,5573,5612,5314,8417,5647,8443,7957,7622,5301,7800,7786,8433,5396,5712,0,5401,8479,5261,7839,0,0,7972,7904,8402,5688,0,7610,0,7893,8039,7558,8484,7864,0,5291,8062,5285,8498,5642,8316,5496,5337,5320,5642,5717,7811,7843,5291,8033,0,0,7914,0,8391,8391,0,8519,8267,0,5309,8287,0,8772,0,7723,0,7558,0,7628,7558,8316,0,8255,0,8170,5347,5476,8164,0,0,7487,0,8048,0,8249,7952,0,0,8370,8255,8287,5699,8217,5301,7904,5381,0,7632,0,8267,0,7982,7482,0,7972,5401,0,8811,5407,7576,7605,7692,8136,8217,8115,7924,7605,7817,5476,0,0,8519,8149,8504,8164,0,8136,8604,0,0,5476,5291,5358,7558,7667,5442,5296,7710,7972,7834,0,0,8099,5224,7999,7653,7628,5491,7999,7893,8302,8093,7738,0,8211,7491,8222,8005,7795,7823,5717,0,7710,0,5612,7839,7576,7658,7908,8196,0,8762,8604,7811,0,8159,5241,7542,0,0,7994,0,0,5237,8136,7734,8359,8170,0,0,0,7533,7738,7542,0,8164,7632,7982,5448,0,7734,7763,0,8851,8349,5320,8093,5666,5419,8873,7972,7757,0,8589,7982,8010,5712,5342,5407,5629,5661,0,5305,8010,5666,0,8554,8093,0,8827,8267,8249,7628,7972,7706,7883,8154,7994,8772,7719,5661,5448,8022,0,7511,5476,0,5666,5301,7893,8577,7516,5274,5642,0,5432,5274,8261,5622,0,7883,8411,8093,8062,7706,8239,8282,5651,7982,7982,0,8604,8456,7723,8391,8484,8201,5712,8589,5712,0,0,5301,7768,0,8869,7521,0,5391,0,0,5347,5464,8484,7834,0,7730,8641,5661,5578,8514,5237,7800,0,5464,8772,7675,8387,5358,7632,8010,5301,8402,5496,5442,8539,8582,5573,0,7877,5651,7542,5688,5655,0,0,0,7786,7834,7710,8625,7715,8073,7473,8845,5642,7790,7599,0,0,0,8613,5291,0,8438,5419,0,8427,7571,8354,5224,0,7576,5358,8504,7628,0,8287,7696,5476,5651,8745,0,7599,5325,8201,0,5432,8010,5642,5301,8010,5726,5564,7696,0,0,0,8154,5309,7706,8170,8277,7935,7930,8816,5476,7500,7511,8329,5564,0,8566,8805,5413,7687,0,0,0,7742,8851,8845,5629,7952,5296,7738,0,5642,0,7706,7653,7672,5651,5651,0,5329,8010,7742,5651,5381,5564,5651,5464,7588,5642,8077,5309,5401,5358,8745,5377,8093,8857,7972,7482,8479,5442,7774,5651,7834,5347,5248,0,0,8721,5617,7632,0,0,7632,7605,0,5442,5676,7592,7696,5476,7872,7774,8504,5564,5347,5342,7628,5642,7828,8093,7605,7738,5255,7542,5291,7805,8201,7786,8879,0,7653,7653,8711,7982,8062,8539,5564,8190,5651,5712,7843,7786,7999,7738,7628,8548,5301,8239,7478,5666,5325,5666,5301,0,8073,8608,8005,5480,0,0,7999,7952,0,7500,8716,8287,7888,0,7582,7952,8073,0,7811,8190,8179,5301,8422,5464,5529,7982,8222,8297,7681,5564,8239,0,5476,5320,8312,7849,8140,0,8136,8345,8783,5329,8427,7738,0,0,0,8255,7588,0,8721,0,0,5573,0,7696,7696,8699,7706,7487,5573,0,7834,7898,8345,0,8884,8519,7904,0,5377,7653,7571,7904,5578,8631,8119,5358,7696,7605,7478,7752,7972,7576,5329,7752,8234,5688,7986,5437,5448,7746,7972,7706,8340,8647,5401,0,7610,7588,7706,8467,8527,0,5291,8539,7858,8721,8816,8504,5655,5671,7920,8772,8783,8104,0,8104,5305,5432,7706,5391,5358,5291,7487,0,5396,8136,5296,0,7696,0,8711,0,5237,7972,8504,8170,5459,5578,8670,8249,5358,7653,7628,5274,0,8766,8179,8201,0,7706,5325,5453,5329,7592,7653,5476,7774,8104,7972,5209,8245,0,5606,7576,0,8104,0,5578,0,8110,8888,5325,5476,8762,5291,5717,5325,0,0,5612,8893,7521,5464,7706,7605,8451,7710,5534,0,8898,7786,8509,8010,0,8340,7972,7877,5666,0,0,5578,7628,7952,5464,5309,0,8039,5514,8772,0,5237,7800,7888,5708,7920,7478,7663,8249,7920,8249,7924,7972,8073,7616,7994,5386,5347,7834,7877,7768,5655,7734,0,7658,7893,5320,0,8903,5496,8297,5606,7710,7738,7977,8598,5464,8239,0,0,0,5309,7864,0,5564,7888,0,7864,0,5642,8539,0,8539,8201,7800,7935,8099,7706,7715,8179,0,0,0,0,5337,0,8245,0,7588,5564,5325,5419,8340,8073,8909,7935,5564,8201,7478,0,7924,8391,8334,8211,0,8249,8340,7681,8509,8126,7675,7528,7706,0,0,8670,7946,8816,7675,8033,0,8010,7482,8608,8297,8915,8048,8010,5726,5391,7849,8411,5491,5305,8919,0,0,0,0,5496,7888,5337,8052,0,0,5274,5666,5309,7706,0,5712,0,0,0,0,7834,5476,7482,7706,5578,5237,5476,7491,7864,0,7972,7653,8287,0,8104,5224,7908,5442,0,5480,7723,0,8484,0,8140,5564,0,8249,5301,8577,7738,0,7786,7779,0,7986,7828,7839,0,0,0,5325,0,5496,7790,8762,0,7637,7872,8727,8821,0,8277,5426,0,5476,5712,0,7849,5325,0,7653,0,5301,5209,8443,5642,0,5301,7706,8811,5301,0,0,0,0,7588,5442,0,5661,7930,7500,5485,7734,8093,7521,7571,7757,8104,8745,7823,0,7672,7710,8670,7628,5396,5301,0,0,0,5301,0,0,8217,0,5476,8381,0,7982,5564,0,7558,7823,8604,0,5377,8196,8473,5342,7994,0,7719,7663,7864,5381,8739,7893,0,7482,0,5514,8307,5296,5661,8115,8772,8227,7972,5476,5564,7864,0,7667,0,5496,5274,7800,0,5358,8093,5381,8005,7977,0,0,7843,0,7893,5329,7687,7849,0,8447,7616,7675,8909,8149,8115,8005,0,7542,8170,8647,8282,8307,7516,5651,5314,5661,7982,5651,0,8925,8827,5717,7823,7605,7834,7653,0,5476,8375,8354,8297,8093,8929,7628,7555,0,5476,7555,8196,7542,5367,5573,5309,5651,0,8136,7653,8443,5367,7834,5647,0,0,5612,7482,5248,7576,8211,8467,7555,7622,5514,5509,5301,0,8136,5391,5358,7839,7898,0,5676,7521,7952,5213,0,8329,7687,5642,0,0,0,7864,7924,7681,5337,5291,5301,0,5301,0,5325,7982,8427,5564,8227,8721,8170,8249,0,7558,8711,0,5301,0,0,0,5476,5224,0,5730,5509,7710,0,5717,7653,8073,0,7511,8179,8179,5476,8190,7834,5612,5524,5218,0,0,0,7628,0,5578,7538,0,0,8617,5309,8721,7734,7516,0,8805,0,7986,5476,0,7839,7834,5381,0,7994,5274,8381,5564,5717,0,5305,5564,8716,5274,7752,5301,8179,7763,0,8447,7637,0,7500,7555,8349,0,8795,0,8560,0,0,7628,7994,7605,0,0,5476,0,0,5564,5358,7605,5209,5320,5367,8447,0,5285,8190,0,8417,8164,5459,7730,0,0,7628,0,0,0,5329,7653,0,5476,8397,8427,7588,5509,8447,8721,5291,5413,8179,0,7888,7706,5642,7952,8699,5730,8329,5661,0,5296,0,5661,8073,7990,0,7500,8126,0,5573,0,5377,5573,7710,5453,8721,8527,5661,0,7888,5291,5296,8833,7730,7888,8093,8249,0,8039,8249,7610,0,7616,0,8721,8140,5301,8762,0,7864,0,0,0,8407,8407,5419,5401,0,8670,5237,0,7994,0,0,7849,5337,5329,8277,7742,8119,5676,7706,0,8267,0,7706,0,8873,5325,8349,8519,8184,0,5301,8093,0,8033,0,5274,7982,0,8443,5491,5491,7643,0,7935,5377,0,7495,7972,7511,7893,5285,0,5708,7516,0,8705,8272,7920,7528,5261,5661,8631,8272,0,5237,5661,7920,5642,5642,7555,8052,8566,7482,8010,7823,7710,7599,8467,5367,7576,8467,5391,5651,0,5717,8174,7893,7982,8447,7839,5651,8411,0,8136,7628,7864,7972,8566,7500,7672,5296,8329,5386,7653,7977,8307,5396,0,0,8140,5237,8022,5651,7528,7478,7723,5241,5442,5426,8484,8170,7643,5377,7800,5407,7706,8087,5666,5509,7935,8179,5381,5337,5296,7687,7588,5717,5386,5237,0,0,7710,7994,5305,8234,0,0,5372,5296,8375,8239,8149,0,0,5280,5708,8077,5342,8349,8772,7528,5485,8345,7738,7877,7994,8641,5325,8033,7935,7500,5274,7528,0,0,0,5386,0,7834,5688,8062,8022,7667,8239,8170,0,7800,8577,0,5573,7710,7521,0,5712,5320,8604,7710,7786,7628,0,0,8239,8149,5320,5578,0,0,0,0,7864,0,8302,8548,5291,7667,0,7957,7994,0,7605,7986,0,5578,7999,0,5476,0,8447,0,0,5476,8052,8387,7811,7800,7800,5726,8149,8052,0,8514,5274,5655,5320,7571,7605,8131,8190,5476,5377,0,5309,8179,0,8201,7779,7972,5325,5509,8721,0,8745,7663,5564,7952,8131,5274,8821,7972,8473,5476,5612,5459,7952,8119,8329,8033,5617,8745,0,0,8104,8062,7582,7558,7994,5459,5320,0,5325,8925,7706,0,5564,5708,5564,8312,0,8772,7687,7935,5726,5337,7849,5730,7952,7864,5419,8267,7853,5688,0,5564,7571,7982,0,8073,0,5291,8539,5291,5564,5464,0,5218,7805,5612,7710,0,0,0,7800,8936,7706,5407,5358,0,7972,7605,5726,8427,5325,7828,0,5301,5301,7986,7768,0,5564,5496,5629,5391,8560,7867,7706,8073,8136,7853,5573,8140,8354,8539,8033,5573,5358,8211,7904,5606,8438,8577,8527,0,5688,7681,5564,8144,5407,0,0,0,5655,5688,8451,7710,8005,8577,7898,8762,7478,0,5291,5329,8227,0,5573,7779,5606,8653,8227,5291,5647,5396,7893,5358,0,5280,8494,5325,5726,8005,7982,8140,5342,0,0,5688,7920,7930,8631,5285,8164,0,8140,5459,7710,8451,8249,8190,7888,5432,5578,5573,7877,7952,8676,7967,8919,8277,8909,8354,7768,5320,8093,0,8196,5564,5617,7681,0,7663,0,7723,7757,8370,5261,8082,8427,7610,8484,5301,0,8438,8164,8354,5291,8756,8010,7706,8062,5564,5329,8062,5480,8438,5606,5676,7706,5241,5485,7817,5606,7843,7790,7482,5329,8267,8170,7920,0,5661,8411,0,0,8164,8940,7478,5496,7786,7877,5352,7800,0,0,8617,8946,5622,7877,0,8519,5342,7930,8509,0,7972,7994,8438,8093,0,8028,8093,7706,8354,0,5476,5237,8033,0,8164,8033,8789,5337,5325,8073,7565,0,0,8140,8539,0,7628,5717,7500,7672,7849,0,8126,7817,7592,0,8387,8293,5337,8022,5661,8777,5642,5320,7834,8952,8239,0,7628,8073,5342,0,7834,5666,7516,7658,8762,8354,5325,0,8267,0,0,0,8170,0,8033,0,7521,5274,0,7952,7628,5666,0,5647,8033,5426,5688,8560,0,8903,7774,8539,7834,0,0,0,7877,0,5301,5612,7538,7538,0,5629,8456,7957,0,8451,5647,7864,8179,5381,5377,5325,8903,5285,5358,5666,5476,8104,8104,8312,5301,5437,7658,8312,5606,8170,7500,8010,0,7500,7696,8154,0,8297,8048,0,0,7779,5377,5651,0,0,8433,5712,7482,7746,5647,8005,0,0,5651,8164,8039,0,5651,8170,7920,5309,7599,5325,8249,5407,5529,8222,8811,7877,5301,5301,5688,5471,7616,8267,8033,5325,8297,0,7672,0,7952,8354,5296,7920,5496,7888,7877,0,8739,5617,5426,5534,5391,7710,7491,7653,7893,7491,7834,8857,0,7482,7888,5274,0,0,7867,7877,7653,8249,8354,8140,7542,5309,5261,7715,0,0,0,5241,7972,8104,7628,8711,5509,5442,0,7588,8959,5606,8739,8140,5342,0,5381,5407,5708,5285,7491,0,7491,7610,5372,8222,5401,0,7811,7672,7994,5274,8964,8073,5241,7605,7904,8249,5661,0,0,0,7675,5301,7687,5651,0,7491,0,0,0,5661,0,0,0,5261,0,0,5261,5291,5325,7565,0,5453,5708,5573,7478,8721,5661,8805,5661,8903,0,8772,0,5367,7500,5358,7487,8154,5241,7478,0,8489,8663,5391,7811,8144,5564,8663,5274,8196,7867,5347,0,8387,0,5699,0,8104,7972,0,8005,7542,8560,5476,8447,5241,8663,0,5391,0,7839,5291,7500,8267,0,0,5573,8479,7828,5717,5377,8062,8447,0,5301,0,5274,0,5237,8190,0,0,0,8277,0,7715,7558,5407,5358,7706,8093,8052,7898,5629,8539,5564,5274,8969,0,0,0,7734,5296,7994,5476,7491,0,7935,8052,0,0,5274,7877,7643,0,7653,7839,0,7706,8005,8110,0,0,7504,0,7982,8402,0,5699,8539,7935,0,0,7834,8239,5358,0,0,0,0,7555,8222,7616,8062,7839,5280,5377,7628,5666,8222,0,5325,5564,5274,8433,0,7719,0,0,7628,0,7663,7653,7883,5717,8539,7982,8334,0,0,8093,8349,8349,5401,0,5529,5655,8312,5564,8417,5237,8217,7883,8267,5274,7482,0,8217,5413,7982,7893,7893,7542,7930,0,0,5391,5606,8427,5407,0,5296,5564,5509,5305,8255,8239,0,7763,5717,8062,7628,7706,7888,8608,0,7904,8539,8190,8539,5358,7576,8174,8184,5476,0,7977,8245,7888,5237,7883,7681,5661,8227,8467,8721,7779,5413,0,5329,5325,8190,5320,0,5320,7982,8267,7877,0,8164,7864,0,7904,8527,0,5578,5501,8721,8201,5717,0,5464,8762,5377,8073,7542,7986,7610,7994,8427,5347,0,8136,5358,5480,5285,0,8201,8170,8312,5367,7994,5564,7849,8010,7877,7982,8925,0,0,0,0,0,8010,5459,8062,7710,8222,7482,0,0,8073,8821,5442,7616,0,5671,0,8267,5655,8387,0,5730,8438,8456,5717,8411,0,8052,5651,8119,8682,5358,7888,8104,8976,8589,7558,8297,8982,7790,8073,8422,7920,5564,0,0,7516,0,8514,7478,7706,7972,8033,5367,7849,8940,0,5730,0,5564,7648,7643,8456,7542,0,5237,8391,8005,5717,0,5708,8589,8617,7957,5655,5296,8179,0,7516,8136,7653,8762,5309,7746,8010,7864,5386,8062,8062,5529,5529,0,5358,0,5337,5476,5529,7730,7628,5301,5301,8467,8217,8052,7867,8873,0,0,8039,8201,7994,7994,0,8345,7500,8987,7946,7924,0,5642,0,5358,0,7946,8272,7511,0,7605,0,7706,8783,5476,8307,8467,8140,0,8073,8179,7924,0,5476,8184,8670,8543,0,8282,5305,7663,8869,5617,0,7972,7924,5407,8467,8217,5377,7994,8329,7972,5476,8504,5391,7982,8884,5476,5407,5342,7616,8222,7982,7982,5661,0,7924,5401,0,8869,0,5407,7605,7616,7834,5309,8519,7883,0,7908,5476,7491,5407,8869,8381,7839,7542,5529,8073,5651,8839,5651,7779,7706,7706,8087,5358,8073,5413,5432,8447,7994,7811,8010,7706,7511,5564,8608,8608,0,7706,8658,5480,5476,8073,7858,8073,0,8190,0,0,7628,7710,5329,5476,5301,0,5453,5237,7478,0,5666,0,7999,8397,8287,0,8345,7696,7628,8873,5564,8302,7811,5573,5448,0,5358,5459,0,8539,5301,0,7653,5437,7786,7558,7957,5305,5651,8349,5708,7883,7605,5464,8010,8919,8316,0,8762,7982,7994,5325,7710,7516,0,8334,5407,8077,7632,5325,0,0,8077,5476,0,5509,8387,5280,8073,0,8093,8811,8154,8370,0,7946,8005,8433,0,7843,5358,7811,8312,7742,5407,5377,5342,0,8217,5708,5407,5305,7952,7908,8345,7538,8745,5501,0,8898,8052,7888,0,8716,0,5381,5564,8994,0,8227,8077,7800,0,7839,0,8144,0,8539,5564,7681,8443,8494,7893,7774,5352,5391,0,8110,7706,5496,7533,8073,7800,8548,7839,7952,5325,5347,0,8249,7746,7533,5329,8498,0,7511,5476,7752,5301,5301,8833,7605,7643,7914,7710,5426,7599,0,8154,7920,0,5712,7935,5712,8783,8170,8417,0,8447,8613,8093,8387,0,7706,0,0,7898,0,8115,5381,7628,7528,7930,7904,5480,0,8255,5337,8539,5381,5305,8119,5261,7706,5261,5651,7883,5476,8845,8845,7853,0,5529,5476,8805,7681,8711,7511,0,5301,0,5329,7500,8451,7904,7511,8170,8427,8170,0,8443,5274,7972,0,0,7972,0,0,7986,0,8845,7864,7715,7548,8539,7834,0,8845,0,7843,7628,0,8136,8370,5396,8427,0,7957,7877,5459,7994,8190,5372,8438,0,0,0,0,0,5480,8433,8739,8909,8028,8149,5329,5476,5661,7834,8745,7734,5442,5274,8721,5381,8277,8249,7491,0,5301,7482,0,0,8909,7628,0,0,5496,8762,8062,0,5237,0,7706,0,8467,5325,8267,7565,0,8560,5655,7982,0,0,5381,8456,8811,8190,0,8479,8255,7839,5459,7904,7681,5509,7839,7972,8126,7478,8222,7883,7904,8316,0,0,8484,7952,7839,5337,8467,8149,8484,8998,8028,7864,7864,5426,7849,7834,5647,8201,5413,5568,7805,0,7706,8467,0,0,0,5730,5464,0,5676,8005,7864,5325,5305,5248,8222,7478,5655,7500,8245,7710,0,8827,7538,5568,7834,8179,0,7849,0,5347,8316,7628,8560,5386,5301,5501,7734,8093,5708,8190,5476,8190,8249,0,5666,7834,8222,5459,8093,5391,5285,8577,5485,8170,5377,7920,8461,5285,8359,8201,8539,7811,5642,5301,5651,0,0,0,8504,0,0,8647,5329,8093,5476,7774,5261,0,5320,5480,5305,7849,8964,5224,5352,7643,0,8039,8201,5301,7877,8126,8451,8249,5612,8322,7972,8582,5358,5708,8249,5274,0,7853,0,7994,0,0,0,8149,7877,8447,8447,5301,7582,8170,5407,5564,8302,7687,8272,5407,8789,8387,5485,8811,8539,5407,5712,8245,5407,5617,5661,5437,8211,5301,8234,7628,7738,5578,7924,8641,0,7849,7757,7994,8267,7687,5726,0,0,0,8805,7786,0,8936,5564,8964,7738,7738,5314,7653,5255,7849,0,7576,8073,7511,7555,0,8174,8539,8302,5377,7908,5377,7706,5325,7710,8994,5309,7990,5301,8170,5726,7986,7786,5280,7706,5274,5407,5391,5274,7663,5396,8170,8115,5642,0,5325,5496,8795,0,0,7834,0,0,8316,7823,8354,0,7710,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5476,5693,8898,7706,8987,5367,0,5305,8015,8589,8987,5367,5325,8201,7628,7746,5671,5442,5442,5622,7867,7920,5391,5391,5688,5224,7491,8827,7582,7487,7558,9003,5688,7779,9003,5337,7779,7834,5224,8940,8234,8484,5717,8387,8322,7548,5325,5381,8527,8721,8732,7877,7516,5314,7605,0,5509,7883,7994,7605,5485,5301,8170,8959,5564,5476,5476,8716,0,8484,8484,5578,5642,5476,0,7972,7478,7687,7982,8048,5333,7548,5255,8334,8255,7982,8625,7558,7558,5476,7482,0,7763,8484,7706,7491,5237,7521,5671,8484,0,0,5320,0,5391,5325,7700,7977,0,5391,0,0,8349,8375,0,8613,5529,7834,5564,8302,8136,8811,0,0,0,0,8811,7710,7952,8811,8375,7516,5476,5291,5333,0,7628,5241,5391,5391,7696,5573,7628,7628,7628,8267,7982,7706,7478,5485,7487,5342,8682,8682,8387,5661,8387,7706,8387,5325,7853,7599,8756,7738,5285,8370,5476,7877,7946,7715,0,7521,5485,8805,7779,7521,5485,7491,8196,8073,5337,5661,7883,5401,7888,7994,8504,8504,5325,0,7599,5476,5391,8504,5333,5693,8115,5309,7734,5442,7904,8015,5309,5381,5224,8705,8827,9008,8589,8099,0,8777,5209,5391,7738,8015,5564,5274,5274,5480,8062,8316,8504,7542,8811,5476,8539,5491,8093,5476,8783,8227,7730,8987,5476,7715,8514,8504,7977,8783,7924,7542,7548,7920,5509,7478,5407,5381,5642,8527,8777,8504,7599,5501,8539,7565,7548,7706,8456,7628,7757,5471,7752,5476,7528,8756,5476,5325,7663,7478,5325,5564,8048,5519,5309,7930,7883,5564,5629,7548,5337,8154,5296,7628,8402,8732,8589,7994,8732,7935,8387,5274,8987,8227,8099,8777,8329,7528,7994,7605,7710,8190,8293,8073,7786,8447,8447,5661,8282,7500,8093,7576,7914,5726,7487,5413,7487,7977,7920,5476,7487,7663,5241,7828,7653,8349,7883,7605,8201,5726,5358,8670,7839,7994,8539,7605,7571,5337,7924,5377,8427,7849,5358,5480,5617,7628,5476,9008,7571,5568,8381,8316,8447,8190,5726,5218,8631,7542,0,9012,0,8456,8359,7849,8149,7521,5717,7924,8126,8048,8391,7977,7696,5224,5325,8727,8154,7952,7768,7681,5285,7839,5476,5391,5426,7914,5480,8795,7972,5671,8277,5476,7710,7473,8687,5301,7482,5391,5651,7605,7500,5337,8345,5329,8711,7696,8099,8316,5496,7710,8329,7817,7576,5476,8613,5407,5367,7977,5437,5329,0,5285,5655,8604,8255,7908,7637,8381,8789,5309,7478,5730,7592,7558,5655,7571,5442,8196,5329,8170,5320,8805,7914,5480,8073,7823,8052,8411,8422,7628,5237,8140,7864,5688,7500,8582,7692,7478,7667,5476,5564,7843,8443,8255,5301,5218,7643,5485,5274,8589,5213,7588,5564,5237,5301,5564,5255,8222,7628,7487,5367,7528,5519,7628,5377,7935,5442,0,7834,9016,5301,8277,7487,5407,5519,5564,7849,8170,7675,5213,5407,8473,5573,7893,8527,8387,8345,7930,7628,7528,5358,5407,8227,7710,5224,8365,5642,8402,9016,5509,8277,8777,8548,5717,8721,7930,5606,5320,5320,8514,8479,8772,5291,7805,5426,5352,5629,5730,5476,7706,5377,8811,5485,5381,5342,8227,5381,7565,7972,8571,5676,8548,8170,7542,8447,8099,5314,7994,7632,8022,8873,7643,7582,5209,8543,7478,8170,8716,7774,5407,8533,7491,7972,8302,8745,0,7616,7558,5407,7834,8699,7548,8104,7977,5381,7994,7628,5224,8893,5377,7994,7858,7834,8527,5629,8494,7864,5661,7935,8119,8795,8539,7571,5496,8805,7930,7491,5464,5642,8427,5442,7834,5337,5407,7500,7687,7839,5564,5358,7972,8548,7746,5325,5476,5661,8329,7487,8676,8604,5655,7742,7853,7853,7930,8925,5329,5381,7994,5501,8227,7952,5448,7972,7588,5367,5564,8467,8164,5301,8613,8267,8131,7538,7898,7828,5629,8052,5381,5237,5261,7542,5291,8777,5573,8589,7930,7675,5218,5642,8772,5612,7924,8048,5224,7977,5491,5437,5629,0,8888,5573,7495,8179,5666,8402,5218,5372,8548,5352,8022,5708,8732,7628,8533,8694,8987,8322,5485,7710,8302,7653,8015,8745,7834,8539,5237,5329,7930,8647,5309,5401,5329,7800,7542,5274,8772,7616,7582,5622,7521,5261,8255,7592,8422,9021,7734,5237,8149,8227,8687,8239,7706,8919,7877,7994,5325,8010,5296,8732,5407,7883,5285,8484,9026,5501,5274,5291,7864,5285,5407,8316,7516,8073,7786,0,7588,5274,5419,8438,7491,5309,7977,8255,7920,8527,7576,7663,8159,5476,7667,8789,7877,5476,8903,5485,8190,7908,5407,5573,7883,5285,8302,5437,0,8888,8345,8613,7658,5442,7637,5347,8447,5309,7542,8249,7478,8381,7723,8447,8227,8577,5666,5329,8582,8222,8762,8473,8365,7972,7487,5337,8052,8033,8033,5301,8909,7994,7610,5573,7877,8261,5320,5296,5301,7632,8365,7495,5629,7834,5666,7700,7696,7478,8267,8365,8467,7504,5248,7999,7504,5274,8687,8217,7990,8217,7746,5647,8227,7696,8217,7696,7528,5647,7487,7834,7834,5391,5391,8903,7542,8307,5501,7790,7914,8307,8126,5485,5301,5464,8613,7599,8028,7768,7779,7628,7904,7817,7511,5237,0,5347,7817,5606,8721,5426,5224,7706,8170,7828,5209,0,7924,5407,8087,5325,5476,5325,7877,7592,0,8297,8028,8467,7790,8461,8297,8604,7592,8604,5437,7795,7592,7790,7576,8711,8131,7500,7746,5651,8164,7482,5661,8795,7667,8789,5476,5651,5391,8721,8249,7548,8170,8467,8391,5568,0,5564,5661,5651,5224,7582,7658,8461,8527,5224,7982,5248,5291,7982,8582,7491,8873,7533,5564,8438,7582,5274,8577,5367,5407,5485,5320,5712,5647,7706,7786,9032,8022,5485,7779,8745,7757,8093,7582,8811,5407,7482,8694,5476,5301,8777,8391,9038,8402,7487,7888,5309,7516,7516,8381,5237,8762,5325,8010,8010,8010,8527,8170,5573,0,8527,0,0,5564,8527,5255,8527,8509,5464,8509,8811,8919,5255,5726,7504,7908,5647,5708,5573,5325,8903,7653,7653,5372,8479,7757,8479,8504,8504,8519,5501,7867,5476,5325,8795,7542,8879,5573,7752,5325,7883,5426,5396,5509,5291,5301,8354,8577,7511,8115,8461,8519,5564,5573,5726,8170,5261,8438,8093,7548,7533,5432,5381,5381,8222,5301,5261,5432,7877,5485,7795,8682,8479,8519,8005,7692,7888,7696,7972,9045,7768,5496,7839,7990,5381,7952,7839,7952,8509,7888,5325,5377,5485,7957,8682,8115,8467,8451,5529,7990,7986,7542,5496,8519,7795,5407,8451,5301,5377,7893,8057,7877,7706,8519,5209,8461,5509,8073,5325,7888,8745,7834,5314,5407,5325,8073,7834,8170,8154,8119,5407,7888,8676,7478,7795,5564,8119,5407,5717,7687,8509,8519,5676,7877,8641,5329,5301,8998,5261,8647,5568,5285,7795,8255,5291,5396,7687,5314,5401,8170,7920,7511,7952,5285,7952,5407,5407,5629,8154,5325,5407,5476,5407,7972,7877,8498,5305,5301,7605,5301,8387,7768,8282,7817,7977,7478,8349,8577,5309,8946,5661,7972,7582,7605,8293,5381,5726,8670,5476,7605,7622,5301,5485,8149,8149,5676,5568,7924,8631,7675,8711,8582,5381,8577,7849,5391,7990,5485,8052,8073,8438,7710,7843,5218,5218,7746,7990,8329,5676,8745,5329,8057,8249,8277,5333,8509,7746,7582,7500,7500,8795,7478,5476,8329,5534,8119,8149,7715,5329,8647,5391,5325,8196,8322,5285,7768,7924,5476,5291,8010,5676,8316,8170,7883,5661,8349,8329,8329,5329,5301,7935,8261,7768,8222,8670,8267,8484,8222,8131,7478,8267,0,0,5671,5671,8222,8827,0,7504,5352,8170,8272,5717,7734,8345,8174,5564,7817,8245,8484,7723,5358,5396,7930,5386,7710,7565,8484,5476,8925,5419,8756,5419,8582,7982,5367,5726,8411,7952,7867,8205,8582,7893,7982,7853,7952,7952,5296,7883,7952,5401,5401,7940,8762,8484,5726,5237,7920,5726,5476,5280,7834,8411,8447,5726,5391,7528,7542,8184,7730,8443,8119,7478,5296,8282,5391,5237,5509,5396,5661,5509,7930,8443,5314,5661,7883,8349,7823,7883,5325,8073,8952,8201,8582,8411,8201,8427,5280,5329,5337,5329,5237,7795,8494,5661,5381,5381,7994,8608,7723,7972,7790,7482,7994,8670,5325,5699,8375,5391,5676,5661,7500,5309,5699,7478,5325,7864,8745,5237,0,7653,8427,7864,8805,5301,5578,7653,5301,8387,8387,7834,5617,7972,7687,8334,0,7849,8387,7972,8604,8334,5377,5301,5617,5730,8805,8397,8451,7893,8467,8604,8467,7521,7834,5301,5291,8302,7828,5573,8484,8196,5708,7952,7834,7487,5301,8227,5347,5301,7834,7834,8467,7487,7817,7516,5688,5726,5726,0,5666,8762,5274,5622,8589,8179,8179,8179,8179,7663,5491,5337,5496,5325,5337,8617,8010,8087,8625,7904,8048,8893,5391,5320,5314,5726,8087,8411,7500,7977,7533,5448,5524,7994,5448,5459,7872,8827,5568,7786,8170,8093,7795,8272,5568,7972,5285,7999,7904,5693,7990,7658,7710,5391,5337,5688,8312,5661,8052,7487,8772,7710,5333,7757,5325,0,8062,7763,7500,8211,7710,5717,8190,5642,5209,7849,5325,7757,8879,8316,8391,5730,5301,5647,7521,7687,5367,7491,5329,9051,7538,7742,5529,5476,7667,5442,7516,8068,7482,7893,5358,8196,8196,5381,8201,8998,7904,5471,5501,7864,7653,8805,8560,8598,7706,8062,7972,7914,7478,7478,8783,8884,5529,5237,7805,8307,7752,5730,7742,7478,5524,8387,7667,5329,7864,40,5712,7663,7706,7558,7478,5524,5564,915,2998,7710,8119,8884,8068,5564,5476,5491,7742,5301,8126,8467,7908,3123,8322,7478,1325,3137,7768,9054,7752,8447,9059,8093,8297,5337,8539,7576,3194,7576,8964,5237,2957,1400,5255,5209,7786,5209,7994,7500,7849,5386,5367,8456,2806,7675,7914,7982,8391,8245,5726,8170,9062,8925,855,7888,670,8174,5358,5564,819,2654,7687,7849,8370,8484,5218,5564,5699,7576,50,9012,7786,9012,7571,8201,7610,7582,8381,0,7487,5274,8154,7706,7696,7864,8687,7687,5237,5476,8577,5209,7763,5651,7972,5642,5325,7576,5717,8952,8365,7675,7990,5296,826,8359,8272,5274,2675,8190,5519,8082,7834,8149,5476,7663,8751,7914,8316,8062,7504,7710,8631,5201,5437,842,2950,5213,7521,7663,8727,8582,8387,8245,7675,5358,8354,2792,5396,1620,7908,7888,2537,5325,8789,8711,9067,8052,8467,7610,855,8211,5717,7864,7710,8670,5301,8136,8443,2665,7692,7675,852,1783,8073,5578,7687,2816,836,3009,5333,5342,2830,8201,7653,7946,8845,8851,5717,5337,7834,7786,7786,8126,7653,5612,8104,5476,7663,7977,5647,5274,36,5301,5237,5629,7571,5688,124,7478,121,8387,8307,28,60,8196,7538,32,4535,905,1518,8149,139,5612,5372,7811,5030,8302,5325,64,371,8527,106,7982,7706,8087,7994,9045,8227,7696,5301,8322,8345,5381,9067,8184,7628,2381,8277,8302,162,5480,7982,7663,7576,26,8800,5314,5314,8987,8539,7946,5347,9051,7675,7521,8068,7478,7752,8402,8302,7982,7706,32,8272,7883,7883,852,1775,109,1318,8287,7849,8387,7706,670,8456,7779,5274,7920,8302,7986,8115,2409,2418,8994,2440,7719,5622,8077,2468,2475,2213,2216,7768,2229,8073,5693,7843,7675,2261,2265,7710,5377,8345,8484,7883,7990,5218,7994,7823,5261,762,56,7986,5381,5730,7653,5459,7576,7637,8082,9071,7628,7482,8365,5642,8494,7746,7982,8267,8732,5296,7811,7811,5237,7849,5325,8005,2792,7994,8302,7500,5464,8762,8077,8190,7779,7628,8940,7710,8255,7834,7883,7440,8179,5301,9067,7687,54,7622,7616,9054,0,8005,5237,7628,5381,7957,7692,5514,8154,5377,5642,7849,9051,5519,8322,7719,5218,7588,8062,5661,5476,5612,7538,5496,8370,7990,7738,8940,5606,8613,5448,8631,5325,8334,7478,5274,5391,5717,5209,5381,7632,5534,8631,7487,7710,8052,5612,7977,8370,5612,8196,8077,7849,0,8159,8044,5717,8136,9075,7663,0,8316,5358,5708,5708,5401,8402,5578,5305,5448,5448,8772,8539,7706,5285,5248,5285,8509,8772,8589,8340,7977,7663,7675,8647,7957,5205,7786,7675,7982,5296,8039,7742,5274,8115,8227,7500,8312,8407,7538,7538,7982,5666,8762,5205,5629,7491,5413,7616,0,5285,5651,7675,5396,8349,8297,7571,5352,8149,7675,5325,7605,8783,8005,8397,7675,7487,8052,5564,8239,8255,5237,5519,8772,8772,5305,8329,7864,9021,8010,7710,5296,8365,8519,8140,7883,7628,8068,7558,5285,7491,5237,7706,8052,5401,7500,8919,5296,7482,7734,5291,8196,8329,8077,5329,7487,7834,7616,8245,7883,7628,7576,5309,8789,8354,5296,7706,7864,5372,5476,5391,5285,8467,7663,5218,5291,7908,7628,9054,8479,5717,7528,5471,8903,0,8159,7920,7920,7482,8397,8381,8539,5347,7738,8154,8613,8052,5367,8387,7888,5237,5442,5325,5309,8052,5377,8227,0,0,8936,5377,5329,5296,8073,5337,8077,7482,7982,5305,5464,5578,5301,7511,0,7700,7675,5305,7834,5301,5329,7610,5564,7883,8033,8751,7920,7521,8154,5305,5237,7888,7692,7834,7877,7616,8261,7828,7986,7738,7675,5285,8287,7811,8154,5622,8077,7675,8936,5301,7877,7637,5301,5476,7632,7700,8365,7588,5237,0,5407,24,7605,64,8077,7700,836,2998,7994,5296,1311,5688,7487,2675,5407,5296,7528,7482,7823,8641,8641,5471,8756,2947,7592,1400,429,8827,7982,1620,8467,5337,2599,8653,8827,7994,5647,7994,8099,819,8658,2651,8093,7786,852,1783,8039,5647,5337,8039,7482,7643,7734,7628,9080,8467,8099,7768,7628,8745,8039,8732,7994,9085,7768,9092,9097,8467,8467,7864,9104,5647,9113,8641,9122,5296,5476,8467,9130,8375,5647,8387,9137,8805,8119,9146,9152,9157,8387,5437,7994,5717,9164,7478,7952,7920,5476,8539,9172,9181,5655,5314,9187,9193,9201,9206,8582,8227,7706,5034,9214,5325,9220,7972,7972,8282,9224,9230,8196,9235,7667,9240,8068,7565,5501,8543,8772,8795,7920,7571,8946,5358,9247,9253,9259,9263,7908,8539,5485,5407,8772,5301,7904,7478,5501,8863,8312,7533,5629,8653,5726,8772,7972,5480,8811,5255,7828,7795,8447,7658,7920,7592,7924,9008,5708,7511,5688,7715,7990,8131,5529,7904,7710,7935,8093,9272,5564,7982,842,7805,7986,7610,7610,5329,9278,9281,8174,9285,5426,7582,9288,2462,5329,9298,9301,5666,5671,7482,5730,2465,7999,5564,8687,5642,7888,9305,7952,5496,7924,7482,8048,8131,8467,5459,5661,8800,7592,9308,7864,9312,7828,9316,7478,7473,9320,76,2406,8509,2468,5386,8345,5426,9326,7811,7898,8456,9329,5476,7482,3679,7500,7805,8433,8845,7663,9332,9335,5699,5218,142,9338,429,8349,7843,4262,7558,7834,5309,2422,5666,8577,8201,7558,9341,303,5391,8329,8039,2274,7491,8184,8539,7628,8201,8387,5676,5342,7920,7558,5509,8170,8099,5241,8571,5248,7582,5491,8772,8345,7774,5320,5413,8795,7972,7888,5476,7972,5476,8456,5325,9032,7706,5514,8170,8447,7692,7990,5629,8653,8447,5407,8217,5309,8461,7628,7864,5352,7786,5647,8873,0,8387,7542,0,8345,0,7888,5386,5647,7558,7482,7643,5358,5401,8039,5459,5622,5730,8222,8222,7628,8539,7790,5381,8821,7864,8845,8795,8548,7628,5464,7864,5642,7834,8222,5325,5712,8227,8762,5480,7482,8527,7548,5381,5655,8527,8461,8732,5407,0,0,7521,5464,8873,8676,0,8190,5325,5325,7908,7616,7738,7558,5218,8509,7588,7588,5391,5651,7738,5534,7558,5647,5629,5699,7548,5301,5352,7898,8159,8205,8217,5606,7478,7558,8329,5717,5655,7663,7817,7834,8795,5606,8467,7752,5661,7511,0,7843,8227,5352,5413,8033,8140,5329,7742,7542,5712,5305,5622,8617,5320,7957,7710,7800,8473,5485,7706,5320,8539,8015,8795,5218,7828,8033,7972,5237,8039,8322,5309,8539,8987,7715,8402,7834,7738,8833,7706,8322,7779,0,8863,8255,8833,7500,5261,5261,7904,7734,7908,7582,8010,5699,5305,5471,5471,5471,8539,8119,7888,7888,8772,8919,7478,5501,5464,8772,8245,7491,0,0,7628,8322,5325,8255,8800,5476,7920,5358,7828,5419,7491,5218,8073,8772,5347,8170,8359,8903,8261,7542,8329,5347,5237,7849,7663,8387,8479,8052,8821,0,5666,8582,5329,7834,8772,7994,8359,7610,7610,7872,5301,7516,8261,0,8329,7834,5666,7904,0,5391,8504,0,9345,9345,8119,5476,8349,7990,7605,9345,7982,7982,8653,5476,7982,7500,8653,8087,8211,9021,8087,5476,7972,7786,5573,5419,8527,7930,0,8411,5342,8658,8093,5564,7571,5367,7504,0,5386,5367,8005,5237,8082,8467,7790,7588,7888,7888,7491,8745,0,8131,8131,7706,8249,8249,7482,8154,8033,8245,8115,5606,7658,5564,8625,5642,7924,8560,5524,5391,8543,7658,7853,7834,5358,5476,7473,7977,7888,0,8582,8267,7977,8994,8869,5401,7811,8539,7834,8174,7768,7706,8716,8527,5661,5501,7828,0,8005,7967,7811,7706,8509,7687,8543,5476,5419,8272,8205,8543,5237,7521,7893,7757,8329,8946,7828,5708,5617,5534,7533,5708,7478,5708,5419,5401,7888,8267,5419,5391,5501,8154,5358,5401,7888,8293,8293,5693,8716,7542,5407,7738,8484,7823,5496,8052,5274,8010,5325,8851,8613,7893,7599,8062,9345,5564,7538,7883,8504,5564,5485,7834,5661,7752,7548,8994,8762,5325,8946,7548,5642,5309,8267,7622,8456,0,8354,9345,8816,7834,8136,7528,5501,5358,7628,5480,8527,8467,7565,7478,7628,7952,8762,7904,8447,7576,5314,7999,8245,8716,8467,7853,8190,7972,7864,5712,5305,7706,5358,7914,7867,7864,7849,7500,8048,5241,8438,8282,7715,5564,8093,5209,5209,5391,8297,5261,7924,7800,5381,7786,7487,5426,7994,5209,8170,5261,8751,8504,5642,8316,5419,5320,5325,5671,8987,8144,5676,5285,8131,7715,5407,5407,5476,8184,7972,5325,5325,5274,7946,8687,7667,7473,7834,8391,8577,8456,7834,5337,7834,5496,7914,7482,7710,7952,7768,5209,8022,7994,8062,8631,7957,8387,7648,5285,8375,5255,5274,8670,5301,7558,7675,8211,0,5612,7478,7994,7990,8277,8287,5693,8929,5426,7823,7628,7495,8456,8028,5676,7914,7972,7653,8539,9345,7864,8751,8422,7653,7746,7478,7834,7478,5237,7834,7628,7487,7628,8631,0,5274,8170,5726,8839,7706,5274,8863,7582,8227,5329,7663,7864,7864,7957,7746,8827,8057,8170,8312,7774,5693,5509,8473,7675,5320,7706,5296,5564,7864,8494,8772,7893,0,5381,5606,8447,7768,5485,7643,5485,7542,7828,5426,7982,8631,8277,5358,7482,5442,8287,5564,5485,8099,7834,5347,8653,7482,8387,0,5301,7828,5274,0,5396,7616,7877,7914,8805,8903,5642,7478,7823,8893,7977,7853,7500,7628,7994,5377,8110,5712,7883,8821,8196,0,7478,7542,8104,7790,5564,5381,8676,5237,7548,5712,5464,7746,8345,7616,8190,0,7994,5712,8467,5655,5688,5377,5661,5480,5401,7790,8297,7487,7858,7994,8631,8484,7982,5708,0,0,7500,7853,7952,7967,5642,7920,5237,8467,7616,8164,8613,7710,7511,5712,7533,5708,7538,8131,8005,8196,7977,5514,5655,8467,5496,5218,8391,7616,5325,7588,5453,5274,8272,8334,7843,5386,8222,7663,7920,8751,5476,8647,7628,5666,8190,7800,5476,8015,8756,5218,8447,5301,8539,7952,7834,5309,5622,8227,8604,7653,5362,8149,5305,5578,7982,7982,5708,5362,8322,0,8959,7786,7478,5285,5480,8427,7790,5274,5274,5305,8239,5401,5396,7952,8919,7972,7500,0,5396,5237,5296,7648,5237,7834,7877,5285,5712,8010,8010,5274,5476,7994,5285,5564,5391,5358,5261,5342,8322,5401,7811,0,5453,7582,5476,8179,7663,8929,8766,7538,7681,5442,5309,7839,7628,8751,0,7500,8716,7834,7658,7616,8179,5453,0,7616,7746,0,7538,7914,5426,8447,8762,7511,8467,5347,7834,8617,8093,5285,8929,5347,5274,7616,8617,8952,7768,5651,5301,8375,7834,7605,5347,5209,5381,5291,5291,8571,8267,5209,5209,8879,7962,5564,7930,5209,7675,7528,8249,7487,7658,8222,5325,7786,8293,5529,0,5622,8370,8375,7962,8359,7763,8827,7542,5320,7877,8387,5209,8447,5564,5209,5301,5529,8073,8613,8277,8077,8077,7616,8375,7746,7605,8164,7738,7952,8533,7723,7628,8087,8345,5647,8022,5509,8110,5642,8062,5476,5485,9032,5651,7605,8903,8062,8548,8721,7576,5693,5509,5476,8222,5391,5391,5248,7914,7990,8190,5391,5337,7811,5476,7681,5391,5274,7817,7558,8658,7817,7675,5437,7687,8222,8370,7834,7675,7977,7914,8033,8164,5464,5651,5568,8739,8888,7588,7774,8365,5325,8467,5325,7482,5651,5568,8745,5699,5655,5407,5564,8381,5224,7811,5712,8196,7653,7628,5391,8381,8196,8381,7972,7628,8222,8739,8381,8196,5305,5496,8170,7795,8196,7982,7478,8239,5391,7605,8533,7914,7994,5661,8739,5296,8239,7667,7811,5476,5476,8222,8196,5248,8222,8527,8879,8217,7487,8329,8479,7672,8217,8087,8312,8527,5622,5564,5617,7990,5419,5419,5622,7994,9021,8329,5320,8217,8479,7675,5325,8670,7696,7696,7696,5205,5407,5320,8033,8987,8987,0,5205,8227,8239,5274,5274,0,7599,7599,5337,8239,8239,5642,8211,7599,7599,7599,5205,5642,7888,8005,7548,8484,8811,7786,8811,8422,8811,8811,5391,7482,7710,8898,8447,5309,7994,5453,8170,7920,8554,5237,5358,8170,5381,5606,5726,5377,5381,7478,7757,5391,5391,5255,5325,5661,8033,5237,8447,8427,5237,0,7800,7864,7957,5255,5377,7893,5320,5661,5647,5329,5329,5237,8554,5329,8170,5661,8170,5291,8239,5291,5320,5237,7935,8068,8447,5261,0,5274,5301,5529,0,0,7610,5476,7706,8504,7478,7653,7914,7628,5573,5342,7706,0,8354,8282,5642,8608,8387,7478,5381,7864,5325,7542,5501,8467,7706,8433,7542,7706,5496,5381,5726,8461,5274,5708,5347,8479,7904,7571,7571,7924,8170,8461,7990,8131,5529,7930,5301,7681,8170,7834,5564,5519,7977,8093,5480,8456,5337,5491,8582,8354,5726,7982,8312,8438,8345,7681,8093,7710,0,7582,0,8005,5519,7790,8316,5529,5476,0,7849,7990,7632,7864,5564,8329,5459,5329,8345,7696,5671,7768,8316,5476,7715,5391,8345,7994,7982,7972,5651,5301,5325,8316,7994,5337,0,5367,5301,5642,5285,5241,8375,5285,7864,7864,7628,7491,8201,8255,8658,8527,8527,5386,5301,5337,5274,8062,8479,7914,7558,5647,8845,7667,8052,5367,7482,7628,7843,8795,8149,8277,5274,5699,8571,5248,5248,7738,0,5396,8170,8277,7706,8438,8653,8653,8443,7786,8402,8387,8387,8461,8099,8099,8873,5291,7888,5241,8039,7482,7582,7653,5241,7994,5642,7920,7834,8022,5381,5413,8443,5291,7582,8427,7786,7482,0,8115,5464,7628,7994,7924,8527,5688,5471,5377,5476,7972,5391,5437,5642,7864,7605,8795,5329,7834,5712,8811,5325,5325,7811,8805,8119,5712,7542,7478,7628,8548,5347,5291,7834,7834,7994,8548,5496,5342,8164,0,8196,7628,0,7957,5647,8217,5237,5534,8777,8903,8062,5381,7663,7511,7478,8022,8255,5209,7994,5413,7828,8451,7542,5218,8077,8451,5325,0,0,8022,8548,5419,8239,0,7877,5459,8433,5693,5647,7957,8015,8427,8427,5708,8340,5726,8322,8397,8397,8479,8479,5237,7628,8073,7610,7521,8554,8073,8196,5285,7834,8149,8427,7516,8239,8239,5309,8919,7888,9021,5285,7952,5642,8255,5642,5291,8427,5476,5529,5386,7491,7491,5476,7864,5274,5325,5661,7990,5237,8539,5708,8397,0,8447,8164,8479,8093,8925,7628,8354,7482,0,5381,5301,5342,7610,7610,7828,7616,5237,8170,7877,7616,8005,7834,7994,5386,7768,8783,8498,8375,7828,7828,0,7834,7834,7834,5386,7834,8732,7952,8732,7576,5464,5485,8329,8783,8783,8577,7817,7571,8255,8170,7817,0,7478,0,0,7637,5688,5688,5325,5476,7487,7904,7972,7999,7521,5476,5309,8329,5676,0,8329,5309,5564,8925,5358,5391,8201,5693,8952,8010,8077,5391,5501,8994,7972,5237,7982,7883,7994,8015,5629,5491,7538,8456,8994,8964,5564,5401,7972,7576,8073,7972,8539,5305,5476,5476,5255,5280,5676,8613,8952,5491,5241,8839,8255,5655,5391,8658,5237,7898,5491,7952,5442,5255,8015,8302,5509,5491,7994,8227,8302,7706,8115,8039,7790,8762,5730,5241,7500,5651,7994,8494,5564,5476,7898,5255,0,7817,7533,5464,8479,5651,7977,8909,5296,7548,5261,7800,7715,8473,8039,8115,7715,7898,8015,7592,7877,5564,7972,7982,8010,8519,5358,0,0,5651,8255,7877,5501,8249,7898,5442,7763,5573,5381,5274,5347,8811,5255,8184,7542,5224,5224,5426,8811,5642,7811,5337,5337,5325,7834,7920,8805,8115,8687,9345,5305,8745,5358,8196,5471,8519,7952,8387,8126,5381,7500,5329,7478,0,7692,7616,8539,8174,7828,8387,7622,8845,7839,5564,8316,8334,7893,7521,5476,7972,7681,7972,5519,8762,7571,7675,5647,7952,7692,8334,8805,5337,8201,5693,7558,8277,7930,5325,8345,5320,8756,5218,5480,8022,5320,8745,7628,7628,7622,5448,8604,7521,7893,8484,7849,5296,7511,8302,5401,7843,5448,5407,8057,8745,8484,8484,8196,5708,8144,7957,7482,8239,5491,5329,5476,5573,7877,5647,7487,7528,7622,8227,7478,7893,8687,0,7908,5358,8322,5661,7516,7516,8239,5329,0,7616,5274,8805,5218,5301,8805,7482,8329,7834,8805,8539,8312,5407,5296,8716,5296,7817,8184,8509,8411,5476,7706,8789,5501,8170,7834,5476,8205,5358,5476,5476,7696,8589,5391,7696,5391,5391,7817,7757,7482,7982,5564,8062,7478,8422,7710,5419,7904,7914,8539,8015,5291,8272,5564,8539,5651,5274,7982,8783,8293,5485,5655,5476,5651,7768,5509,8670,8893,8190,5485,5471,8015,5419,8322,5352,7883,8322,5358,5358,5617,7883,5407,8131,0,8048,5358,5568,0,5432,7675,7883,8144,8322,8062,5651,8144,5509,5291,5325,5325,7952,8903,5329,5329,8641,5296,7972,5296,5296,5476,5476,5476,5476,8154,8154,5261,5426,7752,8316,7834,7834,7628,7710,7774,8473,7757,8888,5352,5218,8647,8015,7616,7710,7710,5314,5480,5325,8334,7548,5476,7599,7935,5358,7710,8093,5564,7599,5241,8140,7599,5485,5491,5237,8329,8727,7924,8359,5329,5730,8631,7558,7972,7500,5485,5647,8827,0,8277,8827,8407,5213,5237,0,7706,8227,7972,8827,7576,8277,7628,5661,5381,5407,7710,7977,8411,5464,5564,8329,7994,8334,5642,7994,5237,5241,8647,7710,8407,5241,5241,5432,8407,5248,7935,5285,7994,5285,5296,5712,7610,7521,8222,5726,7528,5320,5358,5476,5333,8504,8479,5693,5671,5325,8504,8504,7877,8772,8594,8594,8811,7706,5391,8879,7972,7599,8211,5237,8010,7616,5606,8438,8438,7548,5248,7478,5325,7478,5237,7491,5337,5509,8827,7548,7908,7628,8411,7972,7706,8402,8033,7658,8068,7542,8184,7643,7504,8816,7599,5274,7528,8756,7982,8509,5501,8359,7658,8022,7478,7742,8227,8519,8170,5476,8222,8783,8438,8456,7571,8982,7924,5568,8282,8293,5386,5241,8994,8201,8964,7786,7982,8170,8245,5401,7487,5442,8539,5491,5476,5647,5337,5564,5325,5352,5426,8467,8447,8625,5564,5237,5358,5337,7867,8190,8827,5224,5464,7500,5573,7920,5237,5237,8772,0,8548,8217,0,0,5476,7710,8504,8329,5391,5237,8149,7946,8582,7715,7972,5661,5401,7681,5464,5459,8052,8571,7952,7946,5237,7675,5320,0,7473,5688,7924,5296,5296,7710,7977,5476,7504,8625,7516,5296,5209,7500,7710,7521,8293,8727,8869,7990,8316,5606,7817,7768,5237,5280,8354,5352,8800,8407,5296,7864,0,8316,7834,8509,8604,5386,8267,8211,5218,5218,8467,7920,8653,8255,8354,7628,7628,5381,5237,5578,7516,5377,7653,8052,7867,5377,8201,5476,7542,5296,5237,7930,7800,5476,7946,8022,7768,7558,5296,7478,7478,5337,5301,7779,7811,8827,5476,8354,5301,8566,5386,7864,5699,8190,7542,7478,8438,8827,7893,7930,7888,8402,8227,8514,5301,5325,8721,8721,5426,8479,7706,8571,5606,8099,5237,8795,8653,8447,8745,5442,5471,8039,7877,7779,5509,8467,5309,5325,7516,7628,8154,7849,7768,8772,5377,5377,8077,7599,8287,8201,7643,5237,5629,7528,7994,5296,7864,7872,8548,5407,5218,7828,8170,8179,7558,8407,7930,7867,8179,7883,8721,7888,5386,8267,7521,8863,5459,5464,8104,7723,9071,5426,8604,8411,7643,8548,5377,8255,5442,7930,8190,7994,5717,7542,7994,7628,7628,5358,8190,7811,8438,5358,5476,7746,5218,8509,8903,7482,7864,8033,8711,7990,7990,7628,7663,7867,7834,7495,8811,8267,8548,8159,8873,5501,0,8005,8005,5699,7817,8267,7588,7588,7706,5642,8022,5464,5325,7786,8170,8519,8447,5661,8509,5241,5218,8340,7962,7914,8073,8509,8467,5661,7643,5218,7924,5301,5514,8272,7482,7839,7500,7542,7658,5464,8267,7914,8391,0,5617,8827,8647,5305,7715,8039,7710,8179,8179,8179,8402,8438,8594,8015,8316,5708,8617,7800,8721,5699,5372,8484,5296,8509,5401,7742,8022,5309,7482,5329,8987,7994,8641,7800,7542,5617,5218,8745,8756,8539,7972,7957,5676,7706,7706,7628,5255,8239,8827,5337,5248,7972,8316,7558,7706,5726,5352,7930,5432,8104,5285,8833,7710,5401,5255,8005,5296,8255,7558,7482,7482,5237,5237,7883,8772,7888,8217,8217,7588,5291,8604,7675,8010,5501,8149,8149,5248,7653,7706,5274,8438,5485,8239,7478,8179,5291,8140,5274,8772,8119,7920,5476,5573,5274,5386,7883,8772,5485,8022,7653,5285,8082,7811,7582,8196,8196,5726,8340,8800,5661,7616,5708,7790,5476,7516,7719,7667,8359,8514,5347,7663,7516,7834,7795,5367,7710,7628,7482,5491,8249,7663,8249,8217,7706,5305,5347,8514,5442,7972,7495,7495,7834,7924,8073,7972,8582,8582,5337,5564,7511,7763,7558,0,7877,8249,5347,8359,8190,8261,7877,5325,8170,7811,5617,5476,5401,7823,5651,5629,5291,8461,8461,8461,5717,8354,7478,5407,7500,5320,8164,5237,7558,8494,5381,5717,5407,8322,8154,8322,8427,7582,7706,7977,5459,9353,7977,5325,5391,8795,5325,8005,5671,8919,8427,8519,5717,5407,5381,7628,5407,7834,5329,5237,8427,5237,5237,8287,5491,5726,7982,7982,7982,7864,5391,5301,7790,7982,5301,7565,5612,8411,7828,7592,8987,5476,5476,5261,7982,7893,7834,8087,9358,7774,7687,5688,5333,8201,7904,8245,8705,5564,0,5437,5480,8015,7757,8756,7628,7692,7653,7914,5496,5476,7786,5568,7478,5480,7542,7972,8504,8144,8514,5514,7924,8816,8282,7935,7648,5237,7542,8282,7977,7628,5564,5708,5237,7920,7972,5209,7864,5471,7999,7558,5325,7834,8582,8411,8783,8170,7752,7982,8354,5291,7904,8196,5655,5241,8267,5509,8293,5476,5320,9361,8057,7853,8307,8756,5578,8057,5564,8174,7904,5708,8115,8641,5568,8099,8322,0,7982,8222,5224,7972,5352,8772,7734,7920,7658,5642,5655,7687,7924,8222,7849,8447,5476,8539,7710,7920,8925,5305,5712,7914,8387,5442,5726,8438,5573,7977,5564,8427,7605,7883,7637,7715,8184,7986,5564,8174,8282,5213,8093,8136,8625,7542,8170,5480,7904,5480,5480,5708,8170,5314,7533,7999,8126,8126,8539,8447,8170,8144,7610,5407,0,5218,5347,8381,8073,8115,7542,8727,7795,5391,5381,5241,8566,7817,7817,7768,8969,8879,8365,8898,7710,5661,8604,5296,8751,5635,5391,8136,8073,7710,7795,7972,8144,7888,7888,7972,7867,8345,8789,5655,5717,5285,7692,7582,8613,7681,8577,7687,5329,5459,8631,5291,0,8170,7972,8375,8387,9361,7738,5661,5386,8903,5285,5241,5325,5296,8329,0,7663,5480,7495,8451,8073,8845,5377,5655,7558,5612,5612,8946,5712,7610,8170,5352,5407,5476,8805,5261,7495,5509,5661,5496,8527,5285,7478,7667,8484,8316,8653,8154,5647,5314,8057,5333,7904,7653,8670,8077,5676,8115,8022,7491,5291,8201,7811,8201,9021,5476,8533,7672,5448,5480,8149,7962,7898,5407,7811,8479,0,5509,8196,5352,5617,8577,8438,8514,5564,8126,8170,8479,8494,7663,5676,8354,8548,8033,7805,5381,5647,8543,8136,8184,7495,8443,8494,5237,5274,7558,7828,5514,8099,8745,8461,7888,8087,7757,8057,8539,8015,8234,5325,8272,5280,7706,5213,5320,7542,8093,5476,8387,5291,5241,7779,7795,5476,8154,5261,5241,5329,7774,8772,7935,5224,5381,5377,8571,8422,7834,8451,7967,8170,5606,7542,8800,8126,8087,5381,5329,8039,0,0,7972,7883,0,5274,5514,8721,5352,8467,7495,5407,5655,8484,5407,7628,7834,8005,7823,8062,8110,7920,7994,8159,7967,8190,7622,5377,8077,5496,5391,8548,5655,5476,8598,7946,8411,7924,7811,7834,8857,8345,7500,8387,8494,7990,8663,5325,5377,7986,7986,7558,8509,8484,8484,5647,5381,5296,8845,5248,8851,5617,5661,8467,7542,7491,5496,7687,7930,5642,0,0,5347,7752,8196,7877,5717,7957,8548,8845,5285,8131,8196,5218,5699,5442,7710,8427,7962,7962,8073,5573,8164,5413,8451,5514,7990,5642,8479,7972,8293,5661,5407,5448,5688,5524,5274,8851,8940,5309,7763,7864,7805,7495,5261,7738,5381,5629,7533,8282,8245,5301,8272,7982,7972,8631,5367,7542,7752,8322,5606,5655,7864,5237,7495,7768,5496,7687,5717,7478,8869,5617,8756,5617,5320,8126,5642,8888,5448,8772,7730,7877,5612,8239,5248,8519,8039,5224,8527,8952,8539,7706,0,8533,5401,5629,7710,8721,5329,7632,7957,8548,5671,7982,7687,8519,5629,5396,8402,8716,5305,5419,7952,7616,7972,5708,7972,8077,7800,5261,5568,5320,5612,8641,5285,5305,5305,5578,5274,8946,8647,5419,7482,8131,7542,5329,5325,7616,5291,8959,8959,8149,5471,5274,8821,8005,8365,5476,8329,7558,7558,7730,5386,7946,7487,7542,5464,8245,9021,8184,7516,5241,5296,9345,5285,5325,8340,7883,7883,7935,8073,7935,5291,7675,8239,8543,5564,8255,8073,8451,5314,5622,5480,7734,8196,5471,8184,0,5661,5329,5285,8498,7738,7653,8255,9361,5407,8811,5218,5666,8144,5329,5261,5314,8115,7734,8287,5325,8903,5612,8340,8745,5485,7811,5381,8196,7908,7883,5237,5717,7920,8073,5708,8170,7491,0,0,5448,5651,5325,7658,5314,8479,5426,5347,8936,8170,7491,8613,7491,7491,8447,7982,5325,8888,8381,5717,7972,5381,8144,5305,5407,5661,8577,8772,8126,5448,5329,5329,7648,7972,7533,7924,5666,7730,5320,8365,8184,5274,7667,8154,5301,7986,7653,7616,5329,7610,7542,7542,8365,7542,5347,8589,7616,5712,8261,7877,0,8498,5237,5617,7632,7667,5651,5301,8365,5329,8772,8857,5301,7828,8631,8903,5285,5291,5347,5301,7700,7972,7952,7952,7706,7528,7734,5342,8087,8898,5337,5671,5476,7817,8811,8282,7565,5241,8073,7828,5568,8438,7478,7786,7849,8604,7675,5651,5573,5717,8539,7500,5285,8438,8033,8484,5655,7888,7628,7710,5708,8126,7500,5285,7888,5401,8077,5358,8227,7828,7888,5476,8811,8589,7990,7834,5471,7706,8307,7675,7888,5285,5485,7924,5367,8438,5320,7663,8255,7663,5241,8539,5655,5564,5320,8239,5726,7504,5237,5285,0,5285,5314,5476,5401,5358,5301,7675,7675,7752,5529,7706,7706,8533,8888,5291,8533,0,7893,8282,8282,5347,7628,5347,7994,8307,8119,8239,7977,7982,5661,7977,7628,5655,5285,8494,7853,5573,5309,7710,7482,5564,8322,8427,5726,5314,8427,8676,5314,7487,7528,5314,5314,8196,7516,5314,5573,8676,7605,7565,5661,8560,7628,5391,8456,5337,0,8334,7706,8427,7790,5329,8456,7920,7511,5564,7565,8190,5485,5485,8447,8443,7999,5476,7511,8670,5407,0,5476,7706,7972,8010,8647,8010,8427,8625,5342,7628,7478,5325,5325,5325,8154,8915,7883,8484,7972,5372,5661,7681,8329,7500,7500,7500,8811,7653,7658,8539,7500,7877,5391,8582,7924,5391,7599,8438,8438,8068,8104,7478,8267,7930,8608,8115,8282,5381,8653,8119,7982,7565,8052,7952,8227,8190,8391,7643,5501,8196,7982,5476,5237,7528,7834,8115,7565,8772,7914,8170,7924,8365,5224,5426,7616,8190,8456,5655,8093,8104,8052,5712,7571,5305,8411,7500,7643,7999,8548,5391,5391,7628,7893,5459,7790,8447,8227,7752,7582,7663,5224,8608,7576,7610,5274,8582,7681,8783,5391,5464,5464,8190,8005,7500,5285,5309,7972,5309,8987,8316,8062,7834,5671,7521,8635,7675,7491,8267,5296,7653,7653,7667,5509,5274,8653,7576,5237,7558,8119,8201,5578,5578,7478,7653,5296,8052,8033,5509,5606,5314,8397,7576,7734,5485,5237,5261,5476,5237,8745,7706,5485,7952,5655,5476,7972,8345,5509,7516,7779,8227,7599,7675,8365,0,8190,8190,5476,5367,8267,5296,5564,8438,8005,7849,7616,8762,7994,5320,8745,7790,7823,5407,8699,8345,7924,8104,5642,5612,5309,8391,5285,8509,5524,5534,8227,7706,8052,7834,5301,7663,5325,7952,8514,8365,5661,8052,7715,5305,8827,8447,8015,5296,8222,7653,8190,8732,8239,5274,7883,5485,8959,7516,8068,5237,5476,5325,5464,7658,7491,7491,5301,8772,8316,7675,7893,8196,7999,8052,5476,7521,5426,9367,5285,8222,5485,7834,7663,7491,8613,5320,8249,8329,5642,7734,5301,8033,8582,5459,0,7834,8154,5329,5237,8164,7610,7700,7632,8375,8635,5629,7834,5573,8354,8354,5726,5476,8845,7491,7982,7849,5320,8340,8340,0,7800,8554,8554,5377,8397,8179,7500,5296,7482,7867,5501,7920,8354,5337,8821,5352,8282,5564,8093,7893,5726,0,8293,7681,8851,5655,7843,7843,5676,8391,5464,7599,8068,7999,5325,7706,7616,8548,7599,8316,7843,0,8249,7610,7715,8919,7994,5305,8543,7687,7605,7605,0,7605,8504,7904,8312,7888,7888,8479,5301,5688,5325,8484,5358,5519,8805,7839,8302,0,8925,7768,7542,7628,8811,7478,5496,5261,8329,8010,7478,5209,5301,5426,7774,8879,8504,8670,5480,8539,8015,8211,7710,5476,7786,7817,8762,5476,7706,5320,8461,7681,0,0,0,8277,0,8211,7924,7538,7622,7487,8467,8560,7893,7930,5381,7582,5524,5642,5274,5573,8658,8994,5320,5396,7528,5442,7605,5442,8022,8282,8022,5325,8354,5309,8131,5325,7790,7920,5358,7599,8608,7565,5501,5476,5564,7823,5237,7982,8387,8307,8329,5655,5642,7516,7872,7864,5712,7999,8039,7972,8827,8267,0,8307,5320,5372,0,5301,0,5407,7558,0,8322,8527,7823,7675,5274,7692,5255,5476,5237,7972,8625,7675,7977,7977,8131,5564,5305,5476,7491,7491,5391,8077,5358,7888,5476,8093,5224,8447,7986,7849,7533,8010,7548,5491,7839,7924,5564,8073,8711,8811,7706,8297,8110,7790,7999,8022,5578,5367,5337,7914,8170,5480,7571,7823,8381,5218,8539,7972,7687,5496,7883,7786,0,8484,5708,5564,5407,8073,7888,7990,8716,7605,5358,7511,7637,7687,7487,7605,8456,5391,8625,5496,5642,5291,8484,5391,7972,7511,5712,0,8164,7687,0,8473,8427,5381,7610,7582,5329,0,5301,5347,5218,5320,8329,8969,7706,5655,7706,5301,5476,7521,5655,5301,7588,7795,8795,5224,8504,7675,8073,8504,5224,5209,5442,5476,5381,7500,7605,5407,8397,7696,7696,5337,8048,7817,8898,8149,8427,5301,0,7924,7849,8682,7972,7710,5717,5437,8375,5564,5381,8560,7952,8272,8687,7994,8582,8613,8345,8345,5676,5476,5314,7692,7972,7723,8316,8940,8940,7811,5381,8484,5529,7888,8863,7628,8365,0,7616,0,0,8427,8387,5386,5655,8903,7706,8255,5396,7908,5419,9345,8329,5612,8302,5655,7930,8805,7795,5485,8073,8543,5564,7872,8316,5291,5237,7843,7511,8527,7706,7752,7667,8811,8140,7779,5688,8805,5337,5712,7898,7610,5352,9345,8539,8190,5647,5372,5708,7786,5358,8539,7653,5209,7972,8504,7930,8845,5407,7811,5509,7495,7977,7681,5325,7675,5329,7811,8670,8433,5501,8022,8249,7478,5301,5301,8307,8433,7616,5612,8170,5377,5381,8653,5314,5320,7877,8010,7994,5485,5301,5224,0,0,8272,7972,8277,5401,5407,5314,8093,5301,7706,5224,9032,8827,7542,7746,7706,7706,7774,8234,7877,8594,7893,5213,7898,8057,7643,8548,7558,5372,5325,5301,5352,7628,7628,7786,7643,7582,5629,8461,7599,8539,5237,8010,7558,8427,7706,8467,7834,7628,7768,5476,9367,8504,7805,8873,7706,5314,5564,7990,8277,7967,7967,5314,8721,5501,5501,8033,7723,8077,5509,8039,5329,8052,5381,5476,5442,8170,8170,7558,7571,7779,8322,7972,8863,8387,8170,7930,5285,5314,5209,8888,5352,5693,5573,0,0,0,5407,7952,5237,8762,7542,0,0,0,7632,7972,0,0,0,0,5352,8427,0,5480,9367,7888,7491,8998,7994,7994,8676,7977,8548,5407,5476,7914,5237,7834,7834,8732,8427,8104,7628,7849,7687,8494,7858,5358,5642,8154,7500,8893,7924,7986,7834,8543,7643,8005,5381,8005,7487,8527,5661,5480,7972,5407,7715,7790,5573,7982,7982,5401,8873,8805,7823,7628,5337,5712,8022,8190,8190,7616,5655,7858,7491,5325,5381,7491,5573,8598,7628,8694,7994,5712,8539,5325,5381,8509,5688,7616,8110,7946,7957,5333,0,0,8751,0,5671,8772,5347,8267,7628,8033,5329,8170,0,0,0,5661,7977,5476,8427,5564,8716,5651,7843,8164,7786,5407,8170,7738,5642,5655,7898,8302,8467,5209,8391,7588,7977,5642,5381,5464,8811,7893,7687,7542,7538,7962,8467,5661,8164,5391,7972,8402,8267,8805,7511,8164,5629,5699,8533,7487,8119,7738,7990,5237,7786,7706,8898,5524,7962,7533,8272,5717,8329,7828,8417,5325,8851,8131,7805,8322,5496,7542,8359,5712,7500,8461,5524,5314,5480,0,7924,7828,7977,5407,8397,0,0,8888,7605,0,5237,7706,5401,5296,5218,7542,8039,5671,5671,8641,5396,8140,9367,8402,5413,5337,7982,5419,7982,7811,8005,5476,8077,5524,8756,7952,7952,8340,7800,5309,8539,9345,5333,7843,8033,8827,5301,5519,8745,8617,8010,8316,8721,5325,5612,5666,5329,7779,5651,7957,8783,7588,7990,5320,5629,8473,5329,8484,5320,7687,5320,8015,8514,5305,5453,7924,7681,5352,7605,8302,5237,7768,7946,5407,8827,0,0,8322,0,7616,7696,7528,7972,8255,8397,8827,7734,5320,5396,8005,5655,9021,8354,8427,5237,8329,7828,8048,8811,5305,7478,8255,5386,7516,7511,8255,5274,8489,8010,8427,5480,8149,8267,8598,8422,7558,5629,8249,8170,7558,7588,5291,5291,8772,5296,5325,7495,7521,5480,8381,7872,7605,8519,8833,5501,7723,8297,7946,8433,7786,7511,8149,5524,5573,0,0,0,0,5325,8307,0,5329,0,0,8467,7738,5320,5419,8772,7516,8789,7482,5237,5342,5261,8397,8190,7558,8811,8170,8397,5476,8196,7734,7834,5666,8527,5296,8903,5325,5342,8073,5358,5693,5573,9361,7908,5325,5381,7977,8745,5578,5407,8322,8479,5476,8473,8022,5617,7516,0,5496,8249,7482,8272,7658,7746,7706,0,8888,8154,5325,7542,5651,8381,5476,5524,5407,7482,7696,8745,7834,7706,5651,7999,7952,8104,5325,8005,8312,8919,5573,0,7746,5337,7972,8687,7542,5666,5666,7730,5329,5301,8498,7672,7763,5296,7742,5329,5381,5524,8762,8467,5301,7893,5314,5448,5401,0,5573,0,7605,5329,7834,5301,7605,8033,5381,7864,7588,8359,8077,7628,7610,8277,0,5274,0,8170,7675,8093,7616,5407,7482,5337,7616,7521,8789,8073,7877,5347,8170,8617,5617,7994,8827,7768,5726,0,0,7628,7632,5301,8365,8375,8903,5291,0,5347,5347,7605,5666,5301,7904,7700,7628,8093,0,7542,7839,7811,7528,7628,7628,7542,7742,7500,8783,5573,7710,8509,5476,5291,5237,7500,5367,7628,7908,7843,8739,8851,5237,7663,5476,7706,7982,7710,8196,8739,8795,8739,8795,8732,8302,0,7482,7528,5661,7628,7542,7675,5476,5717,7478,8772,5309,7742,7982,5377,7811,7500,9054,5237,7675,7500,5237,0,5401,5573,5476,8795,7628,8287,7811,7972,5241,7839,8201,7478,7706,8201,8201,8082,8170,7542,7710,7719,7710,7719,7548,5342,5342,5301,8451,7628,5301,7952,5491,5491,7487,5407,5578,5305,5305,5642,5305,8140,7924,5606,8119,5491,8987,7500,8329,5320,8179,5291,8647,5280,5688,7588,8745,8267,5401,5476,8998,7972,7491,8179,7643,7883,5325,7491,0,8267,7752,5442,8888,5377,5309,8987,7482,7757,8140,5296,8498,5476,7883,8762,5419,5401,8888,8762,0,5407,5320,7742,8676,7605,7482,8745,5476,8302,5699,7990,5642,7667,7828,5296,5296,7786,5699,5325,5476,7742,5296,8676,5476,7675,7478,8721,7715,5712,5712,7663,7571,8762,7628,5358,5358,5358,7920,7920,5358,7877,7790,0,0,7734,0,7977,8427,8159,7715,5480,8329,7696,8164,5274,8170,7715,8052,5688,8307,0,8772,8456,8170,7888,8402,5358,8589,0,7790,5342,7628,7628,5480,7849,5407,7738,8589,5329,7715,5301,8316,8164,8227,7734,7632,8427,5514,8427,8249,7632,5726,5459,5391,8539,8438,5291,8211,7706,8959,8716,7706,8479,5274,8732,5501,8915,8028,0,5564,7914,7582,8745,7706,7482,5237,8370,7924,8345,8387,7990,8745,8387,7582,7883,5329,8745,7924,8267,8267,8839,8589,5241,5224,5218,8903,0,5333,8447,8068,8010,7952,8805,7972,5688,8888,8115,7746,8498,5296,7757,5476,5496,5314,8732,5509,8010,5491,5237,5480,8211,5688,7706,5261,5213,7653,7542,7478,7478,8329,7487,8613,7817,5419,7710,7478,0,0,5629,7972,7710,7542,5651,7628,7599,8745,5485,7872,7667,8062,7658,7768,5325,8411,8411,8811,7752,7548,7565,7504,5291,8560,0,7706,5501,7500,7500,8196,5476,8494,8267,8467,8560,7834,8033,8033,7883,5337,5471,7982,8154,7867,7715,8110,5712,7738,7746,7478,5248,5606,5358,8302,7972,8456,7533,8863,8925,8131,7511,5712,8115,8527,7742,5301,8099,8777,8527,7994,7982,5305,5712,5320,8170,8365,5480,5480,5480,7849,5301,5651,5476,8048,7893,5337,5606,7972,7999,7786,8427,7834,7487,8582,7592,8170,5661,5688,5476,7977,7548,7768,8073,5296,5651,8543,5314,5573,5333,5337,7786,8062,8811,8048,7790,7487,7982,5255,7839,8010,7710,7588,8381,7558,8093,8174,8073,7706,7487,8447,8170,8354,7605,8119,5666,8190,5529,5564,7533,8438,7658,7924,5325,5218,8721,5413,5529,7610,5329,0,8073,5661,8312,8115,5485,7482,7576,5325,5476,5296,5296,8397,7558,7482,7994,5730,5407,7924,8577,7487,5325,7696,5426,5564,7730,5612,7972,7482,7478,5237,8898,7605,5419,7632,0,8345,7582,5476,8711,5666,5476,5651,7768,5309,5305,7994,7675,5661,8062,7500,8851,8582,8272,7867,8170,7696,7576,8613,5255,8329,7504,7972,8687,7811,8391,8154,8631,5280,5213,7478,5726,7511,5476,7977,5485,7542,5419,5285,7558,5391,5314,8604,0,7908,5325,7511,7877,7643,7982,8789,8329,5309,8577,5688,7994,5578,7667,7653,7898,8211,5314,5337,5237,8073,8307,5509,8062,5476,7734,7616,5347,8052,7843,7653,5301,8845,5564,7478,5401,8104,8527,5476,7628,8316,8149,8370,5309,7977,5248,5534,5712,5342,7558,8456,5274,7914,5647,8653,8751,8077,7653,5485,7675,5617,8184,5647,5218,7675,5274,8805,7864,7588,8068,5676,8433,5377,5301,8839,7487,5666,8839,8272,8762,8149,8277,5401,7994,8494,8267,7811,8539,0,8489,5407,8015,7719,5564,8721,5426,8087,8969,8387,8287,8015,7491,8571,8494,7653,5480,7582,7849,8494,8170,8811,7779,7643,7738,5314,7828,7867,8407,5676,5209,5237,5642,7706,7786,8039,7653,7516,5629,7576,5352,8052,8227,5320,7706,7565,7710,5329,5386,8170,8272,8827,5666,7972,5352,7893,8022,5606,7920,7883,7628,8447,7628,5329,8811,5651,5485,8093,8099,7482,8402,5726,8359,7628,8184,7914,8647,8104,8631,8467,8277,7643,8745,7834,7542,5606,7622,7972,7482,5651,8543,8467,7752,9071,7893,8745,8467,7990,5325,5617,8467,0,5329,5209,8762,0,5407,8438,7920,0,7628,8467,7914,7914,8110,7811,7605,5407,8699,7628,5688,8805,5726,8762,8527,7687,8073,8494,8494,8539,7994,7752,8613,5661,8604,7994,5717,8461,7834,8062,8190,5651,7990,7790,7616,7834,5337,8062,7622,5426,5712,5437,7643,8716,8005,5391,7864,5213,5712,5401,7706,7706,5712,5606,8694,7994,8179,8641,7746,5296,5305,7500,5325,9054,5407,5407,7982,8527,7521,5476,7482,8427,5391,5381,7558,5464,8772,7628,5358,8438,5391,5647,8119,5218,5726,7548,0,8484,5285,0,7482,5629,7610,8154,8467,7823,8282,5337,7768,7805,7779,7877,7834,8104,8077,5209,5509,5391,7800,7616,0,8005,0,5407,7500,7672,7952,5671,0,0,8312,5309,7952,5629,8467,5612,8467,5401,5642,8249,9032,8119,7990,7658,8245,8631,7511,8131,5514,8391,7687,5391,5476,8099,7538,7734,7994,5301,7774,7478,5237,7752,8322,8115,5391,7588,5476,5655,5448,8267,5519,7482,8164,7643,7588,7588,8447,7738,5391,8613,7994,7533,5573,5573,8334,5534,8777,7763,7849,7828,8052,5464,5237,5209,8539,7972,7616,5564,5381,5396,5717,5209,8272,8909,5342,5358,5285,5296,8234,7482,5524,7610,7972,7834,5617,7800,7616,8104,5329,5347,8375,0,8888,8772,7628,5448,7982,5358,8473,7972,5419,5568,7914,8987,8539,5651,5708,5329,7982,7982,5337,7877,8010,5218,8234,7800,8745,7628,8647,5325,8387,5285,7706,5237,5325,5329,5329,7610,5309,8039,8827,7957,7972,5671,8322,5301,7834,8340,5285,8827,8658,8641,7742,8909,7491,5237,5651,5629,5305,8987,7653,5401,5347,8631,5301,7542,8527,8190,5476,7800,5209,8211,8756,5666,5352,5320,8015,8015,5248,7500,7811,8144,5407,7834,8519,8239,8402,5377,7924,5419,8827,7616,8062,8329,5320,8721,7616,7675,0,0,7500,5401,7972,7994,7920,5291,5285,8312,8903,5296,5381,7478,8994,5325,7533,7952,7675,7542,7542,5237,5329,5237,8190,5471,5464,8438,8489,7588,8239,5655,7548,5476,8539,5509,7883,8772,8783,8329,8005,0,8670,5647,7605,8589,9372,5358,5261,7877,7877,5320,8316,7734,7516,7994,7719,7719,7558,7592,7558,5396,5285,8149,7935,8811,8879,7946,7487,5320,5352,7653,8267,7511,8062,5564,8052,7849,5274,5407,5261,5237,0,0,5347,0,7628,7516,8438,5372,8903,5509,8115,5661,7628,5301,5391,5529,8093,7800,5209,5218,7908,8179,5401,7828,7491,5708,7811,5655,7877,7800,7883,7883,5476,5342,7920,8170,7738,8196,5325,5314,5401,8789,8039,5309,5501,7719,5325,5342,5296,5573,7738,8022,5693,0,7605,8277,8888,5401,5651,5391,5320,5651,8381,7616,7616,8249,5309,7706,7920,5325,8052,8613,8397,8494,8272,7482,7482,8217,7839,8154,5485,5347,7667,7663,7734,5314,7994,8888,7786,7542,5426,0,8144,7893,0,0,5407,7994,7632,8365,5329,5301,5337,7924,5329,7487,5381,7616,8762,7599,5296,5661,7533,7914,8473,7877,5305,7511,5407,7734,7719,7558,7786,8888,8154,5274,5329,8196,7653,7616,5218,5301,5485,7675,7616,5342,7994,5661,8359,8052,7605,7610,8277,8217,8010,7982,8277,0,0,7616,5651,5325,5401,5407,7616,5501,7516,5476,5407,8354,7877,5491,8261,5407,5347,8010,8617,8467,5314,8527,5617,5666,5347,8827,5726,5218,7542,8048,8721,5699,7834,8052,0,7730,8365,8375,7834,5301,5651,5261,8772,8903,5285,7877,0,8052,7904,7605,5347,5347,7700,5509,8375,8329,5274,7834,8811,7834,7628,8154,7487,7763,8687,8919,5296,7675,7675,0,8062,8093,5372,5622,8484,7687,5661,7710,5314,5437,5437,8179,7924,7706,7548,5501,8816,7592,7752,8312,5476,5329,7805,7920,8509,7533,7834,8509,8334,7715,8783,8239,7952,8756,8068,8687,7528,7972,8687,8687,7994,8322,0,8245,5367,5241,7605,7990,7999,5564,7904,7888,7888,8093,5314,8190,8170,8772,8174,7592,8174,7658,7658,5224,7924,8316,7653,7605,7687,7828,7994,5391,5401,7710,5464,0,7982,8851,5347,5485,5285,7500,5426,7768,8577,7811,7924,7675,5519,5274,7482,7478,7576,5642,5391,7994,8316,5529,5437,8509,8062,7632,5367,7605,8190,5241,8312,7834,5296,8687,5578,5296,8898,0,8010,8670,7491,5296,0,5629,5309,9071,0,5237,5564,7592,7935,7734,5485,5314,8805,5509,8370,5352,7843,5261,7982,8527,7710,8164,7982,0,5320,7849,8329,7692,5291,7675,5237,7834,7653,9345,7667,8539,5661,5325,7478,0,0,7653,7834,7811,8514,8170,5442,7930,5501,7516,8653,7643,8068,5237,8402,7478,8062,8170,5564,8494,5285,8827,8527,5255,7723,8893,5237,7723,7528,5314,7834,7834,5476,5291,7628,8461,8297,7920,7786,0,8119,8227,8670,0,0,0,0,8033,7849,8005,7548,8676,5367,5296,8527,5401,8190,5426,5464,5285,5407,5358,7616,7605,7994,8641,5606,5237,8699,8699,7877,7834,7592,7592,7628,5712,8302,7994,7491,7528,7723,8110,5712,7811,7500,7999,7994,8010,5688,5342,7924,8577,7605,0,5524,7795,8222,7982,8267,5367,0,0,8411,8131,8422,5629,8783,5261,8869,8073,5464,5661,5237,7478,5237,8267,7849,7849,5237,8267,7504,8716,5261,0,5642,8329,5651,5401,7616,7478,8164,0,0,8322,7511,5237,5358,8473,8548,5329,5407,8087,5476,7957,8527,5342,5476,8316,5291,8548,8267,7982,5476,7972,5708,7994,8239,7653,7742,9071,7592,7800,0,8987,8498,8833,5407,5237,5237,5237,5476,7675,7710,5464,7877,7491,7542,8297,7834,5274,8255,9021,8919,5471,7914,5285,5501,7487,8239,7628,7516,8316,8670,5501,8010,7834,8329,5347,5712,5629,5476,5485,8514,0,0,7616,7628,5476,5529,5347,7658,5372,8179,5342,5237,5699,8827,5401,5296,7605,5661,8093,7994,5476,5661,7999,5296,0,0,5347,8613,5274,8898,8329,5309,5442,5442,5642,7849,5347,8473,8164,8987,5305,8222,8893,8015,8589,5708,5367,8582,8851,7628,7746,5329,5442,5442,7834,5237,7920,5391,5391,5688,8261,8354,5347,7582,5291,8316,8721,5688,7779,5651,5629,7779,7834,5224,8093,8498,7628,9361,8387,8994,5241,5325,5381,8467,7663,7994,7877,7516,5291,5564,7491,7904,5459,7491,5642,8443,8617,7696,8322,8519,5717,8205,8716,8322,8484,8322,7858,8282,7533,8670,5407,5213,8762,7982,7853,5333,7548,5459,7898,8255,7982,5337,7558,8762,8387,7558,8816,7763,8751,8484,8711,5480,7715,8149,8504,9377,7565,7790,7715,5391,7478,7834,7977,7930,5391,5407,7786,5337,8375,8119,7715,8539,7542,7768,7768,5209,8594,5237,8211,8946,7542,7478,7786,5237,5301,5301,7516,5325,8461,7972,8543,7768,8489,8762,8170,7977,5407,8527,7858,7990,8170,8898,5573,7478,7930,7632,5381,7538,8682,7994,5301,8387,8316,8387,5419,7853,5301,8762,8577,7920,8647,8222,5237,5442,7994,8005,7521,7768,8010,7904,8010,5688,7491,7774,8073,8345,5381,9345,8687,7538,5337,8756,8504,5325,8387,7599,7811,9345,8267,8582,8816,8539,8745,7548,5442,7930,7920,8170,8447,8119,8174,5367,9008,7982,8093,7893,8777,8447,5391,5337,8131,8354,8170,7883,8312,7786,8316,8381,7811,8727,5285,7849,7999,5325,5476,5261,8227,8164,8087,8316,8149,8762,5367,5391,5396,5241,7628,7962,7628,5358,7478,5237,5676,5301,5367,7482,7940,7843,7930,7972,7834,8302,5301,8456,5274,7757,5471,7752,5642,7904,8756,8447,8473,7482,7478,5237,5407,8048,8653,7834,8039,7883,5651,8227,5476,7972,5241,5296,8249,7774,8140,5617,7994,8732,7632,7982,8548,8987,7994,8527,8494,8329,5426,7605,7790,7994,8467,5274,8641,8387,5386,5661,7977,5730,8411,5337,5337,5712,7687,5642,7962,8073,7977,5396,8467,5448,7663,8467,5491,7628,5391,7883,7605,8201,5655,8519,8670,7800,5485,5419,7715,8015,7877,5708,5377,8427,7849,5358,5291,5358,5358,5464,7734,7571,7628,8316,5642,8239,8851,7675,5218,8631,5237,5717,5676,8762,5296,7908,5274,5661,7738,5274,8329,8381,8048,8154,8417,7696,8577,5386,8687,8087,7952,7738,5274,5651,8261,7811,5386,7632,5274,7706,5291,8190,7622,5209,5329,5329,7628,7588,7599,7482,5391,5693,7605,7500,5325,8062,8539,7768,8811,8484,7628,5496,7710,8329,7538,7576,7478,8613,7752,5381,8387,5501,5329,5413,8354,5291,8604,5291,5509,7637,5712,7883,7920,7994,7811,5274,7558,8093,5491,5476,8345,5255,7828,5564,8190,5237,8073,7839,7823,7977,8670,8312,7628,7990,8297,7864,7914,5476,8443,7977,8438,5261,7511,5358,5655,5655,5291,8048,7610,5309,5285,5407,8589,8687,5274,7576,7817,7834,5519,7924,7500,7632,7487,7605,7528,5519,8329,5377,7696,5442,5642,5367,7946,7952,5248,7500,8282,8456,5564,7849,8170,7643,5213,7864,7558,5377,7893,7478,8387,7667,7930,7930,8805,5274,8287,7653,8190,5224,5688,7768,7843,5301,5519,5564,7511,7491,5237,8093,5301,5606,5401,5612,8514,8479,8772,5291,7805,8427,5352,5629,5730,8170,8033,5377,7752,5485,5381,7828,5573,5314,8456,7558,7982,7706,8548,8653,8227,7706,5413,5509,5329,8827,8721,7834,7757,7930,7516,8196,7920,8994,8716,7774,5407,8048,7491,7723,8302,8745,7643,7628,7790,5712,7834,8190,7548,5426,7977,5381,7994,5655,7994,8893,7811,5381,8732,5407,5622,7924,8494,7768,5367,7935,8467,8795,5386,7982,7616,7628,7930,7872,7719,7511,8427,8164,5612,5642,5407,7500,5309,7839,7616,7538,7972,8548,5629,5301,7843,7952,5655,8322,8451,8604,7999,5642,7663,7853,5647,8925,5329,5622,8827,5309,5476,8402,8827,7972,5301,5367,5612,5688,8190,5708,5401,7800,8015,7538,7898,7616,5629,5329,5381,8919,5261,8833,8329,8777,8903,7516,7930,7723,5218,5464,8772,7734,5485,8010,5309,7511,5325,5437,5485,7663,5358,8179,5726,5372,8903,8402,8234,5372,5296,7811,7719,5342,8732,7628,7628,5309,5329,8888,5485,7752,8397,7653,5237,5325,8190,8821,8154,5671,5666,8647,5301,5401,5329,7800,7542,7723,5578,7616,7582,5622,7610,5261,8255,8365,8422,7616,5651,7521,5237,8721,8479,8365,7811,7795,8073,5459,8222,7982,7972,8647,8282,8261,5301,8631,9026,5419,7542,5291,7864,7565,5407,5534,7742,8073,7786,0,7588,8093,5476,5255,7839,5309,7977,8255,7710,8527,8903,8249,8057,8863,7667,8411,7576,5655,8903,5485,7616,7706,5407,5573,8539,7742,8249,5622,5352,7516,8833,7478,7920,7883,5534,7616,7834,8811,5491,7914,7478,5606,7653,8227,8119,8577,8119,7877,7877,8222,8119,7588,8365,5218,8952,5329,5564,5218,5413,8527,8381,5606,5337,7977,7914,8261,7828,5296,7823,7828,5255,8805,5396,8805,7768,5209,8249,5358,8267,8365,8467,7511,8227,7715,8267,8010,5564,8845,5606,5381,5381,7768,7994,7696,5606,7696,7528,7533,7487,7834,7834,5391,7511,8543,5655,5573,7715,7790,8604,8307,8126,5358,8245,7972,5329,7706,5329,7768,7779,8010,5396,7628,7628,5237,7667,5476,7817,7628,5459,5442,5224,8903,8287,5325,5509,9382,7924,5407,8641,5666,5325,5496,7877,7592,8641,7516,8028,5419,5442,8461,5459,5459,7920,8077,5437,5320,5320,7972,5476,8519,8131,5693,5358,5381,8239,7817,5717,8484,7667,8789,5261,5651,8293,5301,7924,8033,5337,7904,5381,5568,7972,5564,5661,5358,5476,5301,7658,8519,8527,7605,8174,7920,8811,7990,7839,5661,8873,7883,5655,8048,7972,8909,8170,5325,7893,5726,7828,7977,5274,5496,5224,9032,5241,5218,5485,7768,5248,8316,5476,8509,7994,5296,7888,7924,5301,7972,8727,8387,5712,8307,5485,7675,8316,7516,8211,7920,7675,7940,7734,5301,7972,8451,8170,7478,8302,8149,8087,7811,5274,8527,5352,8527,8721,8772,7893,7538,7516,7710,5325,7828,8345,7710,5358,7930,8438,7786,8217,7864,7994,7628,8402,8479,7605,8504,7883,5501,7867,5325,7628,5730,8527,7986,7924,7752,5647,7628,7795,5396,7811,7692,7977,7482,5476,5329,5514,5291,7710,5237,8721,5476,7752,5325,8519,8282,5666,7511,7637,8087,8293,7828,8245,8959,8772,7877,8473,7795,8140,7632,5476,8548,5442,8015,8533,7972,9045,5699,5496,7957,5329,5305,5309,7482,7616,5464,7888,8239,7610,5485,7957,8149,5274,7893,7883,5529,7990,7834,8282,5496,8519,8625,5407,7628,7994,5476,7893,7920,5666,5301,5320,7883,8461,7828,8073,8277,8959,5325,7710,7924,5274,5381,5241,7834,5301,8154,8277,5407,7828,5274,7616,7482,5726,5218,8772,7828,7687,8509,8375,8073,7877,7952,8312,8048,8919,8048,8126,8126,7867,5358,5372,5372,7790,8407,5314,5381,5464,7952,7839,8093,5524,7952,5407,5285,5352,5524,7663,7864,8052,5407,5358,5509,8498,5305,5237,7663,7592,8052,5464,8052,7817,7977,7478,8349,8577,8312,8946,5285,7972,7582,7605,5301,8647,5726,5285,7920,8811,8174,7576,7667,8149,8879,5699,5568,7786,7667,7667,7576,7687,5381,8427,8762,8438,7990,5485,7628,7930,7999,7548,9054,5218,7616,7849,5476,8329,5676,7849,8608,8427,7834,5407,7849,7628,8005,7582,5671,8479,7533,7478,5688,8119,8479,5329,8427,8249,5329,5309,5391,5407,7533,8721,8345,5329,7924,7576,8903,8010,7786,5301,7715,7558,8811,7500,8211,8211,7972,7706,7935,5606,7521,5471,7528,5237,7533,8222,5471,7478,8267,8119,9389,8267,7746,7533,8827,7622,7883,5712,7790,8795,5274,7742,8345,8174,5241,5642,7914,8154,7723,5655,5396,7930,5386,7710,8119,5712,5476,7605,5655,5655,7746,8582,8467,5459,5476,5209,5209,8783,7687,8190,7893,7982,7853,5342,7952,5651,7883,5476,7696,8391,8190,7710,8582,5676,8711,5655,7681,7924,7834,5305,7548,8447,7999,5241,7528,7667,8670,7653,7811,8539,7478,5296,5676,7864,5509,5274,5237,8653,8277,5407,5261,5314,7643,5509,8093,7823,7883,8577,9394,5485,5241,8582,8277,5342,5676,8839,5342,8762,5329,5358,7715,7643,8447,5329,5476,5381,7774,7576,7628,7643,7482,7994,8670,5325,5699,8375,5391,5676,5661,8762,5309,5699,5377,8005,5524,7872,8711,7500,8048,7752,7622,5442,5301,5651,5534,5568,8387,8033,7990,5448,5367,7723,8334,8391,7800,8387,7972,8015,8334,7757,5301,5305,8598,8827,7957,7710,7800,8647,7558,5325,8249,7834,5301,8959,8302,8255,7734,7605,8919,7914,8598,8239,7487,5301,8795,7491,7500,8316,8711,7516,8005,5274,7516,7663,5296,8427,5407,7786,8762,5726,5622,8589,5514,8179,8329,8179,7663,8249,5337,8888,7746,5337,8582,5291,8190,7746,8154,8048,8893,7610,8721,7800,5726,5573,5629,8795,7605,7790,5309,5524,7628,5448,8604,7872,5391,5476,7786,7972,8093,7914,7616,8170,7972,5285,8451,8676,7663,5285,8833,8154,8126,5391,5688,8312,5325,5209,8811,7478,5480,7692,8322,5237,8010,8582,8903,7538,8302,7883,5717,5301,5564,5209,7849,8154,7757,8879,7706,8795,5381,5501,7864,8387,7920,5578,7834,5342,5622,7935,5471,5529,7834,7667,8527,8048,8068,7687,5726,7628,7632,5480,5381,8539,8533,5655,7663,7893,7715,7605,5301,8411,8293,5255,5352,8093,8174,5333,8312,8073,8884,7914,8925,7495,5301,7610,8316,7742,5301,5524,5573,7667,5329,7605,5717,8154,7663,5386,7977,5329,7994,5564,7632,8800,5426,8119,8316,8068,5237,5476,5491,8851,5367,8255,8467,7908,8255,8322,8946,7675,5699,7768,7843,7752,7628,9059,5367,8211,5337,8539,7935,7935,7864,7511,7616,8211,5699,7834,8302,8653,7628,7994,7500,7849,5386,5407,8456,8022,7675,8946,5352,8391,8863,8571,8554,7706,8925,8745,7516,7667,8073,5693,5564,7757,7849,7687,5329,7663,5342,5699,5564,7930,7576,8217,7478,7706,8099,5509,7828,8461,8456,7706,8033,7487,7834,5459,5693,8762,5480,5381,7616,8411,5476,5358,7994,8851,5655,8676,5367,8903,7576,5386,5352,5407,8539,8772,5496,7473,8359,8272,5274,8190,5699,5699,7538,7834,8184,5476,7663,7839,7914,7487,5629,7898,5396,8631,7994,8940,5666,8451,5655,5367,7706,5401,7533,7994,8245,7675,5578,7990,7986,5396,9399,8234,5622,7877,5325,5708,5352,5329,5329,8795,8322,8033,8211,5666,7864,7706,5305,7706,8402,8443,8745,8287,7675,5325,8149,8073,5726,8833,8919,7904,7763,7516,5342,7828,5464,7533,8925,8255,8539,5301,5337,5285,7786,7786,8255,5471,5612,7628,5476,7849,7977,5647,5666,7811,5573,5237,5699,8903,5476,5419,7920,5372,8387,8751,7706,7663,8196,5407,7628,8821,8249,9406,8149,7779,5612,7763,8936,5274,8582,5666,5666,7972,5301,5301,8010,5699,8087,7616,9045,7610,7696,5301,8073,8617,5381,9067,8365,7834,5391,5391,5301,8705,5617,8354,7487,7706,7738,8800,5661,5274,5274,7972,7706,7972,5413,8217,7548,8068,7478,5391,5337,7972,7681,5564,5564,5651,8443,7972,7491,5209,7571,5726,7982,7605,7914,5342,7839,7637,5651,5329,7628,7521,7986,7681,8647,7605,7952,5367,7719,5325,8077,5655,5524,7834,5309,8033,5671,8073,5693,8479,8154,8272,7706,7710,8721,8397,8484,5329,5224,5309,8479,7823,5329,5261,5578,7986,5296,5730,5237,7482,7576,8154,5655,5564,8467,7482,5274,7924,5377,5476,5712,7487,8539,5296,5314,5655,5218,5325,5325,8005,7487,7538,7738,7500,8190,8762,7628,5237,5325,7628,7877,8772,7908,5342,5301,9054,8447,8479,5301,8261,8354,7622,8365,5666,8110,7628,7628,7710,5381,8196,7849,5647,5519,7487,5642,5647,9051,5496,8811,7719,5274,5224,8062,5688,5476,5248,5501,8052,8370,7990,7622,8940,5606,5358,7972,8631,5651,8334,7478,5274,8447,5301,5337,8443,7920,7904,8631,5305,7558,8052,8196,7924,8370,7972,7972,5320,8170,5209,8154,5325,7558,8136,9075,7663,7849,8316,5209,7972,5381,5401,8762,5476,5305,5296,5448,8772,5237,7706,7843,5564,8115,8839,5329,8589,8811,7977,5377,7558,5647,8670,8839,7786,8099,5329,7786,7571,8504,9032,8115,8126,5325,7972,7972,8653,8873,7982,7990,5224,5442,5629,7628,7588,7616,9411,5476,5651,8411,5396,8349,5358,7571,5352,7548,8548,7500,7558,8479,7752,8397,7675,7487,5325,5564,5655,8255,8479,7977,5325,5524,5524,7786,7864,5342,8010,5325,5296,7715,5261,8514,7972,5325,5329,7588,5325,7616,7752,5688,7719,5401,7500,5464,5296,5325,5342,7675,8196,5285,5314,8316,7487,7834,5342,8789,7719,5296,8261,5309,8789,8354,5325,8422,7752,5372,8154,7817,5329,8473,7663,8261,5314,7908,7628,5529,8631,8484,7723,5237,7723,8571,7628,7920,5485,5642,8732,9032,5647,5347,7867,8533,5642,8411,8222,5642,5224,8631,7605,5285,8255,8052,7478,7952,5642,7972,8519,5642,7681,5285,8519,7592,7675,7482,7982,8498,8494,7883,7692,7990,9417,7500,7588,5305,8005,8307,5329,7482,8494,8631,8387,7628,8170,7723,5642,5642,7681,8783,7628,5606,7757,8170,8239,5237,7986,8005,7675,5285,8287,8739,7610,7558,8402,7675,5372,8498,8255,7637,8307,5372,7632,7867,5301,7588,5391,9423,7542,8925,8062,7706,7542,7700,5391,5320,7994,8170,8427,7605,7487,7610,5224,5485,5485,8015,5476,7487,7667,8062,8316,8022,7592,7858,8267,5320,5485,5296,5381,5255,8676,8670,8827,7994,5647,7994,8099,5485,8795,5485,7605,7605,5333,7658,7628,5647,8811,8211,7605,7643,8762,5325,7675,7904,8099,5325,5655,7999,7962,8732,7994,8467,7982,8461,7675,8433,7920,5358,5407,5647,5647,5261,8119,5367,8539,8467,8136,7710,5647,5301,5647,8438,8222,7681,7487,5564,8387,8543,7994,8245,5209,8539,8093,8115,8293,5564,8131,8010,7977,5717,8010,5391,9427,8312,8582,5407,7723,7610,5305,8411,7977,7952,7972,8282,8329,5642,5274,5358,7723,7795,8919,5285,5309,7768,7962,8613,8048,8582,7605,8316,5426,8245,5485,8604,7908,5568,5485,5358,8635,5301,5241,7478,5501,8267,5647,8994,5301,7843,8170,8721,5407,5480,8316,5255,7828,7558,7940,5391,7920,7571,7675,8670,8805,8604,7834,5524,7908,8811,5442,7904,8461,7935,8093,7542,5564,5407,7972,8277,7986,5358,5358,5358,5717,7710,8174,7628,5407,7999,7786,7710,5241,5485,8302,7576,5671,8073,5325,7516,7940,7667,5564,7687,7786,8571,7542,5496,7667,7805,8721,8467,8467,8170,5661,8115,7867,5480,7632,7883,7675,8893,5407,7473,8411,8272,7521,8509,9435,5655,8119,5426,5391,7605,8354,8456,7904,5476,7482,7675,7500,5381,5464,8845,7663,8010,7565,5622,7858,8329,7605,8062,7867,5730,8190,7622,7542,5309,8005,5666,7957,8267,7849,9441,8201,5442,5524,7893,8893,5241,8131,8077,7542,7675,8498,7817,8334,8762,5301,5358,5314,5573,8635,7710,7605,7582,7610,5347,8345,8201,5301,5296,5358,8539,7888,7834,5352,9367,5622,5325,8647,8307,7628,7763,5622,8115,5237,8039,5476,8447,7877,5568,8140,8461,5442,5325,7952,7588,7957,7687,5329,8184,5612,8498,8345,5612,7888,8676,5647,7558,8073,7643,5358,8903,8239,7883,7828,5564,5407,8438,8005,8539,8255,8919,5464,7962,8149,8365,7675,8762,5391,7864,5642,5213,8222,5325,8811,8227,8762,5480,7834,8073,8903,5381,7990,7610,7542,5401,8073,7576,8277,7521,5347,8873,7628,8354,8052,5655,7658,5377,7616,5442,5325,5218,8509,8909,7588,5391,8010,5448,5534,7558,5314,8909,5329,5329,5407,5480,7898,8159,8277,7610,5606,7478,5347,7542,8354,7548,5726,7817,8411,8676,8857,5301,7752,7632,7616,9447,7843,7500,5651,8539,8010,8140,5329,7914,8293,5661,5666,5274,5209,8447,8879,7710,7542,8473,5358,8282,7478,8539,8015,8110,5501,5358,8402,7972,5301,8039,8322,5309,7904,5301,7715,8170,8312,5305,8140,8184,8322,7779,7588,7681,5301,7706,7588,5261,8316,5209,8604,8354,7576,8010,9345,8126,5471,5501,5471,8539,8119,7779,5274,8772,7779,8179,8845,5301,8354,8245,7491,9452,8402,8126,8015,7516,8548,5485,8039,7920,8589,8589,5419,7491,8110,8073,5730,7500,8170,7982,5407,8365,7839,5301,5347,5564,8447,7930,8387,5519,5524,5629,5688,5666,8340,5329,7834,5651,8589,5501,5372,8756,7872,5305,7516,8261,9457,8329,7779,5419,7904,8073,5391,8504,8548,5274,7495,8641,8077,5301,7990,8539,9345,7681,5347,5651,5476,8484,5237,8653,5241,8484,9021,5325,5496,7576,8329,8354,8062,7628,7849,5509,8504,7752,7752,5688,8136,5209,7853,8816,8998,5381,5320,8104,7920,7482,8302,8062,7972,5325,5501,8267,8504,7883,5309,7588,7565,5612,8249,7482,8154,8033,5309,5564,7972,7658,5209,5442,5642,7687,5666,5237,5564,5661,7914,7853,7605,5726,5476,7605,8670,8811,7924,5564,5480,5655,5491,5314,5391,7548,5274,8443,7592,8282,8170,8716,8527,8397,5501,5485,8504,7946,5661,5381,7605,5442,7795,8126,5291,8582,8272,7839,8543,5237,8272,5661,7757,5285,7768,7828,7632,8190,5224,7533,5708,9462,8316,8164,7681,7482,5666,5419,7768,7500,5329,5459,8316,7487,5419,8293,5296,5291,7972,5407,5676,7710,7746,5655,5329,5274,5688,5320,7898,8613,7675,5509,8196,8845,5564,5377,5519,8504,5564,5237,5564,8805,5342,7548,8653,5564,5485,7982,7548,7834,5309,8267,5325,8456,5391,8548,7516,7888,5301,5329,7779,5442,7768,7628,5480,7706,9032,7565,5509,7542,5274,8087,7994,7706,8272,5509,8461,7599,8716,8863,7774,8190,8402,8099,7511,7706,8427,7930,8287,8093,7746,7706,7867,8772,5241,8234,7967,5476,5209,8093,7779,5309,7893,7872,5261,7628,8548,5381,5329,7500,5426,7994,5209,8170,7521,5407,7990,5274,8821,7746,7706,8676,7548,8539,8438,5676,8467,5661,7715,7487,5688,8411,7811,8845,5325,7746,5274,5564,7994,7628,8131,7622,5661,5401,8456,8411,8267,7834,5377,7795,7972,7710,7952,7768,5209,8022,8467,8062,7500,7957,8387,7648,5285,7616,5309,5476,8863,8447,7898,7786,7533,7576,5407,8451,7768,8617,8267,5367,8952,8929,5426,8272,5564,8267,5655,5524,8613,7982,5437,8909,5274,7588,5655,8548,8422,7653,7774,5358,8527,7478,5301,5612,7482,5237,8548,5666,5622,7542,7800,7972,5329,5305,8647,5401,5651,8190,8548,5325,5261,5342,8010,8952,5413,5476,5514,8711,7576,8322,7643,8473,5237,5320,5519,5296,5564,5296,8255,8833,7571,7972,5261,8589,7516,7628,8010,8239,5485,9021,5407,7648,7877,7872,5519,5296,5396,5442,8287,8322,5485,8245,8119,5296,5237,7482,5407,8170,7811,5372,5274,5381,5285,8073,7877,5476,5386,5342,5476,8635,7500,5325,8888,7542,5347,7734,8329,8857,7706,5712,7883,5305,5666,5661,8857,8582,5337,7500,5666,5274,7877,5237,5329,7877,7616,7774,7521,5274,8397,8857,7632,5237,8479,5347,5688,5377,7487,8484,7628,8062,7576,5274,7849,8354,5325,5509,8582,5325,8504,7994,8267,7853,8104,7920,8302,8461,8136,8467,8548,8099,5501,7972,7752,7565,7533,5708,8816,5320,5274,8811,5661,5564,5401,8443,7914,5237,7972,7548,7576,8282,5564,5314,8670,5476,5485,5491,7898,8164,5381,7692,7648,8647,7946,5296,9462,8190,5419,7632,5291,5285,8316,8845,7675,5676,5519,5309,5342,8272,5325,5377,5325,7500,8093,5578,7500,7706,7893,8287,8015,8234,8119,7994,7746,8087,5413,5442,7790,7511,7768,8548,8427,5509,5329,8863,5209,7990,7521,5407,7746,5347,5377,7622,7706,7834,5296,7487,7888,8676,8411,5274,8131,8863,7811,8539,5329,8411,8438,8467,5309,5564,8613,5655,8527,7582,5524,7877,5301,5476,5407,8888,8647,7800,5666,7616,5612,8711,5514,5661,8239,5261,8833,5419,7542,5372,7811,5285,8857,8998,7538,8461,8998,5699,8762,7511,8073,5255,5730,8617,8093,7616,7616,8745,5367,7616,8617,8772,7768,7706,5534,7616,5666,8772,5329,8721,7599,5291,5291,7599,7653,5209,7632,5564,7962,8048,7786,7605,8170,7790,7786,8438,8461,7904,7795,7786,5676,5564,7516,7834,7999,8397,7795,5314,7628,7972,7542,8048,7823,5320,7487,7920,7538,7511,7904,8048,5301,8783,7994,5358,5325,8833,8316,7746,5325,5676,5651,7952,7632,7920,5325,5325,8375,7924,7763,8322,7738,5712,8062,5476,5337,5501,5651,5524,7924,7786,8548,8721,7576,8093,5476,7605,8539,5519,7542,5476,5274,8164,8658,8658,7542,7811,8397,5274,5476,5274,7817,5274,7628,8845,5381,5629,8467,5237,8010,7834,5501,5261,5401,5666,8845,8417,8322,7877,7628,7817,5476,5248,5534,5501,8062,5325,7710,8402,5564,8745,9054,5476,7986,5476,7920,8093,7511,7972,5726,5237,7628,7521,5237,8196,7706,8255,7628,8222,7675,7719,8196,5419,8539,8402,7893,8272,8302,7478,7605,5407,7605,5391,7858,8548,5509,5296,5730,7849,7849,7653,5708,8647,5476,5401,5726,8222,7516,9054,8255,7487,8010,5476,5296,8190,8479,5642,8149,5622,8898,7790,5578,7706,8131,5622,7994,9021,5459,7696,9045,8821,7696,8716,5622,5666,7696,5209,8845,8783,8052,8267,8994,8987,7710,7487,8227,7977,8170,7849,5573,8149,7599,5325,5629,7742,7957,8417,8131,8334,7599,7957,5642,7888,8005,5337,5305,8811,7696,5578,8805,7491,8245,8022,8196,7710,7710,8447,7883,8062,8845,8170,7920,8077,7883,5358,8170,8093,5241,5337,5377,8239,7610,7972,8381,5391,7696,8387,5459,7795,5237,5401,8427,8119,8577,8751,7864,8170,5255,8539,8039,7752,7500,5647,8827,5329,5237,8805,5329,8170,8005,8170,8005,8827,5291,5534,5237,7935,5401,8827,5708,8119,8022,8805,8005,8077,7972,7883,8381,7706,8504,7478,8805,7610,7628,7628,5342,7487,7588,5476,7588,7487,7999,8387,8110,5476,5333,5325,7542,5501,8340,5717,8433,7811,5464,5496,8504,7675,8461,7576,8467,8762,8461,8164,8811,7571,8494,8227,7786,7972,7653,5564,7893,5301,7681,8170,5407,7565,9032,5337,8136,8560,8800,5337,7817,7628,8354,5642,5309,8312,7542,5237,7681,7893,7710,5485,7658,7914,7548,8015,5661,7914,7994,7576,5459,7883,7893,7904,8073,5726,8329,7930,7795,7658,5209,7692,7972,8316,5476,5209,5564,8334,5655,5274,7571,8456,7977,8119,7972,8222,5337,5726,7982,5301,5476,7482,7500,7924,7610,7582,9012,5329,5485,8293,7834,8658,7473,5476,8687,5671,5717,7582,7972,7675,7914,7504,7696,7864,5426,7858,8766,5209,7628,7558,7681,7972,8277,5274,5661,8402,5661,5305,8349,5325,5396,8170,7908,7730,8205,7706,5386,5651,8789,8375,5509,8073,8461,8721,5688,5285,5485,7888,7946,8451,8577,7582,8805,5712,8104,7914,5661,7834,8397,5381,5519,8443,5480,8174,7558,7786,5564,8307,5480,5453,7628,7994,7924,7795,5688,5401,8015,8402,7972,5391,7576,7920,7864,7605,8795,5329,8005,7805,8811,5606,8028,5485,8805,7986,5476,8345,8504,7914,8287,5564,5564,8170,7893,8322,5509,5496,5342,8164,8548,5693,7478,5712,7957,5622,7972,8694,7795,7742,8903,8345,5426,5386,7628,7478,7823,8255,7548,7994,7864,8159,5647,5642,5407,7994,5285,7628,7977,8676,5358,8227,5564,5274,8447,7746,8631,8909,8422,7898,7957,5329,8631,8427,5708,7588,8863,5717,5476,8073,8239,5325,7920,7628,5524,7610,8888,8554,8227,8196,8039,7834,8663,8647,5419,5564,5629,5305,5237,5622,5329,5285,7952,5329,7972,5642,5291,5401,8316,5352,8447,7687,8239,8402,7864,7675,5325,8010,7972,5285,5261,7478,8402,8489,7521,5342,5501,7616,8772,9361,7883,7908,8789,8068,5401,5358,5218,5291,5573,5476,5676,7986,5401,5514,8005,7786,7706,5386,8888,8329,8613,8154,8205,7828,8473,7834,8739,7986,7834,5329,8154,7952,7632,5726,8205,8375,8447,8205,8033,8577,8494,8687,8205,7516,8687,5209,7478,8077,5442,5325,5688,5688,5325,8077,7972,7972,7972,7999,5459,7972,5519,7658,5655,8484,8329,7628,5496,5642,7628,5248,8201,5237,8484,7628,7628,5426,8170,8484,7528,5617,5617,7883,7994,5391,8772,5320,7538,5606,7883,7734,7734,8721,7972,7576,8732,8598,5367,7478,7576,8479,8461,5261,5476,8613,8994,5491,5241,8282,8732,8560,7565,8282,5699,7898,7548,5407,8777,8093,7605,8115,7888,8073,5274,5717,8732,7977,5329,8062,8334,5237,5730,5564,7500,8527,7994,8670,7628,5476,8805,5255,7994,7817,8249,5464,8479,5464,8087,8548,8411,7790,5459,7800,7898,5496,5476,5514,8777,8340,7957,5491,7877,5642,8316,7982,8903,8772,5329,5476,5381,5407,8255,8249,7706,5329,5329,7877,8617,8682,7779,5407,5407,7719,7834,5564,5255,7834,7834,8340,8340,5476,7811,8805,5688,7663,8716,7681,8805,5381,5352,5509,5476,8745,5358,7763,5471,7542,7628,8387,8851,7723,5647,5367,7706,8211,5209,5296,8539,8174,7521,5358,5564,8068,7994,7478,7972,7706,5261,7752,7542,8504,8998,5568,5381,5329,7500,5325,8196,7706,8467,5501,7904,7872,7528,5325,5396,8277,7565,7478,5291,8131,7972,8267,8196,5708,5573,7790,8387,7893,7622,8560,8827,7482,5337,8411,5476,5296,7994,5301,5401,7864,5274,8227,8777,8249,7588,8484,8467,8297,8093,7658,7681,8539,7622,7723,5301,5573,7990,7715,7487,8222,8131,8438,5476,7893,8170,7920,8893,7692,7605,8447,7516,5224,5261,5688,5642,7616,8543,8062,7616,7687,7582,8170,5459,8077,8811,8539,5255,5712,5564,8716,5296,5358,8184,5564,5314,7790,8010,7867,7790,7790,7904,5209,5305,5358,5476,7710,5325,5485,7610,7696,5453,5391,7817,7616,7723,5717,8391,5301,8164,7482,8682,7542,7904,8033,8539,5519,5291,8577,7478,5325,8005,8604,5248,7839,8131,8687,8211,7710,5285,7768,5564,8670,7605,5209,5419,5296,7482,5496,8048,8164,8613,5248,8211,8316,5617,7883,7605,8329,7811,7473,7768,5671,8245,5485,5426,7768,8340,5485,7696,8345,8087,7710,8504,5407,5377,5464,5255,5301,5329,8149,5296,9345,5296,7977,7648,5358,5476,8255,5296,7706,8903,5426,8164,7643,7616,7632,7616,8329,5309,7962,7482,8349,8062,7576,5642,8015,7616,7710,8307,5564,8504,7482,7849,5358,5476,8267,8539,5647,5342,5342,7558,7599,7478,8140,5237,5485,5301,5564,7605,5305,5301,9345,5325,7888,5688,8302,5612,8033,5485,8170,5712,8249,5426,5485,7935,7528,7667,7834,8805,7757,8451,5509,7653,5337,7628,7977,7599,5337,8816,8670,5274,7843,5564,7864,5305,8334,5642,7823,7715,8795,8277,5401,5391,8494,5241,5401,8154,5337,7616,7616,7994,8104,8211,5712,7839,5509,5485,8539,5726,7582,5407,7967,7786,7986,5329,8170,7994,7667,8504,8653,7849,8772,5209,8594,8039,5564,8099,8227,5442,8022,7805,5237,8514,7774,7904,5301,5647,8073,7653,8222,8196,5676,5237,7491,8539,5381,7734,8903,5309,7999,8411,8845,5396,7786,8093,7893,5309,8345,7599,5413,7576,7834,8170,5407,7516,8772,5320,5606,8479,8359,7706,8022,8402,8272,8227,7930,8227,5476,7893,8277,7706,8249,5255,7994,7883,8302,7811,7834,5476,5241,8994,8201,8964,8716,7982,8170,8484,8964,7632,5442,8539,8249,5476,5647,5337,7616,7616,7616,7675,7730,5688,7990,7521,5509,7628,5337,5564,5401,8190,8467,7823,8115,8494,8104,7675,7628,7858,7790,8345,7864,7632,9467,8154,7795,8504,7746,7571,7571,8170,8222,8110,7715,5661,5661,5726,5407,5564,5329,8052,9054,8527,8062,5237,5730,5320,5647,7605,5688,7521,7914,5717,5381,5485,5651,5464,7914,7734,5671,7582,7500,7542,7994,8073,7994,8994,7990,7982,8205,7977,8676,5642,7667,8827,5352,7994,5274,8005,8805,7643,5237,5391,7706,8604,5386,5642,8467,5218,5218,8467,8539,8653,8721,8354,5529,7877,8509,8005,5578,5419,7957,7994,5213,7867,5448,7482,5476,7542,5391,5237,7930,7843,8293,8249,7849,5699,7940,8467,5301,8170,5237,8334,5573,7811,5647,5476,8354,5655,7542,8443,8312,8451,8467,5573,7628,7908,7533,7734,7734,7888,8851,8227,5501,5301,5241,5407,5325,8893,8777,8869,8571,8052,7962,5655,8391,8653,7500,7511,5237,7786,8039,8164,5534,5671,8451,5391,5629,7632,8613,5612,5274,7930,5642,5377,5358,8548,7599,8287,5519,8604,5237,5347,7528,5325,5301,7864,7872,7628,5407,7616,5305,7757,7681,7790,8316,7605,5352,8179,5401,8827,5237,5255,5688,5309,8104,5459,7994,8422,7723,7628,5476,5325,8239,5358,5329,7588,8201,5442,7632,8190,7994,5717,8093,5325,8647,8234,7653,8539,7588,5329,5617,8987,7746,7763,8329,7800,8073,8539,8451,5578,9367,8190,7994,7663,5708,5651,7957,8140,7548,7774,8159,5419,5651,8827,5453,5296,7667,7482,5407,7834,5320,7482,8005,8033,7982,8302,7786,8170,8519,8205,5661,8322,8307,5401,7516,5568,7663,5358,5301,8467,5325,8329,5218,8010,8249,7742,7877,7828,8329,5285,7872,7872,5464,7628,8255,8391,5396,5407,8827,8267,8222,5337,5726,5329,7648,5296,7864,8909,8438,5291,8397,8316,8613,5501,8005,8598,7872,8387,7500,5564,5291,8903,7478,8316,7487,7675,5237,8987,7994,8641,7706,7542,8267,7962,8093,7628,5367,5218,5325,8062,7487,7706,5666,5255,8267,7628,5237,8052,8073,8052,7663,7994,5296,7994,5325,7839,7521,8375,7763,5476,8873,5419,8005,5296,8322,5401,7990,8467,8245,5377,5629,5661,5358,8427,7962,5401,8010,5693,5381,7786,5661,8149,5407,8898,8062,7730,7528,8329,5642,8239,5448,5320,8329,8217,5274,7994,8766,5442,8349,8613,8647,7658,7706,8772,5485,5655,7696,8447,7616,5524,8827,7675,7706,8048,5407,7500,8005,5381,5708,7757,5476,5347,5309,8777,7622,7723,7867,5274,7516,7742,7795,7588,5329,5476,7500,5491,5301,5476,7986,5666,7706,8539,7972,7930,8909,5642,5305,7495,8647,8010,5448,8772,8422,7723,5337,7994,7511,5296,7558,5524,7877,8249,5301,5320,5573,7663,7516,5372,8170,8033,8277,8154,5480,7478,7872,5629,7706,7946,5329,7588,5717,8211,8267,8751,7628,5320,8164,5237,7558,5305,5381,7588,5407,8322,8077,8073,8261,5476,5274,7628,8170,9353,5401,7482,8322,7616,7610,8316,7994,8354,8093,5726,5717,8010,5651,7628,8005,5347,5325,8903,8427,5666,5666,5396,8527,7972,7994,7982,8427,8359,5301,5325,5651,7982,8359,7632,5325,8411,7828,5407,8987,5476,5666,7768,7675,7893,7834,8514,5432,7982,7982,5329,8293,5329,7487,7675,7542,5573,7982,5437,5480,5564,7500,7500,5329,7692,7628,8052,5496,8170,8039,8222,7834,8427,7542,8005,8282,5237,5320,5248,5688,7757,5325,5688,7558,5391,7542,8539,5578,7628,5564,8811,5237,8062,5296,8249,5717,5342,5367,7637,5391,5476,7538,8411,8783,7864,8994,7710,8354,5291,7752,7872,7898,5358,8354,5509,7667,7706,5320,7482,7706,5606,8467,5291,5296,8196,7706,8174,8387,7839,7715,8641,5568,5501,8560,7817,5372,5564,5224,5381,5564,7710,8307,8647,5274,5642,5655,5688,5337,7687,7849,7710,5337,7849,7653,8170,5301,5564,8297,7687,8131,7920,8073,5274,7605,7757,5432,5224,7786,8297,5726,5617,8964,8093,7786,7883,7786,7616,5301,7558,5726,8711,8170,7839,5296,5480,8211,5564,7924,8631,8577,7605,5476,5274,8144,5285,8170,8144,5255,5325,5201,5218,5476,8381,5305,7723,7867,7952,7817,7972,7800,5241,7972,7977,7648,7487,7706,5426,8577,5688,8670,5218,7977,8839,8751,5635,7864,8136,5320,8190,8527,7914,5655,5564,5274,5578,7478,5564,5301,7843,5358,5573,8467,7582,8613,7883,7834,8805,5329,8839,8631,7795,7972,8022,7893,8170,5651,9361,7706,5661,7667,7687,8249,8322,7487,5237,8099,5301,5459,7681,8272,8451,8811,7653,7628,5655,7893,7920,7994,5471,5476,8467,8170,7768,7542,5476,8447,8745,7994,5509,8039,5509,7582,7864,8282,8539,8484,8316,7924,7616,8039,7877,8057,7986,7990,5325,7849,8805,8349,7500,7746,7628,5381,5391,8190,5464,7834,8411,8110,7994,5564,8467,5712,7994,5564,8821,5661,5337,5688,7790,5367,7616,5391,5248,5629,7605,8548,5377,5564,8467,5296,7663,5676,8762,7982,5688,5459,5301,7795,8267,8170,7823,7957,8658,8164,7616,5274,7558,7843,8334,7487,5237,8461,5237,8467,5534,8057,5448,8015,8164,5325,5325,7478,5688,7653,8196,5476,8093,5396,5629,5476,5241,8716,7849,7982,5296,7538,8391,9471,7774,7616,8909,7628,5381,5377,8571,7800,7834,8987,5578,8340,7710,8539,5708,5329,8087,7972,5629,8033,8647,5726,7920,7628,5407,7632,7957,8196,7548,7482,5419,8952,5655,5285,5509,7972,5578,7538,7538,8039,7511,7790,5237,8827,7967,8190,7622,5377,8015,8211,7521,5305,5655,5476,5688,8594,7972,7924,5352,5285,5726,5432,8239,7533,7952,5237,5325,5622,5464,7893,8149,5291,8509,5296,7482,7877,5285,5261,7648,5248,8851,5248,8329,7487,7542,7558,8903,7687,5661,5261,5476,5485,5347,7877,7706,7839,7521,5617,7632,8845,7616,7637,8196,8115,8190,5401,7605,8115,5661,8211,5661,7706,8164,5413,8249,8052,8821,8154,5314,7972,8888,5661,7763,8010,5666,5524,7986,5651,5301,8104,5301,5688,8077,5329,8762,7675,7605,7610,5301,7610,8245,7491,5413,8987,7786,5676,7616,7542,5237,8179,5726,7768,8762,5237,7800,5291,5381,7605,7687,5661,5464,8816,8456,8293,7528,7548,5642,7696,5448,7763,5237,5426,7834,8795,5248,8519,8039,8190,5320,5274,8316,7610,5381,8533,8387,7982,7994,8721,5248,7994,7957,8548,7946,5726,5476,5476,8201,5396,7628,8473,5496,5476,5261,8033,5564,8234,5501,8952,5381,8136,5568,7883,8915,7548,7548,5305,7706,5578,5274,7972,8504,8196,5309,7667,8467,5712,7637,5708,5453,8959,8959,7977,5480,5274,5491,8222,8174,5642,8329,7558,8670,8772,8170,7946,5320,5519,5655,5476,8994,8073,8539,8631,5296,8234,7972,5325,8297,7533,7883,7790,8073,7935,5568,5274,8239,5726,7930,8234,5237,8451,5314,5622,5564,5476,8196,7692,8504,5419,7977,8879,5305,5381,7738,7632,8613,7605,7952,7482,7774,8577,8144,5329,9345,8903,8115,7734,8800,5453,5320,7977,8653,7558,5301,5676,5381,8057,7908,5476,7628,5655,7511,5688,8658,5325,5476,8201,9345,8839,8302,5325,5301,5401,5401,7692,5407,8936,7877,5358,8613,7491,8467,8093,7982,8227,5442,7888,8473,7779,8227,8456,5305,7893,8467,7632,8039,7834,8762,5237,7972,8170,7972,7533,7972,7795,7730,5320,5377,5377,5564,7667,5661,8411,5459,8653,7616,5519,7610,7849,8467,8222,8222,5564,7628,8641,7834,5296,7930,5573,5329,5237,5617,8267,8245,5651,8245,8359,8164,8772,8613,8716,7592,8631,8282,8068,7710,8940,5453,7616,5381,7542,8359,7692,5401,8222,5301,5578,5391,5329,5671,5476,5661,5274,7994,8473,5241,8073,5419,9367,8222,7521,7786,7734,8604,7675,5407,5573,5325,7883,8925,5285,8073,7521,7528,5432,7648,7628,7710,5708,7888,7952,8340,5342,8789,7877,5401,5358,5309,8903,5320,8772,5358,5347,7834,8613,7706,8307,5320,8631,5391,8126,5661,8073,8033,5320,7610,5237,5274,5347,8539,5491,5564,5320,5391,5407,7533,5476,5285,8387,5237,5314,5642,8527,8772,5301,8473,5564,7904,7849,7706,5325,5407,8888,5291,8533,7952,7893,8282,8282,5651,7628,7763,5391,7487,8427,8239,5291,5651,7681,5401,5642,7972,5285,5237,5261,7994,5237,7710,7628,5347,8322,8762,8196,5314,5325,8391,7616,8131,7528,5314,5309,8721,7516,5314,7632,5726,7605,7994,5291,8560,7628,5347,7914,8539,8721,5407,8721,8427,7952,5651,5391,8427,5347,5291,5325,5237,8539,5485,7628,8131,5309,5726,5285,8484,5309,8140,5291,7706,7972,5325,7786,7883,8397,8539,8987,5396,7768,5717,7710,5381,7952,7817,8062,5381,7643,5688,5661,7588,8447,5325,7500,7972,7687,5358,5426,7982,8987,7877,7972,5529,5573,5391,5476,5329,5291,8068,7834,5237,7834,7930,5693,5564,5688,5381,8653,8062,5325,7565,8329,7952,8062,8190,8272,7500,5381,5476,8504,8329,5237,5261,7706,5480,5325,8772,7914,7864,7924,8387,5642,8022,7616,8190,7528,5291,8093,8884,8052,5337,7571,5305,7565,7500,8783,5617,8548,5391,8068,7500,7893,5476,7588,8447,7849,7752,7582,7500,5655,7605,5241,7610,5209,5209,8427,7786,5391,5464,5459,8093,8539,7500,5301,5309,7533,5309,8811,5476,8170,5708,7687,5480,5476,7675,7999,5255,5358,7504,5726,7904,5509,8443,8653,7576,7839,7558,5218,7500,8329,5476,7478,7653,5391,8052,5285,5391,5606,5496,8131,7952,7768,5485,5381,5396,5671,7521,7994,7914,5442,8316,5655,8427,8391,7864,5296,7516,7779,7795,7599,5413,7681,7710,5666,7605,8582,7473,8267,7628,8909,5325,7972,8504,7616,7843,7994,8582,7864,5391,7823,7883,8073,8345,5337,7946,8255,5612,5309,8391,7628,8509,7558,5381,8227,7478,7478,5524,8022,7734,5296,7952,8851,5655,8851,8762,7715,5305,8839,8447,7990,5296,8222,7653,5342,8732,8190,7893,7710,7738,5476,8136,7482,7757,7920,5325,7877,8033,9032,5325,7706,8772,8863,7994,7893,8745,5407,5241,5476,7967,5426,5314,7779,5407,7605,8052,8099,7914,8473,8359,7858,7628,5459,7500,7858,5391,8190,5391,7643,7834,7864,5329,8539,5661,5564,7700,7632,5407,8635,7628,5209,8732,5237,5296,7482,8345,5274,7823,8033,8527,7605,8340,7834,7946,8190,8554,7495,7706,8711,7491,7994,5296,8909,7967,5261,5407,8073,5337,5464,8154,5717,7616,5651,7675,5651,5237,8322,8033,8293,5377,7588,7898,7710,7542,5391,5419,8467,5661,5529,7706,5642,5642,7599,7628,8473,8827,8745,5301,5305,8919,8987,5578,9476,7687,8115,5381,5401,7957,8504,7742,8312,5237,5314,5708,5301,5309,5578,5476,7982,5419,7800,7839,7653,5396,7877,7768,7542,7719,5237,5617,8217,5241,5386,5347,7864,5209,5291,5325,9021,5285,8073,5296,5622,7715,8982,7687,5391,8255,8772,8982,7734,8119,7706,5372,8772,7681,9481,7542,9487,5285,5485,5401,8417,7538,7663,8427,7920,5296,7893,5476,5476,5261,8261,5642,7542,5573,8658,7982,7576,8052,7491,7616,7605,7628,5407,5296,8762,5325,8190,8365,5666,7914,7994,7920,5301,7599,8411,5578,7834,5476,5442,7823,5442,7982,7877,8261,7482,5347,8033,7516,5578,7811,8365,7999,8903,7972,7834,8762,7834,5325,5693,5564,8504,5325,5381,8062,8272,8261,5261,7706,7823,8329,5476,8329,5325,5476,5419,5296,7742,8170,8387,8884,7500,7864,8099,5476,8022,8527,5617,7565,5291,5241,7786,7849,5255,8443,5391,7849,7687,7588,5655,8539,7967,7839,5726,7999,8711,5476,5476,8582,5666,5391,7795,5391,5381,5296,7558,7605,7914,5396,7883,8131,7768,7952,8329,5476,5241,5671,7521,5496,7643,7500,5708,5381,7478,7628,8255,7478,5524,5337,5296,8582,8851,8022,7663,8745,7605,7967,7495,8762,5314,7893,7990,7994,7628,8839,7914,7757,7877,7779,9032,5342,8772,5381,5476,8711,8033,5459,5464,5407,5661,8293,7491,7994,7864,7628,8190,7877,5209,8345,7834,8539,5651,7858,8795,5305,8903,7588,5661,5642,7898,5391,7542,5651,5309,7800,5578,5407,9476,8772,8982,8255,5347,7734,5291,7834,7542,8427,8033,7786,7849,8062,5367,5291,8062,7653,8062,5237,5381,8154,8239,7653,8687,7610,8307,5419,7706,5391,5301,8919,5509,5358,5320,5274,8527,8940,8940,7811,7834,5391,5573,5296,8863,7628,8068,9494,5407,7904,5730,7864,8387,7521,7811,8903,8800,5671,5726,7908,5237,8888,8052,7628,7605,7977,7930,5237,8073,7752,7811,8888,5564,8548,5501,5476,7972,8222,7637,7653,7920,8136,7667,8811,8136,5337,7757,8805,7487,5519,7811,7610,5352,7849,5305,8190,7790,5301,8447,5358,7628,7977,8641,7790,7920,5237,7930,8170,5305,5573,8772,7495,5291,7930,5655,7675,5564,7774,7817,8594,5476,8560,8249,8751,8154,8010,8772,8721,8115,7972,7696,7605,5391,8653,5209,7696,7952,7858,8411,8349,5491,8119,8582,7893,8964,5476,7710,7790,7849,5358,5329,8504,5296,5671,9032,5209,7977,7746,9353,7706,5671,8205,5407,7616,7893,5655,5617,8888,7643,5688,7558,8732,5325,7576,8277,8365,7487,8033,8987,5606,8456,8845,5274,8349,5407,5342,5564,8772,8827,8467,7972,7628,8015,8762,5309,8504,8598,8267,7706,5437,8277,8527,8277,7967,8119,7663,5622,5564,8131,8159,8115,8077,7994,8039,8033,8052,7972,8863,8473,8267,8833,8772,8365,7834,8322,5261,8863,7616,5309,7930,5285,8205,5358,8762,5320,7834,5573,8190,9501,8811,5407,7930,5655,5564,7542,7817,7774,8115,5274,8126,8015,8154,7972,8010,8772,8751,8411,8131,7605,8721,7696,5407,5358,5391,8964,7952,8582,5491,7790,7893,7849,5476,9353,7972,7977,5296,5209,8504,5329,8205,5209,5671,5309,5688,5655,8277,5617,5564,8762,5325,5320,8987,7487,8005,7487,8845,8456,8772,8277,5407,8863,5564,5622,8159,8115,7834,8365,8805,8833,5261,7616,8811,7710,8190,5617,7823,7504,5237,7491,7849,7558,5578,5367,5629,7967,7967,5301,8484,5564,7999,8811,7706,5261,7723,8110,5274,7957,5291,9509,9516,7839,8033,5671,8772,8170,8783,7628,8033,5329,7605,8354,5726,5381,7710,5573,7795,5529,7839,7681,7888,8577,8811,5612,5407,8170,7999,5642,7482,8170,8302,8467,5629,8287,8402,7977,5726,5381,5688,8811,5377,5726,7542,5464,8255,8255,5642,5372,5377,7972,8365,8365,8443,8994,8222,7692,7990,5471,5291,5564,7738,5407,5237,7972,7730,8898,8994,7706,8354,8322,5480,7658,5476,8184,5325,8851,7904,5459,7692,5480,7977,7839,8174,7500,7605,5524,5459,7972,5564,7533,8170,5261,8073,8447,8170,5301,8682,5476,5325,5491,5274,5285,5296,7605,5622,7473,5635,7952,7723,8005,8140,7710,5329,5413,7972,7977,7681,7774,5407,8005,8255,5358,5274,7888,5325,8170,5325,5320,5647,5573,9345,8077,7675,8073,7710,8658,8711,5320,7843,5218,8316,5442,8255,5612,7738,5407,7706,8494,7994,5314,5381,5325,8077,8170,8170,8099,5329,8873,8359,8170,7710,5509,7768,5377,8073,8443,7867,5325,8140,5237,7774,8548,5352,7972,5241,7828,7972,7628,7888,7696,8104,5218,7548,8539,8827,8033,8663,5407,8005,5629,8170,5564,8443,5459,7858,8110,8048,5377,5496,7478,5407,8479,8451,5237,7738,8322,8427,7828,8427,5325,8077,7800,7511,8427,5358,5248,5329,8170,8140,5629,5491,5612,5237,8005,5325,7972,8322,8539,7800,8548,5401,5413,5372,5501,5432,8422,7946,8919,8554,7511,8010,5573,5573,5471,8073,8140,8589,5291,8307,7516,5329,8255,8255,7734,7738,5320,8498,8772,5372,7738,7482,5358,7605,8170,5573,8255,5471,5442,5325,7478,8354,8184,7516,8577,7972,5301,7972,7877,5573,8676,7994,7610,7516,5274,9361,8857,5347,5661,8261,5661,8077,5407,5726,5381,8498,8857,8022,8005,5291,7516,7914,7817,7482,8272,5726,5377,7888,7888,8888,8154,5325,7542,5651,8381,5476,5629,5407,7482,7834,8745,7834,5726,5651,8149,7952,8104,5325,5377,8312,8919,7628,7935,8387,7952,7667,7994,8354,5666,8539,8190,8010,8504,8498,5329,5320,8068,7742,5329,7864,8136,7706,7790,8821,8504,8010,5622,8062,9525,5726,5464,8539,8093,5491,5301,5655,8010,5381,8608,5352,8359,7786,7628,7610,7605,8164,7864,7904,7576,7972,5642,7990,5476,8745,5391,5255,7696,7723,8170,5285,5347,5329,8509,7768,7576,5437,7972,5617,7779,7628,8670,7632,7811,8267,5578,5509,5564,8745,7576,8447,8093,5578,8427,7779,5209,5296,7599,8287,8039,7839,7811,5352,7542,8234,8184,7742,7500,7930,8099,7710,7779,5476,8110,5325,7500,5367,8732,8694,8658,5717,5337,8062,5320,5564,7706,5564,7710,8196,8427,7628,7752,8795,7786,5655,5367,8467,5329,8484,8783,5622,7675,7643,5717,7935,5285,8427,7734,8149,5501,8010,7516,5717,7719,8795,7811,5476,5325,8217,7696,7628,5274,8062,5666,8456,5301,5301,5329,8201,7935,8387,7952,8190,8082,8504,5622,7864,8099,8821,8062,7692,7990,5329,7576,7972,5476,7982,5655,5301,5642,8745,5391,5437,7972,5285,5642,7768,5329,5285,5578,5564,7779,8184,7930,5296,8234,7599,5291,8287,5209,8694,5564,5325,8267,5320,8456,8467,5367,8427,5329,7935,7516,7528,7786,5655,7839,7920,5442,8888,5377,7986,8987,7482,8282,8411,8411,8411,5476,8411,8762,7839,8411,8411,8033,8613,8789,5320,5655,8676,8287,7482,8745,8411,8411,5699,7990,8239,8245,7877,5296,7542,7628,5699,7706,8329,5476,8039,8349,7706,7675,7599,8721,9032,7616,5712,8267,5309,8560,8387,8010,7478,8245,7864,7811,5578,7877,5309,8467,7994,5401,7710,7935,8447,7681,7972,5476,7592,7977,8164,5491,7696,8539,8312,5688,5459,8115,8272,5320,8170,7888,5485,5296,8316,5337,5651,5413,5325,8716,5476,5381,8863,8577,8329,8422,8022,7795,8272,8334,5730,8447,7632,8427,8190,8329,8201,5337,7675,5688,7853,7605,5688,8539,8539,5647,5485,5377,8307,8479,5578,8316,8687,7478,5391,7558,7588,5372,8658,8527,5401,5612,5237,5301,7924,8345,8387,7990,8745,8387,7811,5491,5329,8745,7576,5309,8267,8287,8005,7994,5224,5476,8245,9531,7632,5358,7495,7628,8903,8805,5471,5342,8888,8732,7710,8498,7628,7757,5655,5337,8267,8732,8411,8039,7864,5367,5464,5688,8479,8245,7994,5213,5476,8131,5642,7478,7511,8329,7738,8539,5419,8467,7588,7738,7542,5629,5642,7710,8015,5708,5391,7599,7710,5485,5372,5578,5471,8539,7768,7710,8451,7516,9021,7648,7542,8190,7628,5476,7478,8239,8170,5391,5358,5642,8349,5661,5342,7811,8427,8909,7696,7622,8411,8411,7986,5471,8467,8154,7632,5301,5476,5712,8919,7706,5218,5248,7653,7628,8211,7972,7723,7533,8582,5564,8756,8888,7565,5426,8136,7742,7914,7898,8467,8879,7994,5661,5305,7478,5320,5337,7653,7658,5688,5358,8456,5224,8670,7930,8048,8539,7967,7491,7516,7999,7952,7605,5391,7487,7628,7592,5717,5237,7972,5476,8329,5730,7768,5285,5237,5651,8543,7843,7977,7908,5241,7786,8267,7834,7558,8307,7710,5329,5564,7478,5564,5524,8052,8381,7977,8845,7667,7616,8073,5237,7930,5301,8267,8201,7675,7706,7920,8745,8039,7533,8772,7967,5255,8277,8447,8033,5337,5529,7521,7883,8170,5218,8255,5476,8762,7834,5712,8893,7930,7864,7628,5676,7628,8267,5730,8005,7521,5407,5448,8577,8391,5237,8613,5573,5209,7967,5612,7478,7482,8267,8052,5218,5358,5305,8783,5564,8239,7482,5622,7982,8255,7834,8015,8010,5309,7500,8919,5358,7528,5358,8554,7994,5241,7790,5261,7516,8312,7883,8249,7491,8340,7908,7972,8249,7967,8582,5401,8631,5280,5337,7706,7834,7877,5476,8093,7811,5237,8527,8365,5301,5337,5693,5688,5314,8456,7576,5476,8560,7643,8484,7834,8397,7790,7883,7500,7853,5578,5501,7653,7898,8267,7658,5606,5280,5480,7571,7653,7786,7588,7734,7616,5437,8391,7632,7653,5442,8845,8316,7972,5274,5419,7558,7482,5647,7675,5712,7692,7940,5666,7734,5534,5712,7834,5237,8929,7994,5329,5347,7834,8272,8456,8427,8104,7675,8397,8287,8140,8653,5377,5274,8762,7982,7500,5426,8772,8433,5377,5301,8312,7487,5448,5651,7768,5442,8451,8267,5218,8608,7687,8370,5629,5419,8647,8489,7628,5301,7719,8149,5476,5419,7734,5285,5237,8772,8312,8789,5476,5301,8170,5480,5476,8929,8613,8329,8811,7834,8093,5651,5314,8929,7528,5285,5237,5209,7834,8411,7774,5391,5337,5391,5391,5301,7548,7491,7779,5301,5309,7706,5688,5476,7757,8479,7999,5693,8312,5688,7849,5564,5655,8022,7904,7920,5564,8716,7757,5261,5329,5568,8732,5209,8010,7628,5396,7653,7817,7706,7628,8184,8539,8647,5237,5476,7637,5391,5647,5237,8293,7542,5606,5407,7920,7482,7482,7904,7565,8033,5426,7853,8354,7834,5564,5325,8136,7528,9542,5291,5471,8131,7482,7542,8282,5325,9550,8783,8816,5501,8267,8110,8196,7920,5407,8062,7628,7667,8805,5726,8762,8527,5671,8915,8154,7658,8756,7994,7752,8613,5661,7888,5209,8447,8461,5391,8438,7786,7864,8093,7790,5476,7786,7904,7920,7628,5426,5712,8201,5337,7828,7977,5391,7864,5213,8964,8170,8427,7706,5617,8190,7999,8222,7849,5358,8119,5573,5391,8073,7605,7710,8925,5407,8316,7542,7521,5476,7511,7542,5391,5381,8354,5464,7924,8282,8073,5381,8131,5320,5241,7715,5726,7548,9556,8484,7715,7883,7482,5476,7487,8582,9012,7823,5407,5337,5426,7805,5285,7710,7696,5717,5224,5391,8711,7977,7482,7616,7576,5248,8987,8316,5305,5666,5325,5671,7500,8504,9021,7972,7723,5629,8467,5274,8467,7768,7920,8964,7605,5730,8391,7473,5642,5274,5274,5519,5514,8391,8149,5325,7920,8613,5651,5476,7482,8687,8345,9562,5337,7719,8898,7542,8375,7588,5476,5655,7653,7872,5391,8946,5688,7675,7588,5519,8447,5377,8451,8613,7994,7533,8307,5329,5712,7843,5509,7528,8267,7930,7478,7588,7628,7877,8805,8073,7653,7977,8062,8498,8527,7877,8272,7972,5342,8190,7478,7478,8234,7482,5524,5329,5426,7542,7914,7653,7616,8104,5573,5347,8170,5274,8964,8211,7734,5448,7982,7872,7558,5524,5301,8594,7914,7605,8539,5651,5708,8397,8170,8227,8467,5407,8461,5218,8272,8227,8099,7473,8093,5325,5329,5285,8322,5237,5676,8438,5329,5352,8345,7920,5274,8387,8863,5671,9051,8571,7738,8653,8762,8827,7805,5209,5241,8543,7491,8827,8658,8772,5693,7706,7542,5314,7706,7628,5309,8721,7790,8227,5606,5237,5401,8272,7786,8451,5352,5320,5329,8249,5329,5396,8473,8144,8277,8800,8519,8239,8716,5377,8365,5419,8827,7616,8062,8329,7779,8721,8514,8509,7616,8539,5401,8039,5274,8762,5647,7994,8827,5564,8110,5296,7864,8110,8033,5377,7687,8062,7924,8676,5367,5237,8946,5237,5464,7994,5464,7999,5496,7867,8239,8062,7548,5476,8711,5509,8255,5730,7853,8329,7811,7491,8670,5647,7605,5407,8022,8005,5261,8190,7888,5381,5655,7734,7548,7994,5519,7719,5712,5213,7558,5396,5285,8149,8287,7738,7898,7946,5401,5612,5237,7511,8267,7491,8417,5564,8164,5629,5612,8322,5717,8851,7805,8073,5347,7730,5358,8940,8438,5209,7478,7616,5464,7681,7511,8509,7962,8302,5448,7800,8052,5655,5655,7632,5476,8467,5629,8077,5237,7977,7877,7538,7883,7883,5476,5342,7920,8170,7738,8196,5329,5314,7742,8443,8641,5309,5501,8190,5401,5342,5708,5573,5305,7632,5358,7994,7511,8539,8255,5401,5476,5329,5708,8946,8322,8402,7706,8249,5629,7800,5218,7877,8052,5485,8473,5320,7957,5519,8827,7610,8365,8154,5485,5347,5367,7663,8756,5314,7994,8888,7786,7542,5426,5396,5325,7734,5296,8322,8312,7516,5612,5519,8589,7558,8005,7924,5329,5386,5381,7616,5342,8010,5296,7768,7924,5629,7521,5291,5285,9021,8239,5381,8149,8217,8772,8365,5325,5274,7548,8919,8387,7616,5218,5301,5485,7675,7734,8964,7994,5661,8359,8052,7605,7610,7893,5342,5329,5676,5296,7628,5401,7616,8255,7542,5314,5666,7616,8964,7811,7491,7888,8039,8903,8170,5501,8245,7930,5476,7663,8548,5314,8527,7605,7946,8863,5325,8077,7738,8154,8745,8888,5699,7834,8381,7500,5407,5407,8375,7834,7972,5651,5301,8772,8903,8857,8582,8987,5352,7986,7605,8851,5347,5329,8473,8222,8329,5274,7834,7610,7864,5329,7528,7487,5347,8073,5407,5642,7877,7521,5407,5237,8721,8777,8077,5666,7687,5661,7710,7632,8857,8375,5372,8077,5291,5629,5320,8816,5309,5688,5476,5655,5693,7999,8312,7757,5647,7542,8732,5261,7715,8010,7757,8514,8293,8068,7637,7528,5671,8201,7658,8170,5471,8783,7548,8136,7930,8461,8282,7542,5642,8196,7667,7478,8131,8756,7565,8154,7853,8816,7904,7628,7542,5241,7542,7786,8073,7864,8312,8201,7904,8354,8857,7715,7883,7977,7828,5337,8964,8073,8093,8851,7542,7511,7768,5391,8190,5617,5358,8447,7920,7888,7478,8898,8149,7696,7994,8316,5337,7558,7977,7491,5386,5367,8903,8316,5329,7757,7482,8509,7687,7605,5274,8805,9003,8010,5396,7768,8302,7977,5642,5717,8391,5241,7719,5285,5476,7877,5248,5629,8345,7482,8613,7605,8149,7734,5261,8277,8594,5401,8946,7675,5612,5301,5241,5573,8039,5524,8964,5519,8451,5329,8527,7528,7511,8509,5407,7872,5377,7843,7542,8170,9051,8322,7888,5606,8451,7790,8543,8653,8407,7473,8653,8402,5237,7786,7632,7805,8863,5309,8827,5274,5676,8154,8057,8772,5693,5407,8170,8249,5209,7867,5291,8509,8322,8022,7864,5496,8255,7548,8676,5448,5464,7811,7663,8762,5712,7957,8005,7893,5712,5305,5655,8527,5717,8077,5426,5676,8077,8136,7511,8052,7898,5476,7977,8888,5237,8473,5629,7800,7632,7592,5218,5485,8539,8443,8190,7491,8772,5291,8239,7734,5329,8217,5347,7734,8498,8322,8919,8154,5666,7605,5314,5401,5476,5496,8473,8267,5367,7521,8227,8411,8131,5708,5459,5726,8239,7555,8227,8514,5661,8969,5261,5237,8267,7834,5358,7653,5358,7504,8509,8756,5413,8062,8489,8589,5274,5274,5407,8438,9568,0,8322,7687,5386,5358,5352,5367,5329,8411,5358,8489,7849,8721,5717,5717,8048,5291,8062,7952,8438,5476,8653,7843,5329,8805,7653,8170,8354,8354,7800,5726,7516,8227,8635,5407,7834,8438,7834,7864,7643,7628,7752,7521,7491,7542,5726,7834,7752,8282,8345,7994,7482,8144,5699,5347,8762,8239,7628,5606,8077,7738,8509,7849,5699,8519,5347,8307,5629,8005,5629,8721,8647,8795,5237,5296,7877,7920,8903,7811,7738,8179,5342,8514,8969,5261,7834,5296,5358,7752,8519,8489,7994,5274,8062,5407,8438,8756,8514,7687,5386,7952,7752,7738,8438,5329,5642,8048,5717,7849,8653,8484,8354,7843,8893,7628,7834,8227,5237,7752,7643,7834,7864,7877,5347,5726,7834,8144,5699,5717,5606,7738,5699,8795,5347,5564,5564,5333,8217,7528,7542,7478,5476,5480,7605,7924,8093,7864,7628,8282,9577,8946,8267,8821,7972,7920,5485,7565,7924,7482,5655,5381,7491,7904,5726,8539,8443,7571,7828,7696,9051,5480,5717,8312,5224,5564,5325,5329,7610,8033,5426,7888,7957,5305,5419,5320,7888,5274,7696,5459,7628,5671,5261,5337,8255,5426,8387,7558,5358,5367,5261,8119,7972,5480,8307,8467,5655,5573,5274,7790,8077,8267,5274,8456,8456,7967,5476,5407,8099,7482,8170,7805,8033,7920,8093,8272,5274,5329,8594,5274,8211,7605,7924,8705,7786,5248,5301,5301,7628,5377,7628,7610,7994,7994,8077,8217,5342,7977,5464,5381,5342,7482,5671,7834,5296,5717,8267,7632,7994,7663,5237,7994,5325,8015,8316,5708,5419,7482,5301,7648,7516,8422,8647,8222,5342,5342,7628,5274,5442,8447,8010,5325,8010,5237,7482,7864,5296,7610,5381,5301,5301,5301,8863,8756,7972,9032,7667,7823,5476,5699,7834,7542,8387,7504,8387,5325,7786,7811,7972,8131,7667,7839,5218,8170,5573,7982,8925,7632,7628,5442,7757,5342,7653,8154,8617,8721,5573,8925,5218,5325,5237,5442,7511,5301,7823,5325,8925,5261,7738,7628,7628,5529,8136,8762,5367,5391,7752,5501,8334,8267,5301,5358,8608,8093,5666,8345,8222,8249,7828,7843,5426,7478,7999,7500,5325,7715,5274,7675,7687,8805,5655,8456,5712,8093,7706,7482,8154,8302,7893,5534,8427,7482,8447,8548,5651,7516,5476,7582,7616,8387,8249,8617,8093,5367,8062,7482,7632,8349,8267,7511,8249,8888,7478,7994,8154,7482,5237,5476,7663,7883,8919,5274,5386,5329,7977,5730,5325,5337,5337,7904,7920,7994,7962,8073,5325,8349,8249,5448,7538,5301,8154,7663,5391,5491,7610,8093,7663,8519,5301,7800,5485,5419,7715,8015,7877,8745,5485,8052,8402,5485,5291,8745,5485,5485,7828,5485,7628,7893,5642,8898,5651,7746,9591,8174,7491,5606,5676,7491,5717,5693,5496,5407,8504,8539,8329,7478,8504,8196,5309,5529,8577,7904,7482,7730,8136,5305,8443,8245,8282,5476,5642,8272,5480,7977,8062,8174,7924,5209,5329,7977,7628,5325,5661,5688,8345,8727,7817,7904,8245,8255,8539,5509,8811,8484,7681,8255,8190,8571,7538,7738,8015,5224,7752,8170,7986,5661,8110,7898,5476,5688,8267,7849,5509,7715,8170,5407,8411,8539,5407,5274,8322,8131,7738,7930,8345,7533,5717,5564,8144,5237,7957,7839,8131,7977,5325,8312,5419,7990,8245,8154,5274,7542,8762,8631,5407,5261,7994,8504,5564,5712,5676,5717,7610,5309,7653,8227,5301,8110,8329,5617,7994,7834,5519,8272,8131,8267,7588,7605,7994,8800,8762,8222,8762,7994,8504,5367,7653,7994,8267,8272,8484,8484,5274,5661,5274,5325,7482,7864,8267,8670,7478,5352,7946,7616,8267,7930,7482,7482,7521,7982,7491,5693,8745,7533,7521,8598,7653,7500,7511,7500,5237,8022,8005,7706,8560,7628,5509,8282,5471,7828,8751,7533,8312,8093,7628,5485,5666,7849,8255,7864,7994,7994,5381,5314,8456,5476,7982,8370,8217,7858,5255,7706,5564,7977,7853,8827,8005,7977,7757,5606,7516,7616,7667,8994,7653,5485,8422,8048,8751,8277,8422,5381,7643,7628,5693,5712,7834,7653,5407,7990,5491,7592,8196,5476,8159,5377,7864,7864,7834,7643,8795,5218,5622,7834,5274,8676,5476,8467,8940,7982,7828,5419,7692,5666,8239,8647,5391,7800,7706,7648,8052,7478,7786,5309,7542,5296,8427,7511,5629,5301,5274,8751,5329,5347,8077,7768,7491,5693,8745,7653,7521,8598,7500,7500,5509,7786,5471,7706,8282,8022,8560,7628,5329,8093,8312,5485,5381,7977,8015,7653,7616,7616,8370,5476,8751,8919,8422,7653,8329,7616,7990,5377,8239,7864,5622,7834,7864,8077,7800,7828,8940,8052,5325,8427,8613,8751,7768,8527,8473,5372,8903,5367,8427,8473,7730,7811,7658,8577,7795,8190,7628,8170,5329,7487,8087,7924,5642,5448,8509,5514,8010,7511,5237,8239,8647,8647,5301,8473,8110,5301,8239,7723,7719,8527,5342,8527,7610,5329,8473,7730,8170,7616,5448,7521,5237,8647,8473,8473,7893,8010,8073,7893,5391,7982,7972,5325,5693,8261,5480,8631,7962,7521,8068,8504,7628,7565,7710,7482,8010,9596,9609,7482,7982,5476,5476,5358,7839,9621,7511,7839,7710,7487,8222,5391,8057,8370,5255,5606,5285,5655,5224,7839,8316,7839,7521,7924,8539,7696,7834,5622,5352,8179,8073,8028,7864,8201,7667,8307,7588,7628,5491,5642,7643,5606,5391,5407,8863,5224,5274,7877,7877,8772,7893,7588,7994,8721,7864,8699,7643,7982,5464,8964,5296,5407,5730,7849,5426,7687,7994,5564,7823,7828,5255,8073,5396,5237,8005,7511,5448,5358,7491,8267,5305,7511,7800,7715,7628,5519,5309,8845,8005,5622,5381,7710,7994,5476,5325,7648,7558,5218,8272,5296,7521,8427,5501,8543,8249,7834,7715,7877,7521,8433,8857,5358,5391,7972,5325,7706,5426,8068,5237,8504,7628,7710,8010,7482,5407,5391,8222,7487,5459,5476,7696,7648,7521,5325,5509,5285,8119,5224,8073,5666,7864,7628,8028,9633,8641,7643,5274,8721,5496,8005,5459,5448,7877,8077,5305,5320,7511,5519,5309,7800,8589,5622,7700,8087,8005,8039,7521,8484,7576,8447,5358,8073,9641,5301,7893,8033,8345,7904,5564,5274,7752,7528,8402,5358,5426,7538,7628,8527,7653,8539,5480,5480,7710,8329,7605,5661,7706,7883,8915,7730,7482,7752,7972,5325,7893,5726,8073,8222,7908,5480,5476,5726,5241,5726,7592,7768,7999,8467,5352,7576,8170,7605,8293,8170,7920,8205,7977,8093,5655,7658,5564,7675,5391,5358,5480,7920,7487,7940,7734,7858,8272,7972,8164,7478,7582,8149,7768,7914,5274,7696,8370,8073,8721,7482,7632,7538,7516,5661,5325,8170,8022,8196,8211,7779,7653,5647,8190,8422,7667,7628,7558,7528,7663,5329,8863,7576,5617,5325,8653,8272,8277,7986,7990,7706,7706,7628,7893,5514,7834,5381,5642,7478,5476,5329,7858,7542,7893,5464,5407,8154,7752,5377,8519,5367,8548,8005,8110,8087,7795,5480,8005,5448,7542,7558,7972,7511,8140,8909,5237,8340,5442,7834,8467,7967,5514,5261,5301,7972,5329,5305,5237,7632,8015,5305,5708,8179,8647,8827,5726,5358,5274,7628,7500,5396,7849,7588,7675,7516,7877,5471,8789,5480,8833,8772,5285,8964,7834,5301,8427,5485,5476,7828,7516,8888,7967,7834,7710,5329,5274,7521,5325,8617,8170,5237,7811,5301,8473,7538,7628,5480,8329,7710,5726,7828,8073,5329,8205,5391,7487,5564,8170,7967,7605,5480,8293,7977,5476,7658,7675,7538,7790,5285,5396,7632,8370,7858,7839,5301,7558,7834,5325,5285,7667,7706,7706,7990,8863,7653,5358,8179,7858,7972,5237,5514,7478,5708,7511,7542,5261,8647,7632,8827,7877,7967,5325,5285,7521,7786,5464,5301,7994,7849,8811,7920,8608,7853,5661,7667,5358,7710,5699,8539,7692,8370,8255,8131,7473,5717,8427,8845,7858,5358,7990,9012,7930,5342,7957,8073,7675,7616,7849,7839,8739,8888,5305,8608,7957,5386,8762,8205,5342,5377,8888,8739,8479,5524,5564,5688,8119,8255,5329,5372,8473,5301,8653,5377,7565,7533,8721,7790,7883,5320,7576,8048,7681,5352,5209,8048,8297,8811,8174,7920,7790,5564,7706,8164,5726,7521,5519,7795,5325,5352,9361,8670,8438,8391,5301,5391,8548,5377,5352,8022,7622,7768,5712,8598,5407,7888,7982,7986,8484,7706,5401,5655,8888,8245,5305,8589,7800,7800,9026,7500,5712,5305,7605,8789,7811,7908,8539,8888,8577,8261,8307,5209,5386,8062,5237,8307,7653,7610,5342,8484,8227,5459,7628,5301,5676,8190,7542,8582,8548,8711,5655,8987,7628,7952,5305,5325,7706,7999,8005,7643,7904,8312,5712,7500,7834,7675,7779,7628,8422,7706,7599,7779,7994,8277,7710,8647,5606,5305,5325,7500,5391,7834,8427,7628,8479,5241,5301,8277,7817,5480,7757,5342,8762,7864,5358,8302,8560,8068,5329,5476,5381,8010,7576,7628,5329,7752,7982,5426,5237,7565,5501,8136,5337,5712,8762,7849,5367,5726,7653,7920,7872,5480,7500,5688,5255,7622,5358,5325,7994,8170,5568,8903,8033,7990,5448,8184,7723,7658,8222,5564,7687,8115,7786,5476,7757,7742,5305,5381,5320,7957,7710,7696,8345,7994,7681,7605,7482,8577,7632,8316,8255,7706,7605,7994,5285,8598,7952,5274,5717,8795,7491,7500,8316,8711,7888,7667,5274,7800,7675,5296,8427,5407,7628,7999,5578,8234,8845,5514,7478,5688,5642,5564,5509,5337,5301,8302,5301,8582,5291,8190,7746,8154,5329,5218,8447,5209,7706,8745,7521,5629,8104,5325,7757,7867,7516,7576,8851,8277,7893,8447,5476,8653,5329,8484,8227,5485,8170,8093,7967,5301,8676,7663,5285,8184,8154,8126,5391,5693,7914,5325,5391,5730,7478,5480,7692,8322,5325,8010,8582,8903,7538,8302,7883,7994,7628,8658,8110,5459,5464,5401,7834,5688,5688,5407,5501,7864,8387,5564,8159,5407,5367,5622,7982,5471,7533,7834,7752,5407,8052,5305,7687,5726,7511,7632,5391,5391,7482,8533,5655,7738,5642,7715,8164,7542,7706,7752,8893,5352,5401,5708,5333,5296,8073,5325,7681,8925,5407,5514,7800,5237,5688,8473,5325,5325,7605,7768,7605,5622,8154,8015,8756,5529,5432,5291,5726,7521,8073,5285,7834,7588,5352,9021,5261,5261,8010,5291,7516,8329,7622,5237,0,8946,7675,8721,5730,7908,8062,5514,8427,5367,8211,8119,5301,8227,7935,7864,7511,7616,5325,7500,7834,8302,5301,7628,5301,7763,5655,7521,7864,5329,8721,5301,8946,7628,7834,8863,5337,8554,5329,7565,8745,7516,8073,5255,8184,5329,7757,5407,5564,5329,8222,5730,5699,7658,7952,5381,8217,7994,7696,8099,5509,7828,8461,5325,5285,5274,8821,7521,8329,8845,5301,5401,5407,5301,5391,7681,8234,8302,5509,5655,5325,5367,8745,8093,5386,7967,7893,8653,8227,7786,8447,8184,7757,5485,8190,5391,5209,5501,5459,8184,7588,7864,7839,7511,8159,7864,5688,5564,5464,7994,8940,8411,5514,5708,7706,7738,7482,5642,7994,8267,5333,8473,5529,5432,7800,8438,5218,5622,7877,5291,5261,7516,9021,5329,8795,5301,8033,7482,5453,5476,5564,5305,8484,8201,7834,5564,7757,5325,5325,5261,8811,5391,8527,8391,8222,8467,7972,5693,8196,5564,5501,7752,5407,8539,7628,8998,5285,5291,5291,7920,7811,5655,8222,7834,8387,8154,8533,5666,7990,7742,5325,7977,8903,8093,5419,5564,5480,7628,5301,8411,7972,8509,8467,7628,5726,7675,7972,7883,8170,5337,7786,8936,7628,7924,5666,8387,8964,7628,5301,7616,8863,7478,7696,7795,7663,7610,7834,8073,8617,5325,8577,7605,7834,5391,8062,5301,8613,8711,5717,8504,5285,5519,7576,7972,7632,5274,7972,7706,7972,7977,5325,7628,7811,5476,8201,7478,5676,7628,8073,8329,5651,5358,5237,7667,8170,5647,5485,5377,7864,7864,8582,7982,7478,5651,5329,7628,7521,7478,5255,5320,5564,8345,7999,5352,5325,5524,7930,7757,5329,5699,5407,8170,5647,8539,8467,7706,5301,7491,8863,8721,8863,8093,8119,7706,5485,5476,7972,5485,5209,8277,7582,7706,8104,8845,8170,5726,8154,5655,5325,5320,7478,7710,7924,8110,5476,5688,5377,7982,5377,7864,5381,7790,5564,8104,5647,7834,7849,8467,7858,8267,7706,7982,7616,5471,7715,8170,8772,8777,7565,5301,7616,8447,8190,5534,5391,7849,5573,5629,5367,8893,7511,5325,7786,5407,8196,7653,5209,5519,8447,5642,7834,5325,5301,5606,5568,5377,5224,8447,5647,7538,5285,5524,5606,8033,5301,5622,5578,8845,5642,5476,7742,7710,7972,8239,8164,7957,5358,8617,5314,7920,7478,5291,7482,7768,7487,5285,7924,5396,5358,7972,7588,5464,8010,5285,8239,7834,8316,8484,5274,5274,5647,8349,9021,7628,7616,5329,8641,7605,5529,5476,7920,5320,7632,7706,5476,5325,5578,7628,8427,8811,7924,5377,8381,5647,5651,8839,8249,8539,8354,7628,5666,8222,5329,7898,7610,5325,8354,7616,8721,8873,5301,8375,5453,8484,5325,8467,8391,9471,7742,8387,5237,8222,5655,7786,7582,5329,8387,7548,7924,7500,7972,5726,8467,5401,5480,7487,7972,7696,7795,5519,8479,7977,5717,8375,8062,7786,8073,5301,5676,7982,7864,7628,5274,5377,7478,5699,7622,8345,5325,7930,5352,5688,8104,8845,5476,5464,7982,5325,7864,7786,7734,5209,5712,7715,8159,5642,5342,8789,7719,5296,5407,7849,7628,7616,5578,8239,8239,5285,8484,7628,5329,8473,8427,7616,8154,5301,8721,5529,5329,8484,7723,5407,5274,8451,7628,5407,7834,8451,5329,5337,8140,5325,8473,8533,5642,7605,7667,7605,5407,5651,5651,7478,7511,8033,7478,7952,8447,7706,5329,8484,7667,5285,5301,7592,7834,7977,5651,5301,5617,7883,7516,5485,5325,7500,7588,8140,8349,8349,5717,7482,5564,7746,8387,7628,8174,5726,5642,8711,7681,8783,7628,5726,7746,8170,8527,5564,5726,7746,7538,5305,5305,5296,7610,7967,8402,5501,5464,5464,8255,7723,8307,7853,7946,7867,5301,7946,5320,5301,8184,7710,9372,9372,5476,7542,7883,5237,7622,5305,5305,7542,5491,5573,5337,7828,5476,5367,5476,5726,7667,7834,5485,7628,5301,8903,8762,8179,7482,5726,7834,8772,8345,5655,5622,8179,5476,5712,7883,7834,8762,5485,7972,5501,5564,5564,8438,8438,8467,8140,5726,9646,8762,5325,7675,8211,5708,5432,5480,5358,8467,8387,5480,5693,5651,8716,7675,5209,7786,5358,7582,5501,8272,8467,7920,8227,5524,7706,8136,5629,7786,5320,5320,5501,7893,7752,7904,5564,8604,8316,5726,8119,5391,5337,5391,8805,7478,8184,5485,7643,8170,7834,5647,5377,5237,5274,7834,5407,5358,7610,5688,7653,7632,7478,5730,5237,5717,7687,5274,5534,7687,8789,7700,7706,8533,5325,5209,5325,5325,7706,5325,7710,5325,8033,7849,8387,8800,5661,8174,5358,5391,5209,7710,8184,8170,7681,8093,8093,5573,7768,5661,8800,8077,7653,8316,8287,8653,7628,7940,5476,8447,8451,7967,5726,8805,5274,7742,7864,8494,5476,8287,7967,7828,8267,9654,8387,7710,8033,7734,8277,8170,8093,8174,7681,5209,8800,7628,8287,5726,8494,7610,7710,5241,8334,7834,7610,5329,7817,8795,7516,7940,7738,7610,7817,8795,7952,5688,7768,7952,8354,8721,7849,8604,5661,8073,8115,7867,5480,7632,7883,7675,8893,5407,8190,8411,7962,7521,5274,5255,5655,8119,7982,5391,7605,8354,8104,5329,7478,7582,7675,5325,5381,5464,5426,5712,8010,7805,5622,7858,8527,7605,8062,7867,5730,5688,7622,7542,0,8005,9657,7957,8267,7849,5629,8354,5442,5524,7893,8893,5241,8131,8077,7542,7675,8498,7817,8334,8762,5301,5358,5314,5573,8635,7710,7605,8322,7610,5347,5407,8201,5301,5296,5358,8539,8827,7834,5352,9367,5622,5213,8647,8307,7628,7763,5622,8115,5237,8039,5476,7982,7877,5568,8140,5255,5442,5325,7952,7588,7957,7687,5329,8184,5612,8498,9662,5612,5333,8676,9670,7675,8073,5651,7487,8903,8239,7883,7828,5564,5407,8438,8005,5491,8255,8919,5464,7962,8149,8365,7675,8762,5391,8365,9676,5213,9680,5213,8811,8170,8762,5329,7834,8073,8903,8159,7990,7610,7542,5401,8073,7576,8277,9686,5347,8005,7628,8354,8052,5655,7658,5377,5651,5442,5325,9690,9680,8909,5622,7576,8010,5448,7576,9695,5314,8909,5329,5329,5407,5480,7616,7628,8277,7610,9701,5401,5347,7542,8354,7548,5726,5401,8411,8676,8857,5301,7487,7632,7616,8354,5314,7500,5651,8539,8010,7914,5578,7914,8293,5661,5666,5274,5209,8447,8879,9710,7542,9715,5358,8282,7478,8302,8756,8110,5501,5358,8402,9721,5301,7710,9726,9736,7904,5301,8811,8170,8312,5305,8140,8184,7576,5301,7588,7681,5301,7706,7588,8062,8316,5209,8604,8354,7576,7516,9345,8126,9740,5501,5325,9745,9751,7779,5274,5564,7779,8179,8845,5301,8354,5329,8170,7706,8402,8126,8015,7516,8548,5485,8039,9759,8589,8589,5629,8411,8110,7746,5730,7500,8548,7982,5407,8365,7839,5301,5661,5564,8447,7930,7786,5519,5524,5629,5688,9751,8340,9345,5629,5651,8589,5501,5372,8756,5419,5305,8589,8589,7495,5237,7779,5419,7908,8073,7811,985,8548,5274,7495,8641,8077,5301,7571,8539,8077,7681,5347,5651,5285,8484,5237,7817,5241,8484,5337,5325,5496,7576,8329,8354,8062,7628,7849,5509,8504,7752,7752,5688,8136,5209,7853,8816,8998,5381,5320,8104,7920,7482,8302,8062,7972,5325,5501,8267,8504,7883,5309,7588,7565,5612,8402,8484,7687,9766,5309,5564,7972,7839,5209,5442,8062,7687,5666,5237,5564,5661,7914,7930,7605,5726,8093,7605,8670,8811,7924,5564,5480,5655,5491,5314,5391,7548,5274,8443,7592,8282,8170,5305,5320,8397,9772,5485,8504,7946,5661,5381,7605,5442,7795,8126,5291,8582,7982,7839,8845,8845,8272,5661,7692,5285,7768,5671,7632,8190,5224,5274,5564,9462,8316,8164,7681,7482,5666,5661,7768,7500,8099,5459,8316,7487,5419,7521,5296,5291,7972,5407,5676,7710,7746,5655,5329,7558,5688,5320,7898,8316,7675,5509,8196,8845,5519,5377,5519,5661,8062,5237,5564,8805,5342,8217,8653,5564,5485,7982,7972,7834,7592,8527,5325,9778,9783,8548,7516,7888,5301,5329,7779,5442,7768,8670,7706,7706,9032,7706,5509,7542,5274,8087,7994,7706,8272,5509,8461,7599,5413,8863,7774,8039,8402,8099,7511,7706,8427,7930,8287,8093,7746,7706,7867,8772,8548,8234,7967,5476,5209,5426,7779,5309,7893,7872,8438,7628,8548,8010,5329,7500,9789,8015,9796,7500,7521,5407,7990,5274,8821,7746,7706,8676,7548,8539,8438,7622,8467,5661,8222,7487,5688,8411,7811,8845,8827,7746,7994,5564,7994,7628,8131,7622,5661,5401,8548,8411,8267,7482,5377,7795,7972,8267,7558,7877,5491,5367,8467,8548,7500,9804,9810,8845,9367,7616,5309,5476,8863,8447,7898,7786,7533,7576,5407,8451,7768,8617,8267,5367,8952,5237,8164,8272,5564,8267,5655,5524,8613,7982,5437,8909,5274,7588,5655,8548,8272,9818,7774,5358,8527,8140,5301,5612,7482,5237,8548,5666,5622,7542,7800,7972,5329,5305,8647,5401,5651,8190,8548,5325,5261,5342,8010,8952,5413,5476,5514,8711,7576,8322,7643,9824,5237,9829,5519,8297,8190,5296,8255,8833,7571,7972,5261,8589,7516,7628,8010,8239,8287,9021,5407,7648,7877,7872,5519,5296,5396,8857,5237,8322,9835,8245,8119,5296,5237,7719,5407,5419,7811,5372,8827,5381,5285,8073,7877,5476,5386,5342,5476,8635,7500,5325,8888,7542,5347,7734,8329,8857,7706,5352,5301,5305,5666,5661,8857,8582,5337,7500,5666,5274,7877,5568,5329,7877,7616,7774,7521,5274,8397,8857,7632,5237,8479,5347,5666,5401,7487,8484,7628,8062,7576,5274,7849,8354,5325,5509,8582,5325,8504,7994,8267,8062,8104,7920,8302,8461,8136,8504,8548,8099,5501,7972,7752,7565,7605,7482,8816,5320,5274,8811,5661,5564,5401,8443,7914,5237,7972,7548,7576,8282,5564,5314,8670,5476,5485,5491,7898,8164,5381,7692,7648,5459,7946,5296,9462,8190,5419,7632,5291,5285,8316,8845,7675,5676,5519,5237,5342,8272,5325,5377,5325,7500,8093,9839,7500,7706,7893,8287,8015,8234,8119,7994,7746,8087,5413,5442,8272,7511,7768,8548,8427,5509,5329,8863,5209,7990,7521,5407,7746,5347,5377,7622,7706,8267,5296,7487,7888,8676,8411,5274,8131,8863,7811,8539,5329,8411,8438,8467,5309,5564,8613,5655,8527,8952,5524,7877,5301,5476,5407,8888,8647,7800,5666,7616,5612,8711,5514,5661,8239,5261,8833,5419,7542,5372,7811,5285,8857,8998,5661,8461,8998,5699,8998,9844,8073,5255,5730,9853,8461,7616,7616,8745,5367,8721,9021,8772,8227,7706,5534,7616,5666,8772,5329,8721,7599,7599,8461,7599,7653,9858,7632,5564,5651,8048,7786,7605,8170,7790,7786,8438,8461,7904,7795,5377,5676,5564,7516,7834,7999,8397,7795,5314,7628,7972,8345,8048,7823,5320,7487,7920,7538,7511,7904,8048,5301,8783,7994,5358,5325,8833,8316,7511,5325,5676,5651,7999,7632,7920,5325,5325,8375,7924,7763,8322,7738,5712,7924,5337,5337,5501,7904,5524,7924,7786,8322,8205,5524,8093,5476,7605,8539,5519,7542,5476,5274,8164,8658,8658,7542,7811,8397,5274,5476,9863,8322,5274,7628,8845,5381,5629,8467,5237,8010,5261,5501,5261,5401,5666,8845,8417,8322,7877,7628,7817,5476,5248,5534,5501,8062,7710,7710,8402,5564,5367,9054,5476,7986,5476,7920,8093,7511,7972,5726,5237,5661,7521,5237,5237,7706,8255,5296,5296,7675,7719,8062,5419,8539,8402,7893,8272,8302,7710,7605,5407,8190,5391,7858,8548,5509,5296,5730,7849,7849,7653,5708,8647,5476,5401,5726,9873,7516,9054,8255,5708,8010,5476,5296,8190,8479,5642,8149,9877,8898,7790,5578,7706,8131,8131,8131,5459,5459,7696,9045,8821,7696,8716,5622,5666,8805,5209,8845,8783,8052,8267,8994,7920,7710,7487,5573,7977,8170,7849,5573,8149,8099,5325,5629,7742,7957,8417,8131,8334,8115,7957,5325,8149,8154,5337,5305,8359,7696,5578,8805,7491,8245,8022,8196,8267,7710,7883,7883,8062,8845,7972,9881,8077,7883,5655,7935,8093,5241,5337,8631,8239,7610,7972,8381,7681,7696,8387,5459,7795,9886,5401,8052,8119,8577,8751,5485,8170,5358,8539,8039,7752,7500,5255,8827,9891,9900,8805,7628,8827,8005,9904,8005,8827,5401,5534,8005,5401,5401,8827,5708,8119,8022,8805,8005,8077,7972,7883,8381,7867,5325,8805,8805,7610,7628,7628,7999,7487,7588,5476,7588,7487,7999,7999,8110,5476,5333,5325,5688,8800,8340,5717,8539,7811,5464,5476,8504,7675,8613,7576,8467,8762,8461,8164,8811,5496,8494,8227,7786,7972,7653,5564,7893,5501,8456,8334,5407,7565,9032,5337,8136,8560,8800,8297,7817,7628,7752,5642,5309,7920,7542,5237,5237,7893,8800,5485,7658,7914,7548,8015,5661,7914,7994,7576,5459,7883,7893,7904,8073,5726,5606,7930,7795,7658,5209,7692,7972,8447,5255,5209,5564,8334,5655,5274,7571,8456,7977,8119,7972,8222,7715,5726,7982,7990,5476,7482,7500,7924,7610,7582,9012,5329,5485,8293,7834,8334,7473,5476,8687,5671,5717,7582,7972,7675,7504,7504,7696,7864,5426,7858,8766,5209,7952,773,9915,2533,3110,9919,3464,9924,2802,9929,9934,9939,9945,3447,2826,2847,3306,9950,9955,2550,2661,3190,9961,2685,2300,3133,3157,2871,3215,9966,3179,3005,9971,3235,2606,2953,2977,40,609,2526,9977,1783,9981,3457,9985,2795,9989,9993,9997,10002,1733,2819,324,3297,10006,10010,2547,2654,3120,10015,2678,2700,3126,3148,2864,3208,10019,347,2998,10023,3228,2599,2950,2968,24,603,10028,429,10033,852,10037,803,972,800,10041,9312,10045,10050,829,833,985,806,955,959,10055,839,819,816,10060,826,988,1044,1464,926,809,10064,855,836,1659,10068,915,845,842,848,776,10073,4081,3560,10077,3573,10082,10087,10091,10096,10101,10107,4021,10112,3609,4035,10116,10121,3809,4003,10127,10131,3849,3912,10136,10140,3707,10145,10149,3959,3661,10154,10160,3747,3787,10164,64,606,2523,10169,3103,10173,3454,10177,2792,10181,10185,10189,10194,2738,2816,2840,3293,10198,10202,2544,2651,3183,10207,2675,2696,3123,3144,2861,3205,10211,670,2995,10215,3225,2596,2947,2964,52,1084,10220,2537,9220,3114,10225,3471,10229,2809,10233,10237,10241,10246,10250,2759,2833,2854,1321,10255,10259,1426,2668,3194,10264,1325,327,3137,3166,2878,3222,10268,1318,1311,10272,3242,2613,1400,2986,32,410,10277,1620,10282,482,10286,3468,10290,2806,10294,10298,10302,10307,10312,10316,3451,2830,2851,3311,10321,10325,2554,2665,432,10330,2689,2305,464,3162,2875,3219,452,1772,3009,10334,10339,3239,10345,2610,2957,2982,106,10349,0,0,0,10360,10360,10360,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,10360,10360,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10366,10366,5777,5777,4344,4344,5078,5078,6688,6688,6694,6694,10373,10373,10379,10379,5898,5898,6704,6704,670,670,6704,6704,10387,10387,10394,10394,6704,6704,1736,1736,0,0,10402,10402,10402,10402,10402,10402,10407,10407,10417,10417,10427,10427,10434,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10446,10446,10450,10450,10455,10455,6540,6540,10460,10460,2851,2851,3613,3613,10465,10465,10470,10470,10475,10475,4198,4198,3917,3917,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10480,10480,10480,10480,10480,10480,10480,10480,168,179,184,10488,193,168,179,184,10488,193,168,179,184,10495,193,618,618,618,10505,249,249,249,249,10511,10520,10520,10527,10527,10527,10527,10541,10541,10546,10546,10549,10549,10558,10558,10558,10558,4971,4971,770,770,1451,1451,908,908,5150,5150,5150,5150,1521,1521,28,28,44,44,44,44,44,44,10568,10568,46,46,52,52,52,52,776,776,54,54,54,54,54,54,56,56,56,56,58,58,10575,10575,66,66,10579,10579,10582,10582,10593,10593,10593,10593,10599,10599,5234,5234,4924,4924,10604,10604,10608,10611,10615,10619,10623,10575,4971,10627,10631,5070,5070,5070,5070,5070,5070,5070,46,46,5070,5070,5070,5070,5070,5070,10634,10645,10651,10658,10658,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,54,48,40,48,24,40,10667,64,32,52,10676,1311,1314,3242,4177,875,2878,3342,3222,10684,3648,4056,2514,10688,2854,1321,3194,10255,1400,3471,3373,2809,10229,2613,2668,1426,10693,1325,2537,24,40,64,32,776,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,836,911,915,922,926,812,809,848,806,955,816,842,803,968,800,845,1144,1454,1148,852,1464,1044,24,855,819,839,988,826,429,24,40,64,32,52,479,2885,833,1060,773,852,855,933,937,942,951,10697,10707,5004,10717,819,819,863,0,0,0,0,0,0,0,0,0,0,0,0,875,884,24,770,40,892,64,895,58,664,46,1057,32,773,905,52,776,908,836,911,915,918,922,926,812,809,929,848,933,937,942,946,951,806,955,816,959,842,803,968,800,972,845,855,819,839,985,988,992,826,429,976,10727,770,40,892,64,895,58,664,46,1057,32,773,905,52,776,908,1011,0,0,0,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,8,10,12,14,16,18,20,22,836,911,915,922,826,988,1044,848,806,10733,842,803,968,845,816,800,819,855,839,852,955,429,985,926,24,109,40,776,0,0,0,0,0,0,0,0,3715,10737,836,915,922,806,816,842,803,800,845,926,809,848,826,819,839,855,852,429,10041,10068,10060,10742,24,40,64,32,905,52,908,1786,10747,106,50,58,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,40,64,32,905,52,836,911,915,918,10750,922,812,10755,809,929,10760,10765,10769,806,955,816,959,10774,842,942,803,10778,968,800,972,10782,845,1073,855,819,839,985,992,826,429,770,40,892,1518,64,109,52,905,908,1440,855,819,839,852,0,0,0,0,0,0,0,0,0,4580,4580,4580,4580,4580,4580,4580,4580,4580,4580,4580,4580,4580,4580,0,0,0,6,8,10,12,14,16,18,20,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7558,7681,7972,8402,5712,5661,8402,5661,5305,8349,5325,10786,0,7908,7730,8205,7706,5386,5651,8789,8375,5509,8073,7752,8721,5688,5285,5485,8110,7946,8451,8577,5261,8805,5712,8104,7914,5661,8407,8397,8140,5519,7528,5480,8174,7558,8028,5564,8307,5480,5453,10798,7920,7786,7795,5301,5401,8015,8402,5381,7779,7576,7920,8473,8099,7858,8039,8005,7805,7883,5606,8028,5485,5381,7986,5476,8345,8504,7914,8287,5564,5564,8170,7893,8322,5509,7582,7920,8039,8548,5693,7478,5712,8821,5622,7972,8694,7795,7742,8539,8345,5426,5386,7628,8732,7823,8267,7548,8110,7864,8159,5647,5642,5407,7994,5285,7628,7977,8676,5358,8227,5564,5274,8447,7746,8631,8909,8422,7898,7542,5329,8631,5448,5629,7588,8863,5717,5476,8073,8239,5325,7920,5661,5524,5329,8888,7742,8227,5329,8039,8334,8663,8647,5419,5564,5629,5305,5237,5622,5329,5285,7616,5329,7972,10803,8104,5401,8316,5352,8447,7687,8239,8402,7952,7675,8909,8010,7972,5285,5261,7478,8402,8489,7521,5342,5501,7616,8772,9361,7883,7908,8789,8068,5401,5358,5218,5291,5573,5476,5676,7986,5401,5514,8068,7786,7706,8402,8888,8329,8613,8154,8205,5274,8473,5426,8739,7986,7834,5329,8154,8205,7632,5726,8205,8375,8447,8205,8033,7542,8494,8687,8205,7516,8687,5209,5285,8077,5442,5325,5524,5442,7746,8077,7972,7972,5717,5407,5459,7972,5519,7658,5655,8484,5274,7628,5496,5642,7628,5248,5237,5237,8484,7628,7628,5426,8170,8484,7528,5617,5617,10809,8484,5391,8772,5320,5391,5606,7883,7734,7734,8721,5396,8772,8732,8598,5367,7478,7576,8479,8461,5261,5476,0,8994,7658,8010,8282,8732,8560,7565,8282,5699,10817,7548,5407,8777,8093,7605,8115,7888,8073,5274,5717,8732,7977,5329,8062,8334,5237,0,5564,5372,8527,5301,8670,7628,7843,8805,0,7994,8073,8249,8184,8170,5464,8087,8548,8411,7790,5459,10825,7898,5496,5476,5514,8777,8340,7957,5491,10830,5642,8316,10836,8903,8772,5329,5476,5381,5407,8154,8249,7706,5329,5329,7877,8617,8682,7779,5407,5407,7719,7834,5564,5255,7834,7834,8340,8340,5476,5333,8805,5688,7663,8716,7681,5285,5381,5352,5509,5476,7637,7746,7763,8811,7542,7628,7628,8851,7723,5647,5367,7706,8211,5209,5296,5342,7972,7521,5358,5564,8068,7994,7478,7972,7706,5261,7752,7542,8504,8998,5568,5381,5329,7500,5325,8196,7706,8467,5501,7904,7872,7528,5325,5396,5396,7565,7478,5291,8131,7972,8267,8196,5708,5573,7790,8387,7893,8705,8560,8827,7482,5337,8411,5476,10844,7994,5301,7920,7864,5274,8227,8777,8249,7588,5476,8467,8297,8093,7658,7681,8539,7622,7723,5301,5218,7990,7715,8456,8222,8131,8438,5476,5476,8170,7920,8893,7692,7605,8447,8073,5224,5261,5688,5642,5274,8543,8062,7616,7687,7582,8170,5459,8077,8811,7982,5255,5712,5564,8119,7786,5358,7994,5564,5314,7790,8010,7867,7790,7790,7904,5209,5305,5209,8539,7710,5325,5485,7610,0,5453,0,5529,7616,7723,5717,8391,5301,8164,7482,8682,7542,7637,8033,7628,5519,5209,8577,7478,5325,8005,8604,5248,7839,8131,8687,8211,7710,5285,5285,5564,7628,7605,5209,5419,5296,7482,5496,8048,8164,8613,5248,8211,8316,7548,0,7605,8329,7811,7473,7768,5671,8245,5485,5426,7768,8340,5485,7696,8345,8087,7710,8504,5407,5377,5464,5255,5301,5309,8149,5320,9345,8245,7977,7648,5358,8115,8255,5296,7706,8903,0,8164,7643,7616,7632,7616,8329,5309,7962,7482,8349,8062,7576,5642,8762,7994,5573,8307,5564,8504,7482,7849,5358,8201,8267,8539,5647,5342,5342,7558,8033,7478,5432,5237,8845,5301,5564,7605,5305,5301,9345,5325,7888,5688,8302,5612,8033,8527,8170,5712,8249,5426,5485,7935,7528,7667,7834,8805,7757,8451,5509,7653,5337,8451,7977,7599,5337,8816,8670,5274,7843,5564,7864,5305,7752,5381,7823,7715,8795,8277,5401,5391,8494,10850,5401,8154,5337,7616,7616,10857,8104,8211,8211,7839,5509,5485,8539,5726,7582,5407,7967,7786,7986,5329,8170,7994,7667,8077,8653,7849,7734,5209,5524,8039,5564,8099,8227,5442,8022,7805,8427,8514,7774,7904,5301,5647,8073,7653,8222,8196,5676,5237,8196,8539,5381,7734,8903,5309,7999,7658,8845,5396,7786,8093,7893,5309,8345,7599,5413,7576,7834,8170,5407,7516,8772,5320,5606,8479,7982,7706,7849,8402,8272,8104,7930,8227,7811,7893,8277,7706,8249,5255,7994,7883,8302,7811,7834,5476,7681,7706,5509,8604,8716,10862,5480,8484,8964,7632,0,0,8249,0,10874,0,7616,7616,7616,7675,7730,5688,7990,7521,5509,7628,7628,5564,5401,8190,8467,7823,8115,8494,8104,7675,7628,7858,7790,8345,7864,7632,7616,8154,7795,5419,7746,7571,7571,8170,8222,8110,5237,5661,8345,5726,5407,5564,5329,5485,9054,8527,8062,8062,5730,7834,5647,7605,5209,7521,7914,5717,5381,5485,5651,5464,7914,7734,5671,7582,7576,7542,7994,8073,7994,8994,5325,7982,8205,7977,8676,5642,7667,8827,7768,7994,5274,8005,8805,7643,5237,5391,7706,5347,8548,5642,8467,5712,8267,5320,8539,5622,8721,9751,5529,7877,8509,8005,0,5419,7957,7994,5213,0,5448,7482,5391,8104,5391,8267,5655,7843,8293,8249,7849,5699,7940,8467,5301,8170,5237,8334,5573,7500,5647,7511,8494,5655,7542,8443,8312,8451,8467,5573,7628,7908,7533,7734,7734,8136,8851,5237,5501,8267,5241,5407,5325,8893,8777,8869,5651,8052,7962,5655,8391,5448,7500,7511,5237,7786,7843,8164,5534,5671,8451,5391,5629,7632,8613,5612,5274,7930,5642,7952,5358,8548,8234,8925,5519,8604,8370,5347,0,5325,5301,8179,7843,7628,0,7616,5305,7757,7681,7790,8316,7605,5352,8345,5401,8827,5237,5255,5688,5309,8104,5381,7994,8422,8340,7628,5476,5325,8239,5358,5329,7588,8201,8015,7632,5419,7800,7834,8093,5325,8647,8234,7653,8539,7588,5329,5617,8987,8539,7763,8329,7800,8073,8539,8451,5578,9367,8190,7994,7982,5708,5651,7957,8140,7548,7774,8312,5419,5651,8827,5453,5296,7667,7482,5407,7834,5320,7482,8005,8033,7982,8302,5568,7542,7538,8205,10880,8322,8307,5401,7516,5568,7663,5358,5301,7994,5325,8329,8119,8010,8249,7742,7877,7828,8329,5285,7872,7872,5666,7628,8255,5573,5396,5407,8349,8267,8222,5337,5726,5329,7648,5296,7864,8909,7893,5291,8397,8048,8613,5501,8005,8598,7872,8387,7500,5564,5291,8903,7478,8316,7487,7675,5237,7610,8267,0,7706,5407,8267,7962,8093,7628,5367,5218,5325,8062,7487,8789,5666,8190,8267,7628,5237,8052,8073,8052,7663,7994,5296,7994,5325,7839,7521,8375,7763,5476,8873,5419,8179,7914,8322,5401,7990,8467,8245,5377,5629,5661,5358,8427,7962,5401,8010,5693,5381,7786,5661,5320,5407,8898,8062,7730,7528,8329,5642,10888,5448,5320,8329,8217,8751,7994,8766,5442,8349,8613,8647,7658,7706,5372,8033,5655,7696,8447,7616,5524,8827,7675,7706,8048,5407,7500,8005,5381,8411,7757,5651,5347,5309,8777,7622,7723,7867,5274,10893,7742,5274,7588,5329,5476,7500,5661,5301,5476,7986,5666,7675,8539,7972,7930,8909,5642,5305,7811,8647,8010,5448,8772,8422,7723,8255,7994,7511,5296,0,5524,8267,0,5301,5320,5573,7663,7516,5372,8190,8033,8277,8154,5480,7478,7872,7872,7706,7946,5329,7588,8422,8211,8267,8751,7628,8329,5325,7592,5274,5305,10898,7588,7592,0,8077,8073,8261,5476,5274,7628,8170,5401,5401,7482,8322,7616,7610,8316,7994,8354,8093,5726,5642,8010,5651,5347,8005,5347,5325,8903,9516,5666,5666,5396,8527,7972,7994,8005,8427,8359,5301,5325,5651,5666,8359,7632,5325,8005,5329,5407,8589,0,5666,7768,7675,5476,8375,8514,5432,7982,7982,5329,9641,5329,7487,7675,7542,5573,7982,7500,8451,5564,7500,7500,5329,7982,7628,8052,5285,8170,8039,8222,7834,8427,8427,8005,8282,5237,5320,5248,5688,7757,5325,5688,7558,5391,0,8539,5578,5617,7628,8811,5209,8062,5296,8249,5717,5342,5367,7637,5391,5476,7538,7521,7565,7864,8994,7710,7542,7752,7752,7872,7898,5358,8354,8354,7667,7706,8297,7482,7706,5606,8467,5291,5296,8196,7706,7548,8387,7839,7715,7599,5501,5501,8560,7817,5372,5564,8297,5381,5564,7710,8307,8647,5274,8447,8594,5688,5337,7687,5255,7710,5337,7849,7653,8170,5301,5564,8297,7687,8131,7920,8073,5274,7605,7757,5432,5224,7786,8297,5726,5617,8964,8093,7786,7883,7786,7616,5301,7558,5726,8711,5717,7839,5296,5296,8211,5564,7924,8631,8577,7605,5476,5274,8144,5285,5301,5476,5255,5325,5201,5617,5476,7599,5305,7723,7867,7952,7817,7972,7800,10904,7972,7977,7648,7487,7706,5426,8577,5688,8670,5218,7977,8839,7935,8126,7864,7653,5320,8190,8527,7914,5655,5564,5274,5578,7478,5564,5301,7843,5358,5573,8467,5337,5381,7883,7834,8805,7828,8839,5296,7795,7972,8022,7893,8170,5651,7706,7706,7628,7667,7687,8249,8322,7487,5237,8099,5301,5459,7681,8272,8653,8811,7653,7628,7898,7893,7920,7994,5471,5476,8467,7588,7768,7542,7872,8447,8745,7994,7779,8039,5509,7582,7864,8282,8539,7599,8179,7924,7616,8039,7877,0,7986,7990,5325,7849,8805,8349,7500,7746,7628,5381,5391,8190,5464,7834,8411,8110,7994,5564,8467,5712,7994,5564,8821,5661,5337,5688,7790,5367,7616,5391,5248,5629,7605,8548,5377,5564,8467,5296,8249,7986,8762,7982,5688,5459,5301,7795,8267,8170,7823,7957,8658,8164,7616,5419,0,7843,8334,7487,5237,7839,5237,8467,5534,5401,5448,8282,8164,8164,5325,7478,5688,7653,8196,5476,5358,5396,5629,5476,8762,8716,7849,7982,5296,7538,8391,5476,7752,7616,8909,7628,8777,9423,10904,7800,0,8987,5578,8340,7710,8539,5708,5329,7994,7972,5629,8033,8647,5726,7920,7628,5407,7632,7957,8196,7548,7482,5419,8952,7752,5285,5509,7972,5578,7538,7538,8039,7511,7790,5237,8827,5301,7800,7675,10909,8015,8211,7521,5305,8397,7487,5688,8594,7972,5352,5352,5285,5726,5432,8239,7533,7952,5237,5325,5622,5464,7893,8149,5291,5291,5296,7482,7877,5285,5261,7648,7675,8711,5248,8329,7487,10919,7558,8903,5301,5661,5261,5476,5485,8245,7877,7706,7839,7521,5617,7632,8196,7616,7637,5301,8115,8190,5401,7605,8115,5661,8211,5661,7706,8164,5407,8249,8052,8821,8154,5314,5381,8888,7849,7763,8010,5666,5381,7986,5651,5301,8104,5301,5688,8077,5329,8762,7675,7605,7610,5301,7610,8527,7491,5413,8987,7786,5676,7616,0,5237,8179,5726,7768,8762,8179,7800,5291,5381,7605,7687,5661,5464,8816,8456,8293,7528,7548,5305,7696,7990,7763,5237,5426,7834,8795,8467,0,8093,8190,5320,5274,8316,7610,5381,5274,8387,7982,7994,8479,5248,7994,8479,9032,7946,5726,5476,5476,8201,8811,7628,8473,5496,5476,5261,8033,5564,8234,5501,8952,5381,8136,7548,7883,8915,7548,7548,5661,7706,5661,8297,7972,8504,8196,5309,7667,8467,5712,7637,5708,5453,5296,8297,7977,5480,8093,5491,8222,8174,5642,7571,5358,8670,8772,8170,7849,5320,5519,5655,5476,8994,8073,8539,8631,5564,8234,7972,7972,8297,7533,5476,7790,5480,8136,5568,5274,7877,5726,7930,8234,5237,8884,8073,9818,5564,5476,7710,7692,8504,5419,7977,8879,5305,5381,7696,7632,8613,7605,7952,7482,7774,8577,10923,10934,9345,8903,0,8653,8800,5453,5320,7977,8653,7558,5301,5676,7914,8057,5717,5476,7628,5655,7511,5688,8658,5325,5476,8201,9345,8839,8302,8658,5301,5401,5401,7692,5407,8998,7877,5358,8234,8272,8467,8093,7710,8227,5442,7888,8473,7779,8227,8456,8456,7893,8467,7632,8039,7834,8762,5237,7972,8170,7706,5325,7972,7795,8800,7935,5377,5377,5564,5248,5661,8411,5459,8653,8676,5519,5519,7849,8467,8222,8222,5564,7628,8641,7834,5296,7930,5573,5329,10940,10945,8267,8245,8131,8245,8359,8164,8467,8613,8716,7592,5612,8282,8068,7710,8940,5453,7616,5381,7542,8359,7692,5401,8222,5301,5578,5391,5329,5476,5459,5661,5274,7994,8473,8340,5325,5419,9367,8222,7521,10952,7734,5407,5622,5407,8397,5325,7883,8925,8010,8073,7521,7528,5432,7648,7800,5347,5708,7888,7952,8340,5342,8789,7877,5401,5358,5309,8903,5320,8772,5358,5347,7734,8613,7972,0,5320,8631,5391,8126,5661,8073,8033,8277,7610,5237,5274,5347,5578,5491,10961,8365,5391,5407,7533,5476,8297,8387,5237,5237,5642,8527,8772,0,8473,5564,7904,7849,5325,5325,5407,8721,8411,7834,7952,5291,7858,7790,5651,5651,7763,5391,7487,8427,7632,5291,5651,7681,5401,5642,7972,7667,5237,5261,7994,5237,5606,7628,5347,7864,8762,8196,8334,5325,8391,7616,8131,5726,8427,5309,8721,8919,5407,7632,5726,5407,7994,5291,5726,7632,5347,7914,8539,8721,5407,8721,7834,7952,5651,5391,8427,5347,5291,5325,5237,8539,5606,7628,8131,5309,5726,5285,8484,5309,8140,5291,7972,8783,5325,7786,7883,8397,8539,8987,5396,7768,5717,7710,5381,7952,7817,8062,5381,7643,5688,5426,7588,8447,5325,5329,7972,7687,5358,5426,7982,8987,7817,7972,5529,5573,5237,5476,5329,5291,5237,7834,5237,7834,7834,5693,5564,5688,5688,5285,8062,5325,8010,8329,7757,8062,5496,8272,7500,5381,5476,8504,8329,7482,5261,7706,5480,5325,8329,7482,7864,5501,8387,5642,8022,7914,7482,7528,5291,7675,8884,8033,5337,7972,7504,7565,7528,8783,5617,5274,8302,8068,7500,5476,5476,7588,7710,7849,7914,7849,7500,5655,7605,5241,8131,5209,5209,8427,7786,5564,7768,5459,8093,8539,7924,5301,5386,7533,7576,8811,5476,8170,5708,7687,5480,5476,5726,7999,5255,5358,7504,5726,7904,8245,8443,8272,7605,7839,5578,5218,7500,8329,5476,5464,7849,5391,5496,5285,5391,8727,5496,8131,7952,7768,5296,5381,5396,5671,7521,7994,7914,5442,8316,5651,8427,8391,7864,5296,7834,7504,7795,7696,5413,7681,7710,5666,7605,8582,7473,7478,7628,8909,5325,7972,8504,7588,7843,7864,8582,7864,5391,8805,7883,8073,7877,5337,7946,8255,8196,9045,8345,7628,7675,7558,5381,5274,7478,7478,5524,8022,7734,5296,5485,8851,5655,8851,8762,10969,8227,8839,5381,7990,5726,8827,7675,5342,7599,8190,7893,7710,7738,5476,8136,7482,7757,7920,8287,7877,8033,9032,5325,7706,8772,8863,7994,8170,8745,5407,5241,8115,7967,7542,5314,7779,5407,7605,8052,8099,7914,8473,8359,7858,7628,5459,7500,7858,5391,8190,5391,7643,5396,7864,7548,8539,5661,5564,7734,8805,5407,7706,7628,5209,8732,5237,5296,7482,8345,5274,7823,8033,8527,7605,7924,7834,7946,8190,5717,7495,7706,8711,7491,7994,0,8909,7967,5261,5407,8073,7692,5464,8154,5717,7616,5651,7675,5651,5237,8322,8033,9641,5377,7588,7898,7710,7542,5391,5419,8467,5661,5529,8272,5642,5642,0,7628,8473,8827,8745,5301,5305,5305,8987,5578,9476,5407,8115,5381,5401,7957,8179,7742,8721,5237,5314,5708,5708,5309,5578,5476,7982,5419,7800,8783,7653,5396,7877,7500,8833,7719,5237,5617,8217,5241,5386,5347,7864,7616,5291,5325,9021,5285,8073,5296,5622,7715,8982,7687,5391,8255,8772,8982,7734,8119,8190,5372,8772,8903,5342,7542,5325,5285,5485,5401,8417,5476,7663,8427,7920,5296,5396,5476,5476,5261,8261,8417,7542,8863,7738,7982,7576,8052,7491,7616,5407,7628,5407,5296,8762,5578,8190,8365,5666,7914,7994,7877,5301,5476,8411,5578,7834,7864,5442,7994,5442,5642,7877,8261,7482,5347,8033,8154,5578,7811,8365,5476,8903,8427,7834,8762,7834,5325,5693,5564,8504,5325,5381,8062,8272,8261,5261,7706,5480,8329,5476,8329,5325,8504,5419,5296,7742,8170,8387,8884,7500,7864,8099,7504,8022,8527,5617,7565,5291,5241,7786,7849,5255,8443,5391,7504,7687,7588,5655,8539,7967,7839,5726,7999,5358,5476,5476,8582,5666,5391,7795,5391,5381,5296,7558,7605,7914,5396,7883,8131,7768,7952,8329,5476,5241,5671,7521,5496,7643,7500,7576,5381,7478,7628,8255,7478,5524,5337,5296,8582,8851,8022,7663,8745,7605,7967,7495,8762,5314,7893,7990,7994,7628,8839,7914,7757,7877,7779,9032,5342,8772,7914,5476,8711,8033,5459,5464,5407,5661,9641,7491,7994,7864,7628,8190,7877,5209,8345,7834,8539,5651,7858,7500,5305,8903,7588,5661,5642,7898,5391,7542,5651,5309,7800,5578,5407,9476,8772,8982,8255,5347,7734,5291,7834,7542,8427,8033,7786,7542,8062,5367,5291,8062,7653,8062,5237,5237,8154,8239,7653,8762,7610,8307,5419,7706,5391,5301,8919,5509,5358,5320,5274,8527,7972,7972,7834,7834,5391,5573,5296,8068,5476,8068,5476,5407,7904,5730,7864,8140,7521,7811,5407,8800,5671,5726,5564,5237,8888,8052,7628,7605,7977,0,5237,8073,7752,7811,8888,8539,8548,5501,5476,7972,8222,7637,7653,7920,8136,7920,8131,8136,5337,7757,5305,7487,5519,7811,8653,7849,7849,5305,7653,7790,5301,8447,5358,7628,7977,8641,7790,7920,5237,7864,8170,5305,5573,8772,7811,5291,7930,5655,8467,5564,7774,7817,8594,5476,8560,5509,8751,8154,8010,8772,8721,8115,7972,7696,7605,5391,5358,5209,7696,7952,7858,8411,8349,5491,8119,8582,7893,8964,5476,7710,7790,7849,5358,5329,8504,5296,5671,7858,5209,7977,7500,9353,5285,5671,8205,5407,7616,8329,5655,5617,8888,8888,5688,5617,8732,8205,7576,8277,8365,7487,8033,8987,5606,8456,8845,5274,8349,5407,5342,5564,8772,8827,8827,7972,5325,8015,8762,5309,7504,8598,8267,8222,5437,8277,8527,8777,5325,8119,7663,5622,5564,8131,8159,8115,8239,7994,8126,8033,5622,7972,8863,8473,8267,8833,8772,8365,7834,10978,5261,5509,7616,5309,8811,5274,8205,5358,8762,5320,7834,9259,8190,7610,8811,8205,7930,5655,5564,8467,7817,7774,8115,5274,8126,8015,8154,7972,8010,8772,8751,8411,8131,7605,8721,7696,5407,5358,5391,8964,7952,8582,5491,7790,7893,7849,5476,9353,7972,7977,5296,5209,8504,5329,8205,5209,5671,5309,5688,5655,8277,5617,5564,8762,5325,5320,8987,7487,5358,7576,8845,8456,8772,8277,8527,8863,5564,5622,8159,8115,7834,8365,8267,8833,5261,7616,8811,7710,5237,5617,7823,7504,5237,7558,7849,7558,5578,5367,5629,7967,7967,5301,8484,5564,7999,8811,7706,5261,7723,8196,5274,7839,5291,8196,7710,7839,8033,8033,5459,8170,8783,8539,7719,8427,7605,8354,5726,5381,7710,5573,7795,5529,7839,7681,7888,8577,8811,5612,8140,5237,7999,7588,7482,8170,5407,5699,5629,8287,8402,5329,5726,8903,5688,5209,5377,5726,7710,5464,8255,8255,5642,5372,5377,10982,8365,8365,8443,8994,8222,7692,7990,5471,5291,5564,8033,5407,7565,7972,7730,8354,8994,7706,8354,8322,5480,7658,5476,8184,8093,5712,7904,5459,7692,5480,7977,7839,8174,5280,7605,8170,5459,7972,5564,7533,8170,5261,8073,8447,8170,5301,8682,5476,5325,5491,5274,5285,7888,7605,5622,7473,5635,7952,7723,8005,7521,7710,5329,5413,7972,7977,7681,7774,5407,7738,8255,5358,5274,7888,5325,8170,5325,5320,5647,5573,5485,8077,7675,8073,7710,8658,8711,5320,7843,5218,5285,5442,8255,0,7738,5407,7706,8494,7994,5314,5381,5325,8077,8170,8170,8099,5329,8873,8359,8170,7710,5509,7768,5377,8073,8443,7867,5325,8140,8548,7774,8548,5352,7972,5241,7828,7972,7628,7888,5471,8104,5218,7548,8539,5578,8033,8663,5407,7795,5629,8170,5564,8443,5459,7858,8110,7495,5377,5496,8422,5407,8479,8451,5237,7738,8322,8427,7828,5377,5325,8077,7800,7511,8427,5358,5248,5329,8239,8140,5629,5491,5612,5237,8005,5325,7972,8322,8539,7800,8548,5401,5413,5372,8184,5432,8422,8170,8919,8554,5274,8010,5573,7548,5471,8073,8140,8443,5291,5291,7516,7738,8255,8255,7734,8239,5471,8498,8582,5372,7738,5358,5358,7605,8170,5573,8255,5471,5442,5325,7478,8354,8184,7516,8577,7972,5301,7972,7877,5573,8676,7994,7610,7516,5274,7994,8857,5347,5661,8261,5661,8077,7738,5726,5381,8498,8857,5237,8005,5291,7516,7914,7817,7883,10986,5726,5377,7888,7888,7817,7843,10994,7632,7663,5325,8170,5629,11001,11009,7834,11013,8756,5726,7957,8149,5480,7972,11023,5377,8479,11023,7628,7935,8387,7952,7667,7994,8354,7528,8539,8190,8010,8504,5261,8322,5320,8068,8033,5491,7864,8136,7706,7790,8821,8504,8010,5622,8062,7982,5726,5464,8539,8093,5491,5708,5655,8010,8582,8608,5352,7930,7786,7692,8174,7605,8164,7864,7904,7576,7972,5642,7990,5476,8745,5391,5255,7696,7723,8170,5285,8687,5329,8509,7768,7576,5437,7972,5617,7779,7628,8670,5337,7811,8267,5578,5509,5564,8745,7576,8447,8093,5578,8427,7779,5209,5296,7599,8287,8039,8795,5476,5352,7542,8234,8184,7858,8721,7930,8099,5213,7779,8821,8110,5325,7924,5274,8732,8694,8658,5717,5337,8062,5320,5564,5209,5564,7752,8543,8427,7628,7752,8272,7786,5655,5367,8467,5329,8484,8783,5622,7994,7643,10830,7935,5285,8427,7734,8149,5501,8010,7516,5717,7719,8795,7811,5476,5325,8217,7696,7628,5274,8062,5666,8456,5301,5301,5329,7516,7935,8387,7952,8190,8539,8504,5622,7864,8099,8821,8062,7692,7990,5329,7576,7972,5476,7982,5655,5301,5642,8745,5391,5437,7972,5285,7576,7768,5329,5285,5578,5564,7779,8184,7930,5296,8234,7599,7779,8287,5209,8694,5564,5325,7752,5320,8456,8467,5367,8427,5329,7935,7516,7528,7786,7839,7839,7920,8397,8411,7986,7986,7839,5529,8282,8411,8411,8411,0,8411,8282,7839,8411,8411,8033,8613,8789,8789,5655,8287,8287,11031,8705,8411,8411,11039,11044,8239,8245,7877,8811,7542,7628,7628,7706,8329,5476,8039,8349,7706,8925,7599,7977,9032,7616,5377,8267,5309,8560,8387,8010,7478,8245,7864,7811,5578,11048,5309,8467,7994,5401,7710,7935,8447,7681,7972,5476,7592,7977,8033,5491,7696,8539,8312,7599,5459,8115,8272,5320,5480,11056,5485,5296,8316,5337,5651,5413,5325,8716,5476,5381,8863,8577,8329,8422,8022,7795,8272,8334,5730,8447,5471,7592,8190,8329,8201,5337,7675,5688,7853,7605,5688,8539,8539,5647,5485,5377,8307,5476,5578,8316,8687,7478,5391,7558,7588,5372,8658,8527,5401,5612,5296,5301,11061,8277,8543,8451,5291,5413,7811,5491,5329,5337,7576,5309,7628,8287,8005,7994,5377,5476,8245,8467,7632,5358,7495,7628,8903,5342,5471,5342,7834,8732,7710,5377,7628,5391,5655,5337,8267,7864,8411,8039,7864,5367,5464,5688,8479,8245,7994,11067,5476,8131,5642,5573,7511,8329,7738,8539,7710,8467,7588,7738,7542,0,5642,8721,8015,5708,5391,8604,7710,7588,5372,5578,5471,8539,7957,7710,8451,7516,9021,7648,7542,8190,7628,5476,7478,8239,8170,5391,5358,5642,8349,5661,5342,7811,8427,8909,7696,7622,8411,8411,7986,5301,8467,5642,7632,5301,5476,8805,8919,7706,5218,5261,7653,7628,8211,8613,7723,0,8582,5564,8756,8888,7565,5426,8136,7528,7914,7898,8467,8879,8154,5661,7898,7478,8170,5337,7653,7658,5688,5358,8456,5224,8670,7930,8631,8539,7967,7491,7516,8582,7952,7605,5391,7692,7628,5671,5717,5237,7972,5325,8329,5730,5426,5285,5237,7952,7495,7843,7977,7908,5241,11075,8267,7834,7558,8307,7710,5329,5564,7478,5564,5524,8052,7843,7977,8845,7667,7616,8073,5237,7930,5301,8267,8201,7675,7706,7920,8745,8039,7576,8772,7967,5255,8277,8447,8033,5337,8201,7521,7883,8170,5218,8255,5476,8762,7834,5712,8893,7930,7864,7628,5676,7628,8267,5730,8005,7521,7521,5448,5629,8391,5237,8613,5573,5209,7967,8170,7478,7521,8267,8052,5218,5358,5305,8783,5564,8239,7482,5622,7982,8255,7834,8015,8010,8255,7500,8919,5358,7528,5358,8554,7994,5241,7790,5261,7516,8312,7883,8249,7491,8340,7908,8527,8249,7967,8582,5401,8365,7877,5337,7706,7834,7877,8365,8093,7811,5237,8527,8365,5301,5337,5693,5688,5314,8456,7576,5476,8560,7628,8484,7834,8397,7790,7883,7500,7853,5655,5501,11082,0,8267,7658,5606,5280,5480,7571,7653,7786,7588,8312,5442,5437,8391,7632,8287,5442,5337,8316,7972,5274,5419,7558,7482,5647,7675,5712,7692,7940,5666,7734,8397,8370,7834,5237,8929,7994,5329,5347,7834,8272,8456,8427,8104,8772,8397,8287,8140,8653,5377,7746,8762,7982,7500,5426,8772,8484,8104,8267,8312,7990,5448,5651,7768,5442,8451,8267,5218,8608,7687,8370,5629,5419,8647,5261,7628,5301,5372,8149,5476,5419,7734,5285,5237,8772,8312,8789,5476,5301,8170,5285,5476,8929,8613,8329,5442,7834,8093,5651,5651,8929,7528,5285,5237,5651,7834,8411,7774,5391,5337,5391,5391,5301,7548,7491,7779,5301,5309,5309,5688,5476,7757,8479,7999,5693,8312,5688,7849,5564,5655,7972,7904,8375,5564,8716,7757,5261,8255,5568,8732,5209,8010,7628,5396,7653,7817,7706,8211,8915,8539,7487,5237,5476,7637,5391,5647,5237,8293,7542,11094,5407,7920,7491,7482,7904,7565,8033,5426,7853,8354,7834,5564,7920,8136,7528,7864,5291,5471,8131,7482,7542,8282,5325,8504,8783,8816,5501,8267,5325,8196,7920,8039,8062,7482,7667,5391,5476,7548,8077,5671,8915,8154,7658,8756,11104,11112,11119,0,7888,5209,8447,5647,5391,8438,7786,7864,8093,5224,5476,7786,7904,7920,7628,7487,7571,8201,5337,7828,7977,5655,5391,5391,8964,8170,8427,5391,5617,8190,7999,8222,7849,5358,8119,5573,5391,8073,7605,7710,8925,8312,8316,7542,5564,8533,7511,7542,5564,5301,8354,7920,7924,8282,8073,5381,8131,5320,5241,7715,8447,8077,8539,7977,7715,7883,7914,5476,7487,8582,9012,7930,5407,8316,5426,8062,5285,7710,7696,5717,5224,5391,8711,7977,7482,7667,7576,5248,8987,8316,5305,5666,5325,7628,7500,8504,9021,7972,7723,5261,7710,5274,5426,7768,7920,8964,7605,5730,8391,7473,5642,5274,5274,5519,7667,5496,8149,5325,7920,8613,5651,5476,7482,8687,8345,9562,5337,7719,8898,7542,8375,11126,7663,7757,7653,7872,5391,8946,5688,7675,5578,5519,8205,5377,8451,7795,7817,7548,8307,5329,5712,7843,5509,7528,8267,7930,7478,7588,7628,7877,8805,8073,7653,7977,8062,8498,8527,7877,5509,7972,7487,8190,7478,7478,7681,7817,5314,9471,5426,7542,7914,7653,9471,5661,5573,5241,8170,5274,8964,8211,7734,5301,7533,7872,7558,5524,5301,8594,11135,7605,11142,5647,7511,8397,8170,8227,8467,5407,8461,7757,8272,8227,8099,7473,8093,7930,5329,5301,8322,8057,5676,8438,8397,5352,8345,7920,5274,8387,8863,5666,9051,8571,7738,8653,8762,7994,7805,5209,5241,8543,8077,8827,8658,8772,5693,7706,7542,5314,7706,7628,5309,8721,7790,8227,5606,5237,5401,8272,7786,8451,5320,5655,5329,8249,5329,5396,8473,5205,8277,8800,11147,8201,8716,5509,8365,0,8407,5274,0,8154,7779,5933,8514,8509,7616,8539,5401,8039,5274,8762,5647,7994,8827,5564,8110,5325,7864,8110,8033,5377,7687,8062,7924,8676,5367,7628,8946,5426,5464,7994,5564,7999,5496,7867,5617,8062,8005,5606,8711,8255,8255,5730,7853,8827,7811,7491,8438,5407,8676,5407,8022,8005,7982,8190,7888,5381,5655,8411,7548,5209,5519,7491,5712,5213,7663,5381,0,5712,8287,7738,7898,5367,5401,5612,5237,7511,8443,7491,8417,5534,8164,5629,5612,8322,5717,8851,7805,8073,7533,7730,5358,8940,8461,5209,7478,7616,5464,7681,7511,8509,7962,8302,5448,5509,8052,5655,5655,7632,5476,8467,5629,8077,5237,7977,5717,7538,5676,11126,11104,5612,0,0,0,8136,5329,5612,7742,8443,8641,5519,8721,8190,5401,8087,5708,7800,5305,7632,5358,7994,7511,8539,8255,8827,5476,5329,5708,8946,8322,8402,7706,8227,5629,7800,5218,7877,8527,5485,8473,5320,7957,5519,8827,7610,8365,5407,8340,5301,5367,5237,8756,0,5413,0,0,5241,5329,5396,5325,7734,5296,8322,8312,7516,5612,5519,8589,7558,8005,7487,7511,5386,7946,8959,5342,8010,8255,7768,7924,5629,7521,5291,5285,9021,8239,5381,8149,8217,8772,8365,5325,5407,7548,8919,8387,7616,8217,5291,8417,5377,7734,8964,0,0,5693,5261,8509,5407,7893,5342,5329,5676,5296,7628,5401,5314,8255,7542,5314,5666,5358,8964,7811,7491,7888,8039,8903,8170,5501,8245,7930,5476,7663,8548,8329,7757,7605,7946,8863,5325,8077,7738,8154,8745,8888,7616,8005,8381,7500,5407,5407,11155,5496,7972,7763,5301,5666,5325,8857,8582,8987,5352,7986,8762,8851,8287,5329,8473,8222,5329,0,0,7610,7864,5329,7528,5407,5347,8073,5407,5642,7877,7521,5407,5237,8721,8777,8077,5666,8498,5726,7533,7632,8857,8375,5372,8077,5291,5629,5320,5347,5309,5688,5476,5655,5693,7999,8312,7757,5647,7542,8732,5261,5325,8010,7757,8514,8293,5237,7637,5426,5671,8201,7658,8170,5471,8783,7548,8136,7930,8461,8282,7542,5642,8196,7667,7478,8131,8756,7565,8154,7853,8816,7904,7628,7542,5241,7542,7786,8073,7864,8312,8201,7904,8354,8857,7715,7883,7977,7828,5337,8964,8073,8093,8851,7542,7511,8222,5391,8190,5617,5358,8447,7920,7888,5426,8898,8149,7696,8211,8387,5337,7558,7977,7491,5386,7972,8903,8316,5329,7757,7482,8509,7687,7605,5274,8805,9003,5274,5396,7768,8302,7977,5642,5717,8391,5241,7719,5285,5476,7877,5248,5629,8345,7482,8613,7605,8149,7734,7478,8277,8594,5401,8946,7675,5612,5301,5241,5573,8039,5524,8964,5519,8451,9471,8527,7528,7511,8509,5407,7872,5377,7843,7542,8170,9051,8322,7888,5606,8451,7790,8543,7706,8407,7473,8653,8227,5237,7786,7632,7805,8863,5309,7972,5274,5676,8154,8057,8772,5693,5407,8170,8249,5209,7867,7616,8509,8322,8022,7864,5496,8255,7548,8676,5448,5464,7811,7663,8762,5712,7957,5209,7893,5712,5305,5655,8073,5717,8077,5629,5676,8077,8136,7511,8052,7898,5476,7977,8888,8164,8473,5629,7800,7632,5413,5218,5485,8539,8443,8190,7511,8772,5291,8239,7734,5329,8217,5347,7734,8498,8322,8919,8154,5666,7811,5314,5401,5476,5496,8473,7986,7877,7521,8227,8227,8484,5708,5459,5726,8239,5651,8227,8514,7538,8969,5261,8154,8514,7834,5358,7653,5358,8589,8509,8756,5413,8062,8489,8589,5274,5274,5407,8438,5367,11161,8307,7687,5386,5407,5352,5367,8093,8411,5358,8489,7849,8721,5717,5717,8048,5573,8062,7952,8438,7719,8653,7843,5329,8805,8302,8170,8354,8354,7877,5726,7516,8227,8635,8653,7834,8438,7834,7864,7643,7628,7752,7521,8222,7521,5726,5642,7752,8282,8345,7994,7482,8144,5699,5347,8762,0,11167,5606,8077,7738,8509,7849,5699,8519,8721,8307,7706,8005,5629,8721,8647,8795,5237,5296,7877,7920,8903,7811,7738,7752,0,8514,8969,5261,7834,7653,5358,7752,8519,8489,7994,5274,8062,5407,8438,8756,8514,7687,5386,7952,7752,7738,8438,5329,8509,8048,5717,7849,8653,8484,8354,7843,8762,7628,7834,8227,5237,7752,7643,7834,7864,7877,5347,5726,5296,8144,5699,5717,5606,7738,5699,8795,7811,5564,5564,5333,8217,7528,7542,7478,5476,5480,7482,7924,8307,7864,8119,8282,8456,8946,8267,8821,7972,7920,5485,7565,7924,7482,5655,5381,7558,7864,5726,8539,8443,7571,7828,8093,9051,5480,5480,8312,5224,5564,5325,5329,7610,8033,5426,7888,7957,5305,5419,5320,7888,5274,7696,7952,7628,5671,5261,8329,8255,5426,8104,5564,5358,5367,5261,8119,7972,7734,8307,8467,5655,5573,5274,7853,8077,8267,5274,8456,8456,7967,5476,8217,8099,7482,8170,7805,8033,7920,8093,8272,5274,5329,8119,5274,7482,7605,7924,8705,5261,5248,7834,7482,7628,5377,7628,7610,7994,7994,8077,8217,5342,8345,5464,5381,5342,7482,5671,7834,5296,5717,8267,7478,7994,7663,5237,5629,5325,8015,8479,5708,5237,7482,11175,7648,7516,8422,8919,7920,5342,5342,7628,5274,5501,8447,7696,5325,8479,5237,7482,7864,5296,7610,5237,5301,5301,5301,8863,7849,7972,9032,7667,7823,5476,5699,7834,7542,8387,7504,8387,5325,7786,7811,7972,8131,7667,7839,5218,8170,5573,7982,8925,7632,7628,5442,7757,5342,7653,8154,8617,8721,5573,8925,5218,5325,5237,5442,7511,5301,7823,5529,8925,5325,5325,7628,7628,5529,8136,8998,7719,7715,7752,5501,8334,8267,5301,5301,8608,8093,5666,8345,8222,8249,7828,8903,5426,7478,7999,7500,5325,7715,8033,7675,7687,8805,5655,8456,5712,8093,7706,5442,8154,8302,7893,5534,8427,7482,8447,5442,8068,7516,0,7582,7616,7478,8093,8617,8093,5367,8062,7482,5305,8349,8267,7511,8249,8888,7478,7994,8154,7482,5237,5476,7663,7883,8919,5274,7628,5329,7610,5491,5325,8334,7924,7904,7920,7994,0,5237,5325,8349,8249,8249,5666,5301,8154,7663,7893,5491,7610,8093,7663,8527,5301,8222,0,7849,7849,5224,8745,8745,5485,5606,5655,5485,7558,8745,5485,5485,7828,5485,5606,7893,7893,8898,5651,7746,7746,8174,7491,5606,5296,7491,5717,5693,5496,5407,8504,8539,8762,7478,8504,8196,5309,5529,8816,7904,7482,7730,8136,5305,8443,8245,8282,5476,5642,8272,5480,7977,8062,8174,7924,11180,7482,7977,8272,5325,5661,8345,8345,8727,7817,11186,8245,8255,8805,5509,7616,5342,7681,8255,8190,8571,5413,7738,8015,5224,8144,8170,7986,5661,8110,7898,5476,5688,5688,7849,7675,7715,8170,5407,8411,8539,5407,11194,8272,8131,7738,7930,5661,7533,5717,5358,8144,5629,7957,7952,8255,5529,5325,5407,5419,7877,8245,8154,5274,7542,8762,8631,5407,5347,7994,8504,5564,5712,5676,5717,7994,8255,7653,8227,0,8110,8329,5617,7994,5717,7930,8272,8131,8267,0,5358,7994,5296,8762,8631,8762,7994,8504,5564,7653,7994,8267,8272,8484,8484,5274,5661,5274,5325,7482,8925,8267,8670,5333,5352,7946,7616,8267,5352,7482,7482,7521,7982,7491,5693,8745,7533,7521,8598,7653,7500,5476,7500,7786,8022,8005,7706,8560,7628,5509,8282,5471,8539,7883,7533,8312,8093,8604,5485,5666,7849,8255,7864,7864,7994,5381,7795,7914,5476,7914,8370,8217,7858,5255,5666,5564,7977,7853,5296,8005,7977,5333,5606,7558,7616,7667,7982,7653,5485,8422,8422,8751,8277,8422,5209,5209,7805,5693,8277,7834,7653,5407,7990,8402,7592,8196,5476,8159,5377,7864,7864,7834,7643,8795,5218,5622,7834,5274,7528,5476,8467,8940,7828,7828,5419,7692,5666,8239,8647,5476,7800,7706,7648,8052,8255,7786,8329,7542,5296,8427,8613,8062,8888,5274,8751,5329,5347,8077,7768,7491,5693,8745,7653,7521,8598,7500,7500,5509,7786,5471,7706,8282,8022,8560,7628,5329,8093,8312,5485,5381,7977,5606,7653,7616,7592,8370,5476,8751,5296,8422,7653,7616,7616,7990,5377,5218,7864,5622,7834,7864,8077,7800,7828,8940,8052,5666,8427,8613,8751,7768,8527,8473,11198,5564,5367,8427,8473,7730,7904,7658,8577,7795,8190,8670,8170,5358,7487,7994,7924,5642,5448,8509,5514,8010,7511,5237,8239,8647,8647,7511,8473,8473,8473,8239,0,7719,8527,8473,8527,7924,8427,8473,7730,8170,7487,5448,5642,7511,8647,8473,8473,7893,8010,7893,7893,5391,5391,8179,5325,5693,7487,5480,8427,7962,7521,8068,8504,7628,5676,7710,7482,8010,8010,7962,7482,7605,5476,8533,5358,5381,8272,7511,7839,8964,7487,8222,5391,7888,8370,8504,5606,5285,7977,5224,5642,8316,7839,7521,7924,7924,7696,7834,5333,5476,8179,8073,8028,7864,8201,7667,8307,7588,7628,5391,5642,7643,8039,5391,5407,8863,5224,5274,8201,7491,8772,7893,8015,7994,8721,7864,8699,7643,7982,5464,8964,5296,5407,5730,7849,5426,5501,7994,7786,5496,8022,5629,8073,5524,5237,8005,7511,5448,8272,7491,8267,5305,5629,7800,5358,7628,5519,5309,8589,8005,5622,5261,7710,7883,5476,5325,7648,7558,5218,8272,5296,7521,8427,5501,8370,8249,7834,5305,7877,7521,7632,8857,7700,5391,5693,5325,5480,5426,8068,5237,8504,7628,7710,8010,7482,5407,5391,8222,7487,7888,5476,7696,7648,7521,7977,5333,5285,5476,5224,8073,8307,7864,7628,8028,7667,8039,7643,5274,8721,5496,8005,7786,5448,7877,7491,5305,8073,7511,5519,5309,7800,8589,5622,7700,8087,8005,8039,7521,5501,7904,8447,5358,8073,5647,7653,7893,5407,8345,7663,5564,5274,7752,7528,5501,8888,5426,7538,7628,8527,7653,8539,5480,5480,7710,8329,7605,7972,8456,7528,8915,7730,7482,7752,7972,7883,5329,5337,8073,8222,7908,5480,5476,5726,0,5726,7592,8073,7999,8467,5352,7576,8170,7605,8293,8170,7920,8205,7977,8093,5655,7658,5564,7924,5391,5358,5480,5480,7487,7511,7538,7858,8272,7972,8164,8909,7582,8329,7768,7914,7592,7696,8370,8073,7605,7482,7632,11206,7883,5661,7511,8170,8022,8196,8211,7779,7653,5647,8190,8422,7667,7864,7558,7528,7663,5329,8863,7576,5617,7858,8653,8272,8277,8548,7990,7706,7706,7834,7893,5514,7834,5381,5642,7478,7858,8827,7858,7542,7893,5464,5407,8154,7628,5377,7768,5367,8548,8005,8110,7487,7795,5480,8005,5448,7542,7558,7972,7511,8334,8909,5237,8340,8762,7834,8467,7967,5514,5261,5301,7972,8617,5329,5237,7632,8015,5305,5708,8179,8647,8827,5726,5358,7972,7628,7500,5396,8073,7588,7675,7516,7877,5471,7511,5480,8833,8772,5285,8964,7834,7632,8427,5485,5476,7491,5480,8888,7967,7834,5352,5329,8909,7521,5325,8617,8170,5237,7811,5301,8473,7538,7628,5480,8329,7710,5726,8915,8073,5329,8205,5391,7487,5564,8170,7967,7605,5480,8293,7977,5476,7658,7675,7538,7482,5285,5396,7632,8370,7858,8473,5301,7558,7834,7528,8196,7667,7706,7706,7990,8863,8548,7588,8179,7858,7972,8005,5514,7478,5708,7511,7542,5261,8647,7632,8827,7877,7967,5325,8617,7521,7786,7994,7994,7994,7628,8811,5476,8608,7853,5661,8222,5358,7710,5377,8539,7692,8370,8255,8131,7473,5717,8422,8845,7858,5358,7990,9012,7628,5342,7957,8073,7675,8334,8073,7839,8739,8888,5305,8239,7957,5386,8762,8205,5342,5377,8888,8739,5329,5524,5524,8255,8898,8255,8149,5372,8473,8653,8653,5377,7565,5519,8297,7790,7883,8653,8888,8048,7681,5352,5209,8048,8297,5377,8174,7920,7790,5564,7696,8164,5726,7817,5519,7795,5325,5352,9361,8670,8302,5377,5301,8888,8548,5377,5352,8022,5241,7768,8548,8598,5407,7888,7982,7986,8484,7706,5401,5655,8888,5352,5305,8589,7800,7800,9026,7500,8577,7542,7542,8789,7811,7908,8381,8888,8577,8261,8307,8307,5386,8062,5237,8307,7653,8307,8307,8484,8227,7628,7628,5301,5676,5564,7542,7952,8548,7511,7952,8987,7628,7952,5712,5325,7706,5381,8005,7643,7904,8312,5712,7500,7834,7675,7779,7628,8422,7706,7599,7779,7994,5407,7710,8647,5358,5305,5325,7500,5442,7834,8427,7628,8479,8504,5325,11212,7817,5480,7757,5325,7500,7864,7864,8302,8560,8068,5305,5381,7914,8010,8467,5358,5329,7752,7982,5329,8925,7565,5501,8136,5337,11217,11225,7849,5367,5726,7653,7920,8093,5480,8073,5688,5255,5564,5358,5325,7994,8170,7757,8073,5337,8234,7920,8184,5426,7658,8222,5564,7687,8170,7786,11231,8316,11239,7738,5381,5320,5730,7521,7696,8345,7994,7681,7605,7482,8577,7632,8316,5476,7706,5224,7994,5285,8201,7952,5274,5717,5296,11243,11249,8898,11255,7888,7667,5377,7800,7675,8925,8302,8196,7628,8028,5578,8234,8845,7588,7478,5688,8805,5564,5509,5337,5301,8302,8302,5524,8249,11261,11267,11274,11285,5218,8447,5209,7706,8745,7521,8028,8104,5325,7757,7867,7786,7576,7582,8277,7893,8447,5476,8653,5329,8484,8227,5485,8099,8093,7967,5301,8249,8548,7972,8184,11295,11303,11310,5391,7914,7643,5391,5730,5426,7605,7986,8548,5325,8411,8411,7811,7768,8115,7994,7994,7628,8658,8110,5459,5464,5401,7834,5688,5688,5407,5358,7864,7924,5564,8159,5407,5367,5480,7982,11319,8893,11328,7752,5407,8052,7478,7565,5514,7511,7538,5391,5391,7482,11336,8909,7738,5642,5717,8164,7542,7706,7752,8893,11339,5401,5708,5333,5296,8438,5325,7681,5726,5407,5514,7800,5237,5688,8473,5325,5325,7605,5419,5688,5622,5218,8015,8756,5529,5432,5291,5726,7521,8073,5285,8329,7588,5464,9021,5261,5261,8010,5291,7516,8329,7622,5237,11349,11354,5501,8721,5730,7908,9353,5514,8427,8811,7952,8119,5301,8227,11339,11360,11368,8613,5325,7500,8154,11360,5301,8577,5301,7763,5655,7521,7864,5329,8721,5301,5274,7628,8479,5325,5337,8068,5329,7565,7904,7849,8073,5255,8184,5329,5337,7687,5564,8073,8222,5730,7920,7658,7952,5381,8898,7994,7696,8316,8115,5730,7908,5325,5285,5274,8925,7521,8329,8845,5301,5401,5407,5301,5391,7681,8234,8302,5509,7667,5325,5218,8745,8093,7706,7967,7893,8653,8227,7786,8447,8184,7757,5485,7914,5391,5209,5501,5459,5358,8227,5391,7752,7994,8159,7864,5688,5564,5464,7768,5419,8411,5514,5708,7706,7738,7482,5642,7565,5407,5333,8473,5529,5432,7800,8438,5218,7994,5237,5291,5261,7516,9021,8154,8811,5301,8427,8721,5453,5476,5564,5301,8484,8201,7834,5564,7757,5325,8527,11375,8811,5391,8527,8391,8222,8467,7972,7982,8196,5564,5501,7752,5407,5391,7628,8998,11380,5291,5291,7920,7811,5655,8222,7834,8387,5241,7565,8527,7752,7742,8594,7977,8293,8093,7763,5564,5480,7752,5301,8411,7972,5717,8467,5209,5726,7675,7972,7883,8170,5337,7786,7548,7628,7924,8164,8387,8964,7628,11386,7616,8863,7478,7696,7795,7663,7972,7834,7946,5285,5325,8577,7605,8504,5476,8062,7632,8613,8711,5717,8504,5285,5519,7576,7972,7632,11393,11402,9457,8375,7977,5325,7628,7811,5480,8201,7478,5676,7628,8073,8329,8329,5358,5237,7667,8170,5647,5485,5377,7864,7864,8582,7982,7478,7914,8277,11408,11417,5329,5255,5320,5564,8345,7999,5352,8196,7542,7930,7757,5329,5699,5407,8170,5647,8387,8467,7706,5301,7491,8863,8571,8863,8653,8119,7706,5485,5476,5476,5485,5209,8277,7582,7706,8104,8845,8170,5726,11425,11432,5325,5320,0,7710,7622,8110,8345,5688,5377,7982,5377,7864,5381,7790,5564,8104,5647,7834,7849,8467,7858,8267,5712,7982,7616,5471,7715,8170,11445,8777,7565,7786,7616,8467,8190,5534,5391,7849,5573,5629,5367,8893,7511,5325,7786,5407,9032,7653,5209,5209,8447,5642,7834,5325,5301,5606,5568,5377,5377,8447,5647,7538,5285,5524,5606,8033,5301,5622,5578,5708,5642,5476,7742,7710,7972,8239,8164,7957,5358,8617,5314,7628,7478,5291,7482,7768,7487,5285,5476,5396,5358,7616,7588,5464,8010,5285,8239,7834,8898,8484,5274,5274,5647,8349,9021,7628,7616,5329,8641,5274,5529,5476,7920,5320,7632,7706,5476,5325,5578,7628,8427,7491,7924,7920,8381,8154,5651,7616,8249,8539,8354,7628,5666,8222,5329,7849,7610,8617,8354,7616,8721,5726,5301,8375,5453,8484,5325,8467,8391,5391,7742,8387,8777,8222,5655,7786,7582,5329,8387,7675,7924,8093,7972,5726,8467,5529,5480,7487,7972,7696,7795,5519,7946,7576,5717,8375,8062,7478,8073,5301,5676,7982,7864,7628,5274,5377,7478,5699,7622,8345,8653,7930,5352,7542,8104,8845,8467,7588,7982,7849,7864,7786,5688,5209,5712,7715,8447,5642,5534,7511,5325,8447,5407,7849,5476,7616,5578,8239,8239,5285,8484,7628,5329,8190,8427,7616,8154,8249,8721,8617,5329,5309,8093,5407,5274,8451,5407,5407,7834,8451,5329,5337,8140,5325,8473,8140,8473,7605,7667,7605,5407,5651,5651,7478,7511,8033,7667,7516,8447,7706,5329,8484,7667,5485,5301,7521,7834,7977,5651,5301,5617,8015,7516,5485,5325,8093,7834,8140,8349,8349,5717,8783,5564,7746,7746,5564,8174,5726,5726,8711,5564,5274,8277,5726,7746,0,8527,5564,5726,7746,7538,5305,5305,5296,7538,7967,5362,5501,5464,5464,5309,8687,5606,7853,7946,5237,8582,7946,5320,5301,8184,7710,9372,9372,5476,7542,7883,5237,8641,5305,5305,7542,5491,5573,5337,7828,5476,5367,7834,5726,5712,7834,5485,7628,5301,8903,8762,8179,7482,8345,7834,8772,8345,5655,5491,8179,5476,5712,7883,7834,8762,5329,7972,5501,5564,5564,8438,8438,8467,8140,5726,8783,8631,7605,7972,8211,5708,5432,5480,5358,8467,8783,5480,5693,5651,8716,5693,5209,7786,7786,7582,5501,8272,8467,7920,8227,5524,7706,8467,5629,8909,5320,5320,5501,7893,7752,7904,7757,5480,8316,5726,8119,5391,5337,5391,8805,7478,8184,5485,7643,8170,7834,5647,5377,5237,5274,7834,5666,5358,5642,5688,7653,7478,7478,5730,5237,5717,7687,5519,5534,7687,8789,7700,7706,8533,5325,5209,5325,5325,7706,5325,7710,8033,8033,7849,8387,8800,5661,8174,8451,5391,5209,7710,8184,8170,7681,8093,8093,5573,7768,5661,8800,8077,8484,5642,8287,8653,7628,5573,5476,8447,8451,7967,5726,5362,5274,7742,7864,8494,5476,8287,7967,7828,5573,5309,8387,7710,8033,7849,8800,8170,8093,8174,7681,5209,8800,7628,8287,5726,8494,7610,8334,7817,8334,7834,7610,7610,7817,8795,7738,8093,7738,7610,7817,8795,7952,5688,5432,7952,8354,7805,7849,5291,5661,7628,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4269,11454,0,0,11459,0,0,0,11464,0,0,0,0,0,0,0,11469,11474,0,11479,0,11484,0,11490,0,0,0,0,11495,11500,0,0,0,0,0,0,11506,0,0,0,0,0,0,0,0,0,0,0,11512,0,0,0,0,0,0,0,11519,0,0,0,0,0,0,0,0,0,0,0,11525,5599,40,11528,11531,11535,335,11539,4899,11543,24,11546,11549,11553,11556,380,11560,52,11563,11566,32,3103,11569,11573,2795,11577,11581,11586,11591,11595,11600,11604,800,11608,11612,11617,11621,11626,11630,2809,11634,11638,2806,11642,11646,11650,2792,9676,11654,11659,11663,11667,11671,11674,11678,11683,11687,11691,3457,11695,11699,11704,11708,11713,11717,803,9686,11721,11726,11730,11735,11739,3471,11743,11747,11751,3454,11755,9690,11759,11763,11767,11771,11774,11778,11783,11787,11792,11797,11801,11806,11812,11818,11823,11829,11834,1073,11839,11844,11850,11855,11861,11866,11871,11875,11880,11885,11889,11894,11899,11904,11908,11913,11919,11924,11929,11934,11938,11943,11948,11953,11957,11962,11968,11973,11979,11984,11989,9710,11993,11998,12003,12007,12012,12017,12022,12026,12031,12037,12042,9721,12047,12051,12056,12062,12067,12072,12077,12081,12086,12092,12097,12103,12108,12113,12117,12122,12128,12133,12139,12144,12149,12153,12158,12163,12168,12172,12177,12183,9740,12188,12192,9745,12197,12202,12206,2599,12210,12214,12219,12223,12228,12232,845,12236,12240,12245,12250,12254,12259,12263,2613,12267,12271,2610,12275,12279,2596,12283,12287,12292,12296,12300,12304,12307,12311,12315,1733,12319,12323,12327,833,12331,12335,2759,12339,12343,12347,2738,12351,12355,12360,12364,12368,12372,12375,12379,12383,2819,12387,12391,12396,12401,12405,12410,12414,985,12418,12422,12426,2833,12430,12434,12438,12442,12446,2816,12450,12454,12459,12463,12467,10579,12471,12475,12480,12484,12488,3120,12492,12496,12501,12505,12510,12514,816,12518,12522,12527,618,12531,3194,12535,12539,432,12543,12547,12551,3183,12555,12559,12564,12568,12572,324,12576,12580,5230,12585,12590,12594,806,12598,12602,12607,12612,12616,12621,12625,2854,4993,12629,2851,12633,12637,12641,2840,12645,12649,12654,12658,12663,2496,12668,12673,12679,12684,12690,12695,942,12700,12705,12711,12716,12722,12727,2514,12732,12737,2510,12742,12747,12752,3201,12757,12762,12768,12773,12778,10015,12783,12788,12794,12799,12804,10060,12809,12814,12819,10264,12824,12829,10330,12834,12839,12844,10207,12849,12854,12860,12865,12870,12875,12879,12884,12890,12896,12901,12907,12912,3064,12917,12922,12928,12933,12938,12943,12948,12953,12957,12962,12967,12971,2950,12975,12979,12984,12988,12993,842,12997,13001,13006,13010,13015,13019,1400,13023,13027,2957,13031,13035,13039,2947,13043,13047,13052,13056,13061,13066,13070,13075,13081,13086,13092,13097,4878,13102,13107,13113,13118,13124,13129,13133,13138,13143,13147,13152,13157,13162,13166,13171,13177,13182,13187,13192,13196,13201,13207,13212,13216,2547,13220,13224,13229,13234,13238,13243,13247,839,13251,13255,13260,13265,13269,13274,13278,1426,13282,13286,2554,13290,13294,13298,2544,13302,13306,13311,13315,13319,4493,13323,13327,13332,13336,13340,3228,13344,13348,13353,13358,13362,13367,13371,915,13375,13379,13384,13389,13393,13398,13402,3242,13406,13410,13414,3239,13418,13422,13426,3225,13430,13434,13439,13443,13447,2998,13451,13455,13460,13464,13469,13473,836,13477,13481,13486,13490,13495,13499,1311,13503,13507,13511,3009,13515,13519,13523,2995,13527,13531,13536,13540,13545,3271,13550,13556,13561,13567,13572,1060,13577,13582,13588,13594,13599,13605,13610,3289,13615,13620,13625,3285,13630,13635,13640,3267,13645,13650,13656,13661,13667,13672,13677,13682,13686,13691,13697,13702,13708,13713,13718,13722,13727,13732,13736,13741,13746,13751,13755,13760,13766,13771,13776,13781,13785,13790,13796,13802,13807,13813,13818,13823,13827,13832,13838,13844,13849,13855,13860,13865,13869,13874,13879,13883,13888,13894,13899,13905,13910,922,13915,13920,13926,13932,13937,13942,4136,13947,13952,13957,13961,13966,13970,1623,13975,13979,429,13983,13987,13992,13997,14001,14006,14010,2537,14014,14018,1620,14022,14026,14030,852,14034,14038,14043,14047,14052,3114,14056,14060,482,14064,14068,14072,3126,14076,14080,14085,14089,14094,14098,1044,14102,14106,14111,14115,14120,14124,3137,14128,14132,464,14136,14140,14144,3123,14148,14152,14157,14161,14165,14169,14172,14176,14181,14185,14189,2864,14193,14197,14202,14207,14211,14216,14220,926,14224,14228,14233,14237,14242,14246,2878,14250,14254,2875,14258,14262,14266,2861,14270,14274,14279,14283,14287,5159,14291,14295,14300,14304,14309,6491,14314,14319,14325,14331,14336,14342,14347,6483,14352,14357,6509,14362,14367,6505,14372,14377,6487,14382,14387,14393,14398,14403,14408,14412,14417,9839,14423,14428,14433,14437,14442,14448,14453,14459,14464,14469,14473,14478,14484,14489,14494,14499,14504,14508,14513,14517,14522,14528,14533,14538,14543,14547,14552,14558,14563,14567,2678,14571,14575,14580,14584,14589,14593,826,14597,14601,14606,14610,14615,14619,1325,14623,14627,2689,14631,14635,14639,2675,14643,14647,14652,14656,14660,14664,14667,14671,14676,14680,14685,6435,14690,14695,14701,14706,14712,14717,992,14722,14727,14732,4430,14737,14742,6449,14747,14752,14757,6431,14762,9853,14767,14772,14776,14781,14787,14792,14797,1464,14802,14807,14813,14818,14824,14829,3166,14834,14839,14844,3162,14849,14854,9858,3144,14859,14864,14870,14875,14880,14885,14889,14894,14900,14905,14910,812,14915,14920,14926,14932,14937,14943,14948,3342,14953,14958,14963,3338,14968,14973,3320,14978,14983,14989,14994,14999,15004,15008,15013,15019,15024,822,15029,15035,15040,15045,10693,15050,15055,15060,15065,15069,15074,15079,15084,15088,15093,15099,15104,15109,15114,15118,15123,15129,15134,15139,4604,15144,15149,15154,15158,15163,15168,15173,15177,15182,15187,15192,15196,15201,15207,15212,15217,15222,15226,15231,15237,15242,15247,988,15252,15257,15263,15268,15274,15279,327,15284,15289,15294,2305,15299,15304,15309,2696,15314,15319,15325,15330,15335,15340,15344,15349,15355,15360,15364,819,15368,15372,15377,15381,15386,15390,2668,15394,15398,2665,15402,15406,15410,2651,15414,15418,15423,15427,15431,4486,15435,15439,15444,15448,15452,3208,15456,15460,15465,15470,15474,15479,15484,15489,15493,15498,15502,3222,15506,15510,15514,3205,15518,15522,15527,9873,15531,5156,15535,15539,15544,15548,15552,2712,15556,15560,15565,9877,15570,15575,15580,3499,15585,15590,15594,2726,15598,15602,15606,2709,15610,15614,15619,15623,15627,15631,15634,15638,15643,15647,15652,4282,15657,15662,15668,15674,15679,15685,15691,15696,15702,15707,4269,15712,15717,15722,4265,15727,15732,15738,15743,15748,15753,15757,15762,15767,10019,15772,15777,15783,15789,15794,15800,15806,15811,15816,10268,15821,15826,10211,9881,15831,15837,15842,15847,15852,15856,15861,15867,15872,15877,2968,15882,15887,15893,15899,15904,15910,9886,15916,15922,15927,2986,15932,15937,15942,2964,15947,15952,15956,2891,15960,15964,15969,9900,15974,15979,15984,15988,15992,2905,15996,16000,16004,16008,16011,16015,16020,16024,16028,347,16032,16036,16041,16046,16050,16055,16060,16065,16069,16074,16078,1318,16082,16086,16090,670,16094,16098,16103,16107,16111,1124,16115,16119,16124,0,0,0,15590,2547,13443,15882,14291,6435,13615,13418,2599,13771,13332,11894,12267,1318,11747,13844,12590,915,0,0,12768,11659,13594,15932,2840,11563,15717,14120,11763,12149,16024,12459,15340,12438,1044,3222,0,15753,13398,15674,3114,3183,15325,13234,5159,14237,14193,13869,15242,0,15284,3338,14319,0,3009,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2995,16128,0,0,16132,0,0,0,16136,0,0,0,0,0,0,0,16140,16144,0,16148,0,16152,0,0,0,0,0,0,16157,0,0,0,16162,0,0,0,16168,0,0,0,0,0,0,0,0,0,0,0,0,16174,0,0,0,0,0,0,3030,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16181,0,0,0,0,0,0,3016,16187,0,0,16192,0,0,0,16197,0,0,0,0,0,0,0,16202,16207,0,16212,0,16217,0,0,0,0,0,0,6584,0,0,0,16223,0,0,0,16228,0,0,0,0,0,0,0,16233,0,0,0,0,0,0,0,0,0,0,0,16238,16242,0,0,16247,0,0,0,16252,0,0,0,0,0,0,0,16257,16262,0,0,0,16267,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2998,16273,0,0,16277,0,0,0,16281,0,0,0,0,0,0,0,16285,16289,0,16293,0,16297,0,0,0,0,0,0,806,16302,0,0,1655,0,0,0,16306,16310,0,0,0,0,0,0,16315,16319,0,16323,16327,16332,0,0,0,0,0,0,16337,16341,0,0,16346,0,0,0,16351,0,0,0,0,0,0,0,16356,16361,0,16366,16371,16377,0,0,0,0,0,0,4073,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16383,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16389,16393,0,0,16398,0,0,0,16403,0,16408,0,0,0,0,0,16414,16419,0,16424,16429,16435,0,0,0,0,0,0,2851,16441,0,0,16445,0,0,0,16449,0,0,0,0,0,0,0,16453,16457,0,16461,0,16465,0,0,0,0,0,0,16470,0,0,0,16475,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16481,0,0,0,0,0,0,0,4061,0,0,0,16488,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2854,16493,0,0,16497,0,0,0,16501,0,0,0,0,0,0,0,16505,16509,0,16513,0,16517,0,0,0,0,4993,0,2857,0,0,0,16522,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16527,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16532,0,0,0,16536,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16541,0,16546,0,0,0,0,0,0,4069,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2840,16552,0,0,16556,0,0,0,16560,0,0,0,0,0,0,0,10627,16564,0,16568,0,16572,0,0,0,0,0,0,16577,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16582,0,0,0,0,0,0,0,3613,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3617,16589,0,0,16594,0,0,0,16599,0,0,0,0,0,0,0,16604,16609,0,0,0,16614,0,0,0,0,0,0,16620,0,0,0,16624,0,0,0,16629,0,0,0,0,0,0,0,16634,0,0,0,0,16639,0,0,0,0,0,0,16645,16649,0,0,16654,0,0,16659,16664,0,16669,0,0,0,0,0,16675,16680,0,16685,0,0,0,0,0,0,0,0,4065,0,0,0,16690,0,0,0,16695,0,0,0,0,0,0,0,16700,16705,0,0,0,0,0,0,0,0,0,0,324,16710,0,0,16714,0,0,0,16718,0,0,0,0,0,0,0,16722,16726,0,16730,0,16734,0,0,0,0,0,0,803,16739,16743,0,16748,0,0,0,16752,0,16756,0,0,0,0,0,16761,16765,0,16769,16773,16778,0,0,0,11713,0,0,16783,16787,0,0,16792,0,0,0,16797,0,0,0,0,0,0,0,16802,16807,0,16812,16817,16823,0,0,0,0,0,0,16829,16833,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16838,16842,0,0,16847,0,0,0,16852,0,0,0,0,0,0,0,16857,16862,0,16867,16872,16878,0,0,0,0,0,0,3468,16884,0,0,16888,0,0,0,16892,0,0,0,0,0,0,0,16896,16900,0,16904,0,16908,0,0,0,0,0,0,16913,0,0,0,16918,0,0,0,16924,0,0,0,0,0,0,0,16930,16936,0,0,16942,16949,0,0,0,0,0,0,16956,0,0,0,0,0,0,0,16960,0,0,0,0,0,0,0,0,16965,0,16970,0,0,0,0,0,0,0,0,3471,16975,0,0,16979,0,0,0,16983,0,0,0,0,0,0,0,16987,16991,0,16995,0,16999,0,0,0,0,0,0,2779,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17004,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17010,0,0,0,17014,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17019,0,0,0,17023,0,0,0,17028,0,0,0,0,0,0,0,0,17033,0,17038,0,0,0,0,0,0,0,0,3454,17043,0,0,17047,0,0,17051,17055,0,17059,0,0,0,0,0,17064,17068,0,17072,0,17076,0,0,0,0,0,0,17081,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17086,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3577,0,0,0,17093,0,0,0,17098,0,0,0,0,0,0,0,17103,0,0,17108,0,0,0,0,0,0,0,0,17113,0,0,0,17117,0,0,0,17122,0,0,0,0,0,0,0,17127,0,0,17132,0,17137,0,0,0,0,0,0,17143,0,0,0,17147,0,0,0,17152,0,0,0,0,0,0,0,17157,17162,0,17167,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3457,17172,0,0,17176,0,0,0,17180,0,0,0,0,0,0,0,17184,17188,0,17192,0,17196,0,0,0,0,0,0,429,17201,0,0,10033,0,0,0,17205,0,0,0,0,17209,0,0,17214,17218,0,17222,0,17226,0,0,0,0,0,0,1616,17231,0,0,17236,0,0,0,17241,0,0,0,0,0,0,0,17246,17251,0,17256,17261,17267,0,0,0,0,0,0,17273,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17277,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17283,17287,0,0,17292,0,0,0,17297,0,17302,0,0,0,0,0,17308,17313,0,17318,0,17323,0,0,0,0,0,0,1620,17329,0,0,10282,0,0,0,17333,0,0,0,0,0,0,0,17337,17341,0,17345,0,10541,0,0,0,0,0,0,17349,17354,0,0,17360,0,0,0,17366,0,0,0,0,0,0,0,17372,17378,0,17384,17390,17397,0,0,0,0,0,0,17404,0,0,0,17408,0,0,0,17413,0,0,0,0,0,0,0,0,17418,0,0,0,0,0,0,0,0,0,0,2537,17423,0,0,9220,0,0,0,17427,0,0,0,0,17431,0,0,17436,17440,0,17444,0,17448,0,0,0,14006,0,0,4211,17453,0,0,17458,0,0,0,17463,0,0,0,0,0,0,0,0,0,0,17468,0,17473,0,0,0,0,0,0,17479,17484,0,0,17490,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17496,0,17502,0,0,0,0,0,0,1631,17509,0,0,17514,0,0,0,17519,0,0,0,0,0,0,0,0,17524,0,17529,0,17534,0,0,0,0,0,0,17540,0,0,0,17544,0,0,0,17549,0,0,0,0,0,0,0,0,17554,0,17559,0,0,0,0,0,0,0,0,2523,17564,0,0,10169,0,0,0,17568,0,0,0,0,17572,0,0,17577,0,0,17581,0,17585,0,0,0,0,0,0,17590,0,0,0,17595,0,0,0,17601,0,0,0,0,0,0,0,17607,0,0,0,0,17613,0,0,0,0,0,0,4198,17620,0,0,915,17625,17629,17634,1639,17639,17644,17649,17653,17657,17662,17667,17672,17677,17682,17687,17692,17696,17700,17705,17709,17714,17719,17723,17727,13367,13375,17731,17735,17739,17744,17750,17756,17761,17767,17773,17778,17783,17789,17795,17801,17807,17813,17819,17825,17830,17835,17841,17846,17852,17858,17863,17868,17873,17878,17883,6640,17888,17893,17899,17905,17910,17916,17922,17927,17932,17938,17944,17950,17956,17962,17968,17974,17979,17984,17990,17995,18001,18007,18012,18017,18022,18027,18032,18037,18042,18048,18055,18062,18068,18075,18082,18088,18094,18101,18108,18115,18122,18129,18136,18143,18149,18155,18162,18168,18175,18182,18188,18194,18200,18206,18212,1140,18218,18223,18229,18235,18240,18246,18252,18257,18262,18268,18274,18280,18286,18292,18298,18304,18309,18314,18320,18325,18331,18337,18342,18347,18352,18357,18362,3239,18367,18371,18376,10345,18381,18386,18391,18395,18399,18404,18409,18414,18419,18424,18429,18434,18438,18442,18447,18451,18456,18461,18465,18469,13410,13418,18473,18477,18482,18488,18495,18502,18508,18515,18522,18528,18534,18541,18548,18555,18562,18569,18576,18583,18589,18595,18602,18608,18615,18622,18628,18634,18640,18646,18652,6662,18658,18663,18669,18675,18680,18686,18692,18697,18702,18708,18714,18720,18726,18732,18738,18744,18749,18754,18760,18765,18771,18777,18782,18787,18792,18797,18802,3242,18807,18811,18816,18821,18825,18830,18835,18839,18843,18848,18853,18858,18863,18868,18873,18878,18882,18886,18891,18895,18900,18905,18909,18913,13398,13406,18917,3245,18921,18926,18932,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,6,24,54,24,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18938,18943,18949,18955,18960,18965,18971,18977,18983,18989,18995,19001,19007,19012,19017,19023,19028,19034,19040,19045,19050,19055,19060,19065,19070,19075,19081,19088,19095,19101,19108,19115,19121,19127,19134,19141,19148,19155,19162,19169,19176,19182,19188,19195,19201,19208,19215,19221,19227,19233,19239,19245,19251,19255,19260,19266,19272,19277,19283,19289,19294,19299,19305,19311,19317,19323,19329,19335,19341,19346,19351,19357,19362,19368,19374,19379,19384,19389,19394,19399,6666,19404,19409,19415,10825,19421,19427,19433,19438,19443,19449,19455,19461,19467,19473,19479,19485,19490,19495,19501,19506,19512,19518,19523,19528,19533,19538,19543,3225,19548,19552,19557,19562,19566,19571,19576,19580,19584,19589,19594,19599,19604,19609,19614,19619,19623,19627,19632,19636,19641,19646,19650,19654,13422,13430,19658,19662,19667,19673,19680,19687,19693,19700,19707,19713,19719,19726,19733,19740,19747,19754,19761,19768,19774,19780,19787,19793,19800,19807,19813,19819,19825,19831,19837,3263,19843,19848,19854,19860,19865,19871,19877,19882,19887,19893,19899,19905,19911,19917,19923,19929,19934,19939,19945,19950,19956,19962,19967,19972,19977,19982,19987,3249,19992,19997,20003,20009,20014,20020,20026,20031,20036,20042,20048,20054,20060,20066,20072,20078,20083,20088,20094,20099,20105,20111,20116,20121,20126,20131,20136,6644,20141,20146,20152,20158,20163,20169,20175,20180,20185,20191,20197,20203,20209,20215,20221,20227,20232,20237,20243,20248,20254,20260,20265,20270,20275,20280,20285,20290,20294,20299,20305,20311,20316,20322,20328,20333,20338,20344,20350,20356,20362,20368,20374,20380,20385,20390,20396,7478,8154,20401,20406,20411,20416,20421,20426,6648,20431,20436,20442,20448,20453,20459,20465,20470,20475,20481,20487,20493,20499,20505,20511,20517,20522,20527,20533,20538,20544,20550,20555,20560,20565,20570,20575,3228,20580,20584,20589,20594,20598,20603,20608,20612,20616,20621,20626,20631,20636,20641,20646,20651,20655,20659,20664,20668,20673,20678,20682,20686,13336,13344,20690,1060,20694,20699,20705,20711,20716,20722,20728,20733,20738,20744,20750,20756,20762,20768,20774,20780,20785,20790,20796,20801,20807,20813,20818,20823,13567,13577,20828,20833,20838,20844,20851,20858,20864,20871,20878,20884,20890,20897,20904,20911,20918,20925,20932,20939,20945,20951,20958,20964,20971,20978,20984,20990,20996,21002,21008,21014,21019,21025,21032,21039,21045,21052,21059,21065,21071,21078,21085,21092,21099,21106,21113,21120,21126,21132,21139,21145,21152,21159,21165,21171,21177,21183,21189,21195,21201,21208,21216,21224,21231,21239,21247,21254,21261,21269,21277,21285,21293,21301,21309,21317,21324,21331,21339,21346,21354,21362,21369,21376,21383,21390,21397,21404,21409,21415,21422,21429,21435,21442,21449,21455,21461,21468,21475,21482,21489,21496,21503,21510,21516,21522,21529,21535,21542,21549,21555,21561,21567,21573,21579,3285,21585,21590,21596,21602,21607,21613,21619,21624,21629,21635,21641,21647,21653,21659,21665,21671,21676,21681,21687,21692,21698,21704,21709,21714,13620,13630,21719,21724,21730,21737,21745,21753,21760,21768,21776,21783,21790,21798,21806,21814,21822,21830,21838,21846,21853,21860,21868,21875,21883,21891,21898,21905,21912,21919,21926,21933,21938,21944,21951,21958,21964,21971,21978,21984,21990,21997,22004,22011,22018,22025,22032,22039,22045,22051,22058,22064,22071,22078,22084,22090,22096,22102,22108,3289,22114,22119,22125,22131,22136,22142,22148,22153,22158,22164,22170,22176,22182,22188,22194,22200,22205,22210,22216,22221,22227,22233,22238,22243,13605,13615,22248,6410,22253,22259,22266,22273,22279,22286,22293,22299,22305,22312,22319,22326,22333,22340,22347,22354,22360,22366,22373,22379,22386,22393,22399,22405,22411,22417,22423,22429,22435,22442,22450,22458,22465,22473,22481,22488,22495,22503,22511,22519,22527,22535,22543,22551,22558,22565,22573,22580,22588,22596,22603,22610,22617,22624,22631,22638,22643,22649,22656,22663,22669,22676,22683,22689,22695,22702,22709,22716,22723,22730,22737,22744,22750,22756,22763,22769,22776,22783,22789,22795,22801,22807,22813,22819,22824,22830,22837,22844,22850,22857,22864,22870,22876,22883,22890,22897,22904,22911,22918,22925,22931,22937,22944,22950,22957,22964,22970,22976,22982,22988,22994,3267,23000,23005,23011,23017,23022,23028,23034,23039,23044,23050,23056,23062,23068,23074,23080,23086,23091,23096,23102,23107,23113,23119,23124,23129,13635,13645,23134,23139,23145,23152,23160,23168,23175,23183,23191,23198,23205,23213,23221,23229,23237,23245,23253,23261,23268,23275,23283,23290,23298,23306,23313,23320,23327,23334,23341,6426,23348,23354,23361,23368,23374,23381,23388,23394,23400,23407,23414,23421,23428,23435,23442,23449,23455,23461,23468,23474,23481,23488,23494,23500,23506,23512,23518,6415,23524,23530,23537,23544,23550,23557,23564,23570,23576,23583,23590,23597,23604,23611,23618,23625,23631,23637,23644,23650,23657,23664,23670,23676,23682,23688,23694,23700,23705,23711,23718,23725,23731,23738,23745,23751,23757,23764,23771,23778,23785,23792,23799,23806,23812,23818,23825,23831,23838,23845,23851,23857,23863,23869,23875,23881,23886,23892,23899,23906,23912,23919,23926,23932,23938,23945,23952,23959,23966,23973,23980,23987,23993,23999,24006,24012,24019,24026,24032,24038,24044,24050,24056,24062,24067,24073,24080,24087,24093,24100,24107,24113,24119,24126,24133,24140,24147,24154,24161,24168,24174,24180,24187,24193,24200,24207,24213,24219,24225,24231,24237,3271,24243,24248,24254,24260,24265,24271,24277,24282,24287,24293,24299,24305,24311,24317,24323,24329,24334,24339,24345,24350,24356,24362,24367,24372,13540,24377,24382,842,24387,24391,24396,24401,24405,24410,24415,24419,24423,24428,24433,24438,24443,24448,24453,24458,24462,24466,24471,24475,24480,24485,24489,24493,24497,12997,3488,24501,24505,24510,24516,24522,24527,24533,24539,24544,24549,24555,24561,24567,24573,24579,24585,24591,24596,24601,24607,24612,24618,24624,24629,24634,24639,24644,24649,848,24654,24659,24665,10893,24671,24677,24683,24688,24693,24699,24705,24711,24717,24723,24729,24735,24740,24745,24751,24756,24762,24768,24773,24778,24783,24788,24793,24798,24803,24809,24816,24823,24829,24836,24843,24849,24855,24862,24869,24876,24883,24890,24897,24904,24910,24916,24923,24929,24936,24943,24949,24955,24961,24967,24973,24979,24983,24988,24994,25000,25005,25011,25017,25022,25027,25033,25039,25045,25051,25057,25063,25069,25074,25079,25085,25090,25096,25102,25107,25112,25117,25122,25127,2957,25132,25136,25141,25146,25150,25155,25160,25164,25168,25173,25178,25183,25188,25193,25198,25203,25207,25211,25216,25220,25225,25230,25234,25238,25242,13031,25246,25250,25255,25261,25268,25275,25281,25288,25295,25301,25307,25314,25321,25328,25335,25342,25349,25356,25362,25368,25375,25381,25388,25395,25401,25407,25413,25419,25425,2982,25431,25436,25442,25448,25453,25459,25465,25470,25475,25481,25487,25493,25499,25505,25511,25517,25522,25527,25533,25538,25544,25550,25555,25560,25565,25570,25575,1400,25580,25584,25589,25594,25598,25603,25608,25612,25616,25621,25626,25631,25636,25641,25646,25651,25655,25659,25664,25668,25673,25678,25682,25686,13015,13023,25690,2960,25694,25699,25705,25711,25716,25722,25728,25733,25738,25744,25750,25756,25762,25768,25774,25780,25785,25790,25796,25801,25807,25813,25818,25823,25828,25833,25838,25843,25848,25854,25861,25868,25874,25881,25888,25894,25900,25907,25914,25921,25928,25935,25942,25949,25955,25961,25968,25974,25981,25988,25994,26000,26006,26012,26018,26024,26028,26033,26039,26045,26050,26056,26062,26067,26072,26078,26084,26090,26096,26102,26108,26114,26119,26124,26130,26135,26141,26147,26152,26157,26162,26167,26172,2986,26177,26182,26188,26194,26199,26205,26211,26216,26221,26227,26233,26239,26245,26251,26257,26263,26268,26273,26279,26284,26290,26296,26301,26306,15922,15932,26311,2947,26316,26320,26325,26330,26334,26339,26344,26348,26352,26357,26362,26367,26372,26377,26382,10623,26387,26391,26396,26400,26405,26410,26414,26418,13035,13043,26422,26426,26431,26437,26444,26451,26457,26464,26471,26477,26483,26490,26497,26504,26511,26518,26525,26532,26538,26544,26551,26557,26564,26571,26577,26583,26589,26595,26601,3791,26607,26612,26618,26624,26629,26635,26641,26646,26651,26657,26663,26669,26675,26681,26687,26693,26698,26703,26709,26714,26720,26726,26731,26736,26741,26746,26751,26756,26760,26765,26771,26777,26782,26788,26794,26799,26804,26810,26816,26822,26828,26834,26840,26846,26851,26856,26862,26867,26873,26879,26884,26889,26894,26899,26904,2964,26909,26914,26920,26926,26931,26937,26943,26948,26953,26959,26965,26971,26977,26983,26989,26995,27000,27005,27011,27016,27022,27028,27033,27038,15937,15947,27043,27048,27052,27057,27063,27069,27074,27080,27086,27091,27096,27102,27108,27114,27120,27126,27132,27138,27143,27148,27154,27159,27165,27171,27176,27181,27186,27191,27196,2968,27201,27206,27212,27218,27223,27229,27235,27240,27245,27251,27257,27263,27269,27275,27281,27287,27292,27297,27303,27308,27314,27320,27325,27330,15872,15882,27335,2950,27340,27344,27349,27354,27358,27363,27368,27372,27376,27381,27386,27391,27396,27401,27406,27411,27415,27419,27424,27428,27433,27438,27442,27446,12967,12975,27450,816,27454,27458,27463,27468,27472,27477,27482,681,27486,27491,27496,27501,27506,27511,27516,27521,10919,27525,27530,27534,27539,27544,27548,27552,12510,12518,27556,27560,27564,27569,27575,27581,27586,27592,27598,27603,27608,27614,27620,27626,27632,27638,27644,27650,27655,27660,27666,27671,27677,27683,27688,27693,27698,27703,27708,27713,27717,27722,27728,27734,27739,27745,27751,27756,27761,27767,27773,27779,27785,27791,27797,27803,27808,27813,27819,27824,27830,27836,27841,27846,27851,27856,27861,27866,27871,27877,27884,27891,27897,27904,27911,27917,27923,27930,27937,27944,27951,27958,27965,27972,27978,27984,27991,27997,28004,28011,28017,28023,28029,28035,28041,28047,28051,28056,28062,28068,28073,28079,28085,28090,28095,28101,28107,28113,28119,28125,28131,28137,28142,28147,28153,28158,28164,28170,28175,28180,28185,28190,28195,432,82,28200,28205,28210,28214,28219,28224,28228,28232,28237,28242,28247,28252,28257,28262,28267,28271,28275,28280,28284,28289,28294,28298,28302,28306,12543,28310,28314,28319,28325,28332,28339,28345,28352,28359,28365,28371,28378,28385,28392,28399,28406,28413,28420,28426,28432,28439,28445,28452,28459,28465,28471,28477,28483,28489,28495,28499,28504,28510,28516,28521,28527,28533,28538,28543,28549,28555,28561,28567,28573,28579,28585,28590,28595,28601,28606,28612,28618,28623,28628,28633,28638,28643,3194,28648,28652,28657,1643,28662,28667,28672,28676,28680,28685,28690,28695,28700,28705,28710,28715,28719,28723,28728,28732,28737,28742,28746,28750,618,12535,28754,3197,28758,28763,28769,28775,28780,28786,28792,28797,28802,28808,28814,28820,28826,28832,28838,28844,28849,28854,28860,28865,28871,28877,28882,28887,28892,28897,28902,28907,28912,28918,28925}
index 6131319ca8d39878b58c7bda481f5ad38fbf6655..797acbb55eebccd279cbcc0be9cdec69a66e84fd 100644 (file)
@@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim.
 """
 from pathlib import Path
 
-PHPLIB_DIR = None
 DATA_DIR = None
 SQLLIB_DIR = None
 CONFIG_DIR = (Path(__file__) / '..' / 'resources' / 'settings').resolve()
index 2294834fc1815a70120f8751890a5871fe17c380..796ff08b3519a205b5f8a926a7a46e17c0a89908 100644 (file)
@@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim.
 """
 from pathlib import Path
 
-PHPLIB_DIR = None
 DATA_DIR = (Path(__file__) / '..' / 'resources').resolve()
 SQLLIB_DIR = (DATA_DIR / 'lib-sql')
 CONFIG_DIR = (DATA_DIR / 'settings')
index 39e703dc20916afe4fa3826cd2239a99ed209129..184ab4c6cd42e6a924623cd70c74ad2f630b75a7 100755 (executable)
@@ -2,4 +2,4 @@
 
 from nominatim_db import cli
 
-exit(cli.nominatim(module_dir=None, osm2pgsql_path=None))
+exit(cli.nominatim(osm2pgsql_path=None))
diff --git a/phpcs.xml b/phpcs.xml
deleted file mode 100644 (file)
index ab9d396..0000000
--- a/phpcs.xml
+++ /dev/null
@@ -1,172 +0,0 @@
-<?xml version="1.0"?>
-<ruleset name="Nominatim Standard">
-
-  <description>Nominatim coding standard</description>
-
-  <!-- based on another standard, you can find it here -->
-  <!-- /usr/share/php/PHP/CodeSniffer/Standards/PSR2/ruleset.xml -->
-  <!-- https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/PSR2/ruleset.xml -->
-  <rule ref="PSR2"/>
-
-  <exclude-pattern>./lib/template/*html*</exclude-pattern>
-  <exclude-pattern>./lib/template/includes/</exclude-pattern>
-  <exclude-pattern>./module/</exclude-pattern>
-  <exclude-pattern>./website/css</exclude-pattern>
-  <exclude-pattern>./website/js</exclude-pattern>
-
-  <rule ref="Generic.Files.LineLength">
-    <properties>
-     <property name="lineLimit" value="194"/>
-     <property name="absoluteLineLimit" value="194"/>
-    </properties>
-  </rule>
-
-
-  <!-- "A file should declare new symbols (classes, functions, constants, etc.) and cause no
-    other side effects, or it should execute logic with side effects, but should not do both."
-    ... we have too many script and includes to be able to enforce that.
-     -->
-  <rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
-    <severity>0</severity>
-  </rule>
-
-
-
-  <!-- eval, system, etc -->
-  <rule ref="Generic.PHP.ForbiddenFunctions">
-    <properties>
-      <property name="forbiddenFunctions" type="array" value="sizeof=>count,delete=>unset,print=>echo,create_function=>null,eval=>null"/>
-    </properties>
-  </rule>
-
-  <!-- **************************************************************
-       DOCUMENTATION
-       ************************************************************** -->
-
-  <rule ref="PEAR.Commenting.FunctionComment.Missing">
-    <severity>0</severity>
-  </rule>
-
-
-
-
-  <!-- **************************************************************
-       COMMENTS
-       ************************************************************** -->
-
-  <!-- any comments in the lines before function() are better than forcing
-       a PHPdoc style right now -->
-  <rule ref="PEAR.Commenting.FunctionComment.WrongStyle">
-    <severity>0</severity>
-  </rule>
-
-  <!-- We allow comments after statements -->
-  <rule ref="Squiz.Commenting.PostStatementComment.Found">
-    <severity>0</severity>
-  </rule>
-  <!-- ... even without space e.g. //some words -->
-  <rule ref="Squiz.Commenting.InlineComment.NoSpaceBefore">
-    <severity>0</severity>
-  </rule>
-  <!-- blank lines after inline comments are fine -->
-  <rule ref="Squiz.Commenting.InlineComment.SpacingAfter">
-    <severity>0</severity>
-  </rule>
-
-  <!-- Comments don't have to start uppercase -->
-  <rule ref="Squiz.Commenting.InlineComment.NotCapital">
-    <severity>0</severity>
-  </rule>
-  <!-- Comments don't have to end with one of .!? -->
-  <rule ref="Squiz.Commenting.InlineComment.InvalidEndChar">
-    <severity>0</severity>
-  </rule>
-  <!-- Empty comments are fine -->
-  <rule ref="Squiz.Commenting.InlineComment.Empty">
-    <severity>0</severity>
-  </rule>
-
-
-
-
-  <!-- **************************************************************
-       INDENTATION, SPACING
-       ************************************************************** -->
-
-  <rule ref="Squiz.Arrays.ArrayDeclaration.KeyNotAligned" />
-
-  <!-- Aligned looks nicer, but causes too many warnings currently -->
-  <rule ref="Squiz.Arrays.ArrayDeclaration.DoubleArrowNotAligned">
-    <severity>0</severity>
-  </rule>
-
-
-
-
-
-
-  <!-- **************************************************************
-       VARIABLES
-       ************************************************************** -->
-
-  <!-- CONST_this_var is fine, we don't need ConstThisVar -->
-  <rule ref="Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase">
-    <severity>0</severity>
-  </rule>
-
-  <!-- simply disagree with "Each line in an array declaration must end in a comma" -->
-  <rule ref="Squiz.Arrays.ArrayDeclaration.NoCommaAfterLast">
-    <severity>0</severity>
-  </rule>
-  <rule ref="Squiz.Arrays.ArrayDeclaration.NoComma">
-    <severity>0</severity>
-  </rule>
-
-  <!-- We allow "$abc = array($aPoint[1], $aPoint[2])" -->
-  <rule ref="Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed">
-    <severity>0</severity>
-  </rule>
-
-  <!-- array() instead of [] for initialisation -->
-  <rule ref="Generic.Arrays.DisallowShortArraySyntax.Found" />
-
-
-
-
-  <!-- **************************************************************
-       STRING QUOTING
-       ************************************************************** -->
-
-  <!-- Prefer single quoted strings -->
-  <rule ref="Squiz.Strings.DoubleQuoteUsage" />
-
-  <!-- We allow variabled inside double-quoted strings "abc $somevar" -->
-  <rule ref="Squiz.Strings.DoubleQuoteUsage.ContainsVar">
-    <severity>0</severity>
-  </rule>
-
-
-
-
-
-  <!-- **************************************************************
-       CONTROL STRUCTURES
-       ************************************************************** -->
-
-  <!-- we allow "if (a) echo 'b'" without brackets -->
-  <rule ref="Generic.ControlStructures.InlineControlStructure.NotAllowed">
-    <severity>0</severity>
-  </rule>
-
-  <!-- We allow "if (a)". No need for "if (a === TRUE)" -->
-  <rule ref="Squiz.Operators.ComparisonOperatorUsage.ImplicitTrue">
-    <severity>0</severity>
-  </rule>
-  <!-- ... same for "if (!a)" -->
-  <rule ref="Squiz.Operators.ComparisonOperatorUsage.NotAllowed">
-    <severity>0</severity>
-  </rule>
-
-
-
-</ruleset>
index f4c33e7720abda310c95fd5012a4aaf5000a5baa..b8c666677ff04616252e0ecf38ba3505ce3a668b 100644 (file)
@@ -12,24 +12,12 @@ NOMINATIM_DATABASE_DSN="pgsql:dbname=nominatim"
 # Nominatim sets up read-only access for this user during installation.
 NOMINATIM_DATABASE_WEBUSER="www-data"
 
-# Directory where to find the PostgreSQL server module.
-# When empty the module is expected to be located in the 'module' subdirectory
-# in the project directory.
-# Changing this value requires to run 'nominatim refresh --functions'.
-NOMINATIM_DATABASE_MODULE_PATH=
-
 # Tokenizer used for normalizing and parsing queries and names.
 # The tokenizer is set up during import and cannot be changed afterwards
 # without a reimport.
 # Currently available tokenizers: icu, legacy
 NOMINATIM_TOKENIZER="icu"
 
-# Number of occurrences of a word before it is considered frequent.
-# Similar to the concept of stop words. Frequent partial words get ignored
-# or handled differently during search.
-# Changing this value requires a reimport.
-NOMINATIM_MAX_WORD_FREQUENCY=50000
-
 # If true, admin level changes on places with many contained children are blocked.
 NOMINATIM_LIMIT_REINDEXING=yes
 
@@ -40,12 +28,6 @@ NOMINATIM_LIMIT_REINDEXING=yes
 # Currently only affects the initial import of country names and special phrases.
 NOMINATIM_LANGUAGES=
 
-# Rules for normalizing terms for comparisons.
-# The default is to remove accents and punctuation and to lower-case the
-# term. Spaces are kept but collapsed to one standard space.
-# Changing this value requires a reimport.
-NOMINATIM_TERM_NORMALIZATION=":: NFD (); [[:Nonspacing Mark:] [:Cf:]] >;  :: lower (); [[:Punctuation:][:Space:]]+ > ' '; :: NFC ();"
-
 # Configuration file for the tokenizer.
 # The content depends on the tokenizer used. If left empty the default settings
 # for the chosen tokenizer will be used. The configuration can only be set
@@ -177,16 +159,6 @@ NOMINATIM_MAPICON_URL=
 # When unset, the local language (i.e. the name tag without suffix) will be used.
 NOMINATIM_DEFAULT_LANGUAGE=
 
-# Enable a special batch query mode.
-# This feature is currently undocumented and potentially broken.
-NOMINATIM_SEARCH_BATCH_MODE=no
-
-# Threshold for searches by name only.
-# Threshold where the lookup strategy in the database is switched. If there
-# are less occurrences of a tem than given, the search does the lookup only
-# against the name, otherwise it uses indexes for name and address.
-NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD=500
-
 # Maximum number of OSM ids accepted by /lookup.
 NOMINATIM_LOOKUP_MAX_COUNT=50
 
index ac5798625cc4900c8de1227892ce67da0716a0bd..ff0db39f820b51585abd3fd4723ee886a455027b 100644 (file)
@@ -7,7 +7,8 @@
 """
 Implementation of classes for API access via libraries.
 """
-from typing import Mapping, Optional, Any, AsyncIterator, Dict, Sequence, List, Tuple, cast
+from typing import Mapping, Optional, Any, AsyncIterator, Dict, Sequence, List,\
+                   Union, Tuple, cast
 import asyncio
 import sys
 import contextlib
@@ -41,7 +42,7 @@ class NominatimAPIAsync: #pylint: disable=too-many-instance-attributes
 
         This class should usually be used as a context manager in 'with' context.
     """
-    def __init__(self, project_dir: Path,
+    def __init__(self, project_dir: Optional[Union[str, Path]] = None,
                  environ: Optional[Mapping[str, str]] = None,
                  loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
         """ Initiate a new frontend object with synchronous API functions.
@@ -365,7 +366,7 @@ class NominatimAPI:
         This class should usually be used as a context manager in 'with' context.
     """
 
-    def __init__(self, project_dir: Path,
+    def __init__(self, project_dir: Optional[Union[str, Path]] = None,
                  environ: Optional[Mapping[str, str]] = None) -> None:
         """ Initiate a new frontend object with synchronous API functions.
 
index 0269cf1f57edd7699853d6eaf2ba9e5388dda425..b4346ee61acf1ef79b037fc8c20e54e0150e52fe 100644 (file)
@@ -167,8 +167,7 @@ class SearchBuilder:
         expected_count = sum(t.count for t in hnrs)
 
         partials = {t.token: t.addr_count for trange in address
-                       for t in self.query.get_partials_list(trange)
-                       if t.is_indexed}
+                       for t in self.query.get_partials_list(trange)}
 
         if not partials:
             # can happen when none of the partials is indexed
@@ -219,11 +218,9 @@ class SearchBuilder:
         addr_partials = [t for r in address for t in self.query.get_partials_list(r)]
         addr_tokens = list({t.token for t in addr_partials})
 
-        partials_indexed = all(t.is_indexed for t in name_partials.values()) \
-                           and all(t.is_indexed for t in addr_partials)
         exp_count = min(t.count for t in name_partials.values()) / (2**(len(name_partials) - 1))
 
-        if (len(name_partials) > 3 or exp_count < 8000) and partials_indexed:
+        if (len(name_partials) > 3 or exp_count < 8000):
             yield penalty, exp_count, dbf.lookup_by_names(list(name_partials.keys()), addr_tokens)
             return
 
@@ -232,8 +229,6 @@ class SearchBuilder:
         name_fulls = self.query.get_tokens(name, TokenType.WORD)
         if name_fulls:
             fulls_count = sum(t.count for t in name_fulls)
-            if partials_indexed:
-                penalty += 1.2 * sum(t.penalty for t in addr_partials if not t.is_indexed)
 
             if fulls_count < 80000 or addr_count < 50000:
                 yield penalty,fulls_count / (2**len(addr_tokens)), \
@@ -243,8 +238,7 @@ class SearchBuilder:
         # To catch remaining results, lookup by name and address
         # We only do this if there is a reasonable number of results expected.
         exp_count = exp_count / (2**len(addr_tokens)) if addr_tokens else exp_count
-        if exp_count < 10000 and addr_count < 20000\
-           and all(t.is_indexed for t in name_partials.values()):
+        if exp_count < 10000 and addr_count < 20000:
             penalty += 0.35 * max(1 if name_fulls else 0.1,
                                   5 - len(name_partials) - len(addr_tokens))
             yield penalty, exp_count,\
@@ -260,11 +254,10 @@ class SearchBuilder:
         addr_restrict_tokens = []
         addr_lookup_tokens = []
         for t in addr_partials:
-            if t.is_indexed:
-                if t.addr_count > 20000:
-                    addr_restrict_tokens.append(t.token)
-                else:
-                    addr_lookup_tokens.append(t.token)
+            if t.addr_count > 20000:
+                addr_restrict_tokens.append(t.token)
+            else:
+                addr_lookup_tokens.append(t.token)
 
         if addr_restrict_tokens:
             lookup.append(dbf.FieldLookup('nameaddress_vector',
@@ -289,7 +282,7 @@ class SearchBuilder:
             addr_restrict_tokens = []
             addr_lookup_tokens = [t.token for t in addr_partials if t.is_indexed]
         else:
-            addr_restrict_tokens = [t.token for t in addr_partials if t.is_indexed]
+            addr_restrict_tokens = [t.token for t in addr_partials]
             addr_lookup_tokens = []
 
         return dbf.lookup_by_any_name([t.token for t in name_fulls],
index 7bd2b09259d70facf28c5505f2c923f5e7aab46e..c2a265105a69d08eb3d7d8a75331e4a8c4d61dc9 100644 (file)
@@ -123,7 +123,7 @@ class ICUToken(qmod.Token):
             lookup_word = row.word_token
 
         return ICUToken(penalty=penalty, token=row.word_id, count=max(1, count),
-                        lookup_word=lookup_word, is_indexed=True,
+                        lookup_word=lookup_word,
                         word_token=row.word_token, info=row.info,
                         addr_count=max(1, addr_count))
 
@@ -264,7 +264,9 @@ class ICUQueryAnalyzer(AbstractQueryAnalyzer):
             if len(part.token) <= 4 and part[0].isdigit()\
                and not node.has_tokens(i+1, qmod.TokenType.HOUSENUMBER):
                 query.add_token(qmod.TokenRange(i, i+1), qmod.TokenType.HOUSENUMBER,
-                                ICUToken(0.5, 0, 1, 1, part.token, True, part.token, None))
+                                ICUToken(penalty=0.5, token=0,
+                                         count=1, addr_count=1, lookup_word=part.token,
+                                         word_token=part.token, info=None))
 
 
     def rerank_tokens(self, query: qmod.QueryStruct, parts: QueryParts) -> None:
diff --git a/src/nominatim_api/search/legacy_tokenizer.py b/src/nominatim_api/search/legacy_tokenizer.py
deleted file mode 100644 (file)
index c7b1011..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Implementation of query analysis for the legacy tokenizer.
-"""
-from typing import Tuple, Dict, List, Optional, Iterator, Any, cast
-from copy import copy
-from collections import defaultdict
-import dataclasses
-
-import sqlalchemy as sa
-
-from ..typing import SaRow
-from ..connection import SearchConnection
-from ..logging import log
-from . import query as qmod
-from .query_analyzer_factory import AbstractQueryAnalyzer
-
-def yield_words(terms: List[str], start: int) -> Iterator[Tuple[str, qmod.TokenRange]]:
-    """ Return all combinations of words in the terms list after the
-        given position.
-    """
-    total = len(terms)
-    for first in range(start, total):
-        word = terms[first]
-        yield word, qmod.TokenRange(first, first + 1)
-        for last in range(first + 1, min(first + 20, total)):
-            word = ' '.join((word, terms[last]))
-            yield word, qmod.TokenRange(first, last + 1)
-
-
-@dataclasses.dataclass
-class LegacyToken(qmod.Token):
-    """ Specialised token for legacy tokenizer.
-    """
-    word_token: str
-    category: Optional[Tuple[str, str]]
-    country: Optional[str]
-    operator: Optional[str]
-
-    @property
-    def info(self) -> Dict[str, Any]:
-        """ Dictionary of additional properties of the token.
-            Should only be used for debugging purposes.
-        """
-        return {'category': self.category,
-                'country': self.country,
-                'operator': self.operator}
-
-
-    def get_category(self) -> Tuple[str, str]:
-        assert self.category
-        return self.category
-
-
-class LegacyQueryAnalyzer(AbstractQueryAnalyzer):
-    """ Converter for query strings into a tokenized query
-        using the tokens created by a legacy tokenizer.
-    """
-
-    def __init__(self, conn: SearchConnection) -> None:
-        self.conn = conn
-
-    async def setup(self) -> None:
-        """ Set up static data structures needed for the analysis.
-        """
-        self.max_word_freq = int(await self.conn.get_property('tokenizer_maxwordfreq'))
-        if 'word' not in self.conn.t.meta.tables:
-            sa.Table('word', self.conn.t.meta,
-                     sa.Column('word_id', sa.Integer),
-                     sa.Column('word_token', sa.Text, nullable=False),
-                     sa.Column('word', sa.Text),
-                     sa.Column('class', sa.Text),
-                     sa.Column('type', sa.Text),
-                     sa.Column('country_code', sa.Text),
-                     sa.Column('search_name_count', sa.Integer),
-                     sa.Column('operator', sa.Text))
-
-
-    async def analyze_query(self, phrases: List[qmod.Phrase]) -> qmod.QueryStruct:
-        """ Analyze the given list of phrases and return the
-            tokenized query.
-        """
-        log().section('Analyze query (using Legacy tokenizer)')
-
-        normalized = []
-        if phrases:
-            for row in await self.conn.execute(sa.select(*(sa.func.make_standard_name(p.text)
-                                                           for p in phrases))):
-                normalized = [qmod.Phrase(p.ptype, r) for r, p in zip(row, phrases) if r]
-                break
-
-        query = qmod.QueryStruct(normalized)
-        log().var_dump('Normalized query', query.source)
-        if not query.source:
-            return query
-
-        parts, words = self.split_query(query)
-        lookup_words = list(words.keys())
-        log().var_dump('Split query', parts)
-        log().var_dump('Extracted words', lookup_words)
-
-        for row in await self.lookup_in_db(lookup_words):
-            for trange in words[row.word_token.strip()]:
-                token, ttype = self.make_token(row)
-                if ttype == qmod.TokenType.NEAR_ITEM:
-                    if trange.start == 0:
-                        query.add_token(trange, qmod.TokenType.NEAR_ITEM, token)
-                elif ttype == qmod.TokenType.QUALIFIER:
-                    query.add_token(trange, qmod.TokenType.QUALIFIER, token)
-                    if trange.start == 0 or trange.end == query.num_token_slots():
-                        token = copy(token)
-                        token.penalty += 0.1 * (query.num_token_slots())
-                        query.add_token(trange, qmod.TokenType.NEAR_ITEM, token)
-                elif ttype != qmod.TokenType.PARTIAL or trange.start + 1 == trange.end:
-                    query.add_token(trange, ttype, token)
-
-        self.add_extra_tokens(query, parts)
-        self.rerank_tokens(query)
-
-        log().table_dump('Word tokens', _dump_word_tokens(query))
-
-        return query
-
-
-    def normalize_text(self, text: str) -> str:
-        """ Bring the given text into a normalized form.
-
-            This only removes case, so some difference with the normalization
-            in the phrase remains.
-        """
-        return text.lower()
-
-
-    def split_query(self, query: qmod.QueryStruct) -> Tuple[List[str],
-                                                            Dict[str, List[qmod.TokenRange]]]:
-        """ Transliterate the phrases and split them into tokens.
-
-            Returns a list of transliterated tokens and a dictionary
-            of words for lookup together with their position.
-        """
-        parts: List[str] = []
-        phrase_start = 0
-        words = defaultdict(list)
-        for phrase in query.source:
-            query.nodes[-1].ptype = phrase.ptype
-            for trans in phrase.text.split(' '):
-                if trans:
-                    for term in trans.split(' '):
-                        if term:
-                            parts.append(trans)
-                            query.add_node(qmod.BreakType.TOKEN, phrase.ptype)
-                    query.nodes[-1].btype = qmod.BreakType.WORD
-            query.nodes[-1].btype = qmod.BreakType.PHRASE
-            for word, wrange in yield_words(parts, phrase_start):
-                words[word].append(wrange)
-            phrase_start = len(parts)
-        query.nodes[-1].btype = qmod.BreakType.END
-
-        return parts, words
-
-
-    async def lookup_in_db(self, words: List[str]) -> 'sa.Result[Any]':
-        """ Return the token information from the database for the
-            given word tokens.
-        """
-        t = self.conn.t.meta.tables['word']
-
-        sql = t.select().where(t.c.word_token.in_(words + [' ' + w for w in words]))
-
-        return await self.conn.execute(sql)
-
-
-    def make_token(self, row: SaRow) -> Tuple[LegacyToken, qmod.TokenType]:
-        """ Create a LegacyToken from the row of the word table.
-            Also determines the type of token.
-        """
-        penalty = 0.0
-        is_indexed = True
-
-        rowclass = getattr(row, 'class')
-
-        if row.country_code is not None:
-            ttype = qmod.TokenType.COUNTRY
-            lookup_word = row.country_code
-        elif rowclass is not None:
-            if rowclass == 'place' and  row.type == 'house':
-                ttype = qmod.TokenType.HOUSENUMBER
-                lookup_word = row.word_token[1:]
-            elif rowclass == 'place' and  row.type == 'postcode':
-                ttype = qmod.TokenType.POSTCODE
-                lookup_word = row.word
-            else:
-                ttype = qmod.TokenType.NEAR_ITEM if row.operator in ('in', 'near')\
-                        else qmod.TokenType.QUALIFIER
-                lookup_word = row.word
-        elif row.word_token.startswith(' '):
-            ttype = qmod.TokenType.WORD
-            lookup_word = row.word or row.word_token[1:]
-        else:
-            ttype = qmod.TokenType.PARTIAL
-            lookup_word = row.word_token
-            penalty = 0.21
-            if row.search_name_count > self.max_word_freq:
-                is_indexed = False
-
-        return LegacyToken(penalty=penalty, token=row.word_id,
-                           count=max(1, row.search_name_count or 1),
-                           addr_count=1, # not supported
-                           lookup_word=lookup_word,
-                           word_token=row.word_token.strip(),
-                           category=(rowclass, row.type) if rowclass is not None else None,
-                           country=row.country_code,
-                           operator=row.operator,
-                           is_indexed=is_indexed),\
-               ttype
-
-
-    def add_extra_tokens(self, query: qmod.QueryStruct, parts: List[str]) -> None:
-        """ Add tokens to query that are not saved in the database.
-        """
-        for part, node, i in zip(parts, query.nodes, range(1000)):
-            if len(part) <= 4 and part.isdigit()\
-               and not node.has_tokens(i+1, qmod.TokenType.HOUSENUMBER):
-                query.add_token(qmod.TokenRange(i, i+1), qmod.TokenType.HOUSENUMBER,
-                                LegacyToken(penalty=0.5, token=0, count=1, addr_count=1,
-                                            lookup_word=part, word_token=part,
-                                            category=None, country=None,
-                                            operator=None, is_indexed=True))
-
-
-    def rerank_tokens(self, query: qmod.QueryStruct) -> None:
-        """ Add penalties to tokens that depend on presence of other token.
-        """
-        for _, node, tlist in query.iter_token_lists():
-            if tlist.ttype == qmod.TokenType.POSTCODE:
-                for repl in node.starting:
-                    if repl.end == tlist.end and repl.ttype != qmod.TokenType.POSTCODE \
-                       and (repl.ttype != qmod.TokenType.HOUSENUMBER
-                            or len(tlist.tokens[0].lookup_word) > 4):
-                        repl.add_penalty(0.39)
-            elif tlist.ttype == qmod.TokenType.HOUSENUMBER \
-                 and len(tlist.tokens[0].lookup_word) <= 3:
-                if any(c.isdigit() for c in tlist.tokens[0].lookup_word):
-                    for repl in node.starting:
-                        if repl.end == tlist.end and repl.ttype != qmod.TokenType.HOUSENUMBER:
-                            repl.add_penalty(0.5 - tlist.tokens[0].penalty)
-
-
-
-def _dump_word_tokens(query: qmod.QueryStruct) -> Iterator[List[Any]]:
-    yield ['type', 'token', 'word_token', 'lookup_word', 'penalty', 'count', 'info', 'indexed']
-    for node in query.nodes:
-        for tlist in node.starting:
-            for token in tlist.tokens:
-                t = cast(LegacyToken, token)
-                yield [tlist.ttype.name, t.token, t.word_token or '',
-                       t.lookup_word or '', t.penalty, t.count, t.info,
-                       'Y' if t.is_indexed else 'N']
-
-
-async def create_query_analyzer(conn: SearchConnection) -> AbstractQueryAnalyzer:
-    """ Create and set up a new query analyzer for a database based
-        on the ICU tokenizer.
-    """
-    out = LegacyQueryAnalyzer(conn)
-    await out.setup()
-
-    return out
index 04b7f1b8a1e6cf695ccbd881a6f034332727036a..53482df84a11d228ec5c771b71dfc739f9258b1e 100644 (file)
@@ -101,7 +101,6 @@ class Token(ABC):
     count: int
     addr_count: int
     lookup_word: str
-    is_indexed: bool
 
 
     @abstractmethod
index d275f4fc314d741daf12bc619305c81950274efc..8c4de5a4712b13973ef4091ffbad5ece60b9a799 100644 (file)
@@ -8,4 +8,4 @@
 Version information for the Nominatim API.
 """
 
-NOMINATIM_API_VERSION = '4.4.99'
+NOMINATIM_API_VERSION = '4.5.0'
index 9fd439f8d0f33684e5429f31c7386f57f7625ae3..8d8a07f7ab2a61d201a83999744b468dee424ee9 100644 (file)
@@ -19,7 +19,6 @@ from pathlib import Path
 
 from .config import Configuration
 from .errors import UsageError
-from .tools.exec_utils import run_php_server
 from . import clicmd
 from . import version
 from .clicmd.args import NominatimArgs, Subcommand
@@ -112,8 +111,7 @@ class CommandlineParser:
 
         args.config = Configuration(args.project_dir,
                                     environ=kwargs.get('environ', os.environ))
-        args.config.set_libdirs(module=kwargs['module_dir'],
-                                osm2pgsql=kwargs['osm2pgsql_path'])
+        args.config.set_libdirs(osm2pgsql=kwargs['osm2pgsql_path'])
 
         log = logging.getLogger()
         log.warning('Using project directory: %s', str(args.project_dir))
@@ -121,10 +119,6 @@ class CommandlineParser:
         try:
             ret = args.command.run(args)
 
-            if args.config.TOKENIZER == 'legacy':
-                log.warning('WARNING: the "legacy" tokenizer is deprecated '
-                            'and will be removed in Nominatim 5.0.')
-
             return ret
         except UsageError as exception:
             if log.isEnabledFor(logging.DEBUG):
@@ -154,10 +148,10 @@ class AdminServe:
     from the current project directory. This webserver is only suitable
     for testing and development. Do not use it in production setups!
 
-    There are different webservers available. The default 'php' engine
-    runs the classic PHP frontend. The other engines are Python servers
-    which run the new Python frontend code. This is highly experimental
-    at the moment and may not include the full API.
+    There are two different webserver implementations for Python available:
+    falcon (the default) and starlette. You need to make sure the
+    appropriate Python packages as well as the uvicorn package are
+    installed to use this function.
 
     By the default, the webserver can be accessed at: http://127.0.0.1:8088
     """
@@ -167,19 +161,12 @@ class AdminServe:
         group.add_argument('--server', default='127.0.0.1:8088',
                            help='The address the server will listen to.')
         group.add_argument('--engine', default='falcon',
-                           choices=('php', 'falcon', 'starlette'),
+                           choices=('falcon', 'starlette'),
                            help='Webserver framework to run. (default: falcon)')
 
 
     def run(self, args: NominatimArgs) -> int:
-        if args.engine == 'php':
-            if args.config.lib_dir.php is None:
-                raise UsageError("PHP frontend not configured.")
-            LOG.warning('\n\nWARNING: the PHP frontend is deprecated '
-                        'and will be removed in Nominatim 5.0.\n\n')
-            run_php_server(args.server, args.project_dir / 'website')
-        else:
-            asyncio.run(self.run_uvicorn(args))
+        asyncio.run(self.run_uvicorn(args))
 
         return 0
 
index adc7ee656caa67004a402aadf66e467f477d4041..741411658e6977ee5dc4552a9f93b5b07d28d3b6 100644 (file)
@@ -69,7 +69,8 @@ class UpdateRefresh:
         group.add_argument('--importance', action='store_true',
                            help='Recompute place importances (expensive!)')
         group.add_argument('--website', action='store_true',
-                           help='Refresh the directory that serves the scripts for the web API')
+                           help='DEPRECATED. This function has no function anymore'
+                                ' and will be removed in a future version.')
         group.add_argument('--data-object', action='append',
                            type=_parse_osm_object, metavar='OBJECT',
                            help='Mark the given OSM object as requiring an update'
@@ -159,14 +160,8 @@ class UpdateRefresh:
                 refresh.recompute_importance(conn)
 
         if args.website:
-            webdir = args.project_dir / 'website'
-            LOG.warning('Setting up website directory at %s', webdir)
-            # This is a little bit hacky: call the tokenizer setup, so that
-            # the tokenizer directory gets repopulated as well, in case it
-            # wasn't there yet.
-            self._get_tokenizer(args.config)
-            with connect(args.config.get_libpq_dsn()) as conn:
-                refresh.setup_website(webdir, args.config, conn)
+            LOG.error('WARNING: Website setup is no longer required. '
+                      'This function will be removed in future version of Nominatim.')
 
         if args.data_object or args.data_area:
             with connect(args.config.get_libpq_dsn()) as conn:
index 07a76f59036d35a4595adbafcd07f357aa2baa87..a7066ff2c4ccc29cba943d5bfea3fac760b7a7aa 100644 (file)
@@ -88,7 +88,7 @@ class SetupAll:
 
     async def async_run(self, args: NominatimArgs) -> int:
         from ..data import country_info
-        from ..tools import database_import, refresh, postcodes, freeze
+        from ..tools import database_import, postcodes, freeze
         from ..indexer.indexer import Indexer
 
         num_threads = args.threads or psutil.cpu_count() or 1
@@ -141,11 +141,6 @@ class SetupAll:
         LOG.warning('Recompute word counts')
         tokenizer.update_statistics(args.config, threads=num_threads)
 
-        webdir = args.project_dir / 'website'
-        LOG.warning('Setup website at %s', webdir)
-        with connect(args.config.get_libpq_dsn()) as conn:
-            refresh.setup_website(webdir, args.config, conn)
-
         self._finalize_database(args.config.get_libpq_dsn(), args.offline)
 
         return 0
index 5ae3dea3b3ea8b14dfef655c0edf45e31aab6bdf..b220b5c7844ab7ef20756d68c9e8e0033e47ec72 100644 (file)
@@ -59,20 +59,20 @@ class Configuration:
         other than string.
     """
 
-    def __init__(self, project_dir: Optional[Path],
+    def __init__(self, project_dir: Optional[Union[Path, str]],
                  environ: Optional[Mapping[str, str]] = None) -> None:
-        self.environ = environ or os.environ
-        self.project_dir = project_dir
+        self.environ = os.environ if environ is None else environ
         self.config_dir = paths.CONFIG_DIR
         self._config = dotenv_values(str(self.config_dir / 'env.defaults'))
-        if self.project_dir is not None and (self.project_dir / '.env').is_file():
-            self.project_dir = self.project_dir.resolve()
-            self._config.update(dotenv_values(str(self.project_dir / '.env')))
+        if project_dir is not None:
+            self.project_dir: Optional[Path] = Path(project_dir).resolve()
+            if (self.project_dir / '.env').is_file():
+                self._config.update(dotenv_values(str(self.project_dir / '.env')))
+        else:
+            self.project_dir = None
 
         class _LibDirs:
-            module: Path
             osm2pgsql: Path
-            php = paths.PHPLIB_DIR
             sql = paths.SQLLIB_DIR
             data = paths.DATA_DIR
 
index aa289708ab055c01b405578e62945985b206d030..2614fa1452a620753fa0fe6586fa7681ce71cba0 100644 (file)
@@ -9,7 +9,6 @@ Path settings for extra data used by Nominatim.
 """
 from pathlib import Path
 
-PHPLIB_DIR = (Path(__file__) / '..' / '..' / '..' / 'lib-php').resolve()
 SQLLIB_DIR = (Path(__file__) / '..' / '..' / '..' / 'lib-sql').resolve()
 DATA_DIR = (Path(__file__) / '..' / '..' / '..' / 'data').resolve()
 CONFIG_DIR = (Path(__file__) / '..' / '..' / '..' / 'settings').resolve()
index b9022d8d0105f3234f7283b0fa9c89286021da1f..5003c322e3764bfb49e1b01c71fbb20112d33543 100644 (file)
@@ -15,9 +15,6 @@ be used consistently when querying and updating the database.
 This module provides the functions to create and configure a new tokenizer
 as well as instantiating the appropriate tokenizer for updating an existing
 database.
-
-A tokenizer usually also includes PHP code for querying. The appropriate PHP
-normalizer module is installed, when the tokenizer is created.
 """
 from typing import Optional
 import logging
index 4eee2c73b0c9a02001c8c0e49b9b88ab6da36c3b..452bf26ce951c949e2bda958b3fb7e1581134879 100644 (file)
@@ -13,7 +13,6 @@ from typing import Optional, Sequence, List, Tuple, Mapping, Any, cast, \
 import itertools
 import logging
 from pathlib import Path
-from textwrap import dedent
 
 from psycopg.types.json import Jsonb
 from psycopg import sql as pysql
@@ -64,7 +63,6 @@ class ICUTokenizer(AbstractTokenizer):
         """
         self.loader = ICURuleLoader(config)
 
-        self._install_php(config.lib_dir.php, overwrite=True)
         self._save_config()
 
         if init_db:
@@ -81,8 +79,6 @@ class ICUTokenizer(AbstractTokenizer):
         with connect(self.dsn) as conn:
             self.loader.load_config_from_db(conn)
 
-        self._install_php(config.lib_dir.php, overwrite=False)
-
 
     def finalize_import(self, config: Configuration) -> None:
         """ Do any required postprocessing to make the tokenizer data ready
@@ -282,22 +278,6 @@ class ICUTokenizer(AbstractTokenizer):
             return list(s[0].split('@')[0] for s in cur)
 
 
-    def _install_php(self, phpdir: Optional[Path], overwrite: bool = True) -> None:
-        """ Install the php script for the tokenizer.
-        """
-        if phpdir is not None:
-            assert self.loader is not None
-            php_file = self.data_dir / "tokenizer.php"
-
-            if not php_file.exists() or overwrite:
-                php_file.write_text(dedent(f"""\
-                    <?php
-                    @define('CONST_Max_Word_Frequency', 10000000);
-                    @define('CONST_Term_Normalization_Rules', "{self.loader.normalization_rules}");
-                    @define('CONST_Transliteration', "{self.loader.get_search_rules()}");
-                    require_once('{phpdir}/tokenizer/icu_tokenizer.php');"""), encoding='utf-8')
-
-
     def _save_config(self) -> None:
         """ Save the configuration that needs to remain stable for the given
             database as database properties.
diff --git a/src/nominatim_db/tokenizer/legacy_tokenizer.py b/src/nominatim_db/tokenizer/legacy_tokenizer.py
deleted file mode 100644 (file)
index 04b7b88..0000000
+++ /dev/null
@@ -1,686 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Tokenizer implementing normalisation as used before Nominatim 4.
-"""
-from typing import Optional, Sequence, List, Tuple, Mapping, Any, Callable, \
-                   cast, Dict, Set, Iterable
-from collections import OrderedDict
-import logging
-from pathlib import Path
-import re
-import shutil
-from textwrap import dedent
-
-from icu import Transliterator
-import psycopg
-from psycopg import sql as pysql
-
-from ..errors import UsageError
-from ..db.connection import connect, Connection, drop_tables, table_exists,\
-                            execute_scalar, register_hstore
-from ..config import Configuration
-from ..db import properties
-from ..db import utils as db_utils
-from ..db.sql_preprocessor import SQLPreprocessor
-from ..data.place_info import PlaceInfo
-from .base import AbstractAnalyzer, AbstractTokenizer
-
-DBCFG_NORMALIZATION = "tokenizer_normalization"
-DBCFG_MAXWORDFREQ = "tokenizer_maxwordfreq"
-
-LOG = logging.getLogger()
-
-def create(dsn: str, data_dir: Path) -> 'LegacyTokenizer':
-    """ Create a new instance of the tokenizer provided by this module.
-    """
-    LOG.warning('WARNING: the legacy tokenizer is deprecated '
-                'and will be removed in Nominatim 5.0.')
-    return LegacyTokenizer(dsn, data_dir)
-
-
-def _install_module(config_module_path: str, src_dir: Optional[Path], module_dir: Path) -> str:
-    """ Copies the PostgreSQL normalisation module into the project
-        directory if necessary. For historical reasons the module is
-        saved in the '/module' subdirectory and not with the other tokenizer
-        data.
-
-        The function detects when the installation is run from the
-        build directory. It doesn't touch the module in that case.
-    """
-    # Custom module locations are simply used as is.
-    if config_module_path:
-        LOG.info("Using custom path for database module at '%s'", config_module_path)
-        return config_module_path
-
-    # Otherwise a source dir must be given.
-    if src_dir is None:
-        raise UsageError("The legacy tokenizer cannot be used with the Nominatim pip module.")
-
-    # Compatibility mode for builddir installations.
-    if module_dir.exists() and src_dir.samefile(module_dir):
-        LOG.info('Running from build directory. Leaving database module as is.')
-        return str(module_dir)
-
-    # In any other case install the module in the project directory.
-    if not module_dir.exists():
-        module_dir.mkdir()
-
-    destfile = module_dir / 'nominatim.so'
-    shutil.copy(str(src_dir / 'nominatim.so'), str(destfile))
-    destfile.chmod(0o755)
-
-    LOG.info('Database module installed at %s', str(destfile))
-
-    return str(module_dir)
-
-
-def _check_module(module_dir: str, conn: Connection) -> None:
-    """ Try to use the PostgreSQL module to confirm that it is correctly
-        installed and accessible from PostgreSQL.
-    """
-    with conn.cursor() as cur:
-        try:
-            cur.execute(pysql.SQL("""CREATE FUNCTION nominatim_test_import_func(text)
-                                     RETURNS text AS {}, 'transliteration'
-                                     LANGUAGE c IMMUTABLE STRICT;
-                                     DROP FUNCTION nominatim_test_import_func(text)
-                                 """).format(pysql.Literal(f'{module_dir}/nominatim.so')))
-        except psycopg.DatabaseError as err:
-            LOG.fatal("Error accessing database module: %s", err)
-            raise UsageError("Database module cannot be accessed.") from err
-
-
-class LegacyTokenizer(AbstractTokenizer):
-    """ The legacy tokenizer uses a special PostgreSQL module to normalize
-        names and queries. The tokenizer thus implements normalization through
-        calls to the database.
-    """
-
-    def __init__(self, dsn: str, data_dir: Path) -> None:
-        self.dsn = dsn
-        self.data_dir = data_dir
-        self.normalization: Optional[str] = None
-
-
-    def init_new_db(self, config: Configuration, init_db: bool = True) -> None:
-        """ Set up a new tokenizer for the database.
-
-            This copies all necessary data in the project directory to make
-            sure the tokenizer remains stable even over updates.
-        """
-        assert config.project_dir is not None
-        module_dir = _install_module(config.DATABASE_MODULE_PATH,
-                                     config.lib_dir.module,
-                                     config.project_dir / 'module')
-
-        self.normalization = config.TERM_NORMALIZATION
-
-        self._install_php(config, overwrite=True)
-
-        with connect(self.dsn) as conn:
-            _check_module(module_dir, conn)
-            self._save_config(conn, config)
-            conn.commit()
-
-        if init_db:
-            self.update_sql_functions(config)
-            self._init_db_tables(config)
-
-
-    def init_from_project(self, config: Configuration) -> None:
-        """ Initialise the tokenizer from the project directory.
-        """
-        assert config.project_dir is not None
-
-        with connect(self.dsn) as conn:
-            self.normalization = properties.get_property(conn, DBCFG_NORMALIZATION)
-
-        if not (config.project_dir / 'module' / 'nominatim.so').exists():
-            _install_module(config.DATABASE_MODULE_PATH,
-                            config.lib_dir.module,
-                            config.project_dir / 'module')
-
-        self._install_php(config, overwrite=False)
-
-    def finalize_import(self, config: Configuration) -> None:
-        """ Do any required postprocessing to make the tokenizer data ready
-            for use.
-        """
-        with connect(self.dsn) as conn:
-            sqlp = SQLPreprocessor(conn, config)
-            sqlp.run_sql_file(conn, 'tokenizer/legacy_tokenizer_indices.sql')
-
-
-    def update_sql_functions(self, config: Configuration) -> None:
-        """ Reimport the SQL functions for this tokenizer.
-        """
-        assert config.project_dir is not None
-
-        with connect(self.dsn) as conn:
-            max_word_freq = properties.get_property(conn, DBCFG_MAXWORDFREQ)
-            modulepath = config.DATABASE_MODULE_PATH or \
-                         str((config.project_dir / 'module').resolve())
-            sqlp = SQLPreprocessor(conn, config)
-            sqlp.run_sql_file(conn, 'tokenizer/legacy_tokenizer.sql',
-                              max_word_freq=max_word_freq,
-                              modulepath=modulepath)
-
-
-    def check_database(self, _: Configuration) -> Optional[str]:
-        """ Check that the tokenizer is set up correctly.
-        """
-        hint = """\
-             The Postgresql extension nominatim.so was not correctly loaded.
-
-             Error: {error}
-
-             Hints:
-             * Check the output of the CMmake/make installation step
-             * Does nominatim.so exist?
-             * Does nominatim.so exist on the database server?
-             * Can nominatim.so be accessed by the database user?
-             """
-        with connect(self.dsn) as conn:
-            try:
-                out = execute_scalar(conn, "SELECT make_standard_name('a')")
-            except psycopg.Error as err:
-                return hint.format(error=str(err))
-
-        if out != 'a':
-            return hint.format(error='Unexpected result for make_standard_name()')
-
-        return None
-
-
-    def migrate_database(self, config: Configuration) -> None:
-        """ Initialise the project directory of an existing database for
-            use with this tokenizer.
-
-            This is a special migration function for updating existing databases
-            to new software versions.
-        """
-        assert config.project_dir is not None
-
-        self.normalization = config.TERM_NORMALIZATION
-        module_dir = _install_module(config.DATABASE_MODULE_PATH,
-                                     config.lib_dir.module,
-                                     config.project_dir / 'module')
-
-        with connect(self.dsn) as conn:
-            _check_module(module_dir, conn)
-            self._save_config(conn, config)
-
-
-    def update_statistics(self, config: Configuration, threads: int = 1) -> None:
-        """ Recompute the frequency of full words.
-        """
-        with connect(self.dsn) as conn:
-            if table_exists(conn, 'search_name'):
-                drop_tables(conn, "word_frequencies")
-                with conn.cursor() as cur:
-                    LOG.info("Computing word frequencies")
-                    cur.execute("""CREATE TEMP TABLE word_frequencies AS
-                                     SELECT unnest(name_vector) as id, count(*)
-                                     FROM search_name GROUP BY id""")
-                    cur.execute("CREATE INDEX ON word_frequencies(id)")
-                    LOG.info("Update word table with recomputed frequencies")
-                    cur.execute("""UPDATE word SET search_name_count = count
-                                   FROM word_frequencies
-                                   WHERE word_token like ' %' and word_id = id""")
-                drop_tables(conn, "word_frequencies")
-            conn.commit()
-
-
-    def update_word_tokens(self) -> None:
-        """ No house-keeping implemented for the legacy tokenizer.
-        """
-        LOG.info("No tokenizer clean-up available.")
-
-
-    def name_analyzer(self) -> 'LegacyNameAnalyzer':
-        """ Create a new analyzer for tokenizing names and queries
-            using this tokinzer. Analyzers are context managers and should
-            be used accordingly:
-
-            ```
-            with tokenizer.name_analyzer() as analyzer:
-                analyser.tokenize()
-            ```
-
-            When used outside the with construct, the caller must ensure to
-            call the close() function before destructing the analyzer.
-
-            Analyzers are not thread-safe. You need to instantiate one per thread.
-        """
-        normalizer = Transliterator.createFromRules("phrase normalizer",
-                                                    self.normalization)
-        return LegacyNameAnalyzer(self.dsn, normalizer)
-
-
-    def most_frequent_words(self, conn: Connection, num: int) -> List[str]:
-        """ Return a list of the `num` most frequent full words
-            in the database.
-        """
-        with conn.cursor() as cur:
-            cur.execute(""" SELECT word FROM word WHERE word is not null
-                              ORDER BY search_name_count DESC LIMIT %s""", (num,))
-            return list(s[0] for s in cur)
-
-
-    def _install_php(self, config: Configuration, overwrite: bool = True) -> None:
-        """ Install the php script for the tokenizer.
-        """
-        if config.lib_dir.php is not None:
-            php_file = self.data_dir / "tokenizer.php"
-
-            if not php_file.exists() or overwrite:
-                php_file.write_text(dedent(f"""\
-                    <?php
-                    @define('CONST_Max_Word_Frequency', {config.MAX_WORD_FREQUENCY});
-                    @define('CONST_Term_Normalization_Rules', "{config.TERM_NORMALIZATION}");
-                    require_once('{config.lib_dir.php}/tokenizer/legacy_tokenizer.php');
-                    """), encoding='utf-8')
-
-
-    def _init_db_tables(self, config: Configuration) -> None:
-        """ Set up the word table and fill it with pre-computed word
-            frequencies.
-        """
-        with connect(self.dsn) as conn:
-            sqlp = SQLPreprocessor(conn, config)
-            sqlp.run_sql_file(conn, 'tokenizer/legacy_tokenizer_tables.sql')
-            conn.commit()
-
-        LOG.warning("Precomputing word tokens")
-        db_utils.execute_file(self.dsn, config.lib_dir.data / 'words.sql')
-
-
-    def _save_config(self, conn: Connection, config: Configuration) -> None:
-        """ Save the configuration that needs to remain stable for the given
-            database as database properties.
-        """
-        assert self.normalization is not None
-
-        properties.set_property(conn, DBCFG_NORMALIZATION, self.normalization)
-        properties.set_property(conn, DBCFG_MAXWORDFREQ, config.MAX_WORD_FREQUENCY)
-
-
-class LegacyNameAnalyzer(AbstractAnalyzer):
-    """ The legacy analyzer uses the special Postgresql module for
-        splitting names.
-
-        Each instance opens a connection to the database to request the
-        normalization.
-    """
-
-    def __init__(self, dsn: str, normalizer: Any):
-        self.conn: Optional[Connection] = connect(dsn)
-        self.conn.autocommit = True
-        self.normalizer = normalizer
-        register_hstore(self.conn)
-
-        self._cache = _TokenCache(self.conn)
-
-
-    def close(self) -> None:
-        """ Free all resources used by the analyzer.
-        """
-        if self.conn:
-            self.conn.close()
-            self.conn = None
-
-
-    def get_word_token_info(self, words: Sequence[str]) -> List[Tuple[str, str, int]]:
-        """ Return token information for the given list of words.
-            If a word starts with # it is assumed to be a full name
-            otherwise is a partial name.
-
-            The function returns a list of tuples with
-            (original word, word token, word id).
-
-            The function is used for testing and debugging only
-            and not necessarily efficient.
-        """
-        assert self.conn is not None
-        with self.conn.cursor() as cur:
-            cur.execute("""SELECT t.term, word_token, word_id
-                           FROM word, (SELECT unnest(%s::TEXT[]) as term) t
-                           WHERE word_token = (CASE
-                                   WHEN left(t.term, 1) = '#' THEN
-                                     ' ' || make_standard_name(substring(t.term from 2))
-                                   ELSE
-                                     make_standard_name(t.term)
-                                   END)
-                                 and class is null and country_code is null""",
-                        (words, ))
-
-            return [(r[0], r[1], r[2]) for r in cur]
-
-
-    def normalize(self, phrase: str) -> str:
-        """ Normalize the given phrase, i.e. remove all properties that
-            are irrelevant for search.
-        """
-        return cast(str, self.normalizer.transliterate(phrase))
-
-
-    def normalize_postcode(self, postcode: str) -> str:
-        """ Convert the postcode to a standardized form.
-
-            This function must yield exactly the same result as the SQL function
-            'token_normalized_postcode()'.
-        """
-        return postcode.strip().upper()
-
-
-    def update_postcodes_from_db(self) -> None:
-        """ Update postcode tokens in the word table from the location_postcode
-            table.
-        """
-        assert self.conn is not None
-
-        with self.conn.cursor() as cur:
-            # This finds us the rows in location_postcode and word that are
-            # missing in the other table.
-            cur.execute("""SELECT * FROM
-                            (SELECT pc, word FROM
-                              (SELECT distinct(postcode) as pc FROM location_postcode) p
-                              FULL JOIN
-                              (SELECT word FROM word
-                                WHERE class ='place' and type = 'postcode') w
-                              ON pc = word) x
-                           WHERE pc is null or word is null""")
-
-            to_delete = []
-            to_add = []
-
-            for postcode, word in cur:
-                if postcode is None:
-                    to_delete.append(word)
-                else:
-                    to_add.append(postcode)
-
-            if to_delete:
-                cur.execute("""DELETE FROM WORD
-                               WHERE class ='place' and type = 'postcode'
-                                     and word = any(%s)
-                            """, (to_delete, ))
-            if to_add:
-                cur.execute("""SELECT count(create_postcode_id(pc))
-                               FROM unnest(%s::text[]) as pc
-                            """, (to_add, ))
-
-
-
-    def update_special_phrases(self, phrases: Iterable[Tuple[str, str, str, str]],
-                               should_replace: bool) -> None:
-        """ Replace the search index for special phrases with the new phrases.
-        """
-        assert self.conn is not None
-
-        norm_phrases = set(((self.normalize(p[0]), p[1], p[2], p[3])
-                            for p in phrases))
-
-        with self.conn.cursor() as cur:
-            # Get the old phrases.
-            existing_phrases = set()
-            cur.execute("""SELECT word, class as cls, type, operator FROM word
-                           WHERE class != 'place'
-                                 OR (type != 'house' AND type != 'postcode')""")
-            for label, cls, typ, oper in cur:
-                existing_phrases.add((label, cls, typ, oper or '-'))
-
-            to_add = norm_phrases - existing_phrases
-            to_delete = existing_phrases - norm_phrases
-
-            if to_add:
-                cur.executemany(
-                    """ INSERT INTO word (word_id, word_token, word, class, type,
-                                          search_name_count, operator)
-                        (SELECT nextval('seq_word'), ' ' || make_standard_name(name), name,
-                                class, type, 0,
-                                CASE WHEN op in ('in', 'near') THEN op ELSE null END
-                           FROM (VALUES (%s, %s, %s, %s)) as v(name, class, type, op))""",
-                    to_add)
-
-            if to_delete and should_replace:
-                cur.executemany(
-                    """ DELETE FROM word
-                          USING (VALUES (%s, %s, %s, %s)) as v(name, in_class, in_type, op)
-                        WHERE word = name and class = in_class and type = in_type
-                              and ((op = '-' and operator is null) or op = operator)""",
-                    to_delete)
-
-        LOG.info("Total phrases: %s. Added: %s. Deleted: %s",
-                 len(norm_phrases), len(to_add), len(to_delete))
-
-
-    def add_country_names(self, country_code: str, names: Mapping[str, str]) -> None:
-        """ Add names for the given country to the search index.
-        """
-        assert self.conn is not None
-
-        with self.conn.cursor() as cur:
-            cur.execute(
-                """INSERT INTO word (word_id, word_token, country_code)
-                   (SELECT nextval('seq_word'), lookup_token, %s
-                      FROM (SELECT DISTINCT ' ' || make_standard_name(n) as lookup_token
-                            FROM unnest(%s::TEXT[])n) y
-                      WHERE NOT EXISTS(SELECT * FROM word
-                                       WHERE word_token = lookup_token and country_code = %s))
-                """, (country_code, list(names.values()), country_code))
-
-
-    def process_place(self, place: PlaceInfo) -> Mapping[str, Any]:
-        """ Determine tokenizer information about the given place.
-
-            Returns a JSON-serialisable structure that will be handed into
-            the database via the token_info field.
-        """
-        assert self.conn is not None
-
-        token_info = _TokenInfo(self._cache)
-
-        names = place.name
-
-        if names:
-            token_info.add_names(self.conn, names)
-
-            if place.is_country():
-                assert place.country_code is not None
-                self.add_country_names(place.country_code, names)
-
-        address = place.address
-        if address:
-            self._process_place_address(token_info, address)
-
-        return token_info.data
-
-
-    def _process_place_address(self, token_info: '_TokenInfo', address: Mapping[str, str]) -> None:
-        assert self.conn is not None
-        hnrs = []
-        addr_terms = []
-
-        for key, value in address.items():
-            if key == 'postcode':
-                # Make sure the normalized postcode is present in the word table.
-                if re.search(r'[:,;]', value) is None:
-                    norm_pc = self.normalize_postcode(value)
-                    token_info.set_postcode(norm_pc)
-                    self._cache.add_postcode(self.conn, norm_pc)
-            elif key in ('housenumber', 'streetnumber', 'conscriptionnumber'):
-                hnrs.append(value)
-            elif key == 'street':
-                token_info.add_street(self.conn, value)
-            elif key == 'place':
-                token_info.add_place(self.conn, value)
-            elif not key.startswith('_') \
-                 and key not in ('country', 'full', 'inclusion'):
-                addr_terms.append((key, value))
-
-        if hnrs:
-            token_info.add_housenumbers(self.conn, hnrs)
-
-        if addr_terms:
-            token_info.add_address_terms(self.conn, addr_terms)
-
-
-
-class _TokenInfo:
-    """ Collect token information to be sent back to the database.
-    """
-    def __init__(self, cache: '_TokenCache') -> None:
-        self.cache = cache
-        self.data: Dict[str, Any] = {}
-
-
-    def add_names(self, conn: Connection, names: Mapping[str, str]) -> None:
-        """ Add token information for the names of the place.
-        """
-        # Create the token IDs for all names.
-        self.data['names'] = execute_scalar(conn, "SELECT make_keywords(%s)::text",
-                                            (names, ))
-
-
-    def add_housenumbers(self, conn: Connection, hnrs: Sequence[str]) -> None:
-        """ Extract housenumber information from the address.
-        """
-        if len(hnrs) == 1:
-            token = self.cache.get_housenumber(hnrs[0])
-            if token is not None:
-                self.data['hnr_tokens'] = token
-                self.data['hnr'] = hnrs[0]
-                return
-
-        # split numbers if necessary
-        simple_list: List[str] = []
-        for hnr in hnrs:
-            simple_list.extend((x.strip() for x in re.split(r'[;,]', hnr)))
-
-        if len(simple_list) > 1:
-            simple_list = list(set(simple_list))
-
-        with conn.cursor() as cur:
-            cur.execute("SELECT * FROM create_housenumbers(%s)", (simple_list, ))
-            result = cur.fetchone()
-            assert result is not None
-            self.data['hnr_tokens'], self.data['hnr'] = result
-
-
-    def set_postcode(self, postcode: str) -> None:
-        """ Set or replace the postcode token with the given value.
-        """
-        self.data['postcode'] = postcode
-
-    def add_street(self, conn: Connection, street: str) -> None:
-        """ Add addr:street match terms.
-        """
-        def _get_street(name: str) -> Optional[str]:
-            return cast(Optional[str],
-                        execute_scalar(conn, "SELECT word_ids_from_name(%s)::text", (name, )))
-
-        tokens = self.cache.streets.get(street, _get_street)
-        self.data['street'] = tokens or '{}'
-
-
-    def add_place(self, conn: Connection, place: str) -> None:
-        """ Add addr:place search and match terms.
-        """
-        def _get_place(name: str) -> Tuple[List[int], List[int]]:
-            with conn.cursor() as cur:
-                cur.execute("""SELECT make_keywords(hstore('name' , %s))::text,
-                                      word_ids_from_name(%s)::text""",
-                            (name, name))
-                return cast(Tuple[List[int], List[int]], cur.fetchone())
-
-        self.data['place_search'], self.data['place_match'] = \
-            self.cache.places.get(place, _get_place)
-
-
-    def add_address_terms(self, conn: Connection, terms: Sequence[Tuple[str, str]]) -> None:
-        """ Add additional address terms.
-        """
-        def _get_address_term(name: str) -> Tuple[List[int], List[int]]:
-            with conn.cursor() as cur:
-                cur.execute("""SELECT addr_ids_from_name(%s)::text,
-                                      word_ids_from_name(%s)::text""",
-                            (name, name))
-                return cast(Tuple[List[int], List[int]], cur.fetchone())
-
-        tokens = {}
-        for key, value in terms:
-            items = self.cache.address_terms.get(value, _get_address_term)
-            if items[0] or items[1]:
-                tokens[key] = items
-
-        if tokens:
-            self.data['addr'] = tokens
-
-
-class _LRU:
-    """ Least recently used cache that accepts a generator function to
-        produce the item when there is a cache miss.
-    """
-
-    def __init__(self, maxsize: int = 128):
-        self.data: 'OrderedDict[str, Any]' = OrderedDict()
-        self.maxsize = maxsize
-
-
-    def get(self, key: str, generator: Callable[[str], Any]) -> Any:
-        """ Get the item with the given key from the cache. If nothing
-            is found in the cache, generate the value through the
-            generator function and store it in the cache.
-        """
-        value = self.data.get(key)
-        if value is not None:
-            self.data.move_to_end(key)
-        else:
-            value = generator(key)
-            if len(self.data) >= self.maxsize:
-                self.data.popitem(last=False)
-            self.data[key] = value
-
-        return value
-
-
-class _TokenCache:
-    """ Cache for token information to avoid repeated database queries.
-
-        This cache is not thread-safe and needs to be instantiated per
-        analyzer.
-    """
-    def __init__(self, conn: Connection):
-        # various LRU caches
-        self.streets = _LRU(maxsize=256)
-        self.places = _LRU(maxsize=128)
-        self.address_terms = _LRU(maxsize=1024)
-
-        # Lookup houseunumbers up to 100 and cache them
-        with conn.cursor() as cur:
-            cur.execute("""SELECT i, ARRAY[getorcreate_housenumber_id(i::text)]::text
-                           FROM generate_series(1, 100) as i""")
-            self._cached_housenumbers: Dict[str, str] = {str(r[0]): r[1] for r in cur}
-
-        # For postcodes remember the ones that have already been added
-        self.postcodes: Set[str] = set()
-
-    def get_housenumber(self, number: str) -> Optional[str]:
-        """ Get a housenumber token from the cache.
-        """
-        return self._cached_housenumbers.get(number)
-
-
-    def add_postcode(self, conn: Connection, postcode: str) -> None:
-        """ Make sure the given postcode is in the database.
-        """
-        if postcode not in self.postcodes:
-            with conn.cursor() as cur:
-                cur.execute('SELECT create_postcode_id(%s)', (postcode, ))
-            self.postcodes.add(postcode)
index 2377abc0b2be5dde5913a742eb5d5af7eac118f1..d53527d181f4f670e9028b0cf27869751e3a9bfa 100644 (file)
@@ -7,7 +7,7 @@
 """
 Exporting a Nominatim database to SQlite.
 """
-from typing import Set, Any
+from typing import Set, Any, Optional, Union
 import datetime as dt
 import logging
 from pathlib import Path
@@ -21,7 +21,8 @@ from nominatim_api.sql.sqlalchemy_types import Geometry, IntArray
 
 LOG = logging.getLogger()
 
-async def convert(project_dir: Path, outfile: Path, options: Set[str]) -> None:
+async def convert(project_dir: Optional[Union[str, Path]],
+                  outfile: Path, options: Set[str]) -> None:
     """ Export an existing database to sqlite. The resulting database
         will be usable against the Python frontend of Nominatim.
     """
index 1adcc777b053bf7507ffaf8f6bff8fd8e84e8977..4cbbf95d9b650c2e8d11f6c2b36b4d5c32e78c60 100644 (file)
@@ -14,19 +14,12 @@ import re
 import subprocess
 import shutil
 
-from ..typing import StrPath
 from ..db.connection import get_pg_env
 from ..errors import UsageError
 from ..version import OSM2PGSQL_REQUIRED_VERSION
 
 LOG = logging.getLogger()
 
-def run_php_server(server_address: str, base_dir: StrPath) -> None:
-    """ Run the built-in server from the given directory.
-    """
-    subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
-                   cwd=str(base_dir), check=True)
-
 
 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
     """ Run osm2pgsql with the given options.
index 5483653230bb6c2eff94109a0c87e6539727c0d9..12abe7fea5d3365ba28d4eb9380c6e0585825bda 100644 (file)
@@ -10,13 +10,11 @@ Functions for database migration to newer software versions.
 from typing import List, Tuple, Callable, Any
 import logging
 
-from psycopg import sql as pysql
-
 from ..errors import UsageError
 from ..config import Configuration
 from ..db import properties
-from ..db.connection import connect, Connection, server_version_tuple,\
-                            table_has_column, table_exists, execute_scalar, register_hstore
+from ..db.connection import connect, Connection,\
+                            table_exists, register_hstore
 from ..version import NominatimVersion, NOMINATIM_VERSION, parse_version
 from ..tokenizer import factory as tokenizer_factory
 from . import refresh
@@ -38,19 +36,24 @@ def migrate(config: Configuration, paths: Any) -> int:
 
         if db_version_str is not None:
             db_version = parse_version(db_version_str)
+        else:
+            db_version = None
 
-            if db_version == NOMINATIM_VERSION:
-                LOG.warning("Database already at latest version (%s)", db_version_str)
-                return 0
+        if db_version is None or db_version < (4, 3, 0, 0):
+            LOG.fatal('Your database version is older than 4.3. '
+                      'Direct migration is not possible.\n'
+                      'You should strongly consider a reimport. If that is not possible\n'
+                      'please upgrade to 4.3 first and then to the newest version.')
+            raise UsageError('Migration not possible.')
 
-            LOG.info("Detected database version: %s", db_version_str)
-        else:
-            db_version = _guess_version(conn)
+        if db_version == NOMINATIM_VERSION:
+            LOG.warning("Database already at latest version (%s)", db_version_str)
+            return 0
 
+        LOG.info("Detected database version: %s", db_version_str)
 
         for version, func in _MIGRATION_FUNCTIONS:
-            if db_version < version or \
-               (db_version == (3, 5, 0, 99) and version == (3, 5, 0, 99)):
+            if db_version < version:
                 title = func.__doc__ or ''
                 LOG.warning("Running: %s (%s)", title.split('\n', 1)[0], version)
                 kwargs = dict(conn=conn, config=config, paths=paths)
@@ -69,25 +72,6 @@ def migrate(config: Configuration, paths: Any) -> int:
     return 0
 
 
-def _guess_version(conn: Connection) -> NominatimVersion:
-    """ Guess a database version when there is no property table yet.
-        Only migrations for 3.6 and later are supported, so bail out
-        when the version seems older.
-    """
-    # In version 3.6, the country_name table was updated. Check for that.
-    cnt = execute_scalar(conn, """SELECT count(*) FROM
-                                  (SELECT svals(name) FROM  country_name
-                                  WHERE country_code = 'gb')x;
-                               """)
-    if cnt < 100:
-        LOG.fatal('It looks like your database was imported with a version '
-                  'prior to 3.6.0. Automatic migration not possible.')
-        raise UsageError('Migration not possible.')
-
-    return NominatimVersion(3, 5, 0, 99)
-
-
-
 def _migration(major: int, minor: int, patch: int = 0,
                dbpatch: int = 0) -> Callable[[Callable[..., None]], Callable[..., None]]:
     """ Decorator for a single migration step. The parameters describe the
@@ -110,273 +94,6 @@ def _migration(major: int, minor: int, patch: int = 0,
     return decorator
 
 
-@_migration(3, 5, 0, 99)
-def import_status_timestamp_change(conn: Connection, **_: Any) -> None:
-    """ Add timezone to timestamp in status table.
-
-        The import_status table has been changed to include timezone information
-        with the time stamp.
-    """
-    with conn.cursor() as cur:
-        cur.execute("""ALTER TABLE import_status ALTER COLUMN lastimportdate
-                       TYPE timestamp with time zone;""")
-
-
-@_migration(3, 5, 0, 99)
-def add_nominatim_property_table(conn: Connection, config: Configuration, **_: Any) -> None:
-    """ Add nominatim_property table.
-    """
-    if not table_exists(conn, 'nominatim_properties'):
-        with conn.cursor() as cur:
-            cur.execute(pysql.SQL("""CREATE TABLE nominatim_properties (
-                                        property TEXT,
-                                        value TEXT);
-                                     GRANT SELECT ON TABLE nominatim_properties TO {};
-                                  """).format(pysql.Identifier(config.DATABASE_WEBUSER)))
-
-@_migration(3, 6, 0, 0)
-def change_housenumber_transliteration(conn: Connection, **_: Any) -> None:
-    """ Transliterate housenumbers.
-
-        The database schema switched from saving raw housenumbers in
-        placex.housenumber to saving transliterated ones.
-
-        Note: the function create_housenumber_id() has been dropped in later
-              versions.
-    """
-    with conn.cursor() as cur:
-        cur.execute("""CREATE OR REPLACE FUNCTION create_housenumber_id(housenumber TEXT)
-                       RETURNS TEXT AS $$
-                       DECLARE
-                         normtext TEXT;
-                       BEGIN
-                         SELECT array_to_string(array_agg(trans), ';')
-                           INTO normtext
-                           FROM (SELECT lookup_word as trans,
-                                        getorcreate_housenumber_id(lookup_word)
-                                 FROM (SELECT make_standard_name(h) as lookup_word
-                                       FROM regexp_split_to_table(housenumber, '[,;]') h) x) y;
-                         return normtext;
-                       END;
-                       $$ LANGUAGE plpgsql STABLE STRICT;""")
-        cur.execute("DELETE FROM word WHERE class = 'place' and type = 'house'")
-        cur.execute("""UPDATE placex
-                       SET housenumber = create_housenumber_id(housenumber)
-                       WHERE housenumber is not null""")
-
-
-@_migration(3, 7, 0, 0)
-def switch_placenode_geometry_index(conn: Connection, **_: Any) -> None:
-    """ Replace idx_placex_geometry_reverse_placeNode index.
-
-        Make the index slightly more permissive, so that it can also be used
-        when matching up boundaries and place nodes. It makes the index
-        idx_placex_adminname index unnecessary.
-    """
-    with conn.cursor() as cur:
-        cur.execute(""" CREATE INDEX IF NOT EXISTS idx_placex_geometry_placenode ON placex
-                        USING GIST (geometry)
-                        WHERE osm_type = 'N' and rank_search < 26
-                              and class = 'place' and type != 'postcode'
-                              and linked_place_id is null""")
-        cur.execute(""" DROP INDEX IF EXISTS idx_placex_adminname """)
-
-
-@_migration(3, 7, 0, 1)
-def install_legacy_tokenizer(conn: Connection, config: Configuration, **_: Any) -> None:
-    """ Setup legacy tokenizer.
-
-        If no other tokenizer has been configured yet, then create the
-        configuration for the backwards-compatible legacy tokenizer
-    """
-    if properties.get_property(conn, 'tokenizer') is None:
-        for table in ('placex', 'location_property_osmline'):
-            if not table_has_column(conn, table, 'token_info'):
-                with conn.cursor() as cur:
-                    cur.execute(pysql.SQL('ALTER TABLE {} ADD COLUMN token_info JSONB')
-                                .format(pysql.Identifier(table)))
-        tokenizer = tokenizer_factory.create_tokenizer(config, init_db=False,
-                                                       module_name='legacy')
-
-        tokenizer.migrate_database(config) # type: ignore[attr-defined]
-
-
-@_migration(4, 0, 99, 0)
-def create_tiger_housenumber_index(conn: Connection, **_: Any) -> None:
-    """ Create idx_location_property_tiger_parent_place_id with included
-        house number.
-
-        The inclusion is needed for efficient lookup of housenumbers in
-        full address searches.
-    """
-    if server_version_tuple(conn) >= (11, 0, 0):
-        with conn.cursor() as cur:
-            cur.execute(""" CREATE INDEX IF NOT EXISTS
-                                idx_location_property_tiger_housenumber_migrated
-                            ON location_property_tiger
-                            USING btree(parent_place_id)
-                            INCLUDE (startnumber, endnumber) """)
-
-
-@_migration(4, 0, 99, 1)
-def create_interpolation_index_on_place(conn: Connection, **_: Any) -> None:
-    """ Create idx_place_interpolations for lookup of interpolation lines
-        on updates.
-    """
-    with conn.cursor() as cur:
-        cur.execute("""CREATE INDEX IF NOT EXISTS idx_place_interpolations
-                       ON place USING gist(geometry)
-                       WHERE osm_type = 'W' and address ? 'interpolation'""")
-
-
-@_migration(4, 0, 99, 2)
-def add_step_column_for_interpolation(conn: Connection, **_: Any) -> None:
-    """ Add a new column 'step' to the interpolations table.
-
-        Also converts the data into the stricter format which requires that
-        startnumbers comply with the odd/even requirements.
-    """
-    if table_has_column(conn, 'location_property_osmline', 'step'):
-        return
-
-    with conn.cursor() as cur:
-        # Mark invalid all interpolations with no intermediate numbers.
-        cur.execute("""UPDATE location_property_osmline SET startnumber = null
-                       WHERE endnumber - startnumber <= 1 """)
-        # Align the start numbers where odd/even does not match.
-        cur.execute("""UPDATE location_property_osmline
-                       SET startnumber = startnumber + 1,
-                           linegeo = ST_LineSubString(linegeo,
-                                                      1.0 / (endnumber - startnumber)::float,
-                                                      1)
-                       WHERE (interpolationtype = 'odd' and startnumber % 2 = 0)
-                              or (interpolationtype = 'even' and startnumber % 2 = 1)
-                    """)
-        # Mark invalid odd/even interpolations with no intermediate numbers.
-        cur.execute("""UPDATE location_property_osmline SET startnumber = null
-                       WHERE interpolationtype in ('odd', 'even')
-                             and endnumber - startnumber = 2""")
-        # Finally add the new column and populate it.
-        cur.execute("ALTER TABLE location_property_osmline ADD COLUMN step SMALLINT")
-        cur.execute("""UPDATE location_property_osmline
-                         SET step = CASE WHEN interpolationtype = 'all'
-                                         THEN 1 ELSE 2 END
-                    """)
-
-
-@_migration(4, 0, 99, 3)
-def add_step_column_for_tiger(conn: Connection, **_: Any) -> None:
-    """ Add a new column 'step' to the tiger data table.
-    """
-    if table_has_column(conn, 'location_property_tiger', 'step'):
-        return
-
-    with conn.cursor() as cur:
-        cur.execute("ALTER TABLE location_property_tiger ADD COLUMN step SMALLINT")
-        cur.execute("""UPDATE location_property_tiger
-                         SET step = CASE WHEN interpolationtype = 'all'
-                                         THEN 1 ELSE 2 END
-                    """)
-
-
-@_migration(4, 0, 99, 4)
-def add_derived_name_column_for_country_names(conn: Connection, **_: Any) -> None:
-    """ Add a new column 'derived_name' which in the future takes the
-        country names as imported from OSM data.
-    """
-    if not table_has_column(conn, 'country_name', 'derived_name'):
-        with conn.cursor() as cur:
-            cur.execute("ALTER TABLE country_name ADD COLUMN derived_name public.HSTORE")
-
-
-@_migration(4, 0, 99, 5)
-def mark_internal_country_names(conn: Connection, config: Configuration, **_: Any) -> None:
-    """ Names from the country table should be marked as internal to prevent
-        them from being deleted. Only necessary for ICU tokenizer.
-    """
-    tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
-    with tokenizer.name_analyzer() as analyzer:
-        with conn.cursor() as cur:
-            cur.execute("SELECT country_code, name FROM country_name")
-
-            for country_code, names in cur:
-                if not names:
-                    names = {}
-                names['countrycode'] = country_code
-                analyzer.add_country_names(country_code, names)
-
-
-@_migration(4, 1, 99, 0)
-def add_place_deletion_todo_table(conn: Connection, **_: Any) -> None:
-    """ Add helper table for deleting data on updates.
-
-        The table is only necessary when updates are possible, i.e.
-        the database is not in freeze mode.
-    """
-    if table_exists(conn, 'place'):
-        with conn.cursor() as cur:
-            cur.execute("""CREATE TABLE IF NOT EXISTS place_to_be_deleted (
-                             osm_type CHAR(1),
-                             osm_id BIGINT,
-                             class TEXT,
-                             type TEXT,
-                             deferred BOOLEAN)""")
-
-
-@_migration(4, 1, 99, 1)
-def split_pending_index(conn: Connection, **_: Any) -> None:
-    """ Reorganise indexes for pending updates.
-    """
-    if table_exists(conn, 'place'):
-        with conn.cursor() as cur:
-            cur.execute("""CREATE INDEX IF NOT EXISTS idx_placex_rank_address_sector
-                           ON placex USING BTREE (rank_address, geometry_sector)
-                           WHERE indexed_status > 0""")
-            cur.execute("""CREATE INDEX IF NOT EXISTS idx_placex_rank_boundaries_sector
-                           ON placex USING BTREE (rank_search, geometry_sector)
-                           WHERE class = 'boundary' and type = 'administrative'
-                                 and indexed_status > 0""")
-            cur.execute("DROP INDEX IF EXISTS idx_placex_pendingsector")
-
-
-@_migration(4, 2, 99, 0)
-def enable_forward_dependencies(conn: Connection, **_: Any) -> None:
-    """ Create indexes for updates with forward dependency tracking (long-running).
-    """
-    if table_exists(conn, 'planet_osm_ways'):
-        with conn.cursor() as cur:
-            cur.execute("""SELECT * FROM pg_indexes
-                           WHERE tablename = 'planet_osm_ways'
-                                 and indexdef LIKE '%nodes%'""")
-            if cur.rowcount == 0:
-                cur.execute("""CREATE OR REPLACE FUNCTION public.planet_osm_index_bucket(bigint[])
-                               RETURNS bigint[]
-                               LANGUAGE sql IMMUTABLE
-                                AS $function$
-                                  SELECT ARRAY(SELECT DISTINCT unnest($1) >> 5)
-                                $function$""")
-                cur.execute("""CREATE INDEX planet_osm_ways_nodes_bucket_idx
-                                 ON planet_osm_ways
-                                 USING gin (planet_osm_index_bucket(nodes))
-                                 WITH (fastupdate=off)""")
-                cur.execute("""CREATE INDEX planet_osm_rels_parts_idx
-                                 ON planet_osm_rels USING gin (parts)
-                                 WITH (fastupdate=off)""")
-                cur.execute("ANALYZE planet_osm_ways")
-
-
-@_migration(4, 2, 99, 1)
-def add_improved_geometry_reverse_placenode_index(conn: Connection, **_: Any) -> None:
-    """ Create improved index for reverse lookup of place nodes.
-    """
-    with conn.cursor() as cur:
-        cur.execute("""CREATE INDEX IF NOT EXISTS idx_placex_geometry_reverse_lookupPlaceNode
-                       ON placex
-                       USING gist (ST_Buffer(geometry, reverse_place_diameter(rank_search)))
-                       WHERE rank_address between 4 and 25 AND type != 'postcode'
-                         AND name is not null AND linked_place_id is null AND osm_type = 'N'
-                    """)
-
 @_migration(4, 4, 99, 0)
 def create_postcode_area_lookup_index(conn: Connection, **_: Any) -> None:
     """ Create index needed for looking up postcode areas from postocde points.
index d48c4e45a01dd68ee71fd338b4f06373d53ffe0c..557c43ae6fd95ee61b10b0a78bec5dad15bc3085 100644 (file)
@@ -11,17 +11,15 @@ from typing import MutableSequence, Tuple, Any, Type, Mapping, Sequence, List, c
 import csv
 import gzip
 import logging
-from textwrap import dedent
 from pathlib import Path
 
 from psycopg import sql as pysql
 
 from ..config import Configuration
 from ..db.connection import Connection, connect, postgis_version_tuple,\
-                            drop_tables, table_exists
+                            drop_tables
 from ..db.utils import execute_file
 from ..db.sql_preprocessor import SQLPreprocessor
-from ..version import NOMINATIM_VERSION
 
 LOG = logging.getLogger()
 
@@ -99,34 +97,6 @@ def create_functions(conn: Connection, config: Configuration,
                      debug=enable_debug)
 
 
-
-WEBSITE_SCRIPTS = (
-    'deletable.php',
-    'details.php',
-    'lookup.php',
-    'polygons.php',
-    'reverse.php',
-    'search.php',
-    'status.php'
-)
-
-# constants needed by PHP scripts: PHP name, config name, type
-PHP_CONST_DEFS = (
-    ('Database_DSN', 'DATABASE_DSN', str),
-    ('Default_Language', 'DEFAULT_LANGUAGE', str),
-    ('Log_DB', 'LOG_DB', bool),
-    ('Log_File', 'LOG_FILE', Path),
-    ('NoAccessControl', 'CORS_NOACCESSCONTROL', bool),
-    ('Places_Max_ID_count', 'LOOKUP_MAX_COUNT', int),
-    ('PolygonOutput_MaximumTypes', 'POLYGON_OUTPUT_MAX_TYPES', int),
-    ('Search_BatchMode', 'SEARCH_BATCH_MODE', bool),
-    ('Search_NameOnlySearchFrequencyThreshold', 'SEARCH_NAME_ONLY_THRESHOLD', str),
-    ('Use_US_Tiger_Data', 'USE_US_TIGER_DATA', bool),
-    ('MapIcon_URL', 'MAPICON_URL', str),
-    ('Search_WithinCountries', 'SEARCH_WITHIN_COUNTRIES', bool),
-)
-
-
 def import_wikipedia_articles(dsn: str, data_path: Path, ignore_errors: bool = False) -> int:
     """ Replaces the wikipedia importance tables with new data.
         The import is run in a single transaction so that the new data
@@ -272,46 +242,6 @@ def _quote_php_variable(var_type: Type[Any], config: Configuration,
     return f"'{quoted}'"
 
 
-def setup_website(basedir: Path, config: Configuration, conn: Connection) -> None:
-    """ Create the website script stubs.
-    """
-    if config.lib_dir.php is None:
-        LOG.info("Python frontend does not require website setup. Skipping.")
-        return
-
-    if not basedir.exists():
-        LOG.info('Creating website directory.')
-        basedir.mkdir()
-
-    assert config.project_dir is not None
-    basedata = dedent(f"""\
-                      <?php
-
-                      @define('CONST_Debug', $_GET['debug'] ?? false);
-                      @define('CONST_LibDir', '{config.lib_dir.php}');
-                      @define('CONST_TokenizerDir', '{config.project_dir / 'tokenizer'}');
-                      @define('CONST_NominatimVersion', '{NOMINATIM_VERSION!s}');
-
-                      """)
-
-    for php_name, conf_name, var_type in PHP_CONST_DEFS:
-        varout = _quote_php_variable(var_type, config, conf_name)
-
-        basedata += f"@define('CONST_{php_name}', {varout});\n"
-
-    template = "\nrequire_once(CONST_LibDir.'/website/{}');\n"
-
-    search_name_table_exists = bool(conn and table_exists(conn, 'search_name'))
-
-    for script in WEBSITE_SCRIPTS:
-        if not search_name_table_exists and script == 'search.php':
-            out = template.format('reverse-only-search.php')
-        else:
-            out = template.format(script)
-
-        (basedir / script).write_text(basedata + out, 'utf-8')
-
-
 def invalidate_osm_object(osm_type: str, osm_id: int, conn: Connection,
                           recursive: bool = True) -> None:
     """ Mark the given OSM object for reindexing. When 'recursive' is set
index 588a31c8c56a0ec2626e698d599534f212849ac0..8cc8e4fe3e0155619edd23453c92b67f19d93b63 100644 (file)
@@ -58,7 +58,7 @@ def parse_version(version: str) -> NominatimVersion:
     return NominatimVersion(*[int(x) for x in parts[:2] + parts[2].split('-')])
 
 
-NOMINATIM_VERSION = parse_version('4.4.99-1')
+NOMINATIM_VERSION = parse_version('4.5.0-0')
 
 POSTGRESQL_REQUIRED_VERSION = (9, 6)
 POSTGIS_REQUIRED_VERSION = (2, 2)
index 5f78eeac878e3a794ada708b6ea1d82778643cdc..9768ebd728809e7615f2de1b509a79289bd67ad2 100644 (file)
@@ -1,14 +1,10 @@
-all: bdd php python
-no-test-db: bdd-no-test-db php
+all: bdd python
 
 bdd:
        cd bdd && behave -DREMOVE_TEMPLATE=1
 
-php:
-       cd php && phpunit ./
-
 python:
        pytest python
 
 
-.PHONY: bdd php no-test-db python
+.PHONY: bdd python
index 0e456aa5bbf962af30693096626e102ae326ca8a..5e0bacc5a945b2909691072885b194e1fd032136 100644 (file)
@@ -42,16 +42,6 @@ Feature: Object details
       | N300209696:highway |
 
 
-    @v1-api-php-only
-    Scenario: Details for interpolation way just return the dependent street
-        When sending details query for W1
-        Then the result is valid json
-        And results contain
-            | category |
-            | highway |
-
-
-     @v1-api-python-only
      Scenario: Details for interpolation way return the interpolation
         When sending details query for W1
         Then the result is valid json
@@ -60,17 +50,6 @@ Feature: Object details
             | place    | houses | W        | 1      | 15          |
 
 
-     @v1-api-php-only
-     @Fail
-     Scenario: Details for Tiger way just return the dependent street
-        When sending details query for 112871
-        Then the result is valid json
-        And results contain
-            | category |
-            | highway |
-
-
-     @v1-api-python-only
      @Fail
      Scenario: Details for interpolation way return the interpolation
         When sending details query for 112871
@@ -81,17 +60,6 @@ Feature: Object details
         And result has not attributes osm_type,osm_id
 
 
-     @v1-api-php-only
-     @Fail
-     Scenario: Details for postcodes just return the dependent place
-        When sending details query for 112820
-        Then the result is valid json
-        And results contain
-            | category |
-            | boundary |
-
-
-     @v1-api-python-only
      @Fail
      Scenario: Details for interpolation way return the interpolation
         When sending details query for 112820
@@ -102,7 +70,6 @@ Feature: Object details
         And result has not attributes osm_type,osm_id
 
 
-    @v1-api-python-only
     Scenario Outline: Details debug output returns no errors
         When sending debug details query for <feature>
         Then the result is valid html
index ef02886478cbc81aae2358b99f15f2a6692c8b55..f1885f0e46f52a28002d1eb8832350d6260f3573 100644 (file)
@@ -3,7 +3,6 @@
 Feature: Layer parameter in reverse geocoding
     Testing correct function of layer selection while reverse geocoding
 
-    @v1-api-python-only
     Scenario: POIs are selected by default
         When sending v1/reverse at 47.14077,9.52414
         Then results contain
@@ -11,7 +10,6 @@ Feature: Layer parameter in reverse geocoding
           | tourism  | viewpoint |
 
 
-    @v1-api-python-only
     Scenario Outline: Same address level POI with different layers
         When sending v1/reverse at 47.14077,9.52414
           | layer   |
@@ -31,7 +29,6 @@ Feature: Layer parameter in reverse geocoding
           | natural,poi     | tourism  |
 
 
-    @v1-api-python-only
      Scenario Outline: POIs are not selected without housenumber for address layer
         When sending v1/reverse at 47.13816,9.52168
           | layer   |
@@ -46,7 +43,6 @@ Feature: Layer parameter in reverse geocoding
           | address     | amenity  | parking  |
 
 
-    @v1-api-python-only
      Scenario: Between natural and low-zoom address prefer natural
          When sending v1/reverse at 47.13636,9.52094
            | layer           | zoom |
@@ -56,7 +52,6 @@ Feature: Layer parameter in reverse geocoding
            | waterway |
 
 
-    @v1-api-python-only
     Scenario Outline: Search for mountain peaks begins at level 12
         When sending v1/reverse at 47.08293,9.57109
           | layer   | zoom   |
@@ -71,7 +66,6 @@ Feature: Layer parameter in reverse geocoding
           | 13   | waterway | river |
 
 
-    @v1-api-python-only
      Scenario Outline: Reverse search with manmade layers
         When sending v1/reverse at 32.46904,-86.44439
           | layer   |
index a1f08afd37d6919e20af7f00741012ff07ba0bae..09a190ed9b236af8cb03f7f2009a0a189d784e1b 100644 (file)
@@ -18,20 +18,6 @@ Feature: v1/reverse Parameter Tests
         When sending v1/reverse at ,52.52
         Then a HTTP 400 is returned
 
-    @v1-api-php-only
-    Scenario: Missing osm_id parameter
-        When sending v1/reverse at ,
-          | osm_type |
-          | N |
-        Then a HTTP 400 is returned
-
-    @v1-api-php-only
-    Scenario: Missing osm_type parameter
-        When sending v1/reverse at ,
-          | osm_id |
-          | 3498564 |
-        Then a HTTP 400 is returned
-
 
     Scenario Outline: Bad format for lat or lon
         When sending v1/reverse at ,
@@ -151,7 +137,6 @@ Feature: v1/reverse Parameter Tests
           | foo; evil |
 
 
-    @v1-api-python-only
     Scenario Outline: Reverse debug mode produces valid HTML
         When sending v1/reverse at , with format debug
           | lat   | lon   |
index e667b690b0d8f2572498c6ceefbf776f86b8422e..e77a00d2d01b5bcc9407c171a50b08a0adc7e5c8 100644 (file)
@@ -69,7 +69,6 @@ Feature: Search queries
           | 0 |
         Then there are duplicates
 
-    @fail-legacy
     Scenario: Search with bounded viewbox in right area
         When sending json search query "post" with address
           | bounded | viewbox |
@@ -353,19 +352,6 @@ Feature: Search queries
         | svg                |
         | geokml             |
 
-    @v1-api-php-only
-    Scenario: Search along a route
-        When sending json search query "rathaus" with address
-        Then result addresses contain
-          | ID | town |
-          | 0  | Schaan |
-        When sending json search query "rathaus" with address
-          | bounded | routewidth | route                              |
-          | 1       | 0.1        |  9.54353,47.11772,9.54314,47.11894 |
-        Then result addresses contain
-          | town |
-          | Triesenberg |
-
 
     Scenario: Array parameters are ignored
         When sending json search query "Vaduz" with address
index 827af1ea799e36a520c497f2c994c2672115d862..bb1b755bf487b1fb3bc5bbb279562380442490b3 100644 (file)
@@ -3,7 +3,7 @@
 Feature: Searches with postcodes
     Various searches involving postcodes
 
-    @v1-api-php-only
+    @Fail
     Scenario: US 5+4 ZIP codes are shortened to 5 ZIP codes if not found
         When sending json search query "36067 1111, us" with address
         Then result addresses contain
index b2793faa3a3696dc0fd9bcf5ea497276e70b1c72..6e640accab02e4d166a65b91e4d2a64b27699f15 100644 (file)
@@ -106,17 +106,6 @@ Feature: Search queries
           | class | type |
           | club  | scout |
 
-    @v1-api-php-only
-    Scenario: With multiple amenity search only the first is used
-        When sending json search query "[club=scout] [church] vaduz"
-        Then results contain
-          | class | type |
-          | club  | scout |
-        When sending json search query "[amenity=place_of_worship] [club=scout] vaduz"
-        Then results contain
-          | class   | type |
-          | amenity | place_of_worship |
-
     Scenario: POI search near given coordinate
         When sending json search query "restaurant near 47.16712,9.51100"
         Then results contain
@@ -129,13 +118,6 @@ Feature: Search queries
           | class   | type |
           | leisure | firepit |
 
-    @v1-api-php-only
-    Scenario: Arbitrary key/value search near given coordinate and named place
-        When sending json search query "[leisure=firepit] ebenholz  47° 9′ 26″ N 9° 36′ 45″ E"
-        Then results contain
-          | class    | type |
-          | leisure | firepit |
-
 
     Scenario: POI search in a bounded viewbox
         When sending json search query "restaurants"
index c349b69f63ee1b1b03053189cf078e02e584148f..55fa6a605b3b4858a81a0ab56d317e4fc9722747 100644 (file)
@@ -104,7 +104,6 @@ Feature: Parenting of objects
          | N3     | W2 |
          | N4     | W1 |
 
-    @fail-legacy
     Scenario: addr:street tag parents to appropriately named street, locale names
         Given the grid
          | 10 |   |   |   |   | 11 |
index 57f90d98d07443f22b543810dcf864f724e83c33..3f4976f15a3d8a671e4b58a5caa68483e93e7892 100644 (file)
@@ -195,7 +195,6 @@ Feature: Import of postcodes
          | E45 2    | gb      | 23          | 5 |
          | Y45      | gb      | 21          | 5 |
 
-    @fail-legacy
     Scenario: Postcodes outside all countries are not added to the postcode and word table
         Given the places
             | osm | class | type  | addr+postcode | addr+housenumber | addr+place  | geometry  |
index 538bcbb3856abd19bf80ff922d9f5008baab67b2..cd581c46c0e1d0eba415dd49f629e83df1f5b22b 100644 (file)
@@ -11,7 +11,6 @@ Feature: Creation of search terms
          | object | name_vector |
          | N1     | #New York, #Big Apple |
 
-    @fail-legacy
     Scenario: Comma-separated names appear as a single full name
         Given the places
          | osm | class   | type | name+alt_name |
index 106bc8bb2cd0c1db653c22a03d2b2064aa9916c2..16d9fd5a4731725d41e4d3f3a99b79bf2731550d 100644 (file)
@@ -27,7 +27,6 @@ Feature: Searching of house numbers
          | N1  |
 
 
-    @fail-legacy
     Scenario Outline: Numeral housenumbers in any script are found
         Given the places
          | osm | class    | type | housenr  | geometry |
@@ -84,7 +83,6 @@ Feature: Searching of house numbers
         | 2, 4, 12 |
 
 
-    @fail-legacy
     Scenario Outline: Housenumber - letter combinations are found
         Given the places
          | osm | class    | type | housenr | geometry |
@@ -150,7 +148,6 @@ Feature: Searching of house numbers
         | 34/10 |
 
 
-    @fail-legacy
     Scenario Outline: a bis housenumber is found
         Given the places
          | osm | class    | type | housenr | geometry |
@@ -184,7 +181,6 @@ Feature: Searching of house numbers
         | 45 bis |
 
 
-    @fail-legacy
     Scenario Outline: a ter housenumber is found
         Given the places
          | osm | class    | type | housenr | geometry |
@@ -218,7 +214,6 @@ Feature: Searching of house numbers
         | 45 TER |
 
 
-    @fail-legacy
     Scenario Outline: a number - letter - number combination housenumber is found
         Given the places
          | osm | class    | type | housenr | geometry |
@@ -252,7 +247,6 @@ Feature: Searching of house numbers
         | 501h1 |
 
 
-    @fail-legacy
     Scenario Outline: Russian housenumbers are found
         Given the places
          | osm | class    | type | housenr | geometry |
index 4960c50b4fe346aae97e9c97013c22c60a6db16b..f21e0f5c9f9bd4b7ee70f4cfc386d353d37c5218 100644 (file)
@@ -1,7 +1,6 @@
 @DB
 Feature: Searches in Japan
     Test specifically for searches of Japanese addresses and in Japanese language.
-    @fail-legacy
     Scenario: A block house-number is parented to the neighbourhood
         Given the grid with origin JP
           | 1 |   |   |   | 2 |
index fa4f6a0b486b60df0bae3fef2133e3b5e723a8b6..e8a2ccc2bb8fde4b24ebfeaf9c73ce69f7256e89 100644 (file)
@@ -14,7 +14,6 @@ Feature: Querying fo postcode variants
             | 0  | postcode | 399174, Singapore |
 
 
-    @fail-legacy
     Scenario Outline: Postcodes in the Netherlands (mixed postcode with spaces)
         Given the grid with origin NL
             | 10 |   |   |   | 11 |
@@ -38,7 +37,6 @@ Feature: Querying fo postcode variants
             | 3993 dx  |
 
 
-    @fail-legacy
     Scenario: Postcodes in Singapore (6-digit postcode)
         Given the grid with origin SG
             | 10 |   |   |   | 11 |
@@ -52,7 +50,6 @@ Feature: Querying fo postcode variants
             | 0  | postcode | 399174, Singapore       |
 
 
-    @fail-legacy
     Scenario Outline: Postcodes in Andorra (with country code)
         Given the grid with origin AD
             | 10 |   |   |   | 11 |
@@ -76,7 +73,6 @@ Feature: Querying fo postcode variants
             | AD675    |
 
 
-    @fail-legacy
     Scenario: Different postcodes with the same normalization can both be found
         Given the places
            | osm | class | type  | addr+postcode | addr+housenumber | geometry |
@@ -97,8 +93,6 @@ Feature: Querying fo postcode variants
            | postcode | E4 7EA, United Kingdom |
 
 
-    @fail-legacy
-    @v1-api-python-only
     Scenario: Postcode areas are preferred over postcode points
         Given the grid with origin DE
             | 1 | 2 |
index 129411026074dd6a398c36d62b4950292b8228ea..11ee868567f926c42175e0b6d1c79ad233c8e034 100644 (file)
@@ -2,7 +2,6 @@
 Feature: Reverse searches
     Test results of reverse queries
 
-    @v1-api-python-only
     Scenario: POI in POI area
         Given the 0.0001 grid with origin 1,1
           | 1 |   |  |  |  |  |  |  | 2 |
index 270d2e554757de72d94a61de0561a4c8dd889270..5fef313214bf2f2f7ba78d06fe3e0468c857a847 100644 (file)
@@ -77,7 +77,6 @@ Feature: Searching of simple objects
          | W1  |
 
 
-     @fail-legacy
      Scenario Outline: Special cased american states will be found
         Given the grid
          | 1 |    | 2 |
index 794b0d0e2be5630f76ef7e8dd63ce9f7490096c3..abc1af0968e5067608d24f9472c83301808016cc 100644 (file)
@@ -8,7 +8,6 @@ Feature: Country handling
             |   | 10 |   |
             | 4 |    | 3 |
 
-    @fail-legacy
     Scenario: When country names are changed old ones are no longer searchable
         Given the places
             | osm | class    | type           | admin | name+name:xy | country | geometry |
@@ -27,7 +26,6 @@ Feature: Country handling
         When sending search query "Wenig, Loudou"
         Then exactly 0 results are returned
 
-    @fail-legacy
     Scenario: When country names are deleted they are no longer searchable
         Given the places
             | osm | class    | type           | admin | name+name:xy | country | geometry |
@@ -83,7 +81,6 @@ Feature: Country handling
             | N10 | Wenig, Lilly |
 
 
-    @fail-legacy
     Scenario: When a localised name is deleted, the standard name takes over
         Given the places
             | osm | class    | type           | admin | name+name:de | country | geometry |
index 155b8d90a01fd18614481e5f22452011d5262f34..7535c5086133a1e9dd2a4ffb62c1f48ec278850a 100644 (file)
@@ -27,7 +27,6 @@ userconfig = {
     'TEST_DB' : 'test_nominatim',
     'API_TEST_DB' : 'test_api_nominatim',
     'API_TEST_FILE'  : TEST_BASE_DIR / 'testdb' / 'apidb-test-data.pbf',
-    'SERVER_MODULE_PATH' : None,
     'TOKENIZER' : None, # Test with a custom tokenizer
     'STYLE' : 'extratags',
     'API_ENGINE': 'falcon'
@@ -60,15 +59,3 @@ def before_scenario(context, scenario):
 def after_scenario(context, scenario):
     if 'DB' in context.tags:
         context.nominatim.teardown_db(context)
-
-
-def before_tag(context, tag):
-    if tag == 'fail-legacy':
-        if context.config.userdata['TOKENIZER'] == 'legacy':
-            context.scenario.skip("Not implemented in legacy tokenizer")
-    if tag == 'v1-api-php-only':
-        if context.config.userdata['API_ENGINE'] != 'php':
-            context.scenario.skip("Only valid with PHP version of v1 API.")
-    if tag == 'v1-api-python-only':
-        if context.config.userdata['API_ENGINE'] == 'php':
-            context.scenario.skip("Only valid with Python version of v1 API.")
index c4b055885d1a61211190d227e47eaef57d9d206c..ba19bb48e4accd40e71b053d8eab052e3d8559bb 100644 (file)
@@ -34,7 +34,6 @@ class NominatimEnvironment:
         self.api_test_file = config['API_TEST_FILE']
         self.tokenizer = config['TOKENIZER']
         self.import_style = config['STYLE']
-        self.server_module_path = config['SERVER_MODULE_PATH']
         self.reuse_template = not config['REMOVE_TEMPLATE']
         self.keep_scenario_db = config['KEEP_TEST_DB']
 
@@ -44,14 +43,9 @@ class NominatimEnvironment:
         self.api_db_done = False
         self.website_dir = None
 
-        self.api_engine = None
-        if config['API_ENGINE'] != 'php':
-            if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"):
-                raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'")
-            self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")()
-
-        if self.tokenizer == 'legacy' and self.server_module_path is None:
-            raise RuntimeError("You must set -DSERVER_MODULE_PATH when testing the legacy tokenizer.")
+        if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"):
+            raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'")
+        self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")()
 
     def connect_database(self, dbname):
         """ Return a connection to the database with the given name.
@@ -102,27 +96,14 @@ class NominatimEnvironment:
         if self.import_style is not None:
             self.test_env['NOMINATIM_IMPORT_STYLE'] = self.import_style
 
-        if self.server_module_path:
-            self.test_env['NOMINATIM_DATABASE_MODULE_PATH'] = self.server_module_path
-
         if self.website_dir is not None:
             self.website_dir.cleanup()
 
         self.website_dir = tempfile.TemporaryDirectory()
 
-        try:
-            conn = self.connect_database(dbname)
-        except:
-            conn = False
-        refresh.setup_website(Path(self.website_dir.name) / 'website',
-                              self.get_test_config(), conn)
-        if conn:
-            conn.close()
-
 
     def get_test_config(self):
         cfg = Configuration(Path(self.website_dir.name), environ=self.test_env)
-        cfg.set_libdirs(module=self.server_module_path)
         return cfg
 
     def get_libpq_dsn(self):
@@ -201,12 +182,8 @@ class NominatimEnvironment:
                     self.run_nominatim('add-data', '--tiger-data', str(testdata / 'tiger'))
                     self.run_nominatim('freeze')
 
-                    if self.tokenizer == 'legacy':
-                        phrase_file = str(testdata / 'specialphrases_testdb.sql')
-                        run_script(['psql', '-d', self.api_test_db, '-f', phrase_file])
-                    else:
-                        csv_path = str(testdata / 'full_en_phrases_test.csv')
-                        self.run_nominatim('special-phrases', '--import-from-csv', csv_path)
+                    csv_path = str(testdata / 'full_en_phrases_test.csv')
+                    self.run_nominatim('special-phrases', '--import-from-csv', csv_path)
                 except:
                     self.db_drop_database(self.api_test_db)
                     raise
@@ -289,8 +266,7 @@ class NominatimEnvironment:
         if self.website_dir is not None:
             cmdline = list(cmdline) + ['--project-dir', self.website_dir.name]
 
-        cli.nominatim(module_dir=self.server_module_path,
-                      osm2pgsql_path=None,
+        cli.nominatim(osm2pgsql_path=None,
                       cli_args=cmdline,
                       environ=self.test_env)
 
index 93501e42c756c3aa24f07a93c40e356e07e7e13e..4d15381d41c4b1a987db38e4047d924a7ad11424 100644 (file)
@@ -1,13 +1,10 @@
-# SPDX-License-Identifier: GPL-2.0-only
+# SPDX-License-Identifier: GPL-3.0-or-later
 #
 # This file is part of Nominatim. (https://nominatim.org)
 #
-# Copyright (C) 2022 by the Nominatim developer community.
+# Copyright (C) 2024 by the Nominatim developer community.
 # For a full list of authors see the git log.
 """ Steps that run queries against the API.
-
-    Queries may either be run directly via PHP using the query script
-    or via the HTTP interface using php-cgi.
 """
 from pathlib import Path
 import json
@@ -25,29 +22,6 @@ from table_compare import NominatimID
 
 LOG = logging.getLogger(__name__)
 
-BASE_SERVER_ENV = {
-    'HTTP_HOST' : 'localhost',
-    'HTTP_USER_AGENT' : 'Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0',
-    'HTTP_ACCEPT' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
-    'HTTP_ACCEPT_ENCODING' : 'gzip, deflate',
-    'HTTP_CONNECTION' : 'keep-alive',
-    'SERVER_SIGNATURE' : '<address>Nominatim BDD Tests</address>',
-    'SERVER_SOFTWARE' : 'Nominatim test',
-    'SERVER_NAME' : 'localhost',
-    'SERVER_ADDR' : '127.0.1.1',
-    'SERVER_PORT' : '80',
-    'REMOTE_ADDR' : '127.0.0.1',
-    'DOCUMENT_ROOT' : '/var/www',
-    'REQUEST_SCHEME' : 'http',
-    'CONTEXT_PREFIX' : '/',
-    'SERVER_ADMIN' : 'webmaster@localhost',
-    'REMOTE_PORT' : '49319',
-    'GATEWAY_INTERFACE' : 'CGI/1.1',
-    'SERVER_PROTOCOL' : 'HTTP/1.1',
-    'REQUEST_METHOD' : 'GET',
-    'REDIRECT_STATUS' : 'CGI'
-}
-
 
 def make_todo_list(context, result_id):
     if result_id is None:
@@ -88,50 +62,12 @@ def send_api_query(endpoint, params, fmt, context):
             for h in context.table.headings:
                 params[h] = context.table[0][h]
 
-    if context.nominatim.api_engine is None:
-        return send_api_query_php(endpoint, params, context)
-
     return asyncio.run(context.nominatim.api_engine(endpoint, params,
                                                     Path(context.nominatim.website_dir.name),
                                                     context.nominatim.test_env,
                                                     getattr(context, 'http_headers', {})))
 
 
-
-def send_api_query_php(endpoint, params, context):
-    env = dict(BASE_SERVER_ENV)
-    env['QUERY_STRING'] = urlencode(params)
-
-    env['SCRIPT_NAME'] = f'/{endpoint}.php'
-    env['REQUEST_URI'] = f"{env['SCRIPT_NAME']}?{env['QUERY_STRING']}"
-    env['CONTEXT_DOCUMENT_ROOT'] = os.path.join(context.nominatim.website_dir.name, 'website')
-    env['SCRIPT_FILENAME'] = os.path.join(env['CONTEXT_DOCUMENT_ROOT'],
-                                          f'{endpoint}.php')
-
-    LOG.debug("Environment:" + json.dumps(env, sort_keys=True, indent=2))
-
-    if hasattr(context, 'http_headers'):
-        for k, v in context.http_headers.items():
-            env['HTTP_' + k.upper().replace('-', '_')] = v
-
-    cmd = ['/usr/bin/env', 'php-cgi', '-f', env['SCRIPT_FILENAME']]
-
-    for k,v in params.items():
-        cmd.append(f"{k}={v}")
-
-    outp, err = run_script(cmd, cwd=context.nominatim.website_dir.name, env=env)
-
-    assert len(err) == 0, f"Unexpected PHP error: {err}"
-
-    if outp.startswith('Status: '):
-        status = int(outp[8:11])
-    else:
-        status = 200
-
-    content_start = outp.find('\r\n\r\n')
-
-    return outp[content_start + 4:], status
-
 @given(u'the HTTP header')
 def add_http_header(context):
     if not hasattr(context, 'http_headers'):
index a0dd9b348e7f60681ad56ed9618f45972d0da945..fb8431d5ffae827c7c681878d57a3b94bc3375b5 100644 (file)
@@ -28,9 +28,8 @@ def check_database_integrity(context):
         assert cur.fetchone()[0] == 0, "Duplicates found in place_addressline"
 
         # word table must not have empty word_tokens
-        if context.nominatim.tokenizer != 'legacy':
-            cur.execute("SELECT count(*) FROM word WHERE word_token = ''")
-            assert cur.fetchone()[0] == 0, "Empty word tokens found in word table"
+        cur.execute("SELECT count(*) FROM word WHERE word_token = ''")
+        assert cur.fetchone()[0] == 0, "Empty word tokens found in word table"
 
 
 
@@ -324,13 +323,8 @@ def check_word_table_for_postcodes(context, exclude, postcodes):
     plist.sort()
 
     with context.db.cursor() as cur:
-        if nctx.tokenizer != 'legacy':
-            cur.execute("SELECT word FROM word WHERE type = 'P' and word = any(%s)",
-                        (plist,))
-        else:
-            cur.execute("""SELECT word FROM word WHERE word = any(%s)
-                             and class = 'place' and type = 'postcode'""",
-                        (plist,))
+        cur.execute("SELECT word FROM word WHERE type = 'P' and word = any(%s)",
+                    (plist,))
 
         found = [row['word'] for row in cur]
         assert len(found) == len(set(found)), f"Duplicate rows for postcodes: {found}"
diff --git a/test/php/Nominatim/AddressDetailsTest.php b/test/php/Nominatim/AddressDetailsTest.php
deleted file mode 100644 (file)
index 2041dcb..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/AddressDetails.php');
-
-
-class AddressDetailsTest extends \PHPUnit\Framework\TestCase
-{
-
-    protected function setUp(): void
-    {
-        // How the fixture got created
-        //
-        // 1) search for '10 downing street'
-        // https://nominatim.openstreetmap.org/details.php?osmtype=R&osmid=1879842
-        //
-        // 2) find place_id in the local database
-        // SELECT place_id, name FROM placex WHERE osm_type='R' AND osm_id=1879842;
-        //
-        // 3) set postgresql to non-align output, e.g. psql -A or \a in the CLI
-        //
-        // 4) query
-        // SELECT row_to_json(row,true) FROM (
-        //   SELECT *, get_name_by_language(name, ARRAY['name:en']) as localname
-        //   FROM get_addressdata(194663412,10)
-        //   ORDER BY rank_address DESC, isaddress DESC
-        // ) AS row;
-        //
-        // 5) copy&paste into file. Add commas between records
-        //
-        $json = file_get_contents(CONST_DataDir.'/test/php/fixtures/address_details_10_downing_street.json');
-        $data = json_decode($json, true);
-
-        $this->oDbStub = $this->getMockBuilder(\DB::class)
-                              ->setMethods(array('getAll'))
-                              ->getMock();
-        $this->oDbStub->method('getAll')
-                      ->willReturn($data);
-    }
-
-    public function testGetLocaleAddress()
-    {
-        $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
-        $expected = join(', ', array(
-            '10 Downing Street',
-            '10',
-            'Downing Street',
-            'St. James\'s',
-            'Covent Garden',
-            'Westminster',
-            'London',
-            'Greater London',
-            'England',
-            'SW1A 2AA',
-            'United Kingdom'
-        ));
-        $this->assertEquals($expected, $oAD->getLocaleAddress());
-    }
-
-    public function testGetAddressDetails()
-    {
-        $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
-        $this->assertEquals(18, count($oAD->getAddressDetails(true)));
-        $this->assertEquals(12, count($oAD->getAddressDetails(false)));
-    }
-
-    public function testGetAddressNames()
-    {
-        $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
-        $expected = array(
-                     'tourism' => '10 Downing Street',
-                     'house_number' => '10',
-                     'road' => 'Downing Street',
-                     'neighbourhood' => 'St. James\'s',
-                     'suburb' => 'Covent Garden',
-                     'city' => 'London',
-                     'state_district' => 'Greater London',
-                     'state' => 'England',
-                     'ISO3166-2-lvl4' => 'GB-ENG',
-                     'ISO3166-2-lvl6' => 'GB-LND',
-                     'postcode' => 'SW1A 2AA',
-                     'country' => 'United Kingdom',
-                     'country_code' => 'gb'
-        );
-
-        $this->assertEquals($expected, $oAD->getAddressNames());
-    }
-
-    public function testGetAdminLevels()
-    {
-        $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
-        $expected = array(
-                     'level8' => 'Westminster',
-                     'level6' => 'London',
-                     'level5' => 'Greater London',
-                     'level4' => 'England',
-                     'level2' => 'United Kingdom'
-        );
-        $this->assertEquals($expected, $oAD->getAdminLevels());
-    }
-
-    public function testDebugInfo()
-    {
-        $oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
-        $this->assertTrue(is_array($oAD->debugInfo()));
-        $this->assertEquals(18, count($oAD->debugInfo()));
-    }
-}
diff --git a/test/php/Nominatim/ClassTypesTest.php b/test/php/Nominatim/ClassTypesTest.php
deleted file mode 100644 (file)
index d2900d8..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-class ClassTypesTest extends \PHPUnit\Framework\TestCase
-{
-    public function testGetLabelTag()
-    {
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative',
-                   'rank_address' => '4', 'place_type' => 'city');
-        $this->assertEquals('city', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative',
-                   'rank_address' => '10');
-        $this->assertEquals('state_district', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative');
-        $this->assertEquals('administrative', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'place', 'type' => 'hamlet', 'rank_address' => '20');
-        $this->assertEquals('hamlet', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'highway', 'type' => 'residential',
-                   'rank_address' => '26');
-        $this->assertEquals('road', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'place', 'type' => 'house_number',
-                   'rank_address' => '30');
-        $this->assertEquals('house_number', ClassTypes\getLabelTag($aPlace));
-
-        $aPlace = array('class' => 'amenity', 'type' => 'prison',
-                   'rank_address' => '30');
-        $this->assertEquals('amenity', ClassTypes\getLabelTag($aPlace));
-    }
-
-    public function testGetLabel()
-    {
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative',
-                   'rank_address' => '4', 'place_type' => 'city');
-        $this->assertEquals('City', ClassTypes\getLabel($aPlace));
-
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative',
-                   'rank_address' => '10');
-        $this->assertEquals('State District', ClassTypes\getLabel($aPlace));
-
-        $aPlace = array('class' => 'boundary', 'type' => 'administrative');
-        $this->assertEquals('Administrative', ClassTypes\getLabel($aPlace));
-
-        $aPlace = array('class' => 'amenity', 'type' => 'prison');
-        $this->assertEquals('Prison', ClassTypes\getLabel($aPlace));
-
-        $aPlace = array('class' => 'amenity', 'type' => 'foobar');
-        $this->assertNull(ClassTypes\getLabel($aPlace));
-    }
-
-    public function testGetBoundaryLabel()
-    {
-        $this->assertEquals('City', ClassTypes\getBoundaryLabel(8, null));
-        $this->assertEquals('Administrative', ClassTypes\getBoundaryLabel(18, null));
-        $this->assertEquals('None', ClassTypes\getBoundaryLabel(18, null, 'None'));
-        $this->assertEquals('State', ClassTypes\getBoundaryLabel(4, 'de', 'None'));
-        $this->assertEquals('County', ClassTypes\getBoundaryLabel(4, 'se', 'None'));
-        $this->assertEquals('Municipality', ClassTypes\getBoundaryLabel(7, 'se', 'None'));
-    }
-
-    public function testGetDefRadius()
-    {
-        $aResult = array('class' => '', 'type' => '');
-        $this->assertEquals(0.00005, ClassTypes\getDefRadius($aResult));
-
-        $aResult = array('class' => 'place', 'type' => 'country');
-        $this->assertEquals(7, ClassTypes\getDefRadius($aResult));
-    }
-
-    public function testGetIcon()
-    {
-        $aResult = array('class' => '', 'type' => '');
-        $this->assertNull(ClassTypes\getIcon($aResult));
-
-        $aResult = array('class' => 'place', 'type' => 'airport');
-        $this->assertEquals('transport_airport2', ClassTypes\getIcon($aResult));
-    }
-
-    public function testGetImportance()
-    {
-        $aResult = array('class' => '', 'type' => '');
-        $this->assertNull(ClassTypes\getImportance($aResult));
-
-        $aResult = array('class' => 'place', 'type' => 'airport');
-        $this->assertGreaterThan(0, ClassTypes\getImportance($aResult));
-    }
-}
diff --git a/test/php/Nominatim/DBTest.php b/test/php/Nominatim/DBTest.php
deleted file mode 100644 (file)
index 1c6f763..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/DB.php');
-
-// subclassing so we can set the protected connection variable
-class NominatimSubClassedDB extends \Nominatim\DB
-{
-    public function setConnection($oConnection)
-    {
-        $this->connection = $oConnection;
-    }
-}
-
-// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
-class DBTest extends \PHPUnit\Framework\TestCase
-{
-    public function testReusingConnection()
-    {
-        $oDB = new NominatimSubClassedDB('');
-        $oDB->setConnection('anything');
-        $this->assertTrue($oDB->connect());
-    }
-
-    public function testCheckConnection()
-    {
-        $oDB = new \Nominatim\DB('');
-        $this->assertFalse($oDB->checkConnection());
-    }
-
-    public function testErrorHandling()
-    {
-        $this->expectException(DatabaseError::class);
-        $this->expectExceptionMessage('Failed to establish database connection');
-
-        $oDB = new \Nominatim\DB('pgsql:dbname=abc');
-        $oDB->connect();
-    }
-
-    public function testErrorHandling2()
-    {
-        $this->expectException(DatabaseError::class);
-        $this->expectExceptionMessage('Database query failed');
-
-        $oPDOStub = $this->getMockBuilder(PDO::class)
-                         ->setMethods(array('query', 'quote'))
-                         ->getMock();
-
-        $oPDOStub->method('query')
-                 ->will($this->returnCallback(function ($sVal) {
-                    return "'$sVal'";
-                 }));
-
-        $oPDOStub->method('query')
-                 ->will($this->returnCallback(function () {
-                     throw new \PDOException('ERROR:  syntax error at or near "FROM"');
-                 }));
-
-        $oDB = new NominatimSubClassedDB('');
-        $oDB->setConnection($oPDOStub);
-        $oDB->getOne('SELECT name FROM');
-    }
-
-    public function testGetPostgresVersion()
-    {
-        $oDBStub = $this->getMockBuilder(\Nominatim\DB::class)
-                        ->disableOriginalConstructor()
-                        ->setMethods(array('getOne'))
-                        ->getMock();
-
-        $oDBStub->method('getOne')
-                ->willReturn('100006');
-
-        $this->assertEquals(10, $oDBStub->getPostgresVersion());
-    }
-
-    public function testGetPostgisVersion()
-    {
-        $oDBStub = $this->getMockBuilder(\Nominatim\DB::class)
-                        ->disableOriginalConstructor()
-                        ->setMethods(array('getOne'))
-                        ->getMock();
-
-        $oDBStub->method('getOne')
-                ->willReturn('2.4.4');
-
-        $this->assertEquals(2.4, $oDBStub->getPostgisVersion());
-    }
-
-    public function testParseDSN()
-    {
-        $this->assertEquals(
-            array(),
-            \Nominatim\DB::parseDSN('')
-        );
-        $this->assertEquals(
-            array(
-             'database' => 'db1',
-             'hostspec' => 'machine1'
-            ),
-            \Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1')
-        );
-        $this->assertEquals(
-            array(
-             'database' => 'db1',
-             'hostspec' => 'machine1',
-             'port' => '1234',
-             'username' => 'john',
-             'password' => 'secret'
-            ),
-            \Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1;port=1234;user=john;password=secret')
-        );
-    }
-
-    public function testGenerateDSN()
-    {
-        $this->assertEquals(
-            'pgsql:',
-            \Nominatim\DB::generateDSN(array())
-        );
-        $this->assertEquals(
-            'pgsql:host=machine1;dbname=db1',
-            \Nominatim\DB::generateDSN(\Nominatim\DB::parseDSN('pgsql:host=machine1;dbname=db1'))
-        );
-    }
-
-    public function testAgainstDatabase()
-    {
-        $unit_test_dsn = getenv('UNIT_TEST_DSN') != false ?
-                            getenv('UNIT_TEST_DSN') :
-                            'pgsql:dbname=nominatim_unit_tests';
-
-        ## Create the database.
-        {
-            $aDSNParsed = \Nominatim\DB::parseDSN($unit_test_dsn);
-            $sDbname = $aDSNParsed['database'];
-            $aDSNParsed['database'] = 'postgres';
-
-            $oDB = new \Nominatim\DB(\Nominatim\DB::generateDSN($aDSNParsed));
-            $oDB->connect();
-            $oDB->exec('DROP DATABASE IF EXISTS ' . $sDbname);
-            $oDB->exec('CREATE DATABASE ' . $sDbname);
-        }
-
-        $oDB = new \Nominatim\DB($unit_test_dsn);
-        $oDB->connect();
-
-        $this->assertTrue(
-            $oDB->checkConnection($sDbname)
-        );
-
-        # Tables, Indices
-        {
-            $oDB->exec('CREATE TABLE table1 (id integer, city varchar, country varchar)');
-
-            $this->assertTrue($oDB->tableExists('table1'));
-            $this->assertFalse($oDB->tableExists('table99'));
-            $this->assertFalse($oDB->tableExists(null));
-        }
-
-        # select queries
-        {
-            $oDB->exec(
-                "INSERT INTO table1 VALUES (1, 'Berlin', 'Germany'), (2, 'Paris', 'France')"
-            );
-
-            $this->assertEquals(
-                array(
-                    array('city' => 'Berlin'),
-                    array('city' => 'Paris')
-                ),
-                $oDB->getAll('SELECT city FROM table1')
-            );
-            $this->assertEquals(
-                array(),
-                $oDB->getAll('SELECT city FROM table1 WHERE id=999')
-            );
-
-
-            $this->assertEquals(
-                array('id' => 1, 'city' => 'Berlin', 'country' => 'Germany'),
-                $oDB->getRow('SELECT * FROM table1 WHERE id=1')
-            );
-            $this->assertEquals(
-                false,
-                $oDB->getRow('SELECT * FROM table1 WHERE id=999')
-            );
-
-
-            $this->assertEquals(
-                array('Berlin', 'Paris'),
-                $oDB->getCol('SELECT city FROM table1')
-            );
-            $this->assertEquals(
-                array(),
-                $oDB->getCol('SELECT city FROM table1 WHERE id=999')
-            );
-
-            $this->assertEquals(
-                'Berlin',
-                $oDB->getOne('SELECT city FROM table1 WHERE id=1')
-            );
-            $this->assertEquals(
-                null,
-                $oDB->getOne('SELECT city FROM table1 WHERE id=999')
-            );
-
-            $this->assertEquals(
-                array('Berlin' => 'Germany', 'Paris' => 'France'),
-                $oDB->getAssoc('SELECT city, country FROM table1')
-            );
-            $this->assertEquals(
-                array(),
-                $oDB->getAssoc('SELECT city, country FROM table1 WHERE id=999')
-            );
-        }
-    }
-}
diff --git a/test/php/Nominatim/DatabaseErrorTest.php b/test/php/Nominatim/DatabaseErrorTest.php
deleted file mode 100644 (file)
index e24049c..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/init-website.php');
-require_once(CONST_LibDir.'/DatabaseError.php');
-
-class DatabaseErrorTest extends \PHPUnit\Framework\TestCase
-{
-
-    public function testSqlMessage()
-    {
-        $oSqlStub = $this->getMockBuilder(PDOException::class)
-                    ->setMethods(array('getMessage'))
-                    ->getMock();
-
-        $oSqlStub->method('getMessage')
-                ->willReturn('Unknown table.');
-
-        $oErr = new DatabaseError('Sql error', 123, null, $oSqlStub);
-        $this->assertEquals('Sql error', $oErr->getMessage());
-        $this->assertEquals(123, $oErr->getCode());
-        $this->assertEquals('Unknown table.', $oErr->getSqlError());
-    }
-
-    public function testSqlObjectDump()
-    {
-        $oErr = new DatabaseError('Sql error', 123, null, array('one' => 'two'));
-        $this->assertStringContainsString('two', $oErr->getSqlDebugDump());
-    }
-}
diff --git a/test/php/Nominatim/DebugTest.php b/test/php/Nominatim/DebugTest.php
deleted file mode 100644 (file)
index 84e8f21..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/DebugHtml.php');
-
-class DebugTest extends \PHPUnit\Framework\TestCase
-{
-
-    protected function setUp(): void
-    {
-        $this->oWithDebuginfo = $this->getMockBuilder(\GeococdeMock::class)
-                                    ->setMethods(array('debugInfo'))
-                                    ->getMock();
-        $this->oWithDebuginfo->method('debugInfo')
-                  ->willReturn(array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3'));
-
-
-        $this->oWithToString = $this->getMockBuilder(\SomeMock::class)
-                                    ->setMethods(array('__toString'))
-                                    ->getMock();
-        $this->oWithToString->method('__toString')->willReturn('me as string');
-    }
-
-    public function testPrintVar()
-    {
-        $this->expectOutputString(<<<EOT
-<pre><b>Var0:</b>  </pre>
-<pre><b>Var1:</b>  <i>True</i></pre>
-<pre><b>Var2:</b>  <i>False</i></pre>
-<pre><b>Var3:</b>  0</pre>
-<pre><b>Var4:</b>  &#039;String&#039;</pre>
-<pre><b>Var5:</b>  0 => &#039;one&#039;
-       1 => &#039;two&#039;
-       2 => &#039;three&#039;</pre>
-<pre><b>Var6:</b>  &#039;key&#039; => &#039;value&#039;
-       &#039;key2&#039; => &#039;value2&#039;</pre>
-<pre><b>Var7:</b>  me as string</pre>
-<pre><b>Var8:</b>  &#039;value&#039;, &#039;value2&#039;</pre>
-
-EOT
-        );
-    
-        Debug::printVar('Var0', null);
-        Debug::printVar('Var1', true);
-        Debug::printVar('Var2', false);
-        Debug::printVar('Var3', 0);
-        Debug::printVar('Var4', 'String');
-        Debug::printVar('Var5', array('one', 'two', 'three'));
-        Debug::printVar('Var6', array('key' => 'value', 'key2' => 'value2'));
-        Debug::printVar('Var7', $this->oWithToString);
-        Debug::printVar('Var8', Debug::fmtArrayVals(array('key' => 'value', 'key2' => 'value2')));
-    }
-
-
-    public function testDebugArray()
-    {
-        $this->expectOutputString(<<<EOT
-<pre><b>Arr0:</b>  &#039;null&#039;</pre>
-<pre><b>Arr1:</b>  &#039;key1&#039; => &#039;val1&#039;
-       &#039;key2&#039; => &#039;val2&#039;
-       &#039;key3&#039; => &#039;val3&#039;</pre>
-
-EOT
-        );
-    
-        Debug::printDebugArray('Arr0', null);
-        Debug::printDebugArray('Arr1', $this->oWithDebuginfo);
-    }
-
-
-    public function testPrintDebugTable()
-    {
-        $this->expectOutputString(<<<EOT
-<b>Table1:</b>
-<table border='1'>
-</table>
-<b>Table2:</b>
-<table border='1'>
-</table>
-<b>Table3:</b>
-<table border='1'>
-  <tr>
-    <th><small>0</small></th>
-    <th><small>1</small></th>
-  </tr>
-  <tr>
-    <td><pre>&#039;one&#039;</pre></td>
-    <td><pre>&#039;two&#039;</pre></td>
-  </tr>
-  <tr>
-    <td><pre>&#039;three&#039;</pre></td>
-    <td><pre>&#039;four&#039;</pre></td>
-  </tr>
-</table>
-<b>Table4:</b>
-<table border='1'>
-  <tr>
-    <th><small>key1</small></th>
-    <th><small>key2</small></th>
-    <th><small>key3</small></th>
-  </tr>
-  <tr>
-    <td><pre>&#039;val1&#039;</pre></td>
-    <td><pre>&#039;val2&#039;</pre></td>
-    <td><pre>&#039;val3&#039;</pre></td>
-  </tr>
-</table>
-
-EOT
-        );
-    
-        Debug::printDebugTable('Table1', null);
-
-        Debug::printDebugTable('Table2', array());
-
-        // Numeric headers
-        Debug::printDebugTable('Table3', array(array('one', 'two'), array('three', 'four')));
-
-        // Associate array
-        Debug::printDebugTable('Table4', array($this->oWithDebuginfo));
-    }
-
-    public function testPrintGroupTable()
-    {
-        $this->expectOutputString(<<<EOT
-<b>Table1:</b>
-<table border='1'>
-</table>
-<b>Table2:</b>
-<table border='1'>
-</table>
-<b>Table3:</b>
-<table border='1'>
-  <tr>
-    <th><small>Group</small></th>
-    <th><small>key1</small></th>
-    <th><small>key2</small></th>
-  </tr>
-  <tr>
-    <td><pre>group1</pre></td>
-    <td><pre>&#039;val1&#039;</pre></td>
-    <td><pre>&#039;val2&#039;</pre></td>
-  </tr>
-  <tr>
-    <td><pre>group1</pre></td>
-    <td><pre>&#039;one&#039;</pre></td>
-    <td><pre>&#039;two&#039;</pre></td>
-  </tr>
-  <tr>
-    <td><pre>group2</pre></td>
-    <td><pre>&#039;val1&#039;</pre></td>
-    <td><pre>&#039;val2&#039;</pre></td>
-  </tr>
-</table>
-<b>Table4:</b>
-<table border='1'>
-  <tr>
-    <th><small>Group</small></th>
-    <th><small>key1</small></th>
-    <th><small>key2</small></th>
-    <th><small>key3</small></th>
-  </tr>
-  <tr>
-    <td><pre>group1</pre></td>
-    <td><pre>&#039;val1&#039;</pre></td>
-    <td><pre>&#039;val2&#039;</pre></td>
-    <td><pre>&#039;val3&#039;</pre></td>
-  </tr>
-  <tr>
-    <td><pre>group1</pre></td>
-    <td><pre>&#039;val1&#039;</pre></td>
-    <td><pre>&#039;val2&#039;</pre></td>
-    <td><pre>&#039;val3&#039;</pre></td>
-  </tr>
-</table>
-
-EOT
-        );
-    
-        Debug::printGroupTable('Table1', null);
-        Debug::printGroupTable('Table2', array());
-
-        // header are taken from first group item, thus no key3 gets printed
-        $aGroups = array(
-                    'group1' => array(
-                                 array('key1' => 'val1', 'key2' => 'val2'),
-                                 array('key1' => 'one', 'key2' => 'two', 'unknown' => 1),
-                                ),
-                    'group2' => array(
-                                 array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3'),
-                                )
-                   );
-        Debug::printGroupTable('Table3', $aGroups);
-
-        $aGroups = array(
-                    'group1' => array($this->oWithDebuginfo, $this->oWithDebuginfo),
-                   );
-        Debug::printGroupTable('Table4', $aGroups);
-    }
-}
diff --git a/test/php/Nominatim/LibTest.php b/test/php/Nominatim/LibTest.php
deleted file mode 100644 (file)
index 5d71124..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/lib.php');
-require_once(CONST_LibDir.'/ClassTypes.php');
-
-class LibTest extends \PHPUnit\Framework\TestCase
-{
-
-    public function testAddQuotes()
-    {
-        // FIXME: not quoting existing quote signs is probably a bug
-        $this->assertSame("'St. John's'", addQuotes("St. John's"));
-        $this->assertSame("''", addQuotes(''));
-    }
-
-    public function testParseLatLon()
-    {
-        // no coordinates expected
-        $this->assertFalse(parseLatLon(''));
-        $this->assertFalse(parseLatLon('abc'));
-        $this->assertFalse(parseLatLon('12 34'));
-
-        // coordinates expected
-        $this->assertNotNull(parseLatLon('0.0 -0.0'));
-
-        $aRes = parseLatLon(' abc 12.456 -78.90 def ');
-        $this->assertEquals($aRes[1], 12.456);
-        $this->assertEquals($aRes[2], -78.90);
-        $this->assertEquals($aRes[0], ' 12.456 -78.90 ');
-
-        $aRes = parseLatLon(' [12.456,-78.90] ');
-        $this->assertEquals($aRes[1], 12.456);
-        $this->assertEquals($aRes[2], -78.90);
-        $this->assertEquals($aRes[0], ' [12.456,-78.90] ');
-
-        $aRes = parseLatLon(' -12.456,-78.90 ');
-        $this->assertEquals($aRes[1], -12.456);
-        $this->assertEquals($aRes[2], -78.90);
-        $this->assertEquals($aRes[0], ' -12.456,-78.90 ');
-
-        // http://en.wikipedia.org/wiki/Geographic_coordinate_conversion
-        // these all represent the same location
-        $aQueries = array(
-                     '40 26.767 N 79 58.933 W',
-                     '40° 26.767′ N 79° 58.933′ W',
-                     "40° 26.767' N 79° 58.933' W",
-                     "40° 26.767'
-                         N 79° 58.933' W",
-                     'N 40 26.767, W 79 58.933',
-                     'N 40°26.767′, W 79°58.933′',
-                     ' N 40°26.767′, W 79°58.933′',
-                     "N 40°26.767', W 79°58.933'",
-                     '40 26 46 N 79 58 56 W',
-                     '40° 26′ 46″ N 79° 58′ 56″ W',
-                     '40° 26′ 46.00″ N 79° 58′ 56.00″ W',
-                     '40°26′46″N 79°58′56″W',
-                     'N 40 26 46 W 79 58 56',
-                     'N 40° 26′ 46″, W 79° 58′ 56″',
-                     'N 40° 26\' 46", W 79° 58\' 56"',
-                     'N 40° 26\' 46", W 79° 58\' 56"',
-                     '40.446 -79.982',
-                     '40.446,-79.982',
-                     '40.446° N 79.982° W',
-                     'N 40.446° W 79.982°',
-                     '[40.446 -79.982]',
-                     '[40.446,\v-79.982]',
-                     '       40.446  ,   -79.982     ',
-                     '       40.446  ,   -79.982     ',
-                     '       40.446    ,   -79.982     ',
-                     '       40.446\v,   -79.982        ',
-                    );
-
-
-        foreach ($aQueries as $sQuery) {
-            $aRes = parseLatLon($sQuery);
-            $this->assertEqualsWithDelta(40.446, $aRes[1], 0.01, 'degrees decimal ' . $sQuery);
-            $this->assertEqualsWithDelta(-79.982, $aRes[2], 0.01, 'degrees decimal ' . $sQuery);
-            $this->assertEquals($sQuery, $aRes[0]);
-        }
-    }
-}
diff --git a/test/php/Nominatim/ParameterParserTest.php b/test/php/Nominatim/ParameterParserTest.php
deleted file mode 100644 (file)
index 82716d4..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/ParameterParser.php');
-
-
-function userError($sError)
-{
-    throw new \Exception($sError);
-}
-
-class ParameterParserTest extends \PHPUnit\Framework\TestCase
-{
-
-
-    public function testGetBool()
-    {
-        $oParams = new ParameterParser(array(
-                                        'bool1' => '1',
-                                        'bool2' => '0',
-                                        'bool3' => 'true',
-                                        'bool4' => 'false',
-                                        'bool5' => ''
-                                       ));
-
-        $this->assertSame(false, $oParams->getBool('non-exists'));
-        $this->assertSame(true, $oParams->getBool('non-exists', true));
-        $this->assertSame(true, $oParams->getBool('bool1'));
-        $this->assertSame(false, $oParams->getBool('bool2'));
-        $this->assertSame(true, $oParams->getBool('bool3'));
-        $this->assertSame(true, $oParams->getBool('bool4'));
-        $this->assertSame(false, $oParams->getBool('bool5'));
-    }
-
-
-    public function testGetInt()
-    {
-        $oParams = new ParameterParser(array(
-                                        'int1' => '5',
-                                        'int2' => '-1',
-                                        'int3' => 0
-                                       ));
-
-        $this->assertSame(false, $oParams->getInt('non-exists'));
-        $this->assertSame(999, $oParams->getInt('non-exists', 999));
-        $this->assertSame(5, $oParams->getInt('int1'));
-
-        $this->assertSame(-1, $oParams->getInt('int2'));
-        $this->assertSame(0, $oParams->getInt('int3'));
-    }
-
-
-    public function testGetIntWithNonNumber()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Integer number expected for parameter 'int4'");
-
-        (new ParameterParser(array('int4' => 'a')))->getInt('int4');
-    }
-
-
-    public function testGetIntWithEmpytString()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Integer number expected for parameter 'int5'");
-
-        (new ParameterParser(array('int5' => '')))->getInt('int5');
-    }
-
-
-    public function testGetFloat()
-    {
-
-        $oParams = new ParameterParser(array(
-                                        'float1' => '1.0',
-                                        'float2' => '-5',
-                                        'float3' => 0
-                                       ));
-
-        $this->assertSame(false, $oParams->getFloat('non-exists'));
-        $this->assertSame(999, $oParams->getFloat('non-exists', 999));
-        $this->assertSame(1.0, $oParams->getFloat('float1'));
-        $this->assertSame(-5.0, $oParams->getFloat('float2'));
-        $this->assertSame(0.0, $oParams->getFloat('float3'));
-    }
-
-    public function testGetFloatWithEmptyString()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Floating-point number expected for parameter 'float4'");
-
-        (new ParameterParser(array('float4' => '')))->getFloat('float4');
-    }
-
-    public function testGetFloatWithTextString()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Floating-point number expected for parameter 'float5'");
-
-        (new ParameterParser(array('float5' => 'a')))->getFloat('float5');
-    }
-
-
-    public function testGetFloatWithInvalidNumber()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Floating-point number expected for parameter 'float6'");
-
-        (new ParameterParser(array('float6' => '-55.')))->getFloat('float6');
-    }
-
-
-    public function testGetString()
-    {
-        $oParams = new ParameterParser(array(
-                                        'str1' => 'abc',
-                                        'str2' => '',
-                                        'str3' => '0'
-                                       ));
-
-        $this->assertSame(false, $oParams->getString('non-exists'));
-        $this->assertSame('default', $oParams->getString('non-exists', 'default'));
-        $this->assertSame('abc', $oParams->getString('str1'));
-        $this->assertSame(false, $oParams->getStringList('str2'));
-        $this->assertSame(false, $oParams->getStringList('str3')); // sadly PHP magic treats 0 as false when returned
-    }
-
-
-    public function testGetSet()
-    {
-        $oParams = new ParameterParser(array(
-                                        'val1' => 'foo',
-                                        'val2' => '',
-                                        'val3' => 0
-                                       ));
-
-        $this->assertSame(false, $oParams->getSet('non-exists', array('foo', 'bar')));
-        $this->assertSame('default', $oParams->getSet('non-exists', array('foo', 'bar'), 'default'));
-        $this->assertSame('foo', $oParams->getSet('val1', array('foo', 'bar')));
-
-        $this->assertSame(false, $oParams->getSet('val2', array('foo', 'bar')));
-        $this->assertSame(false, $oParams->getSet('val3', array('foo', 'bar')));
-    }
-
-
-    public function testGetSetWithValueNotInSet()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage("Parameter 'val4' must be one of: foo, bar");
-
-        (new ParameterParser(array('val4' => 'faz')))->getSet('val4', array('foo', 'bar'));
-    }
-
-
-    public function testGetStringList()
-    {
-        $oParams = new ParameterParser(array(
-                                        'list1' => ',a,b,c,,c,d',
-                                        'list2' => 'a',
-                                        'list3' => '',
-                                        'list4' => '0'
-                                       ));
-
-        $this->assertSame(false, $oParams->getStringList('non-exists'));
-        $this->assertSame(array('a', 'b'), $oParams->getStringList('non-exists', array('a', 'b')));
-        $this->assertSame(array('a', 'b', 'c', 'c', 'd'), $oParams->getStringList('list1'));
-        $this->assertSame(array('a'), $oParams->getStringList('list2'));
-        $this->assertSame(false, $oParams->getStringList('list3'));
-        $this->assertSame(false, $oParams->getStringList('list4'));
-    }
-
-
-    public function testGetPreferredLanguages()
-    {
-        $oParams = new ParameterParser(array('accept-language' => ''));
-        $this->assertSame(array(
-                           'name:default' => 'name:default',
-                           '_place_name:default' => '_place_name:default',
-                           'name' => 'name',
-                           '_place_name' => '_place_name'
-                          ), array_slice($oParams->getPreferredLanguages('default'), 0, 4));
-
-        $oParams = new ParameterParser(array('accept-language' => 'de,en'));
-        $this->assertSame(array(
-                           'name:de' => 'name:de',
-                           '_place_name:de' => '_place_name:de',
-                           'name:en' => 'name:en',
-                           '_place_name:en' => '_place_name:en',
-                           'name' => 'name',
-                           '_place_name' => '_place_name'
-                          ), array_slice($oParams->getPreferredLanguages('default'), 0, 6));
-
-        $oParams = new ParameterParser(array('accept-language' => 'fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3'));
-        $this->assertSame(array(
-                           'name:fr-ca' => 'name:fr-ca',
-                           '_place_name:fr-ca' => '_place_name:fr-ca',
-                           'name:fr' => 'name:fr',
-                           '_place_name:fr' => '_place_name:fr',
-                           'name:en-ca' => 'name:en-ca',
-                           '_place_name:en-ca' => '_place_name:en-ca',
-                           'name:en' => 'name:en',
-                           '_place_name:en' => '_place_name:en',
-                           'name' => 'name',
-                           '_place_name' => '_place_name'
-                          ), array_slice($oParams->getPreferredLanguages('default'), 0, 10));
-
-        $oParams = new ParameterParser(array('accept-language' => 'ja_rm,zh_pinyin'));
-        $this->assertSame(array(
-                           'name:ja_rm' => 'name:ja_rm',
-                           '_place_name:ja_rm' => '_place_name:ja_rm',
-                           'name:zh_pinyin' => 'name:zh_pinyin',
-                           '_place_name:zh_pinyin' => '_place_name:zh_pinyin',
-                           'name:ja' => 'name:ja',
-                           '_place_name:ja' => '_place_name:ja',
-                           'name:zh' => 'name:zh',
-                           '_place_name:zh' => '_place_name:zh',
-                           'name' => 'name',
-                           '_place_name' => '_place_name'
-                          ), array_slice($oParams->getPreferredLanguages('default'), 0, 10));
-    }
-
-    public function testHasSetAny()
-    {
-        $oParams = new ParameterParser(array(
-                                        'one' => '',
-                                        'two' => 0,
-                                        'three' => '0',
-                                        'four' => '1',
-                                        'five' => 'anystring'
-        ));
-        $this->assertFalse($oParams->hasSetAny(array()));
-        $this->assertFalse($oParams->hasSetAny(array('')));
-        $this->assertFalse($oParams->hasSetAny(array('unknown')));
-        $this->assertFalse($oParams->hasSetAny(array('one', 'two', 'three')));
-        $this->assertTrue($oParams->hasSetAny(array('one', 'four')));
-        $this->assertTrue($oParams->hasSetAny(array('four')));
-        $this->assertTrue($oParams->hasSetAny(array('five')));
-    }
-}
diff --git a/test/php/Nominatim/ResultTest.php b/test/php/Nominatim/ResultTest.php
deleted file mode 100644 (file)
index 8b95105..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Result.php');
-
-function mkRankedResult($iId, $iResultRank)
-{
-    $oResult = new Result($iId);
-    $oResult->iResultRank = $iResultRank;
-
-    return $oResult;
-}
-
-
-class ResultTest extends \PHPUnit\Framework\TestCase
-{
-    public function testSplitResults()
-    {
-        $aSplitResults = Result::splitResults(array(
-            mkRankedResult(1, 2),
-            mkRankedResult(2, 0),
-            mkRankedResult(3, 0),
-            mkRankedResult(4, 2),
-            mkRankedResult(5, 1)
-        ));
-
-
-        $aHead = array_keys($aSplitResults['head']);
-        $aTail = array_keys($aSplitResults['tail']);
-
-        $this->assertEquals($aHead, array(2, 3));
-        $this->assertEquals($aTail, array(1, 4, 5));
-    }
-}
diff --git a/test/php/Nominatim/SearchContextTest.php b/test/php/Nominatim/SearchContextTest.php
deleted file mode 100644 (file)
index b5ef1a7..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SearchContext.php');
-
-class SearchContextTest extends \PHPUnit\Framework\TestCase
-{
-    private $oCtx;
-
-
-    protected function setUp(): void
-    {
-        $this->oCtx = new SearchContext();
-    }
-
-    public function testHasNearPoint()
-    {
-        $this->assertFalse($this->oCtx->hasNearPoint());
-        $this->oCtx->setNearPoint(0, 0);
-        $this->assertTrue($this->oCtx->hasNearPoint());
-    }
-
-    public function testNearRadius()
-    {
-        $this->oCtx->setNearPoint(1, 1);
-        $this->assertEquals(0.1, $this->oCtx->nearRadius());
-        $this->oCtx->setNearPoint(1, 1, 0.338);
-        $this->assertEquals(0.338, $this->oCtx->nearRadius());
-    }
-
-    public function testWithinSQL()
-    {
-        $this->oCtx->setNearPoint(0.1, 23, 1);
-
-        $this->assertEquals(
-            'ST_DWithin(foo, ST_SetSRID(ST_Point(23,0.1),4326), 1.000000)',
-            $this->oCtx->withinSQL('foo')
-        );
-    }
-
-    public function testDistanceSQL()
-    {
-        $this->oCtx->setNearPoint(0.1, 23, 1);
-
-        $this->assertEquals(
-            'ST_Distance(ST_SetSRID(ST_Point(23,0.1),4326), foo)',
-            $this->oCtx->distanceSQL('foo')
-        );
-    }
-
-    public function testSetViewboxFromBox()
-    {
-        $viewbox = array(30, 20, 40, 50);
-        $this->oCtx->setViewboxFromBox($viewbox, true);
-        $this->assertEquals(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(30.000000,20.000000),ST_Point(40.000000,50.000000)),4326)',
-            $this->oCtx->sqlViewboxSmall
-        );
-        // height: 10
-        // width: 30
-        $this->assertEquals(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(50.000000,80.000000),ST_Point(20.000000,-10.000000)),4326)',
-            $this->oCtx->sqlViewboxLarge
-        );
-
-
-        $viewbox = array(-1.5, -2, 1.5, 2);
-        $this->oCtx->setViewboxFromBox($viewbox, true);
-        $this->assertEquals(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(-1.500000,-2.000000),ST_Point(1.500000,2.000000)),4326)',
-            $this->oCtx->sqlViewboxSmall
-        );
-        // height: 3
-        // width: 4
-        $this->assertEquals(
-            'ST_SetSRID(ST_MakeBox2D(ST_Point(4.500000,6.000000),ST_Point(-4.500000,-6.000000)),4326)',
-            $this->oCtx->sqlViewboxLarge
-        );
-    }
-}
diff --git a/test/php/Nominatim/ShellTest.php b/test/php/Nominatim/ShellTest.php
deleted file mode 100644 (file)
index 8221949..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/Shell.php');
-
-class ShellTest extends \PHPUnit\Framework\TestCase
-{
-    public function testNew()
-    {
-        $this->expectException('ArgumentCountError');
-        $this->expectExceptionMessage('Too few arguments to function');
-        $oCmd = new \Nominatim\Shell();
-
-
-        $oCmd = new \Nominatim\Shell('wc', '-l', 'file.txt');
-        $this->assertSame(
-            "wc -l 'file.txt'",
-            $oCmd->escapedCmd()
-        );
-    }
-
-    public function testaddParams()
-    {
-        $oCmd = new \Nominatim\Shell('grep');
-        $oCmd->addParams('-a', 'abc')
-               ->addParams(10);
-
-        $this->assertSame(
-            'grep -a abc 10',
-            $oCmd->escapedCmd(),
-            'no escaping needed, chained'
-        );
-
-        $oCmd = new \Nominatim\Shell('grep');
-        $oCmd->addParams();
-        $oCmd->addParams(null);
-        $oCmd->addParams('');
-
-        $this->assertEmpty($oCmd->aParams);
-        $this->assertSame('grep', $oCmd->escapedCmd(), 'empty params');
-
-        $oCmd = new \Nominatim\Shell('echo', '-n', 0);
-        $this->assertSame(
-            'echo -n 0',
-            $oCmd->escapedCmd(),
-            'zero param'
-        );
-
-        $oCmd = new \Nominatim\Shell('/path with space/do.php');
-        $oCmd->addParams('-a', ' b ');
-        $oCmd->addParams('--flag');
-        $oCmd->addParams('two words');
-        $oCmd->addParams('v=1');
-
-        $this->assertSame(
-            "'/path with space/do.php' -a ' b ' --flag 'two words' 'v=1'",
-            $oCmd->escapedCmd(),
-            'escape whitespace'
-        );
-
-        $oCmd = new \Nominatim\Shell('grep');
-        $oCmd->addParams(';', '|more&', '2>&1');
-
-        $this->assertSame(
-            "grep ';' '|more&' '2>&1'",
-            $oCmd->escapedCmd(),
-            'escape shell characters'
-        );
-    }
-
-    public function testaddEnvPair()
-    {
-        $oCmd = new \Nominatim\Shell('date');
-
-        $oCmd->addEnvPair('one', 'two words')
-             ->addEnvPair('null', null)
-             ->addEnvPair(null, 'null')
-             ->addEnvPair('empty', '')
-             ->addEnvPair('', 'empty');
-
-        $this->assertEquals(
-            array('one' => 'two words', 'empty' => ''),
-            $oCmd->aEnv
-        );
-
-        $oCmd->addEnvPair('one', 'overwrite');
-        $this->assertEquals(
-            array('one' => 'overwrite', 'empty' => ''),
-            $oCmd->aEnv
-        );
-    }
-
-    public function testClone()
-    {
-        $oCmd = new \Nominatim\Shell('wc', '-l', 'file.txt');
-        $oCmd2 = clone $oCmd;
-        $oCmd->addParams('--flag');
-        $oCmd2->addParams('--flag2');
-
-        $this->assertSame(
-            "wc -l 'file.txt' --flag",
-            $oCmd->escapedCmd()
-        );
-
-        $this->assertSame(
-            "wc -l 'file.txt' --flag2",
-            $oCmd2->escapedCmd()
-        );
-    }
-
-    public function testRun()
-    {
-        $oCmd = new \Nominatim\Shell('echo');
-
-        $this->assertSame(0, $oCmd->run());
-
-        // var_dump($sStdout);
-    }
-}
diff --git a/test/php/Nominatim/SimpleWordListTest.php b/test/php/Nominatim/SimpleWordListTest.php
deleted file mode 100644 (file)
index 69cb518..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/SimpleWordList.php');
-
-class TokensFullSet
-{
-    public function containsAny($sTerm)
-    {
-        return true;
-    }
-}
-
-// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
-class TokensPartialSet
-{
-    public function __construct($aTokens)
-    {
-        $this->aTokens = array_flip($aTokens);
-    }
-
-    public function containsAny($sTerm)
-    {
-        return isset($this->aTokens[$sTerm]);
-    }
-}
-
-// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
-class SimpleWordListTest extends \PHPUnit\Framework\TestCase
-{
-
-
-    private function serializeSets($aSets)
-    {
-        $aParts = array();
-        foreach ($aSets as $aSet) {
-            $aParts[] = '(' . join('|', $aSet) . ')';
-        }
-        return join(',', $aParts);
-    }
-
-
-    public function testEmptyPhrase()
-    {
-        $oList = new SimpleWordList('');
-        $this->assertNull($oList->getWordSets(new TokensFullSet()));
-    }
-
-
-    public function testSingleWordPhrase()
-    {
-        $oList = new SimpleWordList('a');
-
-        $this->assertEquals(
-            '(a)',
-            $this->serializeSets($oList->getWordSets(new TokensFullSet()))
-        );
-    }
-
-
-    public function testMultiWordPhrase()
-    {
-        $oList = new SimpleWordList('a b');
-        $this->assertEquals(
-            '(a b),(a|b)',
-            $this->serializeSets($oList->getWordSets(new TokensFullSet()))
-        );
-
-        $oList = new SimpleWordList('a b c');
-        $this->assertEquals(
-            '(a b c),(a b|c),(a|b c),(a|b|c)',
-            $this->serializeSets($oList->getWordSets(new TokensFullSet()))
-        );
-
-        $oList = new SimpleWordList('a b c d');
-        $this->assertEquals(
-            '(a b c d),(a b c|d),(a b|c d),(a|b c d),(a b|c|d),(a|b c|d),(a|b|c d),(a|b|c|d)',
-            $this->serializeSets($oList->getWordSets(new TokensFullSet()))
-        );
-    }
-
-    public function testCmpByArraylen()
-    {
-        // Array elements are phrases, we want to sort so longest phrases are first
-        $aList1 = array('hackney', 'bridge', 'london', 'england');
-        $aList2 = array('hackney', 'london', 'bridge');
-        $aList3 = array('bridge', 'hackney', 'london', 'england');
-
-        $this->assertEquals(0, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList1));
-
-        // list2 "wins". Less array elements
-        $this->assertEquals(1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList2));
-        $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList2, $aList3));
-
-        // list1 "wins". Same number of array elements but longer first element
-        $this->assertEquals(-1, \Nominatim\SimpleWordList::cmpByArraylen($aList1, $aList3));
-    }
-
-    public function testMaxWordSets()
-    {
-        $aWords = array_fill(0, 4, 'a');
-        $oList = new SimpleWordList(join(' ', $aWords));
-        $this->assertEquals(8, count($oList->getWordSets(new TokensFullSet())));
-
-        $aWords = array_fill(0, 18, 'a');
-        $oList = new SimpleWordList(join(' ', $aWords));
-        $this->assertEquals(100, count($oList->getWordSets(new TokensFullSet())));
-    }
-
-
-    public function testPartialTokensShortTerm()
-    {
-        $oList = new SimpleWordList('a b c d');
-        $this->assertEquals(
-            '(a|b c d),(a|b c|d)',
-            $this->serializeSets($oList->getWordSets(new TokensPartialSet(array('a', 'b', 'd', 'b c', 'b c d'))))
-        );
-    }
-
-
-    public function testPartialTokensLongTerm()
-    {
-        $aWords = array_fill(0, 18, 'a');
-        $oList = new SimpleWordList(join(' ', $aWords));
-        $this->assertEquals(80, count($oList->getWordSets(new TokensPartialSet(array('a', 'a a a a a')))));
-    }
-}
diff --git a/test/php/Nominatim/StatusTest.php b/test/php/Nominatim/StatusTest.php
deleted file mode 100644 (file)
index 5f8bac6..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-@define('CONST_TokenizerDir', dirname(__FILE__));
-
-require_once(CONST_LibDir.'/DB.php');
-require_once(CONST_LibDir.'/Status.php');
-
-
-class StatusTest extends \PHPUnit\Framework\TestCase
-{
-
-    public function testNoDatabaseGiven()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('No database');
-        $this->expectExceptionCode(700);
-
-        $oDB = null;
-        $oStatus = new Status($oDB);
-        $this->assertEquals('No database', $oStatus->status());
-    }
-
-    public function testNoDatabaseConnectionFail()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('Database connection failed');
-        $this->expectExceptionCode(700);
-
-        $oDbStub = $this->getMockBuilder(Nominatim\DB::class)
-                        ->setMethods(array('connect'))
-                        ->getMock();
-
-        $oDbStub->method('connect')
-                ->will($this->returnCallback(function () {
-                    throw new \Nominatim\DatabaseError('psql connection problem', 500, null, 'unknown database');
-                }));
-
-
-        $oStatus = new Status($oDbStub);
-        $this->assertEquals('No database', $oStatus->status());
-    }
-
-    public function testOK()
-    {
-        $oDbStub = $this->getMockBuilder(Nominatim\DB::class)
-                        ->setMethods(array('connect', 'getOne'))
-                        ->getMock();
-
-        $oDbStub->method('getOne')
-                ->will($this->returnCallback(function ($sql) {
-                    if (preg_match("/make_standard_name\('(\w+)'\)/", $sql, $aMatch)) return $aMatch[1];
-                    if (preg_match('/SELECT word_id, word_token/', $sql)) return 1234;
-                }));
-
-        $oStatus = new Status($oDbStub);
-        $this->assertNull($oStatus->status());
-    }
-
-    public function testDataDate()
-    {
-        $oDbStub = $this->getMockBuilder(Nominatim\DB::class)
-                        ->setMethods(array('getOne'))
-                        ->getMock();
-
-        $oDbStub->method('getOne')
-                ->willReturn(1519430221);
-
-        $oStatus = new Status($oDbStub);
-        $this->assertEquals(1519430221, $oStatus->dataDate());
-    }
-}
diff --git a/test/php/Nominatim/TokenListTest.php b/test/php/Nominatim/TokenListTest.php
deleted file mode 100644 (file)
index 57e3c58..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-require_once(CONST_LibDir.'/TokenList.php');
-
-
-class TokenListTest extends \PHPUnit\Framework\TestCase
-{
-    protected function setUp(): void
-    {
-        $this->oNormalizer = $this->getMockBuilder(\MockNormalizer::class)
-                                  ->setMethods(array('transliterate'))
-                                  ->getMock();
-        $this->oNormalizer->method('transliterate')
-                          ->will($this->returnCallback(function ($text) {
-                              return strtolower($text);
-                          }));
-    }
-
-    private function wordResult($aFields)
-    {
-        $aRow = array(
-                 'word_id' => null,
-                 'word_token' => null,
-                 'word' => null,
-                 'class' => null,
-                 'type' => null,
-                 'country_code' => null,
-                 'count' => 0
-                );
-        return array_merge($aRow, $aFields);
-    }
-
-    public function testList()
-    {
-        $TL = new TokenList;
-
-        $this->assertEquals(0, $TL->count());
-
-        $TL->addToken('word1', 'token1');
-        $TL->addToken('word1', 'token2');
-
-        $this->assertEquals(1, $TL->count());
-
-        $this->assertTrue($TL->contains('word1'));
-        $this->assertEquals(array('token1', 'token2'), $TL->get('word1'));
-
-        $this->assertFalse($TL->contains('unknownword'));
-        $this->assertEquals(array(), $TL->get('unknownword'));
-    }
-}
diff --git a/test/php/Nominatim/tokenizer.php b/test/php/Nominatim/tokenizer.php
deleted file mode 100644 (file)
index 923e0a2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-
-namespace Nominatim;
-
-class Tokenizer
-{
-    private $oDB;
-
-    public function __construct(&$oDB)
-    {
-        $this->oDB =& $oDB;
-    }
-
-    public function checkStatus()
-    {
-    }
-}
diff --git a/test/php/bootstrap.php b/test/php/bootstrap.php
deleted file mode 100644 (file)
index 7d25451..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-/**
- * SPDX-License-Identifier: GPL-2.0-only
- *
- * This file is part of Nominatim. (https://nominatim.org)
- *
- * Copyright (C) 2022 by the Nominatim developer community.
- * For a full list of authors see the git log.
- */
-    @define('CONST_LibDir', '../../lib-php');
-    @define('CONST_DataDir', '../..');
-
-    @define('CONST_Debug', true);
-    @define('CONST_NoAccessControl', false);
diff --git a/test/php/fixtures/address_details_10_downing_street.json b/test/php/fixtures/address_details_10_downing_street.json
deleted file mode 100644 (file)
index 22d75c2..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-[{"place_id":194663412,
- "osm_type":null,
- "osm_id":null,
- "name":{"name": "10 Downing Street", "name:en": "10 Downing Street", "name:es": "10 de Downing Street", "name:he": "דאונינג 10", "name:ko": "다우닝 가 10번지", "name:zh": "唐寧街10號"},
- "class":"tourism",
- "type":"attraction",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":29,
- "distance":0,
- "localname":"10 Downing Street"},
-{"place_id":194663412,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "10"},
- "class":"place",
- "type":"house_number",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":28,
- "distance":0,
- "localname":"10"},
-{"place_id":68310941,
- "osm_type":"W",
- "osm_id":4244999,
- "name":{"name": "Downing Street"},
- "class":"highway",
- "type":"residential",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":26,
- "distance":0,
- "localname":"Downing Street"},
-{"place_id":16037318,
- "osm_type":"N",
- "osm_id":1653239257,
- "name":{"name": "St. James's"},
- "class":"place",
- "type":"neighbourhood",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":22,
- "distance":0.00982435489434447,
- "localname":"St. James's"},
-{"place_id":51691981,
- "osm_type":"N",
- "osm_id":3937587633,
- "name":{"name": "St Clement Danes"},
- "class":"place",
- "type":"neighbourhood",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":22,
- "distance":0.0128768181947227,
- "localname":"St Clement Danes"},
-{"place_id":22208313,
- "osm_type":"N",
- "osm_id":2290086954,
- "name":{"name": "Covent Garden"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":true,
- "rank_address":20,
- "distance":0.00935748249317067,
- "localname":"Covent Garden"},
-{"place_id":21742712,
- "osm_type":"N",
- "osm_id":2288030397,
- "name":{"name": "Millbank"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0106525181285902,
- "localname":"Millbank"},
-{"place_id":122775,
- "osm_type":"N",
- "osm_id":26745371,
- "name":{"name": "St Giles"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0136188357358441,
- "localname":"St Giles"},
-{"place_id":134882,
- "osm_type":"N",
- "osm_id":27553719,
- "name":{"name": "Lambeth"},
- "class":"place",
- "type":"suburb",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":20,
- "distance":0.0093308163978298,
- "localname":"Lambeth"},
-{"place_id":194276676,
- "osm_type":"R",
- "osm_id":51781,
- "name":{"name": "City of Westminster", "name:be": "Вэстмінстэр", "name:cy": "San Steffan", "name:en": "Westminster", "name:he": "וסטמינסטר", "name:ru": "Вестминстер"},
- "class":"place",
- "type":"city",
- "admin_level":8,
- "fromarea":true,
- "isaddress":true,
- "rank_address":16,
- "distance":0.0340909562148044,
- "localname":"Westminster"},
-{"place_id":195398522,
- "osm_type":"N",
- "osm_id":107775,
- "name":{"name": "London", "name:ab": "Лондан", "name:af": "Londen", "name:am": "ለንደን", "name:an": "Londres", "name:ar": "لندن", "name:ba": "Лондон", "name:be": "Лондан", "name:bg": "Лондон", "name:bn": "লন্ডন", "name:bo": "ལོན་ཊོན།", "name:br": "Londrez", "name:ca": "Londres", "name:co": "Londra", "name:cs": "Londýn", "name:cu": "Лондонъ", "name:cv": "Лондон", "name:cy": "Llundain", "name:de": "London", "name:el": "Λονδίνο", "name:en": "London", "name:eo": "Londono", "name:es": "Londres", "name:eu": "Londres", "name:fa": "لندن", "name:fi": "Lontoo", "name:fr": "Londres", "name:fy": "Londen", "name:ga": "Londain", "name:gd": "Lunnainn", "name:gl": "Londres - London", "name:gn": "Londye", "name:gu": "લંડન", "name:gv": "Lunnin", "name:he": "לונדון", "name:hi": "लंदन", "name:ht": "Lonn", "name:hu": "London", "name:hy": "Լոնդոն", "name:is": "Lundúnir", "name:it": "Londra", "name:ja": "ロンドン", "name:ka": "ლონდონი", "name:kk": "Лондон", "name:kn": "ಲಂಡನ್", "name:ko": "런던", "name:ku": "London", "name:kv": "Лондон", "name:kw": "Loundres", "name:ky": "Лондон", "name:la": "Londinium", "name:li": "Londe", "name:ln": "Londoni", "name:lo": "ລອນດອນ", "name:lt": "Londonas", "name:lv": "Londona", "name:mi": "Rānana", "name:mk": "Лондон", "name:ml": "ലണ്ടൻ", "name:mn": "Лондон", "name:mr": "लंडन", "name:mt": "Londra", "name:my": "လန်ဒန်မြို့", "name:ne": "लण्डन", "name:nl": "Londen", "name:no": "London", "name:oc": "Londres", "name:or": "ଲଣ୍ଡନ", "name:os": "Лондон", "name:pl": "Londyn", "name:ps": "لندن", "name:pt": "Londres", "name:rm": "Londra", "name:ro": "Londra", "name:ru": "Лондон", "name:sa": "लन्डन्", "name:sc": "Londra", "name:si": "ලන්ඩන්", "name:sk": "Londýn", "name:sq": "Londra", "name:sr": "Лондон", "name:sv": "London", "name:ta": "இலண்டன்", "name:te": "లండన్", "name:tg": "Лондон", "name:th": "ลอนดอน", "name:tl": "Londres", "name:tr": "Londra", "name:tt": "Лондон", "name:uk": "Лондон", "name:ur": "لندن", "name:vi": "Luân Đôn", "name:wo": "Londar", "name:yi": "לאנדאן", "name:yo": "Lọndọnu", "name:zh": "倫敦", "name:zu": "ILondon", "name:ang": "Lunden", "name:arc": "ܠܘܢܕܘܢ", "name:arz": "لندن", "name:ast": "Londres", "name:bcl": "Londres", "name:cdo": "Lùng-dŭng", "name:ckb": "لەندەن", "name:diq": "Londra", "name:eml": "Lòndra", "name:ext": "Londri", "name:frp": "Londres", "name:gan": "倫敦", "name:haw": "Lākana", "name:ilo": "Londres", "name:jbo": "london", "name:koi": "Лондон", "name:krc": "Лондон", "name:lad": "Londra", "name:lbe": "Лондон", "name:lez": "Лондон", "name:lij": "Londra", "name:lmo": "Lundra", "name:mhr": "Лондон", "name:mrj": "Лондон", "name:mwl": "Londres", "name:mzn": "لندن", "name:nah": "Londres", "name:nap": "Londra", "name:new": "लण्डन", "name:nrm": "Londres", "name:pcd": "Londe", "name:pms": "Londra", "name:pnb": "لندن", "name:pnt": "Λονδίνο", "name:rue": "Лондон", "name:sah": "Лондон", "name:scn": "Londra", "name:sco": "Lunnon", "name:szl": "Lůndůn", "name:tet": "Londres", "name:tpi": "Landen", "name:tzl": "Londra", "name:udm": "Лондон", "name:vec": "Łondra", "name:vls": "Londn", "name:wuu": "伦敦", "name:xmf": "ლონდონი", "name:yue": "倫敦", "name:zea": "Londen", "name:nds-nl": "Londen", "name:bat-smg": "Londons", "name:roa-rup": "Londra", "name:roa-tara": "Londre", "name:be-tarask": "Лёндан", "name:zh_pinyin": "Lúndūn", "name:zh-classical": "倫敦", "name:zh-simplified": "伦敦", "name:zh-traditional": "倫敦"},
- "class":"place",
- "type":"city",
- "admin_level":2,
- "fromarea":true,
- "isaddress":false,
- "rank_address":16,
- "distance":0.00412384196971048,
- "localname":"London"},
-{"place_id":193774423,
- "osm_type":"R",
- "osm_id":65606,
- "name":{"name": "London", "ISO3166-2": "GB-LND", "name:be": "Лондан", "name:ca": "Londres", "name:el": "Λονδίνο", "name:en": "London", "name:eo": "Londono", "name:es": "Londres", "name:fa": "لندن", "name:fi": "Lontoo", "name:fr": "Londres", "name:fy": "Londen", "name:gl": "Londres", "name:hi": "लंदन", "name:lt": "Londonas", "name:nl": "Londen", "name:pl": "Londyn", "name:pt": "Londres", "name:ru": "Лондон", "name:uk": "Лондон", "name:vi": "Luân Đôn", "name:zh": "伦敦", "int_name": "London", "name:szl": "Lůndůn", "name:tzl": "Londra", "name:be-tarask": "Лёндан"},
- "class":"place",
- "type":"city",
- "admin_level":6,
- "fromarea":true,
- "isaddress":true,
- "rank_address":12,
- "distance":0.0172243361058611,
- "localname":"London"},
-{"place_id":194000080,
- "osm_type":"R",
- "osm_id":175342,
- "name":{"name": "Greater London", "name:be": "Вялікі Лондан", "name:de": "Groß-London", "name:en": "Greater London", "name:fr": "Grand Londres", "name:lt": "Didysis Londonas", "name:ru": "Большой Лондон", "name:uk": "Великий Лондон", "official_name": "Greater London (incl. City of London)", "name:be-tarask": "Вялікі Лёндан"},
- "class":"boundary",
- "type":"administrative",
- "admin_level":5,
- "fromarea":true,
- "isaddress":true,
- "rank_address":10,
- "distance":0.0172532381571105,
- "localname":"Greater London"},
-{"place_id":194325361,
- "osm_type":"R",
- "osm_id":58447,
- "name":{"ref": "ENG", "ISO3166-2": "GB-ENG", "name": "England", "name:be": "Англія", "name:br": "Bro-Saoz", "name:ca": "Anglaterra", "name:cs": "Anglie", "name:cy": "Lloegr", "name:de": "England", "name:el": "Αγγλία", "name:en": "England", "name:eo": "Anglujo", "name:es": "Inglaterra", "name:fi": "Englanti", "name:fr": "Angleterre", "name:fy": "Ingelân", "name:ga": "Sasana", "name:gd": "Sasainn", "name:gv": "Sostyn", "name:he": "אנגליה", "name:hu": "Anglia", "name:ia": "Anglaterra", "name:io": "Anglia", "name:it": "Inghilterra", "name:la": "Anglia", "name:lt": "Anglija", "name:nl": "Engeland", "name:pl": "Anglia", "name:pt": "Inglaterra", "name:ru": "Англия", "name:sk": "Anglicko", "name:sv": "England", "name:tr": "İngiltere", "name:uk": "Англія", "name:vi": "Anh", "name:vo": "Linglän", "name:zh": "英格蘭", "name:hsb": "Jendźelska", "name:nds": "England", "name:tok": "ma Inli", "name:tzl": "Anglatzara", "alt_name:eo": "Anglio", "alt_name:ia": "Anglia", "old_name:vi": "Anh Quốc", "alt_name:nds": "Ingland", "name:be-tarask": "Ангельшчына", "name:zh-classical": "英格蘭", "name:zh-simplified": "英格兰", "name:zh-traditional": "英格蘭"},
- "class":"boundary",
- "type":"administrative",
- "admin_level":4,
- "fromarea":true,
- "isaddress":true,
- "rank_address":8,
- "distance":1.75192967136328,
- "localname":"England"},
-{"place_id":null,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "SW1A 2AA"},
- "class":"place",
- "type":"postcode",
- "admin_level":null,
- "fromarea":true,
- "isaddress":true,
- "rank_address":5,
- "distance":0,
- "localname":"SW1A 2AA"},
-{"place_id":40715006,
- "osm_type":"N",
- "osm_id":3055075992,
- "name":{"ref": "SW1A 2AQ"},
- "class":"place",
- "type":"postcode",
- "admin_level":15,
- "fromarea":true,
- "isaddress":false,
- "rank_address":5,
- "distance":0.00172905579146705,
- "localname":"SW1A 2AQ"},
-{"place_id":194354400,
- "osm_type":"R",
- "osm_id":62149,
- "name":{"name": "United Kingdom", "name:ab": "Британиа Ду", "name:af": "Verenigde Koninkryk", "name:ak": "United Kingdom", "name:am": "ዩናይትድ ኪንግደም", "name:an": "Reino Unito", "name:ar": "المملكة المتحدة", "name:az": "Böyük Britaniya", "name:ba": "Бөйөк Британия", "name:be": "Вялікабрытанія", "name:bg": "Обединено кралство Великобритания и Северна Ирландия", "name:bi": "Unaeted Kingdom", "name:bm": "Angilɛtɛri", "name:bn": "যুক্তরাজ্য", "name:bo": "དབྱིན་ཇི་མཉམ་འབྲེལ།", "name:br": "Rouantelezh-Unanet", "name:bs": "Ujedinjeno Kraljevstvo Velike Britanije i Sjeverne Irske", "name:ca": "Regne Unit", "name:ce": "Йоккха Британи", "name:co": "Regnu Unitu", "name:cs": "Spojené království", "name:cu": "Вєлика Британїꙗ", "name:cv": "Аслă Британи", "name:cy": "Deyrnas Unedig", "name:da": "Storbritannien", "name:de": "Vereinigtes Königreich", "name:dv": "ޔުނައިޓެޑް ކިންގްޑަމް", "name:dz": "ཡུ་ནའི་ཊེཊ་ཀིང་ཌམ", "name:ee": "United Kingdom", "name:el": "Ηνωμένο Βασίλειο", "name:en": "United Kingdom", "name:eo": "Britujo", "name:es": "Reino Unido", "name:et": "Suurbritannia", "name:eu": "Erresuma Batua", "name:fa": "بریتانیا", "name:ff": "Laamateeri Rentundi", "name:fi": "Yhdistynyt kuningaskunta", "name:fo": "Stóra Bretland", "name:fr": "Royaume-Uni", "name:fy": "Feriene Keninkryk", "name:ga": "An Ríocht Aontaithe", "name:gd": "An Rìoghachd Aonaichte", "name:gl": "Reino Unido", "name:gn": "Tavetã Joaju", "name:gu": "યુનાઇટેડ કિંગડમ", "name:gv": "Reeriaght Unnaneysit", "name:ha": "Birtaniya", "name:he": "הממלכה המאוחדת", "name:hi": "यूनाइटेड किंगडम", "name:hr": "Ujedinjeno Kraljevstvo", "name:ht": "Wayòm Ini", "name:hu": "Egyesült Királyság", "name:hy": "Միացյալ Թագավորություն", "name:ia": "Regno Unite", "name:id": "Britania Raya", "name:ie": "Reyatu Unit", "name:ig": "Obodoézè Nà Ofú", "name:ii": "ꑱꇩ", "name:io": "Unionita Rejio", "name:is": "Bretland", "name:it": "Regno Unito", "name:ja": "イギリス", "name:jv": "Britania Raya", "name:ka": "გაერთიანებული სამეფო", "name:kg": "Royaume-Uni", "name:ki": "Ngeretha", "name:kk": "Ұлыбритания", "name:kl": "Tuluit Nunaat", "name:km": "រាជាណាចក្ររួម", "name:kn": "ಯುನೈಟೆಡ್ ಕಿಂಗ್ಡಂ", "name:ko": "영국", "name:ks": "یُنایٹِڑ کِنٛگڈَم", "name:ku": "Keyaniya Yekbûyî", "name:kv": "Ыджыд Британия", "name:kw": "Ruwvaneth Unys", "name:ky": "Улуу Британия жана Түндүк Ирландия", "name:la": "Britanniarum Regnum", "name:lb": "Groussbritannien an Nordirland", "name:lg": "Bungereza", "name:li": "Vereineg Keuninkriek", "name:ln": "Ingɛlɛ́tɛlɛ", "name:lo": "ສະຫະລາດຊະອານາຈັກ", "name:lt": "Jungtinė Karalystė", "name:lv": "Apvienotā Karaliste", "name:mg": "Fanjakana Mitambatra", "name:mi": "Kīngitanga Kotahi", "name:mk": "Обединето Кралство", "name:ml": "യുണൈറ്റഡ് കിങ്ഡം", "name:mn": "Их Британи", "name:mr": "युनायटेड किंग्डम", "name:ms": "United Kingdom", "name:mt": "Renju Unit", "name:my": "ယူနိုက်တက်ကင်းဒမ်းနိုင်ငံ", "name:na": "Ingerand", "name:ne": "संयुक्त अधिराज्य", "name:nl": "Verenigd Koninkrijk", "name:nn": "Storbritannia", "name:no": "Storbritannia", "name:nv": "Tótaʼ Dinéʼiʼ Bikéyah", "name:oc": "Reialme Unit", "name:or": "ଯୁକ୍ତରାଜ୍ୟ", "name:os": "Стыр Британи", "name:pa": "ਸੰਯੁਕਤ ਬਾਦਸ਼ਾਹੀ", "name:pl": "Wielka Brytania", "name:ps": "بريتانيا", "name:pt": "Reino Unido", "name:qu": "Hukllachasqa Qhapaq Suyu", "name:rm": "Reginavel Unì", "name:rn": "Ubwongereza", "name:ro": "Regatul Unit al Marii Britanii și al Irlandei de Nord", "name:ru": "Великобритания", "name:rw": "Ubwongereza", "name:sa": "संयुक्त अधिराज्य", "name:sc": "Rennu Auniadu", "name:se": "Ovttastuvvan gonagasriika", "name:sg": "Ködörögbïä--Ôko", "name:sh": "Ujedinjeno Kraljevstvo", "name:si": "එක්සත් රාජධානිය", "name:sk": "Spojené kráľovstvo", "name:sl": "Združeno kraljestvo Velike Britanije in Severne Irske", "name:sn": "United Kingdom", "name:so": "Midowga boqortooyada Britan", "name:sq": "Mbretëria e Bashkuar e Britanisë dhe Irlandës së Veriut", "name:sr": "Уједињено Краљевство", "name:ss": "United Kingdom", "name:su": "Britania", "name:sv": "Storbritannien", "name:sw": "Ufalme wa Muungano", "name:ta": "ஐக்கிய இராச்சியம்", "name:te": "యునైటెడ్ కింగ్డమ్", "name:tg": "Подшоҳии Муттаҳида", "name:th": "สหราชอาณาจักร", "name:ti": "እንግሊዝ", "name:tl": "Nagkakaisang Kaharian", "name:to": "Pilitānia", "name:tr": "Birleşik Krallık", "name:tt": "Бөекбритания", "name:tw": "United Kingdom", "name:ty": "Paratāne", "name:ug": "بۈيۈك بېرىتانىيە", "name:uk": "Велика Британія", "name:ur": "برطانیہ", "name:uz": "Birlashgan Qirollik", "name:vi": "Vương quốc Anh", "name:vo": "Regän Pebalöl", "name:wo": "Nguur-Yu-Bennoo", "name:yi": "פאראייניגטע קעניגרייך", "name:yo": "Ilẹ̀ọba Aṣọ̀kan", "name:za": "Yinghgoz", "name:zh": "英國", "name:zu": "Umbuso Ohlangeneyo", "alt_name": "United Kingdom; UK; Britain; Great Britain", "int_name": "United Kingdom", "name:als": "Vereinigtes Königreich", "name:ang": "Geāned Cynerīce", "name:arc": "ܡܠܟܘܬܐ ܡܚܝܕܬܐ", "name:arz": "المملكه المتحده", "name:ast": "Reinu Xuníu", "name:bar": "Vaeinigts Kinireich", "name:bcl": "Reyno Unido", "name:bjn": "Britania Raya", "name:bpy": "তিলপারাজ্য", "name:bug": "United Kingdom", "name:bxr": "Нэгдсэн Вант Улс", "name:cdo": "Ĭng-guók", "name:ceb": "Hiniusang Gingharian", "name:chr": "ᎡᎵᏏᎯ", "name:chy": "United Kingdom", "name:ckb": "شانشینی یەکگرتوو", "name:crh": "Büyük Britaniya", "name:csb": "Wiôlgô Britanijô", "name:diq": "Qraliya Yewbiyayiye", "name:dsb": "Wjelika Britaniska", "name:eml": "Régn Unî", "name:ext": "Réinu Uniu", "name:frp": "Royômo-Uni", "name:frr": "Feriind Kiningrik", "name:fur": "Ream Unît", "name:gag": "Büük Britaniya", "name:gan": "英國", "name:hak": "Yîn-koet", "name:haw": "Aupuni Mōʻī Hui Pū ʻia", "name:hif": "United Kingdom", "name:hsb": "Zjednoćene kralestwo", "name:ilo": "Nagkaykaysa a Pagarian", "name:jbo": "ritygu'e", "name:kab": "Legliz", "name:kbd": "Британиэшхуэ", "name:koi": "Ыджыт Бритму", "name:krc": "Уллу Британия", "name:ksh": "Jrußbritannie", "name:lad": "Reyno Unido", "name:lez": "ЧIехибритания", "name:lfn": "Rena Unida", "name:lij": "Regno Unïo", "name:lmo": "Regn Ünì", "name:ltg": "Lelbrytaneja", "name:mhr": "Ушымо Королевстве", "name:mrj": "Кого Британи", "name:mwl": "Reino Ounido", "name:mzn": "بریتانیا", "name:nah": "Tlacetilīlli Huēyitlahtohcāyōtl", "name:nap": "Gran Vretagna", "name:nds": "Vereenigt Königriek vun Grootbritannien un Noordirland", "name:nov": "Unionati Regia", "name:nrm": "Rouoyaume Unni", "name:pag": "Reino Unido", "name:pam": "Pisanmetung a Ka-arian", "name:pap": "Reino Uni", "name:pcd": "Roéyôme-Uni", "name:pih": "Yunitid Kingdum", "name:pms": "Regn Unì", "name:pnb": "برطانیہ", "name:pnt": "Ηνωμένο Βασίλειο", "name:rmy": "Phandlo Thagaripen la Bare Britaniyako thai le Nordutne Irlandesko", "name:rue": "Велика Брітанія", "name:sah": "Холбоhуктаах Хоруоллук", "name:scn": "Regnu Unitu", "name:sco": "Unitit Kinrick", "name:srn": "Ingriskondre", "name:stq": "Fereeniged Köönichriek fon Groot-Britannien un Noudirlound", "name:szl": "Wjelgo Brytańijo", "name:tet": "Reinu Naklibur", "name:tok": "ma Juke", "name:tpi": "Yunaitet Kingdom", "name:tzl": "Regipäts Viensiçat", "name:udm": "Великобритания", "name:vec": "Regno Unìo", "name:vep": "Sur' Britanii", "name:vls": "Verênigd Keunienkryk", "name:war": "Reino Unido", "name:wuu": "英国", "name:xal": "Ик Бритишин болн Ар Гәәлгүдин Ниицәтә Нутг", "name:xmf": "გოართოიანაფილი ომაფე", "name:yue": "英國", "name:zea": "Vereênigd Konienkriek", "name:zh_py": "Yingguo", "short_name": "UK", "alt_name:eo": "Britio", "alt_name:sr": "УК;У.К.", "alt_name:vi": "Vương quốc Liên hiệp Anh", "name:nds-nl": "Verienigd Keuninkriek", "name:zh_pyt": "Yīngguó", "name:bat-smg": "Jongtėnė Karalīstė", "name:cbk-zam": "Reinos Unidos de Gran Britania y Norte Irelandia", "name:fiu-vro": "Ütiskuningriik", "name:roa-rup": "Britania Mari", "name:roa-tara": "Regne Aunìte", "official_name": "United Kingdom of Great Britain and Northern Ireland", "short_name:el": "ΗΒ", "short_name:vo": "Britän", "name:be-tarask": "Вялікабрытанія", "name:zh-min-nan": "Liân-ha̍p Ông-kok", "official_name:be": "Злучанае Каралеўства Вялікабрытаніі і Паўночнай Ірландыі", "official_name:br": "Rouantelezh Unanet Breizh-Veur ha Norzhiwerzhon", "official_name:ca": "Regne Unit de Gran Bretanya i Irlanda del Nord", "official_name:cs": "Spojené království Velké Británie a Severního Irska", "official_name:de": "Vereinigtes Königreich Großbritannien und Nordirland", "official_name:el": "Ηνωμένο Βασίλειο της Μεγάλης Βρετανίας και της Βόρειας Ιρλανδίας", "official_name:en": "United Kingdom of Great Britain and Northern Ireland", "official_name:eo": "Unuiĝinta Reĝlando de Granda Britujo kaj Nord-Irlando", "official_name:es": "Reino Unido de Gran Bretaña", "official_name:et": "Suurbritannia ja Põhja-Iiri Ühendkuningriik", "official_name:fr": "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord", "official_name:hr": "Ujedinjeno Kraljevstvo Velike Britanije i Sjeverne Irske", "official_name:id": "Perserikatan Kerajaan Britania Raya dan Irlandia Utara", "official_name:it": "Regno Unito di Gran Bretagna e Irlanda del Nord", "official_name:ja": "グレートブリテン及び北アイルランド連合王国", "official_name:ku": "Keyaniya Yekbûyî ya Brîtaniya Mezin û Bakurê Îrlandê", "official_name:lb": "Vereenegt Kinnekräich vu Groussbritannien an Nordirland", "official_name:no": "Det forente kongeriket Storbritannia og Nord-Irland", "official_name:pl": "Zjednoczone Królestwo Wielkiej Brytanii i Irlandii Północnej", "official_name:pt": "Reino Unido da Grã-Bretanha e Irlanda do Norte", "official_name:ru": "Соединённое королевство Великобритании и Северной Ирландии", "official_name:sk": "Spojené kráľovstvo Veľkej Británie a Severného Írska", "official_name:sl": "Združeno kraljestvo Velike Britanije in Severne Irske", "official_name:sr": "Уједињено Краљевство Велике Британије и Северне Ирске", "official_name:sv": "Förenade konungariket Storbritannien och Nordirland", "official_name:vi": "Vương quốc Liên hiệp Anh và Bắc Ireland", "name:abbreviation": "UK", "name:zh-classical": "英國", "official_name:scn": "Regnu Unitu di Gran Britagna e Irlanna dû Nord", "name:zh-simplified": "英国", "name:zh-traditional": "英國"},
- "class":"place",
- "type":"country",
- "admin_level":2,
- "fromarea":true,
- "isaddress":true,
- "rank_address":4,
- "distance":4.56060933645498,
- "localname":"United Kingdom"},
-{"place_id":null,
- "osm_type":null,
- "osm_id":null,
- "name":{"ref": "gb"},
- "class":"place",
- "type":"country_code",
- "admin_level":null,
- "fromarea":true,
- "isaddress":false,
- "rank_address":4,
- "distance":0,
- "localname":"gb"}
-]
diff --git a/test/php/phpunit.xml b/test/php/phpunit.xml
deleted file mode 100644 (file)
index e46d529..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  backupGlobals="false"
-  backupStaticAttributes="false"
-  colors="true"
-  convertErrorsToExceptions="true"
-  convertNoticesToExceptions="true"
-  convertWarningsToExceptions="true"
-  processIsolation="false"
-  stopOnFailure="false"
-  bootstrap="./bootstrap.php"
-  beStrictAboutTestsThatDoNotTestAnything="true"
-  xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
-  <coverage>
-    <include>
-      <directory>../../lib-php/</directory>
-    </include>
-  </coverage>
-  <php>
-    </php>
-  <testsuites>
-    <testsuite name="Nominatim PHP Test Suite">
-      <directory>./Nominatim</directory>
-    </testsuite>
-  </testsuites>
-</phpunit>
index 0c770980acdada423eb2e8879503c53b744a1b01..3ca0720b4da24f76a76b0f30e3084af5e87d2802 100644 (file)
@@ -7,7 +7,6 @@
 """
 Helper fixtures for API call tests.
 """
-from pathlib import Path
 import pytest
 import pytest_asyncio
 import time
@@ -24,7 +23,7 @@ import nominatim_api.logging as loglib
 class APITester:
 
     def __init__(self):
-        self.api = napi.NominatimAPI(Path('/invalid'))
+        self.api = napi.NominatimAPI()
         self.async_to_sync(self.api._async_api.setup_database())
 
 
@@ -229,11 +228,9 @@ def frontend(request, event_loop, tmp_path):
 
             apiobj.async_to_sync(_do_sql())
 
-            event_loop.run_until_complete(convert_sqlite.convert(Path('/invalid'),
-                                                                 db, options))
-            outapi = napi.NominatimAPI(Path('/invalid'),
-                                       {'NOMINATIM_DATABASE_DSN': f"sqlite:dbname={db}",
-                                        'NOMINATIM_USE_US_TIGER_DATA': 'yes'})
+            event_loop.run_until_complete(convert_sqlite.convert(None, db, options))
+            outapi = napi.NominatimAPI(environ={'NOMINATIM_DATABASE_DSN': f"sqlite:dbname={db}",
+                                                'NOMINATIM_USE_US_TIGER_DATA': 'yes'})
             testapis.append(outapi)
 
             return outapi
@@ -249,5 +246,5 @@ def frontend(request, event_loop, tmp_path):
 
 @pytest_asyncio.fixture
 async def api(temp_db):
-    async with napi.NominatimAPIAsync(Path('/invalid')) as api:
+    async with napi.NominatimAPIAsync() as api:
         yield api
index 7154ae084317845075cb3efe57f40cca7f099f94..71caf5b7f282c400ccb8c6fc2a48e08b5bfa54d4 100644 (file)
@@ -19,7 +19,7 @@ class MyToken(query.Token):
 
 def mktoken(tid: int):
     return MyToken(penalty=3.0, token=tid, count=1, addr_count=1,
-                   lookup_word='foo', is_indexed=True)
+                   lookup_word='foo')
 
 
 @pytest.mark.parametrize('ptype,ttype', [('NONE', 'WORD'),
index 5d984014d18113e15f6198d246db64e5b000edad..371a6f024ca886ab17ca5f789c168b2ddd8242b9 100644 (file)
@@ -33,7 +33,7 @@ def make_query(*args):
                 q.add_token(TokenRange(start, end), ttype,
                             MyToken(penalty=0.5 if ttype == TokenType.PARTIAL else 0.0,
                                     token=tid, count=1, addr_count=1,
-                                    lookup_word=word, is_indexed=True))
+                                    lookup_word=word))
 
 
     return q
@@ -397,14 +397,14 @@ def make_counted_searches(name_part, name_full, address_part, address_full,
     q.add_node(BreakType.END, PhraseType.NONE)
 
     q.add_token(TokenRange(0, 1), TokenType.PARTIAL,
-                MyToken(0.5, 1, name_part, 1, 'name_part', True))
+                MyToken(0.5, 1, name_part, 1, 'name_part'))
     q.add_token(TokenRange(0, 1), TokenType.WORD,
-                MyToken(0, 101, name_full, 1, 'name_full', True))
+                MyToken(0, 101, name_full, 1, 'name_full'))
     for i in range(num_address_parts):
         q.add_token(TokenRange(i + 1, i + 2), TokenType.PARTIAL,
-                    MyToken(0.5, 2, address_part, 1, 'address_part', True))
+                    MyToken(0.5, 2, address_part, 1, 'address_part'))
         q.add_token(TokenRange(i + 1, i + 2), TokenType.WORD,
-                    MyToken(0, 102, address_full, 1, 'address_full', True))
+                    MyToken(0, 102, address_full, 1, 'address_full'))
 
     builder = SearchBuilder(q, SearchDetails())
 
index 7f88879c14fd7d8c0a856997432bc64b007b2d96..ac4bcbb74ba4dcd21739b5096386fc8654654be6 100644 (file)
@@ -7,8 +7,6 @@
 """
 Tests for query analyzer for ICU tokenizer.
 """
-from pathlib import Path
-
 import pytest
 import pytest_asyncio
 
@@ -40,7 +38,7 @@ async def conn(table_factory):
     table_factory('word',
                   definition='word_id INT, word_token TEXT, type TEXT, word TEXT, info JSONB')
 
-    async with NominatimAPIAsync(Path('/invalid'), {}) as api:
+    async with NominatimAPIAsync() as api:
         async with api.begin() as conn:
             yield conn
 
diff --git a/test/python/api/search/test_legacy_query_analyzer.py b/test/python/api/search/test_legacy_query_analyzer.py
deleted file mode 100644 (file)
index 0e967c1..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Tests for query analyzer for legacy tokenizer.
-"""
-from pathlib import Path
-
-import pytest
-import pytest_asyncio
-
-from nominatim_api import NominatimAPIAsync
-from nominatim_api.search.query import Phrase, PhraseType, TokenType, BreakType
-import nominatim_api.search.legacy_tokenizer as tok
-from nominatim_api.logging import set_log_output, get_and_disable
-
-
-async def add_word(conn, word_id, word_token, word, count):
-    t = conn.t.meta.tables['word']
-    await conn.execute(t.insert(), {'word_id': word_id,
-                                    'word_token': word_token,
-                                    'search_name_count': count,
-                                    'word': word})
-
-
-async def add_housenumber(conn, word_id, hnr):
-    t = conn.t.meta.tables['word']
-    await conn.execute(t.insert(), {'word_id': word_id,
-                                    'word_token': ' ' + hnr,
-                                    'word': hnr,
-                                    'class': 'place',
-                                    'type': 'house'})
-
-
-async def add_postcode(conn, word_id, postcode):
-    t = conn.t.meta.tables['word']
-    await conn.execute(t.insert(), {'word_id': word_id,
-                                    'word_token': ' ' + postcode,
-                                    'word': postcode,
-                                    'class': 'place',
-                                    'type': 'postcode'})
-
-
-async def add_special_term(conn, word_id, word_token, cls, typ, op):
-    t = conn.t.meta.tables['word']
-    await conn.execute(t.insert(), {'word_id': word_id,
-                                    'word_token': word_token,
-                                    'word': word_token,
-                                    'class': cls,
-                                    'type': typ,
-                                    'operator': op})
-
-
-def make_phrase(query):
-    return [Phrase(PhraseType.NONE, s) for s in query.split(',')]
-
-
-@pytest_asyncio.fixture
-async def conn(table_factory, temp_db_cursor):
-    """ Create an asynchronous SQLAlchemy engine for the test DB.
-    """
-    table_factory('nominatim_properties',
-                  definition='property TEXT, value TEXT',
-                  content=(('tokenizer_maxwordfreq', '10000'), ))
-    table_factory('word',
-                  definition="""word_id INT, word_token TEXT, word TEXT,
-                                class TEXT, type TEXT, country_code TEXT,
-                                search_name_count INT, operator TEXT
-                             """)
-
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
-                              RETURNS TEXT AS $$ SELECT lower(name); $$ LANGUAGE SQL;""")
-
-    async with NominatimAPIAsync(Path('/invalid'), {}) as api:
-        async with api.begin() as conn:
-            yield conn
-
-
-@pytest.mark.asyncio
-async def test_empty_phrase(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    query = await ana.analyze_query([])
-
-    assert len(query.source) == 0
-    assert query.num_token_slots() == 0
-
-
-@pytest.mark.asyncio
-async def test_single_phrase_with_unknown_terms(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_word(conn, 1, 'foo', 'FOO', 3)
-
-    query = await ana.analyze_query(make_phrase('foo BAR'))
-
-    assert len(query.source) == 1
-    assert query.source[0].ptype == PhraseType.NONE
-    assert query.source[0].text == 'foo bar'
-
-    assert query.num_token_slots() == 2
-    assert len(query.nodes[0].starting) == 1
-    assert not query.nodes[1].starting
-
-
-@pytest.mark.asyncio
-async def test_multiple_phrases(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_word(conn, 1, 'one', 'one', 13)
-    await add_word(conn, 2, 'two', 'two', 45)
-    await add_word(conn, 100, 'one two', 'one two', 3)
-    await add_word(conn, 3, 'three', 'three', 4584)
-
-    query = await ana.analyze_query(make_phrase('one two,three'))
-
-    assert len(query.source) == 2
-
-
-@pytest.mark.asyncio
-async def test_housenumber_token(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_housenumber(conn, 556, '45 a')
-
-    query = await ana.analyze_query(make_phrase('45 A'))
-
-    assert query.num_token_slots() == 2
-    assert len(query.nodes[0].starting) == 2
-
-    query.nodes[0].starting.sort(key=lambda tl: tl.end)
-
-    hn1 = query.nodes[0].starting[0]
-    assert hn1.ttype == TokenType.HOUSENUMBER
-    assert hn1.end == 1
-    assert hn1.tokens[0].token == 0
-
-    hn2 = query.nodes[0].starting[1]
-    assert hn2.ttype == TokenType.HOUSENUMBER
-    assert hn2.end == 2
-    assert hn2.tokens[0].token == 556
-
-
-@pytest.mark.asyncio
-async def test_postcode_token(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_postcode(conn, 34, '45ax')
-
-    query = await ana.analyze_query(make_phrase('45AX'))
-
-    assert query.num_token_slots() == 1
-    assert [tl.ttype for tl in query.nodes[0].starting] == [TokenType.POSTCODE]
-
-
-@pytest.mark.asyncio
-async def test_partial_tokens(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_word(conn, 1, ' foo', 'foo', 99)
-    await add_word(conn, 1, 'foo', 'FOO', 99)
-    await add_word(conn, 1, 'bar', 'FOO', 990000)
-
-    query = await ana.analyze_query(make_phrase('foo bar'))
-
-    assert query.num_token_slots() == 2
-
-    first = query.nodes[0].starting
-    first.sort(key=lambda tl: tl.tokens[0].penalty)
-    assert [tl.ttype for tl in first] == [TokenType.WORD, TokenType.PARTIAL]
-    assert all(tl.tokens[0].lookup_word == 'foo' for tl in first)
-
-    second = query.nodes[1].starting
-    assert [tl.ttype for tl in second] == [TokenType.PARTIAL]
-    assert not second[0].tokens[0].is_indexed
-
-
-@pytest.mark.asyncio
-@pytest.mark.parametrize('term,order', [('23456', ['POSTCODE', 'HOUSENUMBER', 'WORD', 'PARTIAL']),
-                                        ('3', ['HOUSENUMBER', 'POSTCODE', 'WORD', 'PARTIAL'])
-                                       ])
-async def test_penalty_postcodes_and_housenumbers(conn, term, order):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_postcode(conn, 1, term)
-    await add_housenumber(conn, 2, term)
-    await add_word(conn, 3, term, term, 5)
-    await add_word(conn, 4, ' ' + term, term, 1)
-
-    query = await ana.analyze_query(make_phrase(term))
-
-    assert query.num_token_slots() == 1
-
-    torder = [(tl.tokens[0].penalty, tl.ttype.name) for tl in query.nodes[0].starting]
-    torder.sort()
-
-    assert [t[1] for t in torder] == order
-
-
-@pytest.mark.asyncio
-async def test_category_words_only_at_beginning(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_special_term(conn, 1, 'foo', 'amenity', 'restaurant', 'in')
-    await add_word(conn, 2, ' bar', 'BAR', 1)
-
-    query = await ana.analyze_query(make_phrase('foo BAR foo'))
-
-    assert query.num_token_slots() == 3
-    assert len(query.nodes[0].starting) == 1
-    assert query.nodes[0].starting[0].ttype == TokenType.NEAR_ITEM
-    assert not query.nodes[2].starting
-
-
-@pytest.mark.asyncio
-async def test_qualifier_words(conn):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_special_term(conn, 1, 'foo', 'amenity', 'restaurant', '-')
-    await add_word(conn, 2, ' bar', 'w', None)
-
-    query = await ana.analyze_query(make_phrase('foo BAR foo BAR foo'))
-
-    assert query.num_token_slots() == 5
-    assert set(t.ttype for t in query.nodes[0].starting) == {TokenType.NEAR_ITEM, TokenType.QUALIFIER}
-    assert set(t.ttype for t in query.nodes[2].starting) == {TokenType.QUALIFIER}
-    assert set(t.ttype for t in query.nodes[4].starting) == {TokenType.NEAR_ITEM, TokenType.QUALIFIER}
-
-
-@pytest.mark.asyncio
-@pytest.mark.parametrize('logtype', ['text', 'html'])
-async def test_log_output(conn, logtype):
-    ana = await tok.create_query_analyzer(conn)
-
-    await add_word(conn, 1, 'foo', 'FOO', 99)
-
-    set_log_output(logtype)
-    await ana.analyze_query(make_phrase('foo'))
-
-    assert get_and_disable()
index 884d29328380323d08749964046d951f8c37f999..0d89ed5f522cee983acc5c437ef2bc28ee2e6af3 100644 (file)
@@ -20,7 +20,7 @@ class MyToken(Token):
 def make_query(*args):
     q = QueryStruct([Phrase(args[0][1], '')])
     dummy = MyToken(penalty=3.0, token=45, count=1, addr_count=1,
-                    lookup_word='foo', is_indexed=True)
+                    lookup_word='foo')
 
     for btype, ptype, _ in args[1:]:
         q.add_node(btype, ptype)
index 5412ca6e334fad5f8932379a274a7f65284182d2..9341b52774f5cfe5c0170dff72e743e1a4be1685 100644 (file)
@@ -7,7 +7,6 @@
 """
 Tests for the status API call.
 """
-from pathlib import Path
 import datetime as dt
 import pytest
 
@@ -46,7 +45,7 @@ def test_status_full(apiobj, frontend):
 def test_status_database_not_found(monkeypatch):
     monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'dbname=rgjdfkgjedkrgdfkngdfkg')
 
-    api = napi.NominatimAPI(Path('/invalid'), {})
+    api = napi.NominatimAPI()
 
     result = api.status()
 
index 1d9bf90f9f4c35fb8530f0e31c46f4e2f28f431a..b0da52ce0504704e7cb76c9736310381e390f56c 100644 (file)
@@ -14,8 +14,7 @@ import nominatim_db.cli
 @pytest.fixture
 def run_export(tmp_path, capsys):
     def _exec(args):
-        assert 0 == nominatim_db.cli.nominatim(module_dir='MODULE NOT AVAILABLE',
-                                               osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
+        assert 0 == nominatim_db.cli.nominatim(osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
                                                cli_args=['export', '--project-dir', str(tmp_path)]
                                                         + args)
         return capsys.readouterr().out.split('\r\n')
index 5ef169045e0a4712c256bf38d2634a4bbae6e146..6ea790c060b21c740c3ff2e985adeb4bf51ba3b5 100644 (file)
@@ -9,7 +9,6 @@ Tests for the Python web frameworks adaptor, v1 API.
 """
 import json
 import xml.etree.ElementTree as ET
-from pathlib import Path
 
 import pytest
 
@@ -242,7 +241,7 @@ class TestStatusEndpoint:
         a = FakeAdaptor()
         self.status = napi.StatusResult(0, 'foo')
 
-        resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
 
         assert isinstance(resp, FakeResponse)
         assert resp.status == 200
@@ -254,7 +253,7 @@ class TestStatusEndpoint:
         a = FakeAdaptor()
         self.status = napi.StatusResult(405, 'foo')
 
-        resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
 
         assert isinstance(resp, FakeResponse)
         assert resp.status == 500
@@ -266,7 +265,7 @@ class TestStatusEndpoint:
         a = FakeAdaptor(params={'format': 'json'})
         self.status = napi.StatusResult(405, 'foo')
 
-        resp = await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        resp = await glue.status_endpoint(napi.NominatimAPIAsync(), a)
 
         assert isinstance(resp, FakeResponse)
         assert resp.status == 200
@@ -279,7 +278,7 @@ class TestStatusEndpoint:
         self.status = napi.StatusResult(0, 'foo')
 
         with pytest.raises(FakeError):
-            await glue.status_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+            await glue.status_endpoint(napi.NominatimAPIAsync(), a)
 
 
 # details_endpoint()
@@ -305,14 +304,14 @@ class TestDetailsEndpoint:
         a = FakeAdaptor()
 
         with pytest.raises(FakeError, match='^400 -- .*Missing'):
-            await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+            await glue.details_endpoint(napi.NominatimAPIAsync(), a)
 
 
     @pytest.mark.asyncio
     async def test_details_by_place_id(self):
         a = FakeAdaptor(params={'place_id': '4573'})
 
-        await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        await glue.details_endpoint(napi.NominatimAPIAsync(), a)
 
         assert self.lookup_args[0].place_id == 4573
 
@@ -321,7 +320,7 @@ class TestDetailsEndpoint:
     async def test_details_by_osm_id(self):
         a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45'})
 
-        await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        await glue.details_endpoint(napi.NominatimAPIAsync(), a)
 
         assert self.lookup_args[0].osm_type == 'N'
         assert self.lookup_args[0].osm_id == 45
@@ -332,7 +331,7 @@ class TestDetailsEndpoint:
     async def test_details_with_debugging(self):
         a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45', 'debug': '1'})
 
-        resp = await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        resp = await glue.details_endpoint(napi.NominatimAPIAsync(), a)
         content = ET.fromstring(resp.output)
 
         assert resp.content_type == 'text/html; charset=utf-8'
@@ -345,7 +344,7 @@ class TestDetailsEndpoint:
         self.result = None
 
         with pytest.raises(FakeError, match='^404 -- .*found'):
-            await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+            await glue.details_endpoint(napi.NominatimAPIAsync(), a)
 
 
 # reverse_endpoint()
@@ -370,7 +369,7 @@ class TestReverseEndPoint:
         a.params['format'] = 'xml'
 
         with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'):
-            await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+            await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
 
 
     @pytest.mark.asyncio
@@ -380,7 +379,7 @@ class TestReverseEndPoint:
         a.params = params
         a.params['format'] = 'json'
 
-        res = await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
 
         assert res == ''
 
@@ -391,7 +390,7 @@ class TestReverseEndPoint:
         a.params['lat'] = '56.3'
         a.params['lon'] = '6.8'
 
-        assert await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        assert await glue.reverse_endpoint(napi.NominatimAPIAsync(), a)
 
 
     @pytest.mark.asyncio
@@ -400,7 +399,7 @@ class TestReverseEndPoint:
         a.params['q'] = '34.6 2.56'
         a.params['format'] = 'json'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -425,7 +424,7 @@ class TestLookupEndpoint:
         a = FakeAdaptor()
         a.params['format'] = 'json'
 
-        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
 
         assert res.output == '[]'
 
@@ -437,7 +436,7 @@ class TestLookupEndpoint:
         a.params['format'] = 'json'
         a.params['osm_ids'] = f'W34,{param},N33333'
 
-        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -449,7 +448,7 @@ class TestLookupEndpoint:
         a.params['format'] = 'json'
         a.params['osm_ids'] = f'W34,{param},N33333'
 
-        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -460,7 +459,7 @@ class TestLookupEndpoint:
         a.params['format'] = 'json'
         a.params['osm_ids'] = 'N23,W34'
 
-        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.lookup_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -485,7 +484,7 @@ class TestSearchEndPointSearch:
         a = FakeAdaptor()
         a.params['q'] = 'something'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -496,7 +495,7 @@ class TestSearchEndPointSearch:
         a.params['q'] = 'something'
         a.params['format'] = 'xml'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert res.status == 200
         assert res.output.index('something') > 0
@@ -509,7 +508,7 @@ class TestSearchEndPointSearch:
         a.params['city'] = 'ignored'
 
         with pytest.raises(FakeError, match='^400 -- .*cannot be used together'):
-            res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+            res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
 
     @pytest.mark.asyncio
@@ -521,7 +520,7 @@ class TestSearchEndPointSearch:
         if not dedupe:
             a.params['dedupe'] = '0'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == numres
 
@@ -544,7 +543,7 @@ class TestSearchEndPointSearchAddress:
         a = FakeAdaptor()
         a.params['street'] = 'something'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
 
@@ -567,6 +566,6 @@ class TestSearchEndPointSearchCategory:
         a = FakeAdaptor()
         a.params['q'] = '[shop=fog]'
 
-        res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
+        res = await glue.search_endpoint(napi.NominatimAPIAsync(), a)
 
         assert len(json.loads(res.output)) == 1
index 304943f1be27347edef3c189f6a916f1b42d3edd..f0c9986d302f9159c017ea72a7a12ac6197daa7c 100644 (file)
@@ -27,7 +27,6 @@ def setup_database_with_context(apiobj, table_factory):
 
 @pytest.mark.parametrize('args', [['--search-only'], ['--reverse-only']])
 def test_warm_all(tmp_path, args):
-    assert 0 == nominatim_db.cli.nominatim(module_dir='MODULE NOT AVAILABLE',
-                                           osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
+    assert 0 == nominatim_db.cli.nominatim(osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
                                            cli_args=['admin', '--project-dir', str(tmp_path),
                                                      '--warm'] + args)
index d5ade22350a9a7764bfd26678fd1b97d559f22bf..84f2d6598ae8adad77efe389d64d3adb31e80604 100644 (file)
@@ -68,8 +68,7 @@ def cli_call():
         Returns a function that can be called with the desired CLI arguments.
     """
     def _call_nominatim(*args):
-        return nominatim_db.cli.nominatim(module_dir='MODULE NOT AVAILABLE',
-                                          osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
+        return nominatim_db.cli.nominatim(osm2pgsql_path='OSM2PGSQL NOT AVAILABLE',
                                           cli_args=args)
 
     return _call_nominatim
index 2831f84f1bc801d690966827bc2f741f4a440471..d42df50a760066f972628817b830c67899dea04a 100644 (file)
@@ -37,15 +37,6 @@ def test_cli_version(cli_call, capsys):
     assert captured.out.startswith('Nominatim version')
 
 
-def test_cli_serve_php(cli_call, mock_func_factory):
-    func = mock_func_factory(nominatim_db.cli, 'run_php_server')
-
-    cli_call('serve', '--engine', 'php') == 0
-
-    assert func.called == 1
-
-
-
 class TestCliWithDb:
 
     @pytest.fixture(autouse=True)
index e47d713c1f32ab04574eb510fa1677450b9d9132..f833dde347d7b743fbda477b1a2b761b59c6ebf2 100644 (file)
@@ -52,7 +52,6 @@ class TestCliImportWithDb:
             mock_func_factory(nominatim_db.tools.refresh, 'load_address_levels_from_config'),
             mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes'),
             async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'),
-            mock_func_factory(nominatim_db.tools.refresh, 'setup_website'),
         ]
 
         params = ['import', '--osm-file', __file__]
@@ -81,7 +80,6 @@ class TestCliImportWithDb:
             mock_func_factory(nominatim_db.data.country_info, 'create_country_names'),
             mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes'),
             async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'),
-            mock_func_factory(nominatim_db.tools.refresh, 'setup_website'),
             mock_func_factory(nominatim_db.db.properties, 'set_property')
         ]
 
@@ -98,7 +96,6 @@ class TestCliImportWithDb:
             async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'),
             mock_func_factory(nominatim_db.data.country_info, 'create_country_names'),
             async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_full'),
-            mock_func_factory(nominatim_db.tools.refresh, 'setup_website'),
             mock_func_factory(nominatim_db.db.properties, 'set_property')
         ]
 
@@ -115,7 +112,6 @@ class TestCliImportWithDb:
         mocks = [
             async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'),
             mock_func_factory(nominatim_db.data.country_info, 'create_country_names'),
-            mock_func_factory(nominatim_db.tools.refresh, 'setup_website'),
             mock_func_factory(nominatim_db.db.properties, 'set_property')
         ]
 
index 9074b2cc3d44ae4434f68fdef8ecaf778366468c..9f3d7bb241e46b31e3f4a548722ad3b2c60ee5ad 100644 (file)
@@ -25,7 +25,6 @@ class TestRefresh:
                              ('address-levels', 'load_address_levels_from_config'),
                              ('wiki-data', 'import_wikipedia_articles'),
                              ('importance', 'recompute_importance'),
-                             ('website', 'setup_website'),
                              ])
     def test_refresh_command(self, mock_func_factory, command, func):
         mock_func_factory(nominatim_db.tools.refresh, 'create_functions')
index 5c9393ecfde56f76be876cebb6d5c68ff90a70cc..8f90b5da16e3fb266f675612b6432762fe6355d5 100644 (file)
@@ -140,8 +140,8 @@ def test_get_bool(make_config, monkeypatch, value, result):
 def test_get_bool_empty(make_config):
     config = make_config()
 
-    assert config.DATABASE_MODULE_PATH == ''
-    assert not config.get_bool('DATABASE_MODULE_PATH')
+    assert config.TOKENIZER_CONFIG == ''
+    assert not config.get_bool('TOKENIZER_CONFIG')
 
 
 @pytest.mark.parametrize("value,result", [('0', 0), ('1', 1),
@@ -167,10 +167,10 @@ def test_get_int_bad_values(make_config, monkeypatch, value):
 def test_get_int_empty(make_config):
     config = make_config()
 
-    assert config.DATABASE_MODULE_PATH == ''
+    assert config.TOKENIZER_CONFIG == ''
 
     with pytest.raises(UsageError):
-        config.get_int('DATABASE_MODULE_PATH')
+        config.get_int('TOKENIZER_CONFIG')
 
 
 @pytest.mark.parametrize("value,outlist", [('sd', ['sd']),
@@ -193,8 +193,8 @@ def test_get_str_list_empty(make_config):
 def test_get_path_empty(make_config):
     config = make_config()
 
-    assert config.DATABASE_MODULE_PATH == ''
-    assert not config.get_path('DATABASE_MODULE_PATH')
+    assert config.TOKENIZER_CONFIG == ''
+    assert not config.get_path('TOKENIZER_CONFIG')
 
 
 def test_get_path_absolute(make_config, monkeypatch):
index 3ced320558347685543e246dd90b5741c8716625..a25ff8ec5046766416be3bcb87091dff8ff90fd2 100644 (file)
@@ -109,7 +109,7 @@ def table_factory(temp_db_conn):
 @pytest.fixture
 def def_config():
     cfg = Configuration(None)
-    cfg.set_libdirs(module='.', osm2pgsql='.')
+    cfg.set_libdirs(osm2pgsql=None)
     return cfg
 
 
@@ -118,7 +118,7 @@ def project_env(tmp_path):
     projdir = tmp_path / 'project'
     projdir.mkdir()
     cfg = Configuration(projdir)
-    cfg.set_libdirs(module='.', osm2pgsql='.')
+    cfg.set_libdirs(osm2pgsql=None)
     return cfg
 
 
@@ -208,7 +208,7 @@ def osmline_table(temp_db_with_extensions, table_factory):
 def sql_preprocessor_cfg(tmp_path, table_factory, temp_db_with_extensions):
     table_factory('country_name', 'partition INT', ((0, ), (1, ), (2, )))
     cfg = Configuration(None)
-    cfg.set_libdirs(module='.', osm2pgsql='.', sql=tmp_path)
+    cfg.set_libdirs(osm2pgsql=None, sql=tmp_path)
     return cfg
 
 
diff --git a/test/python/mock_legacy_word_table.py b/test/python/mock_legacy_word_table.py
deleted file mode 100644 (file)
index d3f81a4..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Legacy word table for testing with functions to prefil and test contents
-of the table.
-"""
-from nominatim_db.db.connection import execute_scalar
-
-class MockLegacyWordTable:
-    """ A word table for testing using legacy word table structure.
-    """
-    def __init__(self, conn):
-        self.conn = conn
-        with conn.cursor() as cur:
-            cur.execute("""CREATE TABLE word (word_id INTEGER,
-                                              word_token text,
-                                              word text,
-                                              class text,
-                                              type text,
-                                              country_code varchar(2),
-                                              search_name_count INTEGER,
-                                              operator TEXT)""")
-
-        conn.commit()
-
-    def add_full_word(self, word_id, word, word_token=None):
-        with self.conn.cursor() as cur:
-            cur.execute("""INSERT INTO word (word_id, word_token, word)
-                           VALUES (%s, %s, %s)
-                        """, (word_id, ' ' + (word_token or word), word))
-        self.conn.commit()
-
-
-    def add_special(self, word_token, word, cls, typ, oper):
-        with self.conn.cursor() as cur:
-            cur.execute("""INSERT INTO word (word_token, word, class, type, operator)
-                              VALUES (%s, %s, %s, %s, %s)
-                        """, (word_token, word, cls, typ, oper))
-        self.conn.commit()
-
-
-    def add_country(self, country_code, word_token):
-        with self.conn.cursor() as cur:
-            cur.execute("INSERT INTO word (word_token, country_code) VALUES(%s, %s)",
-                        (word_token, country_code))
-        self.conn.commit()
-
-
-    def add_postcode(self, word_token, postcode):
-        with self.conn.cursor() as cur:
-            cur.execute("""INSERT INTO word (word_token, word, class, type)
-                              VALUES (%s, %s, 'place', 'postcode')
-                        """, (word_token, postcode))
-        self.conn.commit()
-
-
-    def count(self):
-        return execute_scalar(self.conn, "SELECT count(*) FROM word")
-
-
-    def count_special(self):
-        return execute_scalar(self.conn, "SELECT count(*) FROM word WHERE class != 'place'")
-
-
-    def get_special(self):
-        with self.conn.cursor() as cur:
-            cur.execute("""SELECT word_token, word, class as cls, type, operator
-                           FROM word WHERE class != 'place'""")
-            result = set((tuple(row) for row in cur))
-            assert len(result) == cur.rowcount, "Word table has duplicates."
-            return result
-
-
-    def get_country(self):
-        with self.conn.cursor() as cur:
-            cur.execute("""SELECT country_code, word_token
-                           FROM word WHERE country_code is not null""")
-            result = set((tuple(row) for row in cur))
-            assert len(result) == cur.rowcount, "Word table has duplicates."
-            return result
-
-
-    def get_postcodes(self):
-        with self.conn.cursor() as cur:
-            cur.execute("""SELECT word FROM word
-                           WHERE class = 'place' and type = 'postcode'""")
-            return set((row[0] for row in cur))
-
-    def get_partial_words(self):
-        with self.conn.cursor() as cur:
-            cur.execute("""SELECT word_token, search_name_count FROM word
-                           WHERE class is null and country_code is null
-                                 and not word_token like ' %'""")
-            return set((tuple(row) for row in cur))
-
diff --git a/test/python/tokenizer/test_legacy.py b/test/python/tokenizer/test_legacy.py
deleted file mode 100644 (file)
index bf208c9..0000000
+++ /dev/null
@@ -1,591 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Test for legacy tokenizer.
-"""
-import shutil
-import re
-
-import pytest
-
-from nominatim_db.data.place_info import PlaceInfo
-from nominatim_db.tokenizer import legacy_tokenizer
-from nominatim_db.db import properties
-from nominatim_db.errors import UsageError
-
-from mock_legacy_word_table import MockLegacyWordTable
-
-# Force use of legacy word table
-@pytest.fixture
-def word_table(temp_db_conn):
-    return MockLegacyWordTable(temp_db_conn)
-
-
-@pytest.fixture
-def test_config(project_env, tmp_path):
-    module_dir = tmp_path / 'module_src'
-    module_dir.mkdir()
-    (module_dir / 'nominatim.so').write_text('TEST nominatim.so')
-
-    project_env.lib_dir.module = module_dir
-
-    sqldir = tmp_path / 'sql'
-    sqldir.mkdir()
-    (sqldir / 'tokenizer').mkdir()
-
-    # Get the original SQL but replace make_standard_name to avoid module use.
-    init_sql = (project_env.lib_dir.sql / 'tokenizer' / 'legacy_tokenizer.sql').read_text()
-    for fn in ('transliteration', 'gettokenstring'):
-        init_sql = re.sub(f'CREATE OR REPLACE FUNCTION {fn}[^;]*;',
-                          '', init_sql, re.DOTALL)
-    init_sql += """
-                   CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
-                   RETURNS TEXT AS $$ SELECT lower(name); $$ LANGUAGE SQL;
-
-                """
-    # Also load util functions. Some are needed by the tokenizer.
-    init_sql += (project_env.lib_dir.sql / 'functions' / 'utils.sql').read_text()
-    (sqldir / 'tokenizer' / 'legacy_tokenizer.sql').write_text(init_sql)
-
-    (sqldir / 'words.sql').write_text("SELECT 'a'")
-
-    shutil.copy(str(project_env.lib_dir.sql / 'tokenizer' / 'legacy_tokenizer_tables.sql'),
-                str(sqldir / 'tokenizer' / 'legacy_tokenizer_tables.sql'))
-
-    project_env.lib_dir.sql = sqldir
-    project_env.lib_dir.data = sqldir
-
-    return project_env
-
-
-@pytest.fixture
-def tokenizer_factory(dsn, tmp_path, property_table):
-    (tmp_path / 'tokenizer').mkdir()
-
-    def _maker():
-        return legacy_tokenizer.create(dsn, tmp_path / 'tokenizer')
-
-    return _maker
-
-
-@pytest.fixture
-def tokenizer_setup(tokenizer_factory, test_config, monkeypatch, sql_preprocessor):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-
-@pytest.fixture
-def analyzer(tokenizer_factory, test_config, monkeypatch, sql_preprocessor,
-             word_table, temp_db_with_extensions, tmp_path):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    monkeypatch.setenv('NOMINATIM_TERM_NORMALIZATION', ':: lower();')
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-    monkeypatch.undo()
-
-    with tok.name_analyzer() as analyzer:
-        yield analyzer
-
-
-@pytest.fixture
-def make_standard_name(temp_db_cursor):
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
-                              RETURNS TEXT AS $$ SELECT '#' || lower(name) || '#'; $$ LANGUAGE SQL""")
-
-
-@pytest.fixture
-def create_postcode_id(temp_db_cursor):
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION create_postcode_id(postcode TEXT)
-                              RETURNS BOOLEAN AS $$
-                              INSERT INTO word (word_token, word, class, type)
-                                VALUES (' ' || postcode, postcode, 'place', 'postcode')
-                              RETURNING True;
-                              $$ LANGUAGE SQL""")
-
-
-def test_init_new(tokenizer_factory, test_config, monkeypatch,
-                  temp_db_conn, sql_preprocessor):
-    monkeypatch.setenv('NOMINATIM_TERM_NORMALIZATION', 'xxvv')
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-    assert properties.get_property(temp_db_conn, legacy_tokenizer.DBCFG_NORMALIZATION) == 'xxvv'
-
-    outfile = test_config.project_dir / 'module' / 'nominatim.so'
-
-    assert outfile.exists()
-    assert outfile.read_text() == 'TEST nominatim.so'
-    assert outfile.stat().st_mode == 33261
-
-
-def test_init_module_load_failed(tokenizer_factory, test_config):
-    tok = tokenizer_factory()
-
-    with pytest.raises(UsageError):
-        tok.init_new_db(test_config)
-
-
-def test_init_module_custom(tokenizer_factory, test_config,
-                            monkeypatch, tmp_path, sql_preprocessor):
-    module_dir = (tmp_path / 'custom').resolve()
-    module_dir.mkdir()
-    (module_dir/ 'nominatim.so').write_text('CUSTOM nomiantim.so')
-
-    monkeypatch.setenv('NOMINATIM_DATABASE_MODULE_PATH', str(module_dir))
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-    assert not (test_config.project_dir / 'module').exists()
-
-
-def test_init_from_project(tokenizer_setup, tokenizer_factory, test_config):
-    tok = tokenizer_factory()
-
-    tok.init_from_project(test_config)
-
-    assert tok.normalization is not None
-
-
-def test_update_sql_functions(sql_preprocessor, temp_db_conn,
-                              tokenizer_factory, test_config, table_factory,
-                              monkeypatch, temp_db_cursor):
-    monkeypatch.setenv('NOMINATIM_MAX_WORD_FREQUENCY', '1133')
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-    monkeypatch.undo()
-
-    assert properties.get_property(temp_db_conn, legacy_tokenizer.DBCFG_MAXWORDFREQ) == '1133'
-
-    table_factory('test', 'txt TEXT')
-
-    func_file = test_config.lib_dir.sql / 'tokenizer' / 'legacy_tokenizer.sql'
-    func_file.write_text("""INSERT INTO test VALUES ('{{max_word_freq}}'),
-                                                   ('{{modulepath}}')""")
-
-    tok.update_sql_functions(test_config)
-
-    test_content = temp_db_cursor.row_set('SELECT * FROM test')
-    assert test_content == set((('1133', ), (str(test_config.project_dir / 'module'), )))
-
-
-def test_finalize_import(tokenizer_factory, temp_db_conn,
-                         temp_db_cursor, test_config, monkeypatch,
-                         sql_preprocessor_cfg):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-
-    func_file = test_config.lib_dir.sql / 'tokenizer' / 'legacy_tokenizer_indices.sql'
-    func_file.write_text("""CREATE FUNCTION test() RETURNS TEXT
-                            AS $$ SELECT 'b'::text $$ LANGUAGE SQL""")
-
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-    tok.finalize_import(test_config)
-
-    temp_db_cursor.scalar('SELECT test()') == 'b'
-
-
-def test_migrate_database(tokenizer_factory, test_config, temp_db_conn, monkeypatch):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    tok = tokenizer_factory()
-    tok.migrate_database(test_config)
-
-    assert properties.get_property(temp_db_conn, legacy_tokenizer.DBCFG_MAXWORDFREQ) is not None
-    assert properties.get_property(temp_db_conn, legacy_tokenizer.DBCFG_NORMALIZATION) is not None
-
-    outfile = test_config.project_dir / 'module' / 'nominatim.so'
-
-    assert outfile.exists()
-    assert outfile.read_text() == 'TEST nominatim.so'
-    assert outfile.stat().st_mode == 33261
-
-
-def test_check_database(test_config, tokenizer_factory, monkeypatch,
-                        temp_db_cursor, sql_preprocessor_cfg):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-    assert tok.check_database(False) is None
-
-
-def test_check_database_no_tokenizer(test_config, tokenizer_factory):
-    tok = tokenizer_factory()
-
-    assert tok.check_database(False) is not None
-
-
-def test_check_database_bad_setup(test_config, tokenizer_factory, monkeypatch,
-                                  temp_db_cursor, sql_preprocessor_cfg):
-    monkeypatch.setattr(legacy_tokenizer, '_check_module', lambda m, c: None)
-    tok = tokenizer_factory()
-    tok.init_new_db(test_config)
-
-    # Inject a bad transliteration.
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
-                              RETURNS TEXT AS $$ SELECT 'garbage'::text; $$ LANGUAGE SQL""")
-
-    assert tok.check_database(False) is not None
-
-
-def test_update_statistics_reverse_only(word_table, tokenizer_factory, test_config):
-    tok = tokenizer_factory()
-    tok.update_statistics(test_config)
-
-
-def test_update_statistics(word_table, table_factory, temp_db_cursor, tokenizer_factory, test_config):
-    word_table.add_full_word(1000, 'hello')
-    table_factory('search_name',
-                  'place_id BIGINT, name_vector INT[]',
-                  [(12, [1000])])
-    tok = tokenizer_factory()
-
-    tok.update_statistics(test_config)
-
-    assert temp_db_cursor.scalar("""SELECT count(*) FROM word
-                                    WHERE word_token like ' %' and
-                                          search_name_count > 0""") > 0
-
-
-def test_update_word_tokens(tokenizer_factory):
-    tok = tokenizer_factory()
-
-    # This is a noop and should just pass.
-    tok.update_word_tokens()
-
-
-def test_normalize(analyzer):
-    assert analyzer.normalize('TEsT') == 'test'
-
-
-def test_update_postcodes_from_db_empty(analyzer, table_factory, word_table,
-                                        create_postcode_id):
-    table_factory('location_postcode', 'postcode TEXT',
-                  content=(('1234',), ('12 34',), ('AB23',), ('1234',)))
-
-    analyzer.update_postcodes_from_db()
-
-    assert word_table.get_postcodes() == {'1234', '12 34', 'AB23'}
-
-
-def test_update_postcodes_from_db_add_and_remove(analyzer, table_factory, word_table,
-                                                 create_postcode_id):
-    table_factory('location_postcode', 'postcode TEXT',
-                  content=(('1234',), ('45BC', ), ('XX45', )))
-    word_table.add_postcode(' 1234', '1234')
-    word_table.add_postcode(' 5678', '5678')
-
-    analyzer.update_postcodes_from_db()
-
-    assert word_table.get_postcodes() == {'1234', '45BC', 'XX45'}
-
-
-def test_update_special_phrase_empty_table(analyzer, word_table, make_standard_name):
-    analyzer.update_special_phrases([
-        ("König bei", "amenity", "royal", "near"),
-        ("Könige", "amenity", "royal", "-"),
-        ("könige", "amenity", "royal", "-"),
-        ("strasse", "highway", "primary", "in")
-    ], True)
-
-    assert word_table.get_special() \
-               == set(((' #könig bei#', 'könig bei', 'amenity', 'royal', 'near'),
-                       (' #könige#', 'könige', 'amenity', 'royal', None),
-                       (' #strasse#', 'strasse', 'highway', 'primary', 'in')))
-
-
-def test_update_special_phrase_delete_all(analyzer, word_table, make_standard_name):
-    word_table.add_special(' #foo#', 'foo', 'amenity', 'prison', 'in')
-    word_table.add_special(' #bar#', 'bar', 'highway', 'road', None)
-
-    assert word_table.count_special() == 2
-
-    analyzer.update_special_phrases([], True)
-
-    assert word_table.count_special() == 0
-
-
-def test_update_special_phrases_no_replace(analyzer, word_table, make_standard_name):
-    word_table.add_special(' #foo#', 'foo', 'amenity', 'prison', 'in')
-    word_table.add_special(' #bar#', 'bar', 'highway', 'road', None)
-
-    assert word_table.count_special() == 2
-
-    analyzer.update_special_phrases([], False)
-
-    assert word_table.count_special() == 2
-
-
-def test_update_special_phrase_modify(analyzer, word_table, make_standard_name):
-    word_table.add_special(' #foo#', 'foo', 'amenity', 'prison', 'in')
-    word_table.add_special(' #bar#', 'bar', 'highway', 'road', None)
-
-    assert word_table.count_special() == 2
-
-    analyzer.update_special_phrases([
-        ('prison', 'amenity', 'prison', 'in'),
-        ('bar', 'highway', 'road', '-'),
-        ('garden', 'leisure', 'garden', 'near')
-    ], True)
-
-    assert word_table.get_special() \
-               == set(((' #prison#', 'prison', 'amenity', 'prison', 'in'),
-                       (' #bar#', 'bar', 'highway', 'road', None),
-                       (' #garden#', 'garden', 'leisure', 'garden', 'near')))
-
-
-def test_add_country_names(analyzer, word_table, make_standard_name):
-    analyzer.add_country_names('de', {'name': 'Germany',
-                                      'name:de': 'Deutschland',
-                                      'short_name': 'germany'})
-
-    assert word_table.get_country() \
-               == {('de', ' #germany#'),
-                   ('de', ' #deutschland#')}
-
-
-def test_add_more_country_names(analyzer, word_table, make_standard_name):
-    word_table.add_country('fr', ' #france#')
-    word_table.add_country('it', ' #italy#')
-    word_table.add_country('it', ' #itala#')
-
-    analyzer.add_country_names('it', {'name': 'Italy', 'ref': 'IT'})
-
-    assert word_table.get_country() \
-               == {('fr', ' #france#'),
-                   ('it', ' #italy#'),
-                   ('it', ' #itala#'),
-                   ('it', ' #it#')}
-
-
-@pytest.mark.parametrize('pcode', ['12345', 'AB 123', '34-345'])
-def test_process_place_postcode(analyzer, create_postcode_id, word_table, pcode):
-    analyzer.process_place(PlaceInfo({'address': {'postcode' : pcode}}))
-
-    assert word_table.get_postcodes() == {pcode, }
-
-
-@pytest.mark.parametrize('pcode', ['12:23', 'ab;cd;f', '123;836'])
-def test_process_place_bad_postcode(analyzer, create_postcode_id, word_table, pcode):
-    analyzer.process_place(PlaceInfo({'address': {'postcode' : pcode}}))
-
-    assert not word_table.get_postcodes()
-
-
-class TestHousenumberName:
-
-    @staticmethod
-    @pytest.fixture(autouse=True)
-    def setup_create_housenumbers(temp_db_cursor):
-        temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION create_housenumbers(
-                                      housenumbers TEXT[],
-                                      OUT tokens TEXT, OUT normtext TEXT)
-                                  AS $$
-                                  SELECT housenumbers::TEXT, array_to_string(housenumbers, ';')
-                                  $$ LANGUAGE SQL""")
-
-
-    @staticmethod
-    @pytest.mark.parametrize('hnr', ['123a', '1', '101'])
-    def test_process_place_housenumbers_simple(analyzer, hnr):
-        info = analyzer.process_place(PlaceInfo({'address': {'housenumber' : hnr}}))
-
-        assert info['hnr'] == hnr
-        assert info['hnr_tokens'].startswith("{")
-
-
-    @staticmethod
-    def test_process_place_housenumbers_lists(analyzer):
-        info = analyzer.process_place(PlaceInfo({'address': {'conscriptionnumber' : '1; 2;3'}}))
-
-        assert set(info['hnr'].split(';')) == set(('1', '2', '3'))
-
-
-    @staticmethod
-    def test_process_place_housenumbers_duplicates(analyzer):
-        info = analyzer.process_place(PlaceInfo({'address': {'housenumber' : '134',
-                                                   'conscriptionnumber' : '134',
-                                                   'streetnumber' : '99a'}}))
-
-        assert set(info['hnr'].split(';')) == set(('134', '99a'))
-
-
-class TestPlaceNames:
-
-    @pytest.fixture(autouse=True)
-    def setup(self, analyzer):
-        self.analyzer = analyzer
-
-
-    def expect_name_terms(self, info, *expected_terms):
-        tokens = self.analyzer.get_word_token_info(list(expected_terms))
-        for token in tokens:
-            assert token[2] is not None, "No token for {0}".format(token)
-
-        assert eval(info['names']) == set((t[2] for t in tokens)),\
-               f"Expected: {tokens}\nGot: {info['names']}"
-
-
-    def process_named_place(self, names):
-        return self.analyzer.process_place(PlaceInfo({'name': names}))
-
-
-    def test_simple_names(self):
-        info = self.process_named_place({'name': 'Soft bAr', 'ref': '34'})
-
-        self.expect_name_terms(info, '#Soft bAr', '#34', 'Soft', 'bAr', '34')
-
-
-    @pytest.mark.parametrize('sep', [',' , ';'])
-    def test_names_with_separator(self, sep):
-        info = self.process_named_place({'name': sep.join(('New York', 'Big Apple'))})
-
-        self.expect_name_terms(info, '#New York', '#Big Apple',
-                                     'new', 'york', 'big', 'apple')
-
-
-    def test_full_names_with_bracket(self):
-        info = self.process_named_place({'name': 'Houseboat (left)'})
-
-        self.expect_name_terms(info, '#Houseboat (left)', '#Houseboat',
-                                     'houseboat', '(left)')
-
-
-    def test_country_name(self, word_table):
-        place = PlaceInfo({'name' : {'name': 'Norge'},
-                           'country_code': 'no',
-                           'rank_address': 4,
-                           'class': 'boundary',
-                           'type': 'administrative'})
-
-        info = self.analyzer.process_place(place)
-
-        self.expect_name_terms(info, '#norge', 'norge')
-        assert word_table.get_country() == {('no', ' norge')}
-
-
-class TestPlaceAddress:
-
-    @pytest.fixture(autouse=True)
-    def setup(self, analyzer):
-        self.analyzer = analyzer
-
-
-    @pytest.fixture
-    def getorcreate_hnr_id(self, temp_db_cursor):
-        temp_db_cursor.execute("""CREATE SEQUENCE seq_hnr start 1;
-                                  CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
-                                  RETURNS INTEGER AS $$
-                                  SELECT -nextval('seq_hnr')::INTEGER; $$ LANGUAGE SQL""")
-
-    def process_address(self, **kwargs):
-        return self.analyzer.process_place(PlaceInfo({'address': kwargs}))
-
-
-    def name_token_set(self, *expected_terms):
-        tokens = self.analyzer.get_word_token_info(list(expected_terms))
-        for token in tokens:
-            assert token[2] is not None, "No token for {0}".format(token)
-
-        return set((t[2] for t in tokens))
-
-
-    @pytest.mark.parametrize('pcode', ['12345', 'AB 123', '34-345'])
-    def test_process_place_postcode(self, word_table, pcode):
-        self.process_address(postcode=pcode)
-
-        assert word_table.get_postcodes() == {pcode, }
-
-
-    @pytest.mark.parametrize('pcode', ['12:23', 'ab;cd;f', '123;836'])
-    def test_process_place_bad_postcode(self, word_table, pcode):
-        self.process_address(postcode=pcode)
-
-        assert not word_table.get_postcodes()
-
-
-    @pytest.mark.parametrize('hnr', ['123a', '0', '101'])
-    def test_process_place_housenumbers_simple(self, hnr, getorcreate_hnr_id):
-        info = self.process_address(housenumber=hnr)
-
-        assert info['hnr'] == hnr.lower()
-        assert info['hnr_tokens'] == "{-1}"
-
-
-    def test_process_place_housenumbers_lists(self, getorcreate_hnr_id):
-        info = self.process_address(conscriptionnumber='1; 2;3')
-
-        assert set(info['hnr'].split(';')) == set(('1', '2', '3'))
-        assert info['hnr_tokens'] == "{-1,-2,-3}"
-
-
-    def test_process_place_housenumbers_duplicates(self, getorcreate_hnr_id):
-        info = self.process_address(housenumber='134',
-                                    conscriptionnumber='134',
-                                    streetnumber='99A')
-
-        assert set(info['hnr'].split(';')) == set(('134', '99a'))
-        assert info['hnr_tokens'] == "{-1,-2}"
-
-
-    def test_process_place_street(self):
-        # legacy tokenizer only indexes known names
-        self.analyzer.process_place(PlaceInfo({'name': {'name' : 'Grand Road'}}))
-        info = self.process_address(street='Grand Road')
-
-        assert eval(info['street']) == self.name_token_set('#Grand Road')
-
-
-    def test_process_place_street_empty(self):
-        info = self.process_address(street='🜵')
-
-        assert info['street'] == '{}'
-
-
-    def test_process_place_place(self):
-        self.analyzer.process_place(PlaceInfo({'name': {'name' : 'Honu Lulu'}}))
-        info = self.process_address(place='Honu Lulu')
-
-        assert eval(info['place_search']) == self.name_token_set('#Honu Lulu',
-                                                                 'Honu', 'Lulu')
-        assert eval(info['place_match']) == self.name_token_set('#Honu Lulu')
-
-
-    def test_process_place_place_empty(self):
-        info = self.process_address(place='🜵')
-
-        assert 'place' not in info
-
-
-    def test_process_place_address_terms(self):
-        for name in ('Zwickau', 'Haupstraße', 'Sachsen'):
-            self.analyzer.process_place(PlaceInfo({'name': {'name' : name}}))
-        info = self.process_address(country='de', city='Zwickau', state='Sachsen',
-                                    suburb='Zwickau', street='Hauptstr',
-                                    full='right behind the church')
-
-        city = self.name_token_set('ZWICKAU')
-        state = self.name_token_set('SACHSEN')
-
-        print(info)
-        result = {k: eval(v[0]) for k,v in info['addr'].items()}
-
-        assert result == {'city': city, 'suburb': city, 'state': state}
-
-
-    def test_process_place_address_terms_empty(self):
-        info = self.process_address(country='de', city=' ', street='Hauptstr',
-                                    full='right behind the church')
-
-        assert 'addr' not in info
-
index 2c7b2d5664cbf16591a848934e87ac9bf72a680e..0b4d2ec6e18585afd6ee233dfa5c31eb06086d04 100644 (file)
@@ -14,8 +14,6 @@ from nominatim_db.errors import UsageError
 from nominatim_db.db.connection import server_version_tuple
 import nominatim_db.version
 
-from mock_legacy_word_table import MockLegacyWordTable
-
 class DummyTokenizer:
 
     def update_sql_functions(self, config):
@@ -28,40 +26,14 @@ def postprocess_mock(monkeypatch):
     monkeypatch.setattr(migration.tokenizer_factory, 'get_tokenizer_for_db',
                         lambda *args: DummyTokenizer())
 
-@pytest.fixture
-def legacy_word_table(temp_db_conn):
-    return MockLegacyWordTable(temp_db_conn)
-
 
-def test_no_migration_old_versions(temp_db_with_extensions, table_factory, def_config):
-    table_factory('country_name', 'name HSTORE, country_code TEXT')
+def test_no_migration_old_versions(temp_db_with_extensions, def_config, property_table):
+    property_table.set('database_version', '4.2.99-0')
 
     with pytest.raises(UsageError, match='Migration not possible'):
         migration.migrate(def_config, {})
 
 
-def test_set_up_migration_for_36(temp_db_with_extensions, temp_db_cursor,
-                                 table_factory, def_config, monkeypatch,
-                                 postprocess_mock):
-    # don't actually run any migration, except the property table creation
-    monkeypatch.setattr(migration, '_MIGRATION_FUNCTIONS',
-                        [((3, 5, 0, 99), migration.add_nominatim_property_table)])
-    # Use a r/o user name that always exists
-    monkeypatch.setenv('NOMINATIM_DATABASE_WEBUSER', 'postgres')
-
-    table_factory('country_name', 'name HSTORE, country_code TEXT',
-                  (({str(x): 'a' for x in range(200)}, 'gb'),))
-
-    assert not temp_db_cursor.table_exists('nominatim_properties')
-
-    assert migration.migrate(def_config, {}) == 0
-
-    assert temp_db_cursor.table_exists('nominatim_properties')
-
-    assert 1 == temp_db_cursor.scalar(""" SELECT count(*) FROM nominatim_properties
-                                          WHERE property = 'database_version'""")
-
-
 def test_already_at_version(temp_db_with_extensions, def_config, property_table):
 
     property_table.set('database_version',
@@ -72,8 +44,7 @@ def test_already_at_version(temp_db_with_extensions, def_config, property_table)
 
 def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_cursor,
                               property_table, monkeypatch, postprocess_mock):
-    oldversion = [x for x in nominatim_db.version.NOMINATIM_VERSION]
-    oldversion[0] -= 1
+    oldversion = [4, 4, 99, 0]
     property_table.set('database_version',
                        str(nominatim_db.version.NominatimVersion(*oldversion)))
 
@@ -86,7 +57,7 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso
         """ Dummy migration"""
         done['old'] = True
 
-    oldversion[0] = 0
+    oldversion[1] = 0
     monkeypatch.setattr(migration, '_MIGRATION_FUNCTIONS',
                         [(tuple(oldversion), _old_migration),
                          (nominatim_db.version.NOMINATIM_VERSION, _migration)])
@@ -103,131 +74,3 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso
 # Each migration should come with two tests:
 #  1. Test that migration from old to new state works as expected.
 #  2. Test that the migration can be rerun on the new state without side effects.
-
-
-@pytest.mark.parametrize('in_attr', ('', 'with time zone'))
-def test_import_status_timestamp_change(temp_db_conn, temp_db_cursor,
-                                        table_factory, in_attr):
-    table_factory('import_status',
-                  f"""lastimportdate timestamp {in_attr},
-                     sequence_id integer,
-                     indexed boolean""")
-
-    migration.import_status_timestamp_change(temp_db_conn)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.scalar("""SELECT data_type FROM information_schema.columns
-                                    WHERE table_name = 'import_status'
-                                      and column_name = 'lastimportdate'""")\
-            == 'timestamp with time zone'
-
-
-def test_add_nominatim_property_table(temp_db_conn, temp_db_cursor,
-                                      def_config, monkeypatch):
-    # Use a r/o user name that always exists
-    monkeypatch.setenv('NOMINATIM_DATABASE_WEBUSER', 'postgres')
-
-    assert not temp_db_cursor.table_exists('nominatim_properties')
-
-    migration.add_nominatim_property_table(temp_db_conn, def_config)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.table_exists('nominatim_properties')
-
-
-def test_add_nominatim_property_table_repeat(temp_db_conn, temp_db_cursor,
-                                             def_config, property_table):
-    assert temp_db_cursor.table_exists('nominatim_properties')
-
-    migration.add_nominatim_property_table(temp_db_conn, def_config)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.table_exists('nominatim_properties')
-
-
-def test_change_housenumber_transliteration(temp_db_conn, temp_db_cursor,
-                                            legacy_word_table, placex_table):
-    placex_table.add(housenumber='3A')
-
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION make_standard_name(name TEXT)
-                              RETURNS TEXT AS $$ SELECT lower(name) $$ LANGUAGE SQL """)
-    temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
-                              RETURNS INTEGER AS $$ SELECT 4325 $$ LANGUAGE SQL """)
-
-    migration.change_housenumber_transliteration(temp_db_conn)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.scalar('SELECT housenumber from placex') == '3a'
-
-    migration.change_housenumber_transliteration(temp_db_conn)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.scalar('SELECT housenumber from placex') == '3a'
-
-
-def test_switch_placenode_geometry_index(temp_db_conn, temp_db_cursor, placex_table):
-    temp_db_cursor.execute("""CREATE INDEX idx_placex_adminname
-                              ON placex (place_id)""")
-
-    migration.switch_placenode_geometry_index(temp_db_conn)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.index_exists('placex', 'idx_placex_geometry_placenode')
-    assert not temp_db_cursor.index_exists('placex', 'idx_placex_adminname')
-
-
-def test_switch_placenode_geometry_index_repeat(temp_db_conn, temp_db_cursor, placex_table):
-    temp_db_cursor.execute("""CREATE INDEX idx_placex_geometry_placenode
-                              ON placex (place_id)""")
-
-    migration.switch_placenode_geometry_index(temp_db_conn)
-    temp_db_conn.commit()
-
-    assert temp_db_cursor.index_exists('placex', 'idx_placex_geometry_placenode')
-    assert not temp_db_cursor.index_exists('placex', 'idx_placex_adminname')
-    assert temp_db_cursor.scalar("""SELECT indexdef from pg_indexes
-                                    WHERE tablename = 'placex'
-                                      and indexname = 'idx_placex_geometry_placenode'
-                                 """).endswith('(place_id)')
-
-
-def test_install_legacy_tokenizer(temp_db_conn, temp_db_cursor, project_env,
-                                  property_table, table_factory, monkeypatch,
-                                  tmp_path):
-    table_factory('placex', 'place_id BIGINT')
-    table_factory('location_property_osmline', 'place_id BIGINT')
-
-    # Setting up the tokenizer is problematic
-    class MiniTokenizer:
-        def migrate_database(self, config):
-            pass
-
-    monkeypatch.setattr(migration.tokenizer_factory, 'create_tokenizer',
-                        lambda cfg, **kwargs: MiniTokenizer())
-
-    migration.install_legacy_tokenizer(temp_db_conn, project_env)
-    temp_db_conn.commit()
-
-
-
-def test_install_legacy_tokenizer_repeat(temp_db_conn, temp_db_cursor,
-                                         def_config, property_table):
-
-    property_table.set('tokenizer', 'dummy')
-    migration.install_legacy_tokenizer(temp_db_conn, def_config)
-    temp_db_conn.commit()
-
-
-def test_create_tiger_housenumber_index(temp_db_conn, temp_db_cursor, table_factory):
-    table_factory('location_property_tiger',
-                  'parent_place_id BIGINT, startnumber INT, endnumber INT')
-
-    migration.create_tiger_housenumber_index(temp_db_conn)
-    temp_db_conn.commit()
-
-    if server_version_tuple(temp_db_conn) >= (11, 0, 0):
-        assert temp_db_cursor.index_exists('location_property_tiger',
-                                           'idx_location_property_tiger_housenumber_migrated')
-
-    migration.create_tiger_housenumber_index(temp_db_conn)
-    temp_db_conn.commit()
diff --git a/test/python/tools/test_refresh_setup_website.py b/test/python/tools/test_refresh_setup_website.py
deleted file mode 100644 (file)
index fe29dd5..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of Nominatim. (https://nominatim.org)
-#
-# Copyright (C) 2024 by the Nominatim developer community.
-# For a full list of authors see the git log.
-"""
-Tests for setting up the website scripts.
-"""
-import subprocess
-
-import pytest
-
-from nominatim_db.tools import refresh
-
-@pytest.fixture
-def test_script(tmp_path):
-    (tmp_path / 'php').mkdir()
-
-    website_dir = (tmp_path / 'php' / 'website')
-    website_dir.mkdir()
-
-    def _create_file(code):
-        outfile = website_dir / 'reverse-only-search.php'
-        outfile.write_text('<?php\n{}\n'.format(code), 'utf-8')
-
-    return _create_file
-
-
-@pytest.fixture
-def run_website_script(tmp_path, project_env, temp_db_conn):
-    project_env.lib_dir.php = tmp_path / 'php'
-
-    def _runner():
-        refresh.setup_website(tmp_path, project_env, temp_db_conn)
-
-        proc = subprocess.run(['/usr/bin/env', 'php', '-Cq',
-                               tmp_path / 'search.php'], check=False)
-
-        return proc.returncode
-
-    return _runner
-
-
-def test_basedir_created(tmp_path, project_env, temp_db_conn):
-    webdir = tmp_path / 'website'
-
-    assert not webdir.exists()
-
-    refresh.setup_website(webdir, project_env, temp_db_conn)
-
-    assert webdir.exists()
-
-
-@pytest.mark.parametrize("setting,retval", (('yes', 10), ('no', 20)))
-def test_setup_website_check_bool(monkeypatch, test_script, run_website_script,
-                                  setting, retval):
-    monkeypatch.setenv('NOMINATIM_CORS_NOACCESSCONTROL', setting)
-
-    test_script('exit(CONST_NoAccessControl ? 10 : 20);')
-
-    assert run_website_script() == retval
-
-
-@pytest.mark.parametrize("setting", (0, 10, 99067))
-def test_setup_website_check_int(monkeypatch, test_script, run_website_script, setting):
-    monkeypatch.setenv('NOMINATIM_LOOKUP_MAX_COUNT', str(setting))
-
-    test_script('exit(CONST_Places_Max_ID_count == {} ? 10 : 20);'.format(setting))
-
-    assert run_website_script() == 10
-
-
-def test_setup_website_check_empty_str(monkeypatch, test_script, run_website_script):
-    monkeypatch.setenv('NOMINATIM_DEFAULT_LANGUAGE', '')
-
-    test_script('exit(CONST_Default_Language === false ? 10 : 20);')
-
-    assert run_website_script() == 10
-
-
-def test_setup_website_check_str(monkeypatch, test_script, run_website_script):
-    monkeypatch.setenv('NOMINATIM_DEFAULT_LANGUAGE', 'ffde 2')
-
-    test_script('exit(CONST_Default_Language === "ffde 2" ? 10 : 20);')
-
-    assert run_website_script() == 10
-
-
-def test_relative_log_file(project_env, monkeypatch, test_script, run_website_script):
-    monkeypatch.setenv('NOMINATIM_LOG_FILE', 'access.log')
-
-    expected_file = str(project_env.project_dir / 'access.log')
-    test_script(f'exit(CONST_Log_File === "{expected_file}" ? 10 : 20);')
-
-    assert run_website_script() == 10
-
-def test_variable_with_bracket(project_env, monkeypatch, test_script, run_website_script):
-    monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'pgsql:dbname=nominatim;user=foo;password=4{5')
-
-    test_script('exit(CONST_Database_DSN === "pgsql:dbname=nominatim;user=foo;password=4{5" ? 10 : 20);')
-
-    assert run_website_script() == 10
-
diff --git a/test/testdb/specialphrases_testdb.sql b/test/testdb/specialphrases_testdb.sql
deleted file mode 100644 (file)
index 7e72076..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-CREATE OR REPLACE FUNCTION test_getorcreate_amenity(lookup_word TEXT, normalized_word TEXT,
-                                               lookup_class text, lookup_type 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 word = normalized_word
-        and class = lookup_class and type = lookup_type
-  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, normalized_word,
-                             lookup_class, lookup_type, null, 0);
-  END IF;
-  RETURN return_word_id;
-END;
-$$
-LANGUAGE plpgsql;
-
-
-CREATE OR REPLACE FUNCTION test_getorcreate_amenityoperator(lookup_word TEXT,
-                                                       normalized_word TEXT,
-                                                       lookup_class text,
-                                                       lookup_type text,
-                                                       op 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 word = normalized_word
-        and class = lookup_class and type = lookup_type and operator = op
-  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, normalized_word,
-                             lookup_class, lookup_type, null, 0, op);
-  END IF;
-  RETURN return_word_id;
-END;
-$$
-LANGUAGE plpgsql;
-
-SELECT test_getorcreate_amenity(make_standard_name('Aerodrome'), 'aerodrome', 'aeroway', 'aerodrome');
-SELECT test_getorcreate_amenity(make_standard_name('Aerodromes'), 'aerodromes', 'aeroway', 'aerodrome');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Aerodrome in'), 'aerodrome in', 'aeroway', 'aerodrome', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Aerodromes in'), 'aerodromes in', 'aeroway', 'aerodrome', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Aerodrome near'), 'aerodrome near', 'aeroway', 'aerodrome', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Aerodromes near'), 'aerodromes near', 'aeroway', 'aerodrome', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Airport'), 'airport', 'aeroway', 'aerodrome');
-SELECT test_getorcreate_amenity(make_standard_name('Airports'), 'airports', 'aeroway', 'aerodrome');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Airport in'), 'airport in', 'aeroway', 'aerodrome', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Airports in'), 'airports in', 'aeroway', 'aerodrome', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Airport near'), 'airport near', 'aeroway', 'aerodrome', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Airports near'), 'airports near', 'aeroway', 'aerodrome', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Bar'), 'bar', 'amenity', 'bar');
-SELECT test_getorcreate_amenity(make_standard_name('Bars'), 'bars', 'amenity', 'bar');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bar in'), 'bar in', 'amenity', 'bar', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bars in'), 'bars in', 'amenity', 'bar', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bar near'), 'bar near', 'amenity', 'bar', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bars near'), 'bars near', 'amenity', 'bar', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Bar'), 'bar', 'amenity', 'pub');
-SELECT test_getorcreate_amenity(make_standard_name('Bars'), 'bars', 'amenity', 'pub');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bar in'), 'bar in', 'amenity', 'pub', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bars in'), 'bars in', 'amenity', 'pub', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bar near'), 'bar near', 'amenity', 'pub', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Bars near'), 'bars near', 'amenity', 'pub', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Food'), 'food', 'amenity', 'restaurant');
-SELECT test_getorcreate_amenity(make_standard_name('Food'), 'food', 'amenity', 'restaurant');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Food in'), 'food in', 'amenity', 'restaurant', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Food in'), 'food in', 'amenity', 'restaurant', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Food near'), 'food near', 'amenity', 'restaurant', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Food near'), 'food near', 'amenity', 'restaurant', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Pub'), 'pub', 'amenity', 'bar');
-SELECT test_getorcreate_amenity(make_standard_name('Pubs'), 'pubs', 'amenity', 'bar');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pub in'), 'pub in', 'amenity', 'bar', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pubs in'), 'pubs in', 'amenity', 'bar', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pub near'), 'pub near', 'amenity', 'bar', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pubs near'), 'pubs near', 'amenity', 'bar', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Pub'), 'pub', 'amenity', 'pub');
-SELECT test_getorcreate_amenity(make_standard_name('Pubs'), 'pubs', 'amenity', 'pub');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pub in'), 'pub in', 'amenity', 'pub', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pubs in'), 'pubs in', 'amenity', 'pub', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pub near'), 'pub near', 'amenity', 'pub', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Pubs near'), 'pubs near', 'amenity', 'pub', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Restaurant'), 'restaurant', 'amenity', 'restaurant');
-SELECT test_getorcreate_amenity(make_standard_name('Restaurants'), 'restaurants', 'amenity', 'restaurant');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Restaurant in'), 'restaurant in', 'amenity', 'restaurant', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Restaurants in'), 'restaurants in', 'amenity', 'restaurant', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Restaurant near'), 'restaurant near', 'amenity', 'restaurant', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Restaurants near'), 'restaurants near', 'amenity', 'restaurant', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Mural'), 'mural', 'artwork_type', 'mural');
-SELECT test_getorcreate_amenity(make_standard_name('Murals'), 'murals', 'artwork_type', 'mural');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Mural in'), 'mural in', 'artwork_type', 'mural', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Murals in'), 'murals in', 'artwork_type', 'mural', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Mural near'), 'mural near', 'artwork_type', 'mural', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Murals near'), 'murals near', 'artwork_type', 'mural', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Sculpture'), 'sculpture', 'artwork_type', 'sculpture');
-SELECT test_getorcreate_amenity(make_standard_name('Sculptures'), 'sculptures', 'artwork_type', 'sculpture');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Sculpture in'), 'sculpture in', 'artwork_type', 'sculpture', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Sculptures in'), 'sculptures in', 'artwork_type', 'sculpture', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Sculpture near'), 'sculpture near', 'artwork_type', 'sculpture', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Sculptures near'), 'sculptures near', 'artwork_type', 'sculpture', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Statue'), 'statue', 'artwork_type', 'statue');
-SELECT test_getorcreate_amenity(make_standard_name('Statues'), 'statues', 'artwork_type', 'statue');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statue in'), 'statue in', 'artwork_type', 'statue', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statues in'), 'statues in', 'artwork_type', 'statue', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statue near'), 'statue near', 'artwork_type', 'statue', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statues near'), 'statues near', 'artwork_type', 'statue', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('ATM'), 'atm', 'atm', 'yes');
-SELECT test_getorcreate_amenity(make_standard_name('ATMs'), 'atms', 'atm', 'yes');
-SELECT test_getorcreate_amenityoperator(make_standard_name('ATM in'), 'atm in', 'atm', 'yes', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('ATMs in'), 'atms in', 'atm', 'yes', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('ATM near'), 'atm near', 'atm', 'yes', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('ATMs near'), 'atms near', 'atm', 'yes', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('National Park'), 'national park', 'boundary', 'national_park');
-SELECT test_getorcreate_amenity(make_standard_name('National Parks'), 'national parks', 'boundary', 'national_park');
-SELECT test_getorcreate_amenityoperator(make_standard_name('National Park in'), 'national park in', 'boundary', 'national_park', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('National Parks in'), 'national parks in', 'boundary', 'national_park', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('National Park near'), 'national park near', 'boundary', 'national_park', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('National Parks near'), 'national parks near', 'boundary', 'national_park', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Changing table'), 'changing table', 'changing_table', 'yes');
-SELECT test_getorcreate_amenity(make_standard_name('Changing tables'), 'changing tables', 'changing_table', 'yes');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Changing table in'), 'changing table in', 'changing_table', 'yes', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Changing tables in'), 'changing tables in', 'changing_table', 'yes', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Changing table near'), 'changing table near', 'changing_table', 'yes', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Changing tables near'), 'changing tables near', 'changing_table', 'yes', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Roundabout'), 'roundabout', 'junction', 'roundabout');
-SELECT test_getorcreate_amenity(make_standard_name('Roundabouts'), 'roundabouts', 'junction', 'roundabout');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Roundabout in'), 'roundabout in', 'junction', 'roundabout', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Roundabouts in'), 'roundabouts in', 'junction', 'roundabout', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Roundabout near'), 'roundabout near', 'junction', 'roundabout', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Roundabouts near'), 'roundabouts near', 'junction', 'roundabout', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Plaque'), 'plaque', 'memorial', 'plaque');
-SELECT test_getorcreate_amenity(make_standard_name('Plaques'), 'plaques', 'memorial', 'plaque');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Plaque in'), 'plaque in', 'memorial', 'plaque', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Plaques in'), 'plaques in', 'memorial', 'plaque', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Plaque near'), 'plaque near', 'memorial', 'plaque', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Plaques near'), 'plaques near', 'memorial', 'plaque', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Statue'), 'statue', 'memorial', 'statue');
-SELECT test_getorcreate_amenity(make_standard_name('Statues'), 'statues', 'memorial', 'statue');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statue in'), 'statue in', 'memorial', 'statue', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statues in'), 'statues in', 'memorial', 'statue', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statue near'), 'statue near', 'memorial', 'statue', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Statues near'), 'statues near', 'memorial', 'statue', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('Stolperstein'), 'stolperstein', 'memorial', 'stolperstein');
-SELECT test_getorcreate_amenity(make_standard_name('Stolpersteins'), 'stolpersteins', 'memorial', 'stolperstein');
-SELECT test_getorcreate_amenity(make_standard_name('Stolpersteine'), 'stolpersteine', 'memorial', 'stolperstein');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolperstein in'), 'stolperstein in', 'memorial', 'stolperstein', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolpersteins in'), 'stolpersteins in', 'memorial', 'stolperstein', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolpersteine in'), 'stolpersteine in', 'memorial', 'stolperstein', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolperstein near'), 'stolperstein near', 'memorial', 'stolperstein', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolpersteins near'), 'stolpersteins near', 'memorial', 'stolperstein', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('Stolpersteine near'), 'stolpersteine near', 'memorial', 'stolperstein', 'near');
-SELECT test_getorcreate_amenity(make_standard_name('War Memorial'), 'war memorial', 'memorial', 'war_memorial');
-SELECT test_getorcreate_amenity(make_standard_name('War Memorials'), 'war memorials', 'memorial', 'war_memorial');
-SELECT test_getorcreate_amenityoperator(make_standard_name('War Memorial in'), 'war memorial in', 'memorial', 'war_memorial', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('War Memorials in'), 'war memorials in', 'memorial', 'war_memorial', 'in');
-SELECT test_getorcreate_amenityoperator(make_standard_name('War Memorial near'), 'war memorial near', 'memorial', 'war_memorial', 'near');
-SELECT test_getorcreate_amenityoperator(make_standard_name('War Memorials near'), 'war memorials near', 'memorial', 'war_memorial', 'near');
-CREATE INDEX idx_placex_classtype ON placex (class, type);CREATE TABLE place_classtype_aeroway_aerodrome AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'aeroway' AND type = 'aerodrome';
-CREATE INDEX idx_place_classtype_aeroway_aerodrome_centroid ON place_classtype_aeroway_aerodrome USING GIST (centroid);
-CREATE INDEX idx_place_classtype_aeroway_aerodrome_place_id ON place_classtype_aeroway_aerodrome USING btree(place_id);
-GRANT SELECT ON place_classtype_aeroway_aerodrome TO "www-data";
-CREATE TABLE place_classtype_amenity_bar AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'amenity' AND type = 'bar';
-CREATE INDEX idx_place_classtype_amenity_bar_centroid ON place_classtype_amenity_bar USING GIST (centroid);
-CREATE INDEX idx_place_classtype_amenity_bar_place_id ON place_classtype_amenity_bar USING btree(place_id);
-GRANT SELECT ON place_classtype_amenity_bar TO "www-data";
-CREATE TABLE place_classtype_amenity_pub AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'amenity' AND type = 'pub';
-CREATE INDEX idx_place_classtype_amenity_pub_centroid ON place_classtype_amenity_pub USING GIST (centroid);
-CREATE INDEX idx_place_classtype_amenity_pub_place_id ON place_classtype_amenity_pub USING btree(place_id);
-GRANT SELECT ON place_classtype_amenity_pub TO "www-data";
-CREATE TABLE place_classtype_amenity_restaurant AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'amenity' AND type = 'restaurant';
-CREATE INDEX idx_place_classtype_amenity_restaurant_centroid ON place_classtype_amenity_restaurant USING GIST (centroid);
-CREATE INDEX idx_place_classtype_amenity_restaurant_place_id ON place_classtype_amenity_restaurant USING btree(place_id);
-GRANT SELECT ON place_classtype_amenity_restaurant TO "www-data";
-CREATE TABLE place_classtype_artwork_type_mural AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'artwork_type' AND type = 'mural';
-CREATE INDEX idx_place_classtype_artwork_type_mural_centroid ON place_classtype_artwork_type_mural USING GIST (centroid);
-CREATE INDEX idx_place_classtype_artwork_type_mural_place_id ON place_classtype_artwork_type_mural USING btree(place_id);
-GRANT SELECT ON place_classtype_artwork_type_mural TO "www-data";
-CREATE TABLE place_classtype_artwork_type_sculpture AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'artwork_type' AND type = 'sculpture';
-CREATE INDEX idx_place_classtype_artwork_type_sculpture_centroid ON place_classtype_artwork_type_sculpture USING GIST (centroid);
-CREATE INDEX idx_place_classtype_artwork_type_sculpture_place_id ON place_classtype_artwork_type_sculpture USING btree(place_id);
-GRANT SELECT ON place_classtype_artwork_type_sculpture TO "www-data";
-CREATE TABLE place_classtype_artwork_type_statue AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'artwork_type' AND type = 'statue';
-CREATE INDEX idx_place_classtype_artwork_type_statue_centroid ON place_classtype_artwork_type_statue USING GIST (centroid);
-CREATE INDEX idx_place_classtype_artwork_type_statue_place_id ON place_classtype_artwork_type_statue USING btree(place_id);
-GRANT SELECT ON place_classtype_artwork_type_statue TO "www-data";
-CREATE TABLE place_classtype_atm_yes AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'atm' AND type = 'yes';
-CREATE INDEX idx_place_classtype_atm_yes_centroid ON place_classtype_atm_yes USING GIST (centroid);
-CREATE INDEX idx_place_classtype_atm_yes_place_id ON place_classtype_atm_yes USING btree(place_id);
-GRANT SELECT ON place_classtype_atm_yes TO "www-data";
-CREATE TABLE place_classtype_boundary_national_park AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'boundary' AND type = 'national_park';
-CREATE INDEX idx_place_classtype_boundary_national_park_centroid ON place_classtype_boundary_national_park USING GIST (centroid);
-CREATE INDEX idx_place_classtype_boundary_national_park_place_id ON place_classtype_boundary_national_park USING btree(place_id);
-GRANT SELECT ON place_classtype_boundary_national_park TO "www-data";
-CREATE TABLE place_classtype_changing_table_yes AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'changing_table' AND type = 'yes';
-CREATE INDEX idx_place_classtype_changing_table_yes_centroid ON place_classtype_changing_table_yes USING GIST (centroid);
-CREATE INDEX idx_place_classtype_changing_table_yes_place_id ON place_classtype_changing_table_yes USING btree(place_id);
-GRANT SELECT ON place_classtype_changing_table_yes TO "www-data";
-CREATE TABLE place_classtype_junction_roundabout AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'junction' AND type = 'roundabout';
-CREATE INDEX idx_place_classtype_junction_roundabout_centroid ON place_classtype_junction_roundabout USING GIST (centroid);
-CREATE INDEX idx_place_classtype_junction_roundabout_place_id ON place_classtype_junction_roundabout USING btree(place_id);
-GRANT SELECT ON place_classtype_junction_roundabout TO "www-data";
-CREATE TABLE place_classtype_memorial_plaque AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'memorial' AND type = 'plaque';
-CREATE INDEX idx_place_classtype_memorial_plaque_centroid ON place_classtype_memorial_plaque USING GIST (centroid);
-CREATE INDEX idx_place_classtype_memorial_plaque_place_id ON place_classtype_memorial_plaque USING btree(place_id);
-GRANT SELECT ON place_classtype_memorial_plaque TO "www-data";
-CREATE TABLE place_classtype_memorial_statue AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'memorial' AND type = 'statue';
-CREATE INDEX idx_place_classtype_memorial_statue_centroid ON place_classtype_memorial_statue USING GIST (centroid);
-CREATE INDEX idx_place_classtype_memorial_statue_place_id ON place_classtype_memorial_statue USING btree(place_id);
-GRANT SELECT ON place_classtype_memorial_statue TO "www-data";
-CREATE TABLE place_classtype_memorial_stolperstein AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'memorial' AND type = 'stolperstein';
-CREATE INDEX idx_place_classtype_memorial_stolperstein_centroid ON place_classtype_memorial_stolperstein USING GIST (centroid);
-CREATE INDEX idx_place_classtype_memorial_stolperstein_place_id ON place_classtype_memorial_stolperstein USING btree(place_id);
-GRANT SELECT ON place_classtype_memorial_stolperstein TO "www-data";
-CREATE TABLE place_classtype_memorial_war_memorial AS SELECT place_id AS place_id,st_centroid(geometry) AS centroid FROM placex WHERE class = 'memorial' AND type = 'war_memorial';
-CREATE INDEX idx_place_classtype_memorial_war_memorial_centroid ON place_classtype_memorial_war_memorial USING GIST (centroid);
-CREATE INDEX idx_place_classtype_memorial_war_memorial_place_id ON place_classtype_memorial_war_memorial USING btree(place_id);
-GRANT SELECT ON place_classtype_memorial_war_memorial TO "www-data";
-DROP INDEX idx_placex_classtype;
-
-DROP FUNCTION test_getorcreate_amenity;
-DROP FUNCTION test_getorcreate_amenityoperator;